From f48643cbdf2a6ca93d01b4cbdb962ab3b353c067 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 8 Sep 2025 20:44:44 +0530 Subject: [PATCH] v1.0.0-rc --- .../propertyTemplateSelector.css | 33 +- .../propertyTemplateSelector.html | 5 - .../propertyTemplateSelector.js | 14671 +++++++++------- template samples/the_vertice.html | 604 + 4 files changed, 8452 insertions(+), 6861 deletions(-) create mode 100644 template samples/the_vertice.html diff --git a/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.css b/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.css index 3370e35..da76ac6 100644 --- a/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.css +++ b/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.css @@ -9814,25 +9814,21 @@ img[draggable="true"] { } /* Bullet and numbering styles */ -/* Allow bullets and numbers by default inside content */ -.enhanced-editor-content ul { list-style-type: disc; padding-left: 22px; } -.enhanced-editor-content ol { list-style-type: decimal; padding-left: 22px; } - -ul li, ol li { - margin-left: 20px; - position: relative; +/* Use native list markers so nested bullets and numbers render correctly */ +.enhanced-editor-content ul { + list-style-type: disc; + list-style-position: outside; + padding-left: 24px; + margin: 0 0 8px 0; } - -ul li:before { - content: "* "; - position: absolute; - left: -20px; +.enhanced-editor-content ol { + list-style-type: decimal; + list-style-position: outside; + padding-left: 24px; + margin: 0 0 8px 0; } - -ol li:before { - content: "1. "; - position: absolute; - left: -20px; +.enhanced-editor-content li { + margin: 4px 0; } /* Image Insertion Modal Styles */ @@ -11844,6 +11840,9 @@ ol li:before { background: linear-gradient(to top, rgba(0,0,0,0.7), transparent); } + + + .vertice-preview .gallery-item span { font-weight: 600; z-index: 2; font-size: 0.4rem; } .vertice-preview .g-item-1 { grid-column: 1 / 3; grid-row: 1 / 2; background-image: url('https://images.unsplash.com/photo-1616046229478-9901c5536a45?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=800'); } .vertice-preview .g-item-2 { grid-column: 3 / 4; grid-row: 1 / 3; background-image: url('https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=800'); } diff --git a/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.html b/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.html index f03212f..196fd68 100644 --- a/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.html +++ b/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.html @@ -780,11 +780,6 @@ {currentImage.title} - diff --git a/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.js b/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.js index 7f62aeb..7ed9660 100644 --- a/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.js +++ b/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.js @@ -1,1389 +1,1595 @@ -import { LightningElement, track, wire } from 'lwc'; -import { CurrentPageReference } from 'lightning/navigation'; -import getProperties from '@salesforce/apex/PropertyDataController.getProperties'; -import generatePDFFromHTML from '@salesforce/apex/PDFGenerationProxy.generatePDFFromHTML'; -import generateCompressedPDF from '@salesforce/apex/PDFGenerationProxy.generateCompressedPDF'; -import getPropertyImages from '@salesforce/apex/PropertyDataController.getPropertyImages'; +import { LightningElement, track, wire } from "lwc"; +import { CurrentPageReference } from "lightning/navigation"; +import getProperties from "@salesforce/apex/PropertyDataController.getProperties"; +import generatePDFFromHTML from "@salesforce/apex/PDFGenerationProxy.generatePDFFromHTML"; +import generateCompressedPDF from "@salesforce/apex/PDFGenerationProxy.generateCompressedPDF"; +import getPropertyImages from "@salesforce/apex/PropertyDataController.getPropertyImages"; export default class PropertyTemplateSelector extends LightningElement { - @track currentStep = 1; - htmlContent = ''; // Remove @track to prevent reactive updates + @track currentStep = 1; + htmlContent = ""; // Remove @track to prevent reactive updates - // Lifecycle method - called when component is rendered - renderedCallback() { - // If we're on step 3 and have template/property selected, load the template - if (this.currentStep === 3 && this.selectedTemplateId && this.selectedPropertyId) { - this.loadTemplateInStep3(); - } - }; - @track properties = []; - @track selectedPropertyId = ''; - @track propertyData = {}; - @track isLoading = false; - @track error = ''; - @track marketAnalysis = { - includeMarketData: true, - includeROIAnalysis: true, - includeComparableSales: true, - includeRentalYield: true, - includeGrowthProjection: true - }; - - // PDF generation properties - @track exportPdfButtonText = '📄 Generate PDF'; - @track showPdfPreview = false; - @track editorContent = ''; - @track pageCount = 0; - @track progressMessage = ''; - @track selectedPageSize = 'A4'; // Default page size - @track zoom = 1.0; // Step 3 viewport zoom - @track isGeneratingPdf = false; // Loading state for PDF generation - @track previewPages = []; // Array of pages for viewport display - cachedTemplateContent = null; // Cache template content to prevent regeneration - - // Image review properties - @track showImageReview = false; - @track selectedCategory = 'Interior'; // Will be updated when images load - @track currentImageIndex = 0; - @track totalImages = 0; - @track currentImage = null; - - // Real property images from Salesforce - @track realPropertyImages = []; - @track propertyImages = []; - @track imagesByCategory = { - 'Interior': [ - { url: 'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200', title: 'Interior View 1', category: 'Interior' }, - { url: 'https://images.unsplash.com/photo-1618221195710-dd6b41faaea6?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=800', title: 'Interior View 2', category: 'Interior' }, - { url: 'https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200', title: 'Interior View 3', category: 'Interior' } - ], - 'Exterior': [ - { url: 'https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200', title: 'Exterior View 1', category: 'Exterior' }, - { url: 'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200', title: 'Exterior View 2', category: 'Exterior' } - ], - 'Kitchen': [ - { url: 'https://images.unsplash.com/photo-1556909114-f6e7ad7d3136?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200', title: 'Kitchen View 1', category: 'Kitchen' }, - { url: 'https://images.unsplash.com/photo-1556909114-f6e7ad7d3136?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200', title: 'Kitchen View 2', category: 'Kitchen' } - ], - 'Bedroom': [ - { url: 'https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200', title: 'Bedroom View 1', category: 'Bedroom' }, - { url: 'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200', title: 'Bedroom View 2', category: 'Bedroom' } - ], - 'Living Area': [ - { url: 'https://images.unsplash.com/photo-1618221195710-dd6b41faaea6?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=800', title: 'Living Area View 1', category: 'Living Area' }, - { url: 'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200', title: 'Living Area View 2', category: 'Living Area' } - ], - 'Parking': [ - { url: 'https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200', title: 'Parking View 1', category: 'Parking' } - ], - 'Anchor': [ - { url: 'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200', title: 'Anchor View 1', category: 'Anchor' } - ], - 'Maps': [ - { url: 'https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200', title: 'Map View 1', category: 'Maps' } - ] - }; - - // Capture URL param c__propertyId and hydrate selection - @wire(CurrentPageReference) - setPageRef(ref) { - try { - const pid = ref && ref.state && ref.state.c__propertyId; - if (pid && pid !== this.selectedPropertyId) { - this.selectedPropertyId = pid; - const hydrate = () => { - this.loadPropertyData(); - this.loadPropertyImages(); - }; - if (this.properties && this.properties.length > 0) { - hydrate(); - } else { - this._deferHydrateFromUrl = hydrate; - } - } - } catch (e) { - // ignore - } + // Lifecycle method - called when component is rendered + renderedCallback() { + // If we're on step 3 and have template/property selected, load the template + if ( + this.currentStep === 3 && + this.selectedTemplateId && + this.selectedPropertyId + ) { + this.loadTemplateInStep3(); } + } + @track properties = []; + @track selectedPropertyId = ""; + @track propertyData = {}; + @track isLoading = false; + @track error = ""; + @track marketAnalysis = { + includeMarketData: true, + includeROIAnalysis: true, + includeComparableSales: true, + includeRentalYield: true, + includeGrowthProjection: true, + }; - // Template selection states - simplified approach - @track selectedTemplateId = ''; + // PDF generation properties + @track exportPdfButtonText = "📄 Generate PDF"; + @track showPdfPreview = false; + @track editorContent = ""; + @track pageCount = 0; + @track progressMessage = ""; + @track selectedPageSize = "A4"; // Default page size + @track zoom = 1.0; // Step 3 viewport zoom + @track isGeneratingPdf = false; // Loading state for PDF generation + @track previewPages = []; // Array of pages for viewport display + cachedTemplateContent = null; // Cache template content to prevent regeneration - // Image Replacement Variables - @track showImageReplacement = false; - @track selectedImageElement = null; - @track replacementActiveTab = 'property'; // 'property' or 'upload' - @track replacementSelectedCategory = 'Interior'; // Will be updated when images load - @track filteredReplacementImages = []; - @track uploadedImagePreview = null; + // Image review properties + @track showImageReview = false; + @track selectedCategory = "Interior"; // Will be updated when images load + @track currentImageIndex = 0; + @track totalImages = 0; + @track currentImage = null; - // Triple click detection for image replacement - @track imageClickCount = 0; - @track lastClickedImage = null; - @track clickTimeout = null; + // Real property images from Salesforce + @track realPropertyImages = []; + @track propertyImages = []; + @track imagesByCategory = { + Interior: [ + { + url: "https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200", + title: "Interior View 1", + category: "Interior", + }, + { + url: "https://images.unsplash.com/photo-1618221195710-dd6b41faaea6?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=800", + title: "Interior View 2", + category: "Interior", + }, + { + url: "https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200", + title: "Interior View 3", + category: "Interior", + }, + ], + Exterior: [ + { + url: "https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200", + title: "Exterior View 1", + category: "Exterior", + }, + { + url: "https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200", + title: "Exterior View 2", + category: "Exterior", + }, + ], + Kitchen: [ + { + url: "https://images.unsplash.com/photo-1556909114-f6e7ad7d3136?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200", + title: "Kitchen View 1", + category: "Kitchen", + }, + { + url: "https://images.unsplash.com/photo-1556909114-f6e7ad7d3136?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200", + title: "Kitchen View 2", + category: "Kitchen", + }, + ], + Bedroom: [ + { + url: "https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200", + title: "Bedroom View 1", + category: "Bedroom", + }, + { + url: "https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200", + title: "Bedroom View 2", + category: "Bedroom", + }, + ], + "Living Area": [ + { + url: "https://images.unsplash.com/photo-1618221195710-dd6b41faaea6?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=800", + title: "Living Area View 1", + category: "Living Area", + }, + { + url: "https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200", + title: "Living Area View 2", + category: "Living Area", + }, + ], + Parking: [ + { + url: "https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200", + title: "Parking View 1", + category: "Parking", + }, + ], + Anchor: [ + { + url: "https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200", + title: "Anchor View 1", + category: "Anchor", + }, + ], + Maps: [ + { + url: "https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200", + title: "Map View 1", + category: "Maps", + }, + ], + }; - // Undo/Redo functionality - @track undoStack = []; - @track redoStack = []; - @track maxUndoSteps = 20; - - // Category selection tracking - @track initialCategorySelected = false; - - // Template Save/Load Variables - @track showSaveDialog = false; - @track showLoadDialog = false; - @track savedTemplates = []; - @track saveTemplateName = ''; - @track showHtmlDialog = false; - @track exportedHtml = ''; - - // Table Dialog Variables - @track showTableDialog = false; - @track tableRows = 3; - @track tableCols = 3; - @track includeHeader = true; - - // Image insertion modal properties - @track showImageModal = false; - @track imageSource = 'property'; // 'property' or 'local' - @track selectedImageCategory = 'all'; - @track selectedImageUrl = ''; - @track selectedImageName = ''; - @track uploadedImageData = ''; - @track renderKey = 0; // For forcing re-renders - @track insertButtonDisabled = true; // Explicit button state - - // Table Drag and Drop Variables - @track isDraggingTable = false; - @track draggedTableData = null; - @track selectorMode = false; - @track showDownloadModal = false; - @track downloadInfo = {}; - @track selectedElement = null; - // z-index controls removed per request - - // Undo functionality - @track undoStack = []; - @track redoStack = []; - maxUndoSteps = 50; - - // Computed properties for image replacement tabs - get propertyImagesTabClass() { - return this.replacementActiveTab === 'property' ? 'source-tab active' : 'source-tab'; + // Capture URL param c__propertyId and hydrate selection + @wire(CurrentPageReference) + setPageRef(ref) { + try { + const pid = ref && ref.state && ref.state.c__propertyId; + if (pid && pid !== this.selectedPropertyId) { + this.selectedPropertyId = pid; + const hydrate = () => { + this.loadPropertyData(); + this.loadPropertyImages(); + }; + if (this.properties && this.properties.length > 0) { + hydrate(); + } else { + this._deferHydrateFromUrl = hydrate; + } + } + } catch (e) { + // ignore } + } - // Unified gallery section used across templates - generateUnifiedGallerySectionHTML() { - const imagesHTML = this.generatePropertyGalleryHTML(); - return ` + // Template selection states - simplified approach + @track selectedTemplateId = ""; + + // Image Replacement Variables + @track showImageReplacement = false; + @track selectedImageElement = null; + @track replacementActiveTab = "property"; // 'property' or 'upload' + @track replacementSelectedCategory = "Interior"; // Will be updated when images load + @track filteredReplacementImages = []; + @track uploadedImagePreview = null; + + // Triple click detection for image replacement + @track imageClickCount = 0; + @track lastClickedImage = null; + @track clickTimeout = null; + + // Undo/Redo functionality + @track undoStack = []; + @track redoStack = []; + @track maxUndoSteps = 20; + + // Category selection tracking + @track initialCategorySelected = false; + + // Template Save/Load Variables + @track showSaveDialog = false; + @track showLoadDialog = false; + @track savedTemplates = []; + @track saveTemplateName = ""; + @track showHtmlDialog = false; + @track exportedHtml = ""; + // Table Dialog Variables + @track showTableDialog = false; + @track tableRows = 3; + @track tableCols = 3; + @track includeHeader = true; + + // Image insertion modal properties + @track showImageModal = false; + @track imageSource = "property"; // 'property' or 'local' + @track selectedImageCategory = "all"; + @track selectedImageUrl = ""; + @track selectedImageName = ""; + @track uploadedImageData = ""; + @track renderKey = 0; // For forcing re-renders + @track insertButtonDisabled = true; // Explicit button state + + // Table Drag and Drop Variables + @track isDraggingTable = false; + @track draggedTableData = null; + @track selectorMode = false; + @track showDownloadModal = false; + @track downloadInfo = {}; + @track selectedElement = null; + // z-index controls removed per request + + // Undo functionality + @track undoStack = []; + @track redoStack = []; + maxUndoSteps = 50; + + // Computed properties for image replacement tabs + get propertyImagesTabClass() { + return this.replacementActiveTab === "property" + ? "source-tab active" + : "source-tab"; + } + + // Unified gallery section used across templates + generateUnifiedGallerySectionHTML() { + const imagesHTML = this.generatePropertyGalleryHTML(); + return `

Property Gallery

`; + } + + // z-index functions removed + + get localUploadTabClass() { + return this.replacementActiveTab === "upload" + ? "source-tab active" + : "source-tab"; + } + + get showPropertyImagesTab() { + return this.replacementActiveTab === "property"; + } + + get showLocalUploadTab() { + return this.replacementActiveTab === "upload"; + } + + // Image insertion modal getters + get isImageModalOpen() { + return this.showImageModal; + } + + get imageCategories() { + const categories = [ + { label: "All Images", value: "all" }, + { label: "Exterior", value: "exterior" }, + { label: "Interior", value: "interior" }, + { label: "Kitchen", value: "kitchen" }, + { label: "Bedroom", value: "bedroom" }, + { label: "Bathroom", value: "bathroom" }, + { label: "Living Room", value: "living" }, + { label: "Maps", value: "maps" }, + { label: "None", value: "none" }, + ]; + return categories; + } + get filteredPropertyImages() { + if (!this.propertyImages || this.propertyImages.length === 0) { + return []; } - // z-index functions removed - - get localUploadTabClass() { - return this.replacementActiveTab === 'upload' ? 'source-tab active' : 'source-tab'; + if (this.selectedImageCategory === "all") { + return this.propertyImages; } - get showPropertyImagesTab() { - return this.replacementActiveTab === 'property'; - } + const filtered = this.propertyImages.filter((image) => { + const category = image.category ? image.category.toLowerCase() : "none"; + return category === this.selectedImageCategory; + }); - get showLocalUploadTab() { - return this.replacementActiveTab === 'upload'; - } - - // Image insertion modal getters - get isImageModalOpen() { - return this.showImageModal; - } - - get imageCategories() { - const categories = [ - { label: 'All Images', value: 'all' }, - { label: 'Exterior', value: 'exterior' }, - { label: 'Interior', value: 'interior' }, - { label: 'Kitchen', value: 'kitchen' }, - { label: 'Bedroom', value: 'bedroom' }, - { label: 'Bathroom', value: 'bathroom' }, - { label: 'Living Room', value: 'living' }, - { label: 'Maps', value: 'maps' }, - { label: 'None', value: 'none' } - ]; - return categories; - } - get filteredPropertyImages() { - - if (!this.propertyImages || this.propertyImages.length === 0) { - return []; + return filtered; + } + + get propertyTabClass() { + return this.imageSource === "property" ? "tab-btn active" : "tab-btn"; + } + + get localTabClass() { + return this.imageSource === "local" ? "tab-btn active" : "tab-btn"; + } + + getCategoryButtonClass(categoryValue) { + return this.selectedImageCategory === categoryValue + ? "category-btn active" + : "category-btn"; + } + + get showPropertyImagesSection() { + return this.imageSource === "property"; + } + + get showLocalUploadSection() { + return this.imageSource === "local"; + } + + get isInsertButtonDisabled() { + const disabled = !this.selectedImageUrl || this.selectedImageUrl === ""; + return disabled; + } + + // Computed properties for template selection + get isBlankTemplateSelected() { + return this.selectedTemplateId === "blank-template"; + } + + get isEverkindTemplateSelected() { + return this.selectedTemplateId === "everkind-template"; + } + + get isShiftTemplateSelected() { + return this.selectedTemplateId === "shift-template"; + } + + get isSaintbartsTemplateSelected() { + return this.selectedTemplateId === "saintbarts-template"; + } + + get isLearnoyTemplateSelected() { + return this.selectedTemplateId === "learnoy-template"; + } + + get isLeafampTemplateSelected() { + return this.selectedTemplateId === "leafamp-template"; + } + + get isCoreshiftTemplateSelected() { + return this.selectedTemplateId === "coreshift-template"; + } + + get isModernHomeTemplateSelected() { + return this.selectedTemplateId === "modern-home-template"; + } + + get isGrandOakVillaTemplateSelected() { + return this.selectedTemplateId === "grand-oak-villa-template"; + } + + get isSampleTemplateSelected() { + return this.selectedTemplateId === "sample-template"; + } + + get isSerenityHouseTemplateSelected() { + return this.selectedTemplateId === "serenity-house-template"; + } + + get isLuxuryMansionTemplateSelected() { + return this.selectedTemplateId === "luxury-mansion-template"; + } + + // Image review computed properties + get isFirstImage() { + return this.currentImageIndex === 0; + } + + get isLastImage() { + return this.currentImageIndex === this.totalImages - 1; + } + + get displayImageIndex() { + return this.currentImageIndex + 1; + } + + // Computed properties for step visibility + get step1Class() { + return this.currentStep === 1 ? "step-content active" : "step-content"; + } + + get step2Class() { + return this.currentStep === 2 ? "step-content active" : "step-content"; + } + get step3Class() { + return this.currentStep === 3 ? "step-content active" : "step-content"; + } + + // Step navigation classes (for header stepper circles only) + get step1NavClass() { + return this.currentStep === 1 ? "active-circle active" : ""; + } + + get step2NavClass() { + return this.currentStep === 2 ? "active-circle active" : ""; + } + + get step3NavClass() { + return this.currentStep === 3 ? "active-circle active" : ""; + } + + // Inline styles to hard-force blue fill for active circles + get step1NavStyle() { + return this.currentStep === 1 + ? "background:#1e88e5;border-color:#1e88e5;color:#ffffff;" + : ""; + } + get step2NavStyle() { + return this.currentStep === 2 + ? "background:#1e88e5;border-color:#1e88e5;color:#ffffff;" + : ""; + } + get step3NavStyle() { + return this.currentStep === 3 + ? "background:#1e88e5;border-color:#1e88e5;color:#ffffff;" + : ""; + } + + // Image availability flag + get hasPropertyImages() { + return ( + Array.isArray(this.realPropertyImages) && + this.realPropertyImages.length > 0 + ); + } + + // Dynamic class for pdf viewport to enable placeholder styling when no images + get pdfViewportClass() { + return this.hasPropertyImages ? "pdf-viewport" : "pdf-viewport no-images"; + } + + originalTemplateGridHTML = null; + originalStylesText = null; + + renderedCallback() { + if (this.currentStep === 1) { + const gridLive = this.template.querySelector("#all-templates"); + if (gridLive) { + // Always keep a clean snapshot from the DOM the first time we hit step 1 in a render pass + if ( + this.originalTemplateGridHTML === null || + this.originalTemplateGridHTML.length < 50 + ) { + this.originalTemplateGridHTML = gridLive.innerHTML; } - - if (this.selectedImageCategory === 'all') { - return this.propertyImages; - } - - const filtered = this.propertyImages.filter(image => { - const category = image.category ? image.category.toLowerCase() : 'none'; - return category === this.selectedImageCategory; - }); - - return filtered; + } } - - get propertyTabClass() { - return this.imageSource === 'property' ? 'tab-btn active' : 'tab-btn'; + if (this.originalTemplateGridHTML === null) { + const grid = this.template.querySelector("#all-templates"); + if (grid) { + this.originalTemplateGridHTML = grid.innerHTML; + } } - - get localTabClass() { - return this.imageSource === 'local' ? 'tab-btn active' : 'tab-btn'; - } - - getCategoryButtonClass(categoryValue) { - return this.selectedImageCategory === categoryValue ? 'category-btn active' : 'category-btn'; - } - - get showPropertyImagesSection() { - return this.imageSource === 'property'; - } - - get showLocalUploadSection() { - return this.imageSource === 'local'; - } - - get isInsertButtonDisabled() { - const disabled = !this.selectedImageUrl || this.selectedImageUrl === ''; - return disabled; - } - - // Computed properties for template selection - get isBlankTemplateSelected() { - return this.selectedTemplateId === 'blank-template'; - } - - get isEverkindTemplateSelected() { - return this.selectedTemplateId === 'everkind-template'; - } - - get isShiftTemplateSelected() { - return this.selectedTemplateId === 'shift-template'; - } - - get isSaintbartsTemplateSelected() { - return this.selectedTemplateId === 'saintbarts-template'; - } - - get isLearnoyTemplateSelected() { - return this.selectedTemplateId === 'learnoy-template'; - } - - get isLeafampTemplateSelected() { - return this.selectedTemplateId === 'leafamp-template'; - } - - get isCoreshiftTemplateSelected() { - return this.selectedTemplateId === 'coreshift-template'; - } - - get isModernHomeTemplateSelected() { - return this.selectedTemplateId === 'modern-home-template'; - } - - get isGrandOakVillaTemplateSelected() { - return this.selectedTemplateId === 'grand-oak-villa-template'; - } - - get isSampleTemplateSelected() { - return this.selectedTemplateId === 'sample-template'; - } - - get isSerenityHouseTemplateSelected() { - return this.selectedTemplateId === 'serenity-house-template'; - } - - get isLuxuryMansionTemplateSelected() { - return this.selectedTemplateId === 'luxury-mansion-template'; + if (this.originalStylesText === null) { + const styles = this.template.querySelectorAll("style"); + this.originalStylesText = Array.from(styles).map((s) => s.textContent); } + } - // Image review computed properties - get isFirstImage() { - return this.currentImageIndex === 0; - } + resetStep1Grid() { + const grid = this.template.querySelector("#all-templates"); + if (!grid) return; + // Clear any selected states on cards + const cards = grid.querySelectorAll(".template-card"); + cards.forEach((card) => { + card.classList.remove("selected"); + }); + // DON'T clear selectedTemplateId - preserve the selection + // this.selectedTemplateId = ''; + } - get isLastImage() { - return this.currentImageIndex === this.totalImages - 1; - } + restoreComponentStyles() { + if (!this.originalStylesText) return; + const styles = this.template.querySelectorAll("style"); + const snapshots = this.originalStylesText; + styles.forEach((styleEl, idx) => { + if ( + snapshots[idx] !== undefined && + styleEl.textContent !== snapshots[idx] + ) { + styleEl.textContent = snapshots[idx]; + } + }); + } + get isNextButtonDisabled() { + return !this.selectedTemplateId; + } - get displayImageIndex() { - return this.currentImageIndex + 1; - } + get isNextButtonDisabledStep2() { + return !this.selectedPropertyId; + } - // Computed properties for step visibility - get step1Class() { - return this.currentStep === 1 ? 'step-content active' : 'step-content'; - } - - get step2Class() { - return this.currentStep === 2 ? 'step-content active' : 'step-content'; - } - - get step3Class() { - return this.currentStep === 3 ? 'step-content active' : 'step-content'; - } - - // Step navigation classes (for header stepper circles only) - get step1NavClass() { - return this.currentStep === 1 ? 'active-circle active' : ''; - } - - get step2NavClass() { - return this.currentStep === 2 ? 'active-circle active' : ''; - } - - get step3NavClass() { - return this.currentStep === 3 ? 'active-circle active' : ''; - } - - // Inline styles to hard-force blue fill for active circles - get step1NavStyle() { - return this.currentStep === 1 ? 'background:#1e88e5;border-color:#1e88e5;color:#ffffff;' : ''; - } - get step2NavStyle() { - return this.currentStep === 2 ? 'background:#1e88e5;border-color:#1e88e5;color:#ffffff;' : ''; - } - get step3NavStyle() { - return this.currentStep === 3 ? 'background:#1e88e5;border-color:#1e88e5;color:#ffffff;' : ''; - } - - // Image availability flag - get hasPropertyImages() { - return Array.isArray(this.realPropertyImages) && this.realPropertyImages.length > 0; - } - - // Dynamic class for pdf viewport to enable placeholder styling when no images - get pdfViewportClass() { - return this.hasPropertyImages ? 'pdf-viewport' : 'pdf-viewport no-images'; - } - - originalTemplateGridHTML = null; - originalStylesText = null; - - renderedCallback() { - if (this.currentStep === 1) { - const gridLive = this.template.querySelector('#all-templates'); - if (gridLive) { - // Always keep a clean snapshot from the DOM the first time we hit step 1 in a render pass - if (this.originalTemplateGridHTML === null || this.originalTemplateGridHTML.length < 50) { - this.originalTemplateGridHTML = gridLive.innerHTML; - } - } - } - if (this.originalTemplateGridHTML === null) { - const grid = this.template.querySelector('#all-templates'); - if (grid) { - this.originalTemplateGridHTML = grid.innerHTML; - } - } - if (this.originalStylesText === null) { - const styles = this.template.querySelectorAll('style'); - this.originalStylesText = Array.from(styles).map(s => s.textContent); - } - } - - resetStep1Grid() { - const grid = this.template.querySelector('#all-templates'); - if (!grid) return; - // Clear any selected states on cards - const cards = grid.querySelectorAll('.template-card'); - cards.forEach(card => { - card.classList.remove('selected'); - }); - // DON'T clear selectedTemplateId - preserve the selection - // this.selectedTemplateId = ''; - } - - restoreComponentStyles() { - if (!this.originalStylesText) return; - const styles = this.template.querySelectorAll('style'); - const snapshots = this.originalStylesText; - styles.forEach((styleEl, idx) => { - if (snapshots[idx] !== undefined && styleEl.textContent !== snapshots[idx]) { - styleEl.textContent = snapshots[idx]; - } - }); - } - get isNextButtonDisabled() { - return !this.selectedTemplateId; - } - - get isNextButtonDisabledStep2() { - return !this.selectedPropertyId; - } - - // Wire method to get properties - @wire(getProperties) - wiredProperties({ error, data }) { - if (data) { - this.properties = data; - if (this._deferHydrateFromUrl) { - try { this._deferHydrateFromUrl(); } catch (e) {} - this._deferHydrateFromUrl = null; - } - } else if (error) { - this.error = 'Error loading properties: ' + error.body.message; - } - } - - // Derived list with selected flag to force dropdown selection rendering - get propertiesWithSelected() { - const selectedId = this.selectedPropertyId || ''; - return (this.properties || []).map(p => ({ - ...p, - _selected: p.Id === selectedId - })); - } - - // Template selection handler - handleTemplateSelect(event) { - const templateId = event.currentTarget.dataset.templateId; - - // Clear cached content when selecting a new template - this.cachedTemplateContent = null; - - // Set the selected template - switch (templateId) { - case 'blank-template': - this.selectedTemplateId = 'blank-template'; - break; - case 'everkind-template': - this.selectedTemplateId = 'everkind-template'; - break; - case 'shift-template': - this.selectedTemplateId = 'shift-template'; - break; - case 'saintbarts-template': - this.selectedTemplateId = 'saintbarts-template'; - break; - case 'learnoy-template': - this.selectedTemplateId = 'learnoy-template'; - break; - case 'leafamp-template': - this.selectedTemplateId = 'leafamp-template'; - break; - case 'coreshift-template': - this.selectedTemplateId = 'coreshift-template'; - break; - case 'modern-home-template': - this.selectedTemplateId = 'modern-home-template'; - break; - case 'grand-oak-villa-template': - this.selectedTemplateId = 'grand-oak-villa-template'; - break; - case 'serenity-house-template': - this.selectedTemplateId = 'serenity-house-template'; - break; - case 'sample-template': - this.selectedTemplateId = 'sample-template'; - break; - case 'luxury-mansion-template': - this.selectedTemplateId = 'luxury-mansion-template'; - break; - default: - break; - } - - - // Visually mark the selected template card with a black border + // Wire method to get properties + @wire(getProperties) + wiredProperties({ error, data }) { + if (data) { + this.properties = data; + if (this._deferHydrateFromUrl) { try { - this.template.querySelectorAll('.template-card').forEach(card => { - card.classList.remove('selected'); - }); - if (event.currentTarget && event.currentTarget.classList) { - event.currentTarget.classList.add('selected'); - } - } catch (e) { - } + this._deferHydrateFromUrl(); + } catch (e) {} + this._deferHydrateFromUrl = null; + } + } else if (error) { + this.error = "Error loading properties: " + error.body.message; + } + } + + // Derived list with selected flag to force dropdown selection rendering + get propertiesWithSelected() { + const selectedId = this.selectedPropertyId || ""; + return (this.properties || []).map((p) => ({ + ...p, + _selected: p.Id === selectedId, + })); + } + + // Template selection handler + handleTemplateSelect(event) { + const templateId = event.currentTarget.dataset.templateId; + + // Clear cached content when selecting a new template + this.cachedTemplateContent = null; + + // Set the selected template + switch (templateId) { + case "blank-template": + this.selectedTemplateId = "blank-template"; + break; + case "everkind-template": + this.selectedTemplateId = "everkind-template"; + break; + case "shift-template": + this.selectedTemplateId = "shift-template"; + break; + case "saintbarts-template": + this.selectedTemplateId = "saintbarts-template"; + break; + case "learnoy-template": + this.selectedTemplateId = "learnoy-template"; + break; + case "leafamp-template": + this.selectedTemplateId = "leafamp-template"; + break; + case "coreshift-template": + this.selectedTemplateId = "coreshift-template"; + break; + case "modern-home-template": + this.selectedTemplateId = "modern-home-template"; + break; + case "grand-oak-villa-template": + this.selectedTemplateId = "grand-oak-villa-template"; + break; + case "serenity-house-template": + this.selectedTemplateId = "serenity-house-template"; + break; + case "sample-template": + this.selectedTemplateId = "sample-template"; + break; + case "luxury-mansion-template": + this.selectedTemplateId = "luxury-mansion-template"; + break; + default: + break; } - resetTemplateSelections() { - this.selectedTemplateId = ''; + // Visually mark the selected template card with a black border + try { + this.template.querySelectorAll(".template-card").forEach((card) => { + card.classList.remove("selected"); + }); + if (event.currentTarget && event.currentTarget.classList) { + event.currentTarget.classList.add("selected"); + } + } catch (e) {} + } + + resetTemplateSelections() { + this.selectedTemplateId = ""; + } + // Page size change handler + handlePageSizeChange(event) { + const newPageSize = event.target.value; + this.selectedPageSize = newPageSize; + + // Update the preview frame with new dimensions + this.updatePreviewFrameSize(newPageSize); + + // Re-fit to width when page size changes + setTimeout(() => this.fitToWidth(), 0); + } + + // ===== Step 3 viewport zoom controls ===== + get zoomPercent() { + try { + return `${Math.round((this.zoom || 1) * 100)}%`; + } catch (e) { + return "100%"; + } + } + + get pdfCanvasStyle() { + const scale = this.zoom || 1; + return `transform: scale(${scale}) !important;`; + } + + zoomIn() { + this.zoom = Math.min((this.zoom || 1) + 0.1, 3); + } + zoomOut() { + this.zoom = Math.max((this.zoom || 1) - 0.1, 0.3); + } + resetZoom() { + this.zoom = 1; + } + + // Handler methods for HTML onclick events + handleZoomIn() { + this.zoomIn(); + } + handleZoomOut() { + this.zoomOut(); + } + handleZoom100() { + this.resetZoom(); + } + handleFitWidth() { + this.fitToWidth(); + } + handleFitPage() { + this.fitToPage(); + } + + // Page management methods + addNewPage() { + const newPage = { + id: `page-${Date.now()}`, + content: "

New page content...

", + }; + this.previewPages = [...this.previewPages, newPage]; + this.updatePageCount(); + } + + updatePageCount() { + const pageInfo = this.template?.querySelector(".page-info"); + if (pageInfo) { + const pageCount = this.previewPages.length || 1; + pageInfo.innerHTML = `${ + this.selectedPageSize + } - ${pageCount} page${pageCount > 1 ? "s" : ""}`; + } + } + + // Split content into pages based on page breaks + splitContentIntoPages(content) { + if (!content) return []; + + // Look for page break markers or split by content length + const pageBreakMarkers = [ + '
', + '
', + ]; + let pages = []; + + // Check if content has explicit page breaks + let hasPageBreaks = false; + pageBreakMarkers.forEach((marker) => { + if (content.includes(marker)) { + hasPageBreaks = true; + } + }); + + if (hasPageBreaks) { + // Split by page breaks + const pageContent = content.split(/]*page-break[^>]*><\/div>/i); + pages = pageContent.map((pageContent, index) => ({ + id: `page-${index + 1}`, + content: pageContent.trim() || "

Empty page

", + })); + } else { + // Single page for now - can be enhanced to auto-split based on content length + pages = [ + { + id: "page-1", + content: content, + }, + ]; } - // Page size change handler - handlePageSizeChange(event) { - const newPageSize = event.target.value; - this.selectedPageSize = newPageSize; - - // Update the preview frame with new dimensions - this.updatePreviewFrameSize(newPageSize); + return pages; + } - // Re-fit to width when page size changes - setTimeout(() => this.fitToWidth(), 0); + // Update preview pages when content changes + updatePreviewPages() { + if (this.htmlContent) { + this.previewPages = this.splitContentIntoPages(this.htmlContent); + } else { + this.previewPages = []; } + this.updatePageCount(); + } - // ===== Step 3 viewport zoom controls ===== - get zoomPercent() { - try { return `${Math.round((this.zoom || 1) * 100)}%`; } catch (e) { return '100%'; } + fitToWidth() { + const container = this.template?.querySelector(".pdf-viewport"); + if (!container) { + return; } + const baseWidth = this.selectedPageSize === "A3" ? 1123 : 794; + const available = Math.max((container.clientWidth || baseWidth) - 32, 100); + this.zoom = Math.max(Math.min(available / baseWidth, 4), 0.3); + } - get pdfCanvasStyle() { - const scale = this.zoom || 1; - return `transform: scale(${scale}) !important;`; + fitToPage() { + const container = this.template?.querySelector(".pdf-viewport"); + if (!container) { + return; } + const baseWidth = this.selectedPageSize === "A3" ? 1123 : 794; + const baseHeight = this.selectedPageSize === "A3" ? 1587 : 1123; + const availableW = Math.max((container.clientWidth || baseWidth) - 32, 100); + const availableH = Math.max( + (container.clientHeight || baseHeight) - 32, + 100 + ); + const scaleW = availableW / baseWidth; + const scaleH = availableH / baseHeight; + this.zoom = Math.max(Math.min(Math.min(scaleW, scaleH), 4), 0.3); + } + // Update preview frame size based on selected page size + updatePreviewFrameSize(pageSize) { + const previewFrame = this.template.querySelector( + ".enhanced-editor-content" + ); + if (previewFrame) { + // Update the data attribute for the CSS content + previewFrame.setAttribute("data-page-size", pageSize); - zoomIn() { this.zoom = Math.min((this.zoom || 1) + 0.1, 3); } - zoomOut() { this.zoom = Math.max((this.zoom || 1) - 0.1, 0.3); } - resetZoom() { this.zoom = 1; } - - // Handler methods for HTML onclick events - handleZoomIn() { this.zoomIn(); } - handleZoomOut() { this.zoomOut(); } - handleZoom100() { this.resetZoom(); } - handleFitWidth() { this.fitToWidth(); } - handleFitPage() { this.fitToPage(); } - - // Page management methods - addNewPage() { - const newPage = { - id: `page-${Date.now()}`, - content: '

New page content...

' - }; - this.previewPages = [...this.previewPages, newPage]; - this.updatePageCount(); - } - - updatePageCount() { - const pageInfo = this.template?.querySelector('.page-info'); - if (pageInfo) { - const pageCount = this.previewPages.length || 1; - pageInfo.innerHTML = `${this.selectedPageSize} - ${pageCount} page${pageCount > 1 ? 's' : ''}`; - } - } - - // Split content into pages based on page breaks - splitContentIntoPages(content) { - if (!content) return []; - - // Look for page break markers or split by content length - const pageBreakMarkers = ['
', '
']; - let pages = []; - - // Check if content has explicit page breaks - let hasPageBreaks = false; - pageBreakMarkers.forEach(marker => { - if (content.includes(marker)) { - hasPageBreaks = true; - } - }); - - if (hasPageBreaks) { - // Split by page breaks - const pageContent = content.split(/]*page-break[^>]*><\/div>/i); - pages = pageContent.map((pageContent, index) => ({ - id: `page-${index + 1}`, - content: pageContent.trim() || '

Empty page

' - })); - } else { - // Single page for now - can be enhanced to auto-split based on content length - pages = [{ - id: 'page-1', - content: content - }]; - } - - return pages; - } - - // Update preview pages when content changes - updatePreviewPages() { - if (this.htmlContent) { - this.previewPages = this.splitContentIntoPages(this.htmlContent); - } else { - this.previewPages = []; - } - this.updatePageCount(); + // Re-render content with new page size + this.renderContentInPages(pageSize); + + // Update page count based on new page size + this.updatePageCountForSize(pageSize); } + } - fitToWidth() { - const container = this.template?.querySelector('.pdf-viewport'); - if (!container) { return; } - const baseWidth = this.selectedPageSize === 'A3' ? 1123 : 794; - const available = Math.max((container.clientWidth || baseWidth) - 32, 100); - this.zoom = Math.max(Math.min(available / baseWidth, 4), 0.3); - } + // Render content in separate pages based on selected size + renderContentInPages(pageSize) { + const previewFrame = this.template.querySelector( + ".enhanced-editor-content" + ); + if (!previewFrame) return; - fitToPage() { - const container = this.template?.querySelector('.pdf-viewport'); - if (!container) { return; } - const baseWidth = this.selectedPageSize === 'A3' ? 1123 : 794; - const baseHeight = this.selectedPageSize === 'A3' ? 1587 : 1123; - const availableW = Math.max((container.clientWidth || baseWidth) - 32, 100); - const availableH = Math.max((container.clientHeight || baseHeight) - 32, 100); - const scaleW = availableW / baseWidth; - const scaleH = availableH / baseHeight; - this.zoom = Math.max(Math.min(Math.min(scaleW, scaleH), 4), 0.3); - } - // Update preview frame size based on selected page size - updatePreviewFrameSize(pageSize) { - const previewFrame = this.template.querySelector('.enhanced-editor-content'); - if (previewFrame) { - // Update the data attribute for the CSS content - previewFrame.setAttribute('data-page-size', pageSize); - - - // Re-render content with new page size - this.renderContentInPages(pageSize); - - // Update page count based on new page size - this.updatePageCountForSize(pageSize); - } - } + // Get the current template content + const templateHTML = this.createTemplateHTML(); + if (!templateHTML) return; - // Render content in separate pages based on selected size - renderContentInPages(pageSize) { - const previewFrame = this.template.querySelector('.enhanced-editor-content'); - if (!previewFrame) return; + // Clear existing content + previewFrame.innerHTML = ""; - // Get the current template content - const templateHTML = this.createTemplateHTML(); - if (!templateHTML) return; + // Split content into pages based on size + const pages = this.splitContentIntoPages(templateHTML, pageSize); - // Clear existing content - previewFrame.innerHTML = ''; + // Create page containers + pages.forEach((pageContent, index) => { + const pageContainer = document.createElement("div"); + pageContainer.className = `preview-page page-size-${pageSize.toLowerCase()}`; + pageContainer.setAttribute("data-page-number", `Page ${index + 1}`); + pageContainer.innerHTML = pageContent; + previewFrame.appendChild(pageContainer); + }); + } + // Split HTML content into pages based on page size + splitContentIntoPages(htmlContent, pageSize) { + const pages = []; + let currentPage = ""; + let currentHeight = 0; - // Split content into pages based on size - const pages = this.splitContentIntoPages(templateHTML, pageSize); - - // Create page containers - pages.forEach((pageContent, index) => { - const pageContainer = document.createElement('div'); - pageContainer.className = `preview-page page-size-${pageSize.toLowerCase()}`; - pageContainer.setAttribute('data-page-number', `Page ${index + 1}`); - pageContainer.innerHTML = pageContent; - previewFrame.appendChild(pageContainer); - }); + // Define page heights in mm + const pageHeights = { + A4: 297, + A3: 420, + }; - } + const maxHeight = pageHeights[pageSize] || 297; - // Split HTML content into pages based on page size - splitContentIntoPages(htmlContent, pageSize) { - const pages = []; - let currentPage = ''; - let currentHeight = 0; - - // Define page heights in mm - const pageHeights = { - 'A4': 297, - 'A3': 420 - }; - - const maxHeight = pageHeights[pageSize] || 297; - - // Create a temporary div to parse HTML - const tempDiv = document.createElement('div'); - tempDiv.innerHTML = htmlContent; - - // Get all direct children (brochure pages) - const children = Array.from(tempDiv.children); - - children.forEach((child, index) => { - // If it's a brochure-page div, treat it as a separate page - if (child.classList.contains('brochure-page')) { - if (currentPage) { - pages.push(currentPage); - } - currentPage = child.outerHTML; - currentHeight = 0; - } else { - // For other content, check if it fits in current page - const estimatedHeight = this.estimateElementHeight(child, pageSize); - - if (currentHeight + estimatedHeight > maxHeight && currentPage) { - // Start new page - pages.push(currentPage); - currentPage = child.outerHTML; - currentHeight = estimatedHeight; - } else { - // Add to current page - currentPage += child.outerHTML; - currentHeight += estimatedHeight; - } - } - }); - - // Add the last page + // Create a temporary div to parse HTML + const tempDiv = document.createElement("div"); + tempDiv.innerHTML = htmlContent; + + // Get all direct children (brochure pages) + const children = Array.from(tempDiv.children); + + children.forEach((child, index) => { + // If it's a brochure-page div, treat it as a separate page + if (child.classList.contains("brochure-page")) { if (currentPage) { - pages.push(currentPage); + pages.push(currentPage); } - - return pages; + currentPage = child.outerHTML; + currentHeight = 0; + } else { + // For other content, check if it fits in current page + const estimatedHeight = this.estimateElementHeight(child, pageSize); + + if (currentHeight + estimatedHeight > maxHeight && currentPage) { + // Start new page + pages.push(currentPage); + currentPage = child.outerHTML; + currentHeight = estimatedHeight; + } else { + // Add to current page + currentPage += child.outerHTML; + currentHeight += estimatedHeight; + } + } + }); + + // Add the last page + if (currentPage) { + pages.push(currentPage); } - // Estimate element height based on page size - estimateElementHeight(element, pageSize) { - // Base height estimation in mm - let baseHeight = 50; // Default height - - // Adjust based on element type - if (element.tagName === 'IMG') { - baseHeight = 100; - } else if (element.tagName === 'H1') { - baseHeight = 30; - } else if (element.tagName === 'H2') { - baseHeight = 25; - } else if (element.tagName === 'P') { - baseHeight = 20; - } else if (element.tagName === 'DIV') { - baseHeight = 80; - } - - // Adjust for page size - if (pageSize === 'A3') { - baseHeight *= 1.4; // A3 is larger - } - - return baseHeight; + return pages; + } + + // Estimate element height based on page size + estimateElementHeight(element, pageSize) { + // Base height estimation in mm + let baseHeight = 50; // Default height + + // Adjust based on element type + if (element.tagName === "IMG") { + baseHeight = 100; + } else if (element.tagName === "H1") { + baseHeight = 30; + } else if (element.tagName === "H2") { + baseHeight = 25; + } else if (element.tagName === "P") { + baseHeight = 20; + } else if (element.tagName === "DIV") { + baseHeight = 80; } - // Update page count based on selected page size - updatePageCountForSize(pageSize) { - const previewFrame = this.template.querySelector('.enhanced-editor-content'); - if (previewFrame) { - let pageHeight = 297; // Default A4 height in mm - - switch (pageSize) { - case 'A3': - pageHeight = 420; // A3 height in mm - break; - case 'A4': - default: - pageHeight = 297; // A4 height in mm - break; - } - - // Calculate content height and estimate pages needed - const contentHeight = previewFrame.scrollHeight; - const contentHeightMm = (contentHeight * 0.264583); // Convert px to mm - const pagesNeeded = Math.ceil(contentHeightMm / pageHeight); - - this.pageCount = Math.max(1, Math.min(pagesNeeded, 20)); // Limit to 1-20 pages - } + // Adjust for page size + if (pageSize === "A3") { + baseHeight *= 1.4; // A3 is larger } - // Navigation methods - nextStep() { - if (this.currentStep === 1 && !this.selectedTemplateId) { - this.showError('Please select a template first.'); - return; - } - if (this.currentStep < 3) { - this.currentStep++; - // Reset click tracking when changing steps - this.resetImageClickTracking(); - // If moving to step 3, automatically load the template - if (this.currentStep === 3) { - this.loadTemplateInStep3(); - requestAnimationFrame(() => { - this.updatePreviewFrameSize(this.selectedPageSize || 'A4'); - }); - } - this.scrollToTop(); - } - } + return baseHeight; + } - previousStep() { - if (this.currentStep > 1) { - this.currentStep--; - // Reset click tracking when changing steps - this.resetImageClickTracking(); - if (this.currentStep === 1) { - this.resetStep1Grid(); - } - this.scrollToTop(); - } - } - goToStep(event) { - const step = parseInt(event.currentTarget.dataset.step); - this.currentStep = step; - // Reset click tracking when changing steps - this.resetImageClickTracking(); - if (this.currentStep === 1) { - this.resetStep1Grid(); - // Also reconstruct grid HTML from original snapshot if available to fully reset content - if (this.originalTemplateGridHTML && this.originalTemplateGridHTML.length > 50) { - const grid = this.template.querySelector('#all-templates'); - if (grid) { - grid.innerHTML = this.originalTemplateGridHTML; - } - } - // Restore any styles that could have been mutated during step 3 - this.restoreComponentStyles(); - // Rebind click handlers for fresh grid without page reload - requestAnimationFrame(() => { - const cards = this.template.querySelectorAll('#all-templates .template-card'); - cards.forEach(card => { - card.onclick = this.handleTemplateSelect.bind(this); - // Restore selected state if this card matches the selected template - if (this.selectedTemplateId && card.dataset.templateId === this.selectedTemplateId) { - card.classList.add('selected'); - } - }); - }); - } - // If going directly to step 3, load the template only if not already loaded - if (this.currentStep === 3 && (!this.htmlContent || this.htmlContent.trim() === '')) { - this.loadTemplateInStep3(); - requestAnimationFrame(() => { - this.updatePreviewFrameSize(this.selectedPageSize || 'A4'); - // Auto fit width for better initial experience - this.fitToWidth && this.fitToWidth(); - }); - } - this.scrollToTop(); - } + // Update page count based on selected page size + updatePageCountForSize(pageSize) { + const previewFrame = this.template.querySelector( + ".enhanced-editor-content" + ); + if (previewFrame) { + let pageHeight = 297; // Default A4 height in mm - // Scroll to top of page when changing steps - scrollToTop() { - window.scrollTo({ - top: 0, - behavior: 'smooth' + switch (pageSize) { + case "A3": + pageHeight = 420; // A3 height in mm + break; + case "A4": + default: + pageHeight = 297; // A4 height in mm + break; + } + + // Calculate content height and estimate pages needed + const contentHeight = previewFrame.scrollHeight; + const contentHeightMm = contentHeight * 0.264583; // Convert px to mm + const pagesNeeded = Math.ceil(contentHeightMm / pageHeight); + + this.pageCount = Math.max(1, Math.min(pagesNeeded, 20)); // Limit to 1-20 pages + } + } + + // Navigation methods + nextStep() { + if (this.currentStep === 1 && !this.selectedTemplateId) { + this.showError("Please select a template first."); + return; + } + if (this.currentStep < 3) { + this.currentStep++; + // Reset click tracking when changing steps + this.resetImageClickTracking(); + // If moving to step 3, automatically load the template + if (this.currentStep === 3) { + this.loadTemplateInStep3(); + requestAnimationFrame(() => { + this.updatePreviewFrameSize(this.selectedPageSize || "A4"); }); + } + this.scrollToTop(); } + } - // Load template content into step 3 enhanced editor - async loadTemplateInStep3() { - if (this.selectedTemplateId && this.selectedPropertyId) { - try { - // Use cached content if available to prevent regeneration - if (this.cachedTemplateContent && this.cachedTemplateContent.templateId === this.selectedTemplateId && this.cachedTemplateContent.propertyId === this.selectedPropertyId) { - this.htmlContent = this.cachedTemplateContent.html; - this.updatePreviewPages(); - this.updatePreviewFrameSize(this.selectedPageSize); - setTimeout(() => { - this.updatePageCountForSize(this.selectedPageSize); - this.updatePageCount(); - this.fitToWidth && this.fitToWidth(); - }, 100); - return; - } - - // Ensure property images are loaded before creating template - if (this.realPropertyImages.length === 0) { - await this.loadPropertyImages(); - } - - const templateHTML = this.createTemplateHTML(); - - // Replace any hardcoded background-image URLs with property images - const processedTemplateHTML = this.replaceBackgroundImagesInHTML(templateHTML); - - // Cache the template content - this.cachedTemplateContent = { - templateId: this.selectedTemplateId, - propertyId: this.selectedPropertyId, - html: processedTemplateHTML - }; - - // Set the HTML content for the template binding - this.htmlContent = processedTemplateHTML; - - // Update preview pages with the new content - this.updatePreviewPages(); - - // Set initial page size class and data attribute - this.updatePreviewFrameSize(this.selectedPageSize); - - // Update page count after template is loaded - setTimeout(() => { - this.updatePageCountForSize(this.selectedPageSize); - this.updatePageCount(); // Update page count display - // After content settles, fit viewport to width - this.fitToWidth && this.fitToWidth(); - }, 100); - } catch (error) { - this.error = 'Error loading template: ' + error.message; - - // Show error message in preview frame - const previewFrame = this.template.querySelector('.enhanced-editor-content'); - if (previewFrame) { - previewFrame.innerHTML = `
+ previousStep() { + if (this.currentStep > 1) { + this.currentStep--; + // Reset click tracking when changing steps + this.resetImageClickTracking(); + if (this.currentStep === 1) { + this.resetStep1Grid(); + } + this.scrollToTop(); + } + } + + + replaceStaticWithDynamic(content) { +if (!this.propertyData || !content) return content; + +let result = content; + +// Replace hardcoded values with actual property data +result = result.replace(/Concorde Tower/g, this.propertyData.propertyName || 'Property Name'); +result = result.replace(/AED 81,999/g, this.propertyData.rentPriceMin || this.propertyData.price || 'Price'); +result = result.replace(/Modern Villa/g, this.propertyData.propertyName || 'Property Name'); +result = result.replace(/AED 2,500,000/g, this.propertyData.salePriceMin || this.propertyData.price || 'Price'); +result = result.replace(/Dubai/g, this.propertyData.city || 'Location'); +result = result.replace(/This beautiful property offers exceptional value and modern amenities\./g, +this.propertyData.descriptionEnglish || 'Property description'); + +return result; +} + + goToStep(event) { + const step = parseInt(event.currentTarget.dataset.step); + this.currentStep = step; + // Reset click tracking when changing steps + this.resetImageClickTracking(); + if (this.currentStep === 1) { + this.resetStep1Grid(); + // Also reconstruct grid HTML from original snapshot if available to fully reset content + if ( + this.originalTemplateGridHTML && + this.originalTemplateGridHTML.length > 50 + ) { + const grid = this.template.querySelector("#all-templates"); + if (grid) { + grid.innerHTML = this.originalTemplateGridHTML; + } + } + // Restore any styles that could have been mutated during step 3 + this.restoreComponentStyles(); + // Rebind click handlers for fresh grid without page reload + requestAnimationFrame(() => { + const cards = this.template.querySelectorAll( + "#all-templates .template-card" + ); + cards.forEach((card) => { + card.onclick = this.handleTemplateSelect.bind(this); + // Restore selected state if this card matches the selected template + if ( + this.selectedTemplateId && + card.dataset.templateId === this.selectedTemplateId + ) { + card.classList.add("selected"); + } + }); + }); + } + // If going directly to step 3, load the template only if not already loaded + if ( + this.currentStep === 3 && + (!this.htmlContent || this.htmlContent.trim() === "") + ) { + this.loadTemplateInStep3(); + requestAnimationFrame(() => { + this.updatePreviewFrameSize(this.selectedPageSize || "A4"); + // Auto fit width for better initial experience + this.fitToWidth && this.fitToWidth(); + }); + } + this.scrollToTop(); + } + + // Scroll to top of page when changing steps + scrollToTop() { + window.scrollTo({ + top: 0, + behavior: "smooth", + }); + } + // Load template content into step 3 enhanced editor + async loadTemplateInStep3() { + if (this.selectedTemplateId && this.selectedPropertyId) { + try { + // Use cached content if available to prevent regeneration + if ( + this.cachedTemplateContent && + this.cachedTemplateContent.templateId === this.selectedTemplateId && + this.cachedTemplateContent.propertyId === this.selectedPropertyId + ) { + this.htmlContent = this.cachedTemplateContent.html; + this.updatePreviewPages(); + this.updatePreviewFrameSize(this.selectedPageSize); + setTimeout(() => { + this.updatePageCountForSize(this.selectedPageSize); + this.updatePageCount(); + this.fitToWidth && this.fitToWidth(); + }, 100); + return; + } + + // Ensure property images are loaded before creating template + if (this.realPropertyImages.length === 0) { + await this.loadPropertyImages(); + } + + const templateHTML = this.createTemplateHTML(); + + // Replace any hardcoded background-image URLs with property images + const processedTemplateHTML = + this.replaceBackgroundImagesInHTML(templateHTML); + + // Cache the template content + this.cachedTemplateContent = { + templateId: this.selectedTemplateId, + propertyId: this.selectedPropertyId, + html: processedTemplateHTML, + }; + + // Set the HTML content for the template binding + this.htmlContent = processedTemplateHTML; + + // Update preview pages with the new content + this.updatePreviewPages(); + + // Set initial page size class and data attribute + this.updatePreviewFrameSize(this.selectedPageSize); + + // Update page count after template is loaded + setTimeout(() => { + this.updatePageCountForSize(this.selectedPageSize); + this.updatePageCount(); // Update page count display + // After content settles, fit viewport to width + this.fitToWidth && this.fitToWidth(); + // Ensure CSS background images reflect current property images + this.updateCSSBackgroundImages(); + }, 100); + } catch (error) { + this.error = "Error loading template: " + error.message; + + // Show error message in preview frame + const previewFrame = this.template.querySelector( + ".enhanced-editor-content" + ); + if (previewFrame) { + previewFrame.innerHTML = `

Error Loading Template

${error.message}

Please try selecting a different template or contact support.

`; - } - } - } else { - // Show a message if template or property is not selected - const previewFrame = this.template.querySelector('.enhanced-editor-content'); - if (previewFrame) { - if (!this.selectedTemplateId) { - previewFrame.innerHTML = '

No Template Selected

Please go back to step 1 and select a template.

'; - } else if (!this.selectedPropertyId) { - previewFrame.innerHTML = '

No Property Selected

Please go back to step 2 and select a property.

'; - } - } } - } - - // Property selection handler - handlePropertySelection(event) { - this.selectedPropertyId = event.target.value; - - // Clear cached content when selecting a new property - this.cachedTemplateContent = null; - - if (this.selectedPropertyId) { - this.loadPropertyData(); - // Auto-scroll to property preview section after a short delay to ensure data is loaded - setTimeout(() => { - this.scrollToPropertyPreview(); - // Initialize image review with Interior category - // Auto-select category will be handled in loadPropertyImages - }, 300); + } + } else { + // Show a message if template or property is not selected + const previewFrame = this.template.querySelector( + ".enhanced-editor-content" + ); + if (previewFrame) { + if (!this.selectedTemplateId) { + previewFrame.innerHTML = + '

No Template Selected

Please go back to step 1 and select a template.

'; + } else if (!this.selectedPropertyId) { + previewFrame.innerHTML = + '

No Property Selected

Please go back to step 2 and select a property.

'; } + } } + } - // Scroll to property preview section - scrollToPropertyPreview() { - const propertyPreviewSection = this.template.querySelector('.property-details-layout'); - if (propertyPreviewSection) { - // Get the element's position and add offset for better positioning - const elementTop = propertyPreviewSection.offsetTop; - const offset = 100; // 100px offset from the top - - window.scrollTo({ - top: elementTop - offset, - behavior: 'smooth' - }); - } + // Property selection handler + handlePropertySelection(event) { + this.selectedPropertyId = event.target.value; + + // Clear cached content when selecting a new property + this.cachedTemplateContent = null; + + if (this.selectedPropertyId) { + this.loadPropertyData(); + // Auto-scroll to property preview section after a short delay to ensure data is loaded + setTimeout(() => { + this.scrollToPropertyPreview(); + // Initialize image review with Interior category + // Auto-select category will be handled in loadPropertyImages + }, 300); } + } - // Helper function to safely get property values with validation - getPropertyValue(property, fieldPath, defaultValue = 'N/A') { - try { - if (!property) return defaultValue; - - // Handle nested object paths like 'Contact__r.FirstName' - if (fieldPath.includes('.')) { - const parts = fieldPath.split('.'); - let value = property; - for (const part of parts) { - if (value && typeof value === 'object' && value[part] !== undefined) { - value = value[part]; - } else { - return defaultValue; - } - } - return value || defaultValue; - } - - // Handle direct field access - return property[fieldPath] || defaultValue; - } catch (error) { + // Scroll to property preview section + scrollToPropertyPreview() { + const propertyPreviewSection = this.template.querySelector( + ".property-details-layout" + ); + if (propertyPreviewSection) { + // Get the element's position and add offset for better positioning + const elementTop = propertyPreviewSection.offsetTop; + const offset = 100; // 100px offset from the top + + window.scrollTo({ + top: elementTop - offset, + behavior: "smooth", + }); + } + } + + // Helper function to safely get property values with validation + getPropertyValue(property, fieldPath, defaultValue = "N/A") { + try { + if (!property) return defaultValue; + + // Handle nested object paths like 'Contact__r.FirstName' + if (fieldPath.includes(".")) { + const parts = fieldPath.split("."); + let value = property; + for (const part of parts) { + if (value && typeof value === "object" && value[part] !== undefined) { + value = value[part]; + } else { return defaultValue; + } } - } - // Load property data with comprehensive validation - async loadPropertyData() { - const selectedProperty = this.properties.find(prop => prop.Id === this.selectedPropertyId); - if (selectedProperty) { - - // Set the selectedProperty for use in PDF generation - this.selectedProperty = selectedProperty; - - // Helper function for safe property access - const get = (fieldPath, defaultValue = 'N/A') => this.getPropertyValue(selectedProperty, fieldPath, defaultValue); - - this.propertyData = { - // Basic Information - propertyName: (get('pcrm__Title_English__c') !== 'N/A' ? get('pcrm__Title_English__c') : (get('Title_English__c') !== 'N/A' ? get('Title_English__c') : get('Name', 'Property Name'))), - propertyType: get('pcrm__Property_Type__c', 'Property Type'), - status: get('pcrm__Status__c', 'Available'), - referenceNumber: get('Name', 'REF-001'), - - // Location Details - location: get('pcrm__City_Bayut_Dubizzle__c') !== 'N/A' ? get('pcrm__City_Bayut_Dubizzle__c') : - get('pcrm__City_Propertyfinder__c') !== 'N/A' ? get('pcrm__City_Propertyfinder__c') : 'Location', - city: get('pcrm__City_Propertyfinder__c', 'City'), - community: get('pcrm__Community_Propertyfinder__c', 'Community'), - subCommunity: get('pcrm__Sub_Community_Propertyfinder__c', 'Sub Community'), - locality: get('pcrm__Locality_Bayut_Dubizzle__c', 'Locality'), - subLocality: get('pcrm__Sub_Locality_Bayut_Dubizzle__c', 'Sub Locality'), - tower: get('pcrm__Tower_Bayut_Dubizzle__c', 'Tower'), - unitNumber: get('pcrm__Unit_Number__c', 'Unit Number'), - - // Additional Location Fields - cityBayut: get('pcrm__City_Bayut_Dubizzle__c'), - cityPropertyfinder: get('pcrm__City_Propertyfinder__c'), - communityBayut: get('pcrm__Community_Propertyfinder__c'), - subCommunityBayut: get('pcrm__Sub_Community_Propertyfinder__c'), - localityBayut: get('pcrm__Locality_Bayut_Dubizzle__c'), - subLocalityBayut: get('pcrm__Sub_Locality_Bayut_Dubizzle__c'), - towerBayut: get('pcrm__Tower_Bayut_Dubizzle__c'), - - // Rent Availability - rentAvailableFrom: get('pcrm__Rent_Available_From__c'), - rentAvailableTo: get('pcrm__Rent_Available_To__c'), - - // Contact Details - contactName: (() => { - const firstName = get('Contact__r.FirstName', ''); - const lastName = get('Contact__r.LastName', ''); - if (firstName !== 'N/A' && lastName !== 'N/A') { - return `${firstName} ${lastName}`.trim(); - } else if (firstName !== 'N/A') { - return firstName; - } else if (lastName !== 'N/A') { - return lastName; - } - return 'Contact Not Linked'; - })(), - // Prefer property-level fields if populated; otherwise fall back to linked Contact - contactEmail: (() => { - const propEmail = get('Email__c'); - if (propEmail && propEmail !== 'N/A') return propEmail; - const contactEmail = get('Contact__r.Email'); - return contactEmail && contactEmail !== 'N/A' ? contactEmail : 'N/A'; - })(), - contactPhone: (() => { - const propPhone = get('Phone__c'); - if (propPhone && propPhone !== 'N/A') return propPhone; - const contactPhone = get('Contact__r.Phone'); - return contactPhone && contactPhone !== 'N/A' ? contactPhone : 'N/A'; - })(), - - // Specifications - bedrooms: get('pcrm__Bedrooms__c'), - bathrooms: get('pcrm__Bathrooms__c'), - floor: get('pcrm__Floor__c'), - size: get('pcrm__Size__c'), - sizeUnit: 'sq ft', // Default unit since field doesn't exist - buildYear: get('pcrm__Build_Year__c'), - - // Parking & Amenities - parkingSpaces: get('pcrm__Parking_Spaces__c'), - - // Furnishing & Details - furnished: get('pcrm__Furnished__c'), - - // Pricing Information - rentPriceMin: (() => { - const value = get('pcrm__Rent_Price_min__c'); - return value !== 'N/A' && !isNaN(value) ? `AED ${parseFloat(value).toLocaleString()}` : 'N/A'; - })(), - rentPriceMax: (() => { - const value = get('pcrm__Rent_Price_max__c'); - return value !== 'N/A' && !isNaN(value) ? `AED ${parseFloat(value).toLocaleString()}` : 'N/A'; - })(), - salePriceMin: (() => { - const value = get('pcrm__Sale_Price_min__c'); - return value !== 'N/A' && !isNaN(value) ? `AED ${parseFloat(value).toLocaleString()}` : 'N/A'; - })(), - salePriceMax: (() => { - const value = get('pcrm__Sale_Price_max__c'); - return value !== 'N/A' && !isNaN(value) ? `AED ${parseFloat(value).toLocaleString()}` : 'N/A'; - })(), - - // Description & Title - titleEnglish: get('pcrm__Title_English__c'), - descriptionEnglish: get('pcrm__Description_English__c', 'This beautiful property offers exceptional value and modern amenities.'), - - // Offering Type - offeringType: get('pcrm__Offering_Type__c'), - - // Legacy fields for backward compatibility - price: (() => { - const value = get('pcrm__Rent_Price_min__c'); - return value !== 'N/A' && !isNaN(value) ? `AED ${parseFloat(value).toLocaleString()}` : 'Price on Request'; - })(), - area: (() => { - const value = get('pcrm__Size__c'); - return value !== 'N/A' && !isNaN(value) ? `${value} sq ft` : 'N/A'; - })(), - yearBuilt: get('pcrm__Build_Year__c'), - parking: (() => { - const value = get('pcrm__Parking_Spaces__c'); - return value !== 'N/A' && !isNaN(value) ? `${value} Parking Space(s)` : 'N/A'; - })(), - furnishing: get('pcrm__Furnished__c') - }; - + return value || defaultValue; + } - // Load property images - await this.loadPropertyImages(); - } + // Handle direct field access + return property[fieldPath] || defaultValue; + } catch (error) { + return defaultValue; + } + } + // Load property data with comprehensive validation + async loadPropertyData() { + const selectedProperty = this.properties.find( + (prop) => prop.Id === this.selectedPropertyId + ); + if (selectedProperty) { + // Set the selectedProperty for use in PDF generation + this.selectedProperty = selectedProperty; + + // Helper function for safe property access + const get = (fieldPath, defaultValue = "N/A") => + this.getPropertyValue(selectedProperty, fieldPath, defaultValue); + + this.propertyData = { + // Basic Information + propertyName: + get("pcrm__Property_Name_Propertyfinder__c") !== "N/A" + ? get("pcrm__Property_Name_Propertyfinder__c") + : get("Name", "Property Name"), + propertyType: get("pcrm__Property_Type__c", "Property Type"), + status: get("pcrm__Status__c", "Available"), + referenceNumber: get("Name", "REF-001"), + + // Location Details + location: + get("pcrm__City_Bayut_Dubizzle__c") !== "N/A" + ? get("pcrm__City_Bayut_Dubizzle__c") + : get("pcrm__City_Propertyfinder__c") !== "N/A" + ? get("pcrm__City_Propertyfinder__c") + : "Location", + city: get("pcrm__City_Propertyfinder__c", "City"), + community: get("pcrm__Community_Propertyfinder__c", "Community"), + subCommunity: get( + "pcrm__Sub_Community_Propertyfinder__c", + "Sub Community" + ), + locality: get("pcrm__Locality_Bayut_Dubizzle__c", "Locality"), + subLocality: get( + "pcrm__Sub_Locality_Bayut_Dubizzle__c", + "Sub Locality" + ), + tower: get("pcrm__Tower_Bayut_Dubizzle__c", "Tower"), + unitNumber: get("pcrm__Unit_Number__c", "Unit Number"), + + // Additional Location Fields + cityBayut: get("pcrm__City_Bayut_Dubizzle__c"), + cityPropertyfinder: get("pcrm__City_Propertyfinder__c"), + communityBayut: get("pcrm__Community_Propertyfinder__c"), + subCommunityBayut: get("pcrm__Sub_Community_Propertyfinder__c"), + localityBayut: get("pcrm__Locality_Bayut_Dubizzle__c"), + subLocalityBayut: get("pcrm__Sub_Locality_Bayut_Dubizzle__c"), + towerBayut: get("pcrm__Tower_Bayut_Dubizzle__c"), + + // Rent Availability + rentAvailableFrom: get("pcrm__Rent_Available_From__c"), + rentAvailableTo: get("pcrm__Rent_Available_To__c"), + + // Contact Details + contactName: (() => { + const firstName = get("Contact__r.FirstName", ""); + const lastName = get("Contact__r.LastName", ""); + if (firstName !== "N/A" && lastName !== "N/A") { + return `${firstName} ${lastName}`.trim(); + } else if (firstName !== "N/A") { + return firstName; + } else if (lastName !== "N/A") { + return lastName; + } + return "Contact Not Linked"; + })(), + // Prefer property-level fields if populated; otherwise fall back to linked Contact + contactEmail: (() => { + const propEmail = get("Email__c"); + if (propEmail && propEmail !== "N/A") return propEmail; + const contactEmail = get("Contact__r.Email"); + return contactEmail && contactEmail !== "N/A" ? contactEmail : "N/A"; + })(), + contactPhone: (() => { + const propPhone = get("Phone__c"); + if (propPhone && propPhone !== "N/A") return propPhone; + const contactPhone = get("Contact__r.Phone"); + return contactPhone && contactPhone !== "N/A" ? contactPhone : "N/A"; + })(), + + // Specifications + bedrooms: get("pcrm__Bedrooms__c"), + bathrooms: get("pcrm__Bathrooms__c"), + floor: get("pcrm__Floor__c"), + size: get("pcrm__Size__c"), + sizeUnit: "sq ft", // Default unit since field doesn't exist + buildYear: get("pcrm__Build_Year__c"), + + // Parking & Amenities + parkingSpaces: get("pcrm__Parking_Spaces__c"), + + // Furnishing & Details + furnished: get("pcrm__Furnished__c"), + + // Pricing Information + rentPriceMin: (() => { + const value = get("pcrm__Rent_Price_min__c"); + return value !== "N/A" && !isNaN(value) + ? `AED ${parseFloat(value).toLocaleString()}` + : "N/A"; + })(), + rentPriceMax: (() => { + const value = get("pcrm__Rent_Price_max__c"); + return value !== "N/A" && !isNaN(value) + ? `AED ${parseFloat(value).toLocaleString()}` + : "N/A"; + })(), + salePriceMin: (() => { + const value = get("pcrm__Sale_Price_min__c"); + return value !== "N/A" && !isNaN(value) + ? `AED ${parseFloat(value).toLocaleString()}` + : "N/A"; + })(), + salePriceMax: (() => { + const value = get("pcrm__Sale_Price_max__c"); + return value !== "N/A" && !isNaN(value) + ? `AED ${parseFloat(value).toLocaleString()}` + : "N/A"; + })(), + + // Description & Title + titleEnglish: get("pcrm__Title_English__c"), + descriptionEnglish: get( + "pcrm__Description_English__c", + "This beautiful property offers exceptional value and modern amenities." + ), + + // Offering Type + offeringType: get("pcrm__Offering_Type__c"), + + // Legacy fields for backward compatibility + price: (() => { + const value = get("pcrm__Rent_Price_min__c"); + return value !== "N/A" && !isNaN(value) + ? `AED ${parseFloat(value).toLocaleString()}` + : "Price on Request"; + })(), + area: (() => { + const value = get("pcrm__Size__c"); + return value !== "N/A" && !isNaN(value) ? `${value} sq ft` : "N/A"; + })(), + yearBuilt: get("pcrm__Build_Year__c"), + parking: (() => { + const value = get("pcrm__Parking_Spaces__c"); + return value !== "N/A" && !isNaN(value) + ? `${value} Parking Space(s)` + : "N/A"; + })(), + furnishing: get("pcrm__Furnished__c"), + }; + + // Load property images + await this.loadPropertyImages(); + } + } + // Load property images from Image Genie + async loadPropertyImages() { + if (!this.selectedPropertyId) { + this.realPropertyImages = []; + this.initialCategorySelected = false; // Reset flag when no property selected + return; } - // Load property images from Image Genie - async loadPropertyImages() { - if (!this.selectedPropertyId) { - this.realPropertyImages = []; - this.initialCategorySelected = false; // Reset flag when no property selected - return; - } + try { + const images = await getPropertyImages({ + propertyId: this.selectedPropertyId, + }); - try { - - const images = await getPropertyImages({ propertyId: this.selectedPropertyId }); - - // Transform the data to match expected format - this.realPropertyImages = images.map(img => ({ - id: img.id, - name: img.name, - title: img.name, - category: img.category, - url: img.url - })); - - - if (this.realPropertyImages && this.realPropertyImages.length > 0) { - // Find the first category that has images - const firstAvailableCategory = this.findFirstAvailableCategory(); - this.filterImagesByCategory(firstAvailableCategory); - this.selectedCategory = firstAvailableCategory; - this.initialCategorySelected = true; - - // Update active button visually - setTimeout(() => { - const categoryButtons = this.template.querySelectorAll('.category-btn-step2'); - categoryButtons.forEach(btn => { - btn.classList.remove('active'); - if (btn.dataset.category === firstAvailableCategory) { - btn.classList.add('active'); - } - }); - }, 100); + // Transform the data to match expected format + this.realPropertyImages = images.map((img) => ({ + id: img.id, + name: img.name, + title: img.name, + category: img.category, + url: img.url, + })); + + if (this.realPropertyImages && this.realPropertyImages.length > 0) { + // Find the first category that has images + const firstAvailableCategory = this.findFirstAvailableCategory(); + this.filterImagesByCategory(firstAvailableCategory); + this.selectedCategory = firstAvailableCategory; + this.initialCategorySelected = true; + + // Update active button visually + setTimeout(() => { + const categoryButtons = this.template.querySelectorAll( + ".category-btn-step2" + ); + categoryButtons.forEach((btn) => { + btn.classList.remove("active"); + if (btn.dataset.category === firstAvailableCategory) { + btn.classList.add("active"); } - - } catch (error) { - this.realPropertyImages = []; - } + }); + }, 100); + } + } catch (error) { + this.realPropertyImages = []; + } + } + + // Market analysis change handler + handleMarketAnalysisChange(event) { + const { name, checked } = event.target; + this.marketAnalysis[name] = checked; + } + // Generate template content + async generateTemplateContent() { + if (!this.selectedTemplateId || !this.selectedPropertyId) { + this.error = "Please select both a template and a property."; + return; } - // Market analysis change handler - handleMarketAnalysisChange(event) { - const { name, checked } = event.target; - this.marketAnalysis[name] = checked; + this.isLoading = true; + this.error = ""; + + try { + // Get the current HTML content from the editor + const editorFrame = this.template.querySelector( + ".enhanced-editor-content" + ); + let htmlContent = ""; + + if (editorFrame && editorFrame.innerHTML) { + htmlContent = editorFrame.innerHTML; + } else { + // Fallback: generate template HTML if editor is empty + htmlContent = this.createCompleteTemplateHTML(); + } + + // Generate PDF using the template HTML + const result = await generatePropertyPDF({ + propertyData: JSON.stringify(this.propertyData), + templateName: this.selectedTemplateId, + generatePDF: true, + htmlContent: htmlContent, + }); + + if (result.success) { + // Handle successful PDF generation + this.showSuccess("PDF generated successfully!"); + // You can add logic here to display the PDF or provide download link + } else { + this.error = + result.message || result.error || "Failed to generate PDF."; + } + } catch (error) { + this.error = + "Error generating PDF: " + + (error.body?.message || error.message || "Unknown error"); + } finally { + this.isLoading = false; } - // Generate template content - async generateTemplateContent() { - if (!this.selectedTemplateId || !this.selectedPropertyId) { - this.error = 'Please select both a template and a property.'; - return; - } + } - this.isLoading = true; - this.error = ''; - - try { - // Get the current HTML content from the editor - const editorFrame = this.template.querySelector('.enhanced-editor-content'); - let htmlContent = ''; - - if (editorFrame && editorFrame.innerHTML) { - htmlContent = editorFrame.innerHTML; - } else { - // Fallback: generate template HTML if editor is empty - htmlContent = this.createCompleteTemplateHTML(); - } - - // Generate PDF using the template HTML - const result = await generatePropertyPDF({ - propertyData: JSON.stringify(this.propertyData), - templateName: this.selectedTemplateId, - generatePDF: true, - htmlContent: htmlContent - }); - - if (result.success) { - // Handle successful PDF generation - this.showSuccess('PDF generated successfully!'); - // You can add logic here to display the PDF or provide download link - } else { - this.error = result.message || result.error || 'Failed to generate PDF.'; - } - } catch (error) { - this.error = 'Error generating PDF: ' + (error.body?.message || error.message || 'Unknown error'); - } finally { - this.isLoading = false; - } - } - - // Scroll to Generate PDF button at the top - scrollToGeneratePdf() { - try { - // Find the Generate PDF button at the top - const generatePdfButton = this.template.querySelector('.export-pdf-btn'); - if (generatePdfButton) { - // Scroll to the button with smooth animation - generatePdfButton.scrollIntoView({ - behavior: 'smooth', - block: 'center' - }); - - // Add a subtle highlight effect - generatePdfButton.style.boxShadow = '0 0 20px rgba(102, 126, 234, 0.6)'; - setTimeout(() => { - generatePdfButton.style.boxShadow = ''; - }, 2000); - - } else { - } - } catch (error) { - } - } - - // Simple PDF generation method for the export button - using the working approach from first prompt - async generatePdfSimple() { - if (!this.selectedTemplateId || !this.selectedPropertyId) { - this.error = 'Please select both a template and a property.'; - return; - } - - try { - - // Set loading state - this.isGeneratingPdf = true; - - // Show progress - this.showProgress('Starting AI-powered PDF generation...'); - - // Get current editor content - const editorFrame = this.template.querySelector('.enhanced-editor-content'); - if (!editorFrame) { - throw new Error('Editor frame not found'); - } - - this.editorContent = editorFrame.innerHTML; - - // Generate PDF using external API - await this.generatePdfViaExternalApi(); - - // Success message is handled in generatePdfViaExternalApi - - } catch (error) { - - // Provide more user-friendly error messages - let errorMessage = 'PDF generation failed. '; - if (error.message && error.message.includes('timeout')) { - errorMessage += 'The service is taking longer than expected. Please try again.'; - } else if (error.message && error.message.includes('unavailable')) { - errorMessage += 'The service is temporarily unavailable. Please try again in a few minutes.'; - } else if (error.message && error.message.includes('connection')) { - errorMessage += 'Please check your internet connection and try again.'; - } else { - errorMessage += error.message || 'Please try again.'; - } - - this.showError(errorMessage); - } finally { - // Reset loading state - this.isGeneratingPdf = false; - } - } - - // Clean HTML content for PDF generation by removing editor controls - cleanHtmlForPdf(htmlContent) { - // Wrap editor content so we can sanitize before PDF - const tempDiv = document.createElement('div'); - tempDiv.innerHTML = htmlContent; - - // Ensure list styling is preserved in output - const lists = tempDiv.querySelectorAll('ul, ol'); - lists.forEach(list => { - if (list.tagName.toLowerCase() === 'ul') { - list.style.listStyleType = 'disc'; - list.style.paddingLeft = '22px'; - list.style.margin = '0 0 8px 0'; - } else { - list.style.listStyleType = 'decimal'; - list.style.paddingLeft = '22px'; - list.style.margin = '0 0 8px 0'; - } + // Scroll to Generate PDF button at the top + scrollToGeneratePdf() { + try { + // Find the Generate PDF button at the top + const generatePdfButton = this.template.querySelector(".export-pdf-btn"); + if (generatePdfButton) { + // Scroll to the button with smooth animation + generatePdfButton.scrollIntoView({ + behavior: "smooth", + block: "center", }); - // Handle
-separated bullet/number lines inside a single block - const brBlocks = tempDiv.querySelectorAll('p, div'); - brBlocks.forEach(block => { - if (block.closest('ul,ol')) return; - const html = block.innerHTML || ''; - if (!/br\s*\/?/i.test(html)) return; - const parts = html.split(/(?:\s*)/i).map(s => s.trim()).filter(Boolean); - if (parts.length < 2) return; - const bulletMarker = /^\s*(?: \s*)*(\*|\-|•)\s+/i; - const numberMarker = /^\s*(?: \s*)*\d+[\.)]\s+/i; - const allBullets = parts.every(p => bulletMarker.test(p.replace(/<[^>]+>/g, ''))); - const allNumbers = parts.every(p => numberMarker.test(p.replace(/<[^>]+>/g, ''))); - if (!(allBullets || allNumbers)) return; - const list = document.createElement(allNumbers ? 'ol' : 'ul'); - list.style.listStyleType = allNumbers ? 'decimal' : 'disc'; - list.style.paddingLeft = '22px'; - list.style.margin = '0 0 8px 0'; - list.style.breakInside = 'avoid'; - list.style.pageBreakInside = 'avoid'; - parts.forEach(line => { - const li = document.createElement('li'); - li.innerHTML = line.replace(/^\s*(?: \s*)*(\*|\-|•|\d+[\.)])\s+/i, ''); - li.style.breakInside = 'avoid'; - li.style.pageBreakInside = 'avoid'; - list.appendChild(li); - }); - block.replaceWith(list); - }); + // Add a subtle highlight effect + generatePdfButton.style.boxShadow = "0 0 20px rgba(102, 126, 234, 0.6)"; + setTimeout(() => { + generatePdfButton.style.boxShadow = ""; + }, 2000); + } else { + } + } catch (error) {} + } - return tempDiv.innerHTML; + // Simple PDF generation method for the export button - using the working approach from first prompt + async generatePdfSimple() { + if (!this.selectedTemplateId || !this.selectedPropertyId) { + this.error = "Please select both a template and a property."; + return; } - // Generate PDF via external API using Apex proxy - async generatePdfViaExternalApi() { - try { - - // Show loading state - this.isLoading = true; - this.showProgress('Preparing content for AI processing...'); - - // First, ensure we have template content loaded - let htmlContent = ''; - - // Check if preview frame has content - const previewFrame = this.template.querySelector('.enhanced-editor-content'); - if (!previewFrame) { - throw new Error('Editor content not found'); - } - - htmlContent = previewFrame.innerHTML; - - // Debug: Check if draggable elements are present - const tempDiv = document.createElement('div'); - tempDiv.innerHTML = htmlContent; - const draggableElements = tempDiv.querySelectorAll('.draggable-element, .draggable-image-container, .draggable-table-container'); - draggableElements.forEach((el, index) => { - }); - - // If preview frame is empty, generate the template HTML first - if (!htmlContent || htmlContent.trim() === '' || htmlContent.length < 100) { - this.showProgress('Generating template content...'); - - // Generate the template HTML using the selected template and property - if (this.selectedTemplateId && this.selectedPropertyId) { - // Create a complete HTML template with property data - htmlContent = this.createCompleteTemplateHTML(); - - // Load it into the preview frame so user can see it - previewFrame.innerHTML = htmlContent; - } else { - throw new Error('No template or property selected'); - } - } - - // Ensure we have a complete HTML document with page size information - if (!htmlContent.includes('')) { - htmlContent = ` + + try { + // Set loading state + this.isGeneratingPdf = true; + + // Show progress + this.showProgress("Starting AI-powered PDF generation..."); + + // Get current editor content + const editorFrame = this.template.querySelector( + ".enhanced-editor-content" + ); + if (!editorFrame) { + throw new Error("Editor frame not found"); + } + + this.editorContent = editorFrame.innerHTML; + + // Generate PDF using external API + await this.generatePdfViaExternalApi(); + + // Success message is handled in generatePdfViaExternalApi + } catch (error) { + // Provide more user-friendly error messages + let errorMessage = "PDF generation failed. "; + if (error.message && error.message.includes("timeout")) { + errorMessage += + "The service is taking longer than expected. Please try again."; + } else if (error.message && error.message.includes("unavailable")) { + errorMessage += + "The service is temporarily unavailable. Please try again in a few minutes."; + } else if (error.message && error.message.includes("connection")) { + errorMessage += "Please check your internet connection and try again."; + } else { + errorMessage += error.message || "Please try again."; + } + + this.showError(errorMessage); + } finally { + // Reset loading state + this.isGeneratingPdf = false; + } + } + // Clean HTML content for PDF generation by removing editor controls + cleanHtmlForPdf(htmlContent) { + // Wrap editor content so we can sanitize before PDF + const tempDiv = document.createElement("div"); + tempDiv.innerHTML = htmlContent; + + // Ensure list styling is preserved in output + const lists = tempDiv.querySelectorAll("ul, ol"); + lists.forEach((list) => { + if (list.tagName.toLowerCase() === "ul") { + list.style.listStyleType = "disc"; + list.style.paddingLeft = "22px"; + list.style.margin = "0 0 8px 0"; + } else { + list.style.listStyleType = "decimal"; + list.style.paddingLeft = "22px"; + list.style.margin = "0 0 8px 0"; + } + }); + + // Handle
-separated bullet/number lines inside a single block + const brBlocks = tempDiv.querySelectorAll("p, div"); + brBlocks.forEach((block) => { + if (block.closest("ul,ol")) return; + const html = block.innerHTML || ""; + if (!/br\s*\/?/i.test(html)) return; + const parts = html + .split(/(?:\s*)/i) + .map((s) => s.trim()) + .filter(Boolean); + if (parts.length < 2) return; + const bulletMarker = /^\s*(?: \s*)*(\*|\-|•)\s+/i; + const numberMarker = /^\s*(?: \s*)*\d+[\.)]\s+/i; + const allBullets = parts.every((p) => + bulletMarker.test(p.replace(/<[^>]+>/g, "")) + ); + const allNumbers = parts.every((p) => + numberMarker.test(p.replace(/<[^>]+>/g, "")) + ); + if (!(allBullets || allNumbers)) return; + const list = document.createElement(allNumbers ? "ol" : "ul"); + list.style.listStyleType = allNumbers ? "decimal" : "disc"; + list.style.paddingLeft = "22px"; + list.style.margin = "0 0 8px 0"; + list.style.breakInside = "avoid"; + list.style.pageBreakInside = "avoid"; + parts.forEach((line) => { + const li = document.createElement("li"); + li.innerHTML = line.replace( + /^\s*(?: \s*)*(\*|\-|•|\d+[\.)])\s+/i, + "" + ); + li.style.breakInside = "avoid"; + li.style.pageBreakInside = "avoid"; + list.appendChild(li); + }); + block.replaceWith(list); + }); + + return tempDiv.innerHTML; + } + // Generate PDF via external API using Apex proxy + async generatePdfViaExternalApi() { + try { + // Show loading state + this.isLoading = true; + this.showProgress("Preparing content for AI processing..."); + + // First, ensure we have template content loaded + let htmlContent = ""; + + // Check if preview frame has content + const previewFrame = this.template.querySelector( + ".enhanced-editor-content" + ); + if (!previewFrame) { + throw new Error("Editor content not found"); + } + + htmlContent = previewFrame.innerHTML; + + // Debug: Check if draggable elements are present + const tempDiv = document.createElement("div"); + tempDiv.innerHTML = htmlContent; + const draggableElements = tempDiv.querySelectorAll( + ".draggable-element, .draggable-image-container, .draggable-table-container" + ); + draggableElements.forEach((el, index) => {}); + + // If preview frame is empty, generate the template HTML first + if ( + !htmlContent || + htmlContent.trim() === "" || + htmlContent.length < 100 + ) { + this.showProgress("Generating template content..."); + + // Generate the template HTML using the selected template and property + if (this.selectedTemplateId && this.selectedPropertyId) { + // Create a complete HTML template with property data + htmlContent = this.createCompleteTemplateHTML(); + + // Load it into the preview frame so user can see it + previewFrame.innerHTML = htmlContent; + } else { + throw new Error("No template or property selected"); + } + } + + // Ensure we have a complete HTML document with page size information + if (!htmlContent.includes("")) { + htmlContent = ` @@ -1405,19 +1611,31 @@ export default class PropertyTemplateSelector extends LightningElement { } /* A4 specific styles */ - ${this.selectedPageSize === 'A4' ? ` + ${ + this.selectedPageSize === "A4" + ? ` body { max-width: 210mm; margin: 0 auto; } - ` : ''} + ` + : "" + } /* A3 specific styles */ - ${this.selectedPageSize === 'A3' ? ` + ${ + this.selectedPageSize === "A3" + ? ` body { max-width: 297mm; margin: 0 auto; } - ` : ''} + ` + : "" + } /* A6 specific styles */ - ${this.selectedPageSize === 'A6' ? ` + ${ + this.selectedPageSize === "A6" + ? ` body { max-width: 105mm; margin: 0 auto; } - ` : ''} + ` + : "" + } @@ -1425,96 +1643,118 @@ export default class PropertyTemplateSelector extends LightningElement { ${htmlContent} `; - } - - - // Update progress message with timeout information - this.showProgress('Wait, our AI is generating report... (This may take up to 2 minutes)'); - - // Start progress timer - const startTime = Date.now(); - const progressInterval = setInterval(() => { - const elapsed = Math.floor((Date.now() - startTime) / 1000); - const minutes = Math.floor(elapsed / 60); - const seconds = elapsed % 60; - this.showProgress(`Generating PDF... (${minutes}:${seconds.toString().padStart(2, '0')} elapsed)`); - }, 1000); - - // Call the Apex method with the complete HTML and page size - // Set timeout to 2 minutes (120000ms) for API response - const pdfResult = await Promise.race([ - generatePDFFromHTML({ - htmlContent: htmlContent, - pageSize: this.selectedPageSize - }), - new Promise((_, reject) => - setTimeout(() => reject(new Error('PDF generation timeout - service took too long to respond')), 120000) - ) - ]).catch(error => { - // Clear progress timer - clearInterval(progressInterval); - - // Provide more specific error messages - if (error.message && error.message.includes('timeout')) { - throw new Error('PDF generation timed out. The service is taking longer than expected. Please try again.'); - } else if (error.message && error.message.includes('unavailable')) { - throw new Error('PDF generation service is temporarily unavailable. Please try again in a few minutes.'); - } else if (error.body && error.body.message) { - throw new Error(`PDF generation failed: ${error.body.message}`); - } else { - throw new Error('PDF generation failed. Please check your internet connection and try again.'); - } - }); - - // Clear progress timer on success - clearInterval(progressInterval); - - // Handle the new response format - if (pdfResult && pdfResult.success) { - - // Update progress message - this.showProgress('PDF ready for download...'); - - // Handle different status types - if (pdfResult.status === 'download_ready' || pdfResult.status === 'compressed_download_ready') { - await this.handlePDFDownloadReady(pdfResult); - } else { - throw new Error('Unexpected PDF status: ' + pdfResult.status); - } - - } else { - // Handle error response - const errorMessage = pdfResult?.error || pdfResult?.message || 'PDF generation failed with unknown error'; - throw new Error(errorMessage); - } - } catch (error) { - this.showError('PDF generation failed: ' + error.message); - } finally { - this.isLoading = false; - this.hideProgress(); - } - } + } - // Helper method to convert base64 to blob (from first prompt) - base64ToBlob(base64, mimeType) { - const byteCharacters = atob(base64); - const byteNumbers = new Array(byteCharacters.length); - for (let i = 0; i < byteCharacters.length; i++) { - byteNumbers[i] = byteCharacters.charCodeAt(i); + // Update progress message with timeout information + this.showProgress( + "Wait, our AI is generating report... (This may take up to 2 minutes)" + ); + + // Start progress timer + const startTime = Date.now(); + const progressInterval = setInterval(() => { + const elapsed = Math.floor((Date.now() - startTime) / 1000); + const minutes = Math.floor(elapsed / 60); + const seconds = elapsed % 60; + this.showProgress( + `Generating PDF... (${minutes}:${seconds + .toString() + .padStart(2, "0")} elapsed)` + ); + }, 1000); + + // Call the Apex method with the complete HTML and page size + // Set timeout to 2 minutes (120000ms) for API response + const pdfResult = await Promise.race([ + generatePDFFromHTML({ + htmlContent: htmlContent, + pageSize: this.selectedPageSize, + }), + new Promise((_, reject) => + setTimeout( + () => + reject( + new Error( + "PDF generation timeout - service took too long to respond" + ) + ), + 120000 + ) + ), + ]).catch((error) => { + // Clear progress timer + clearInterval(progressInterval); + + // Provide more specific error messages + if (error.message && error.message.includes("timeout")) { + throw new Error( + "PDF generation timed out. The service is taking longer than expected. Please try again." + ); + } else if (error.message && error.message.includes("unavailable")) { + throw new Error( + "PDF generation service is temporarily unavailable. Please try again in a few minutes." + ); + } else if (error.body && error.body.message) { + throw new Error(`PDF generation failed: ${error.body.message}`); + } else { + throw new Error( + "PDF generation failed. Please check your internet connection and try again." + ); } - const byteArray = new Uint8Array(byteNumbers); - return new Blob([byteArray], { type: mimeType }); + }); + + // Clear progress timer on success + clearInterval(progressInterval); + + // Handle the new response format + if (pdfResult && pdfResult.success) { + // Update progress message + this.showProgress("PDF ready for download..."); + + // Handle different status types + if ( + pdfResult.status === "download_ready" || + pdfResult.status === "compressed_download_ready" + ) { + await this.handlePDFDownloadReady(pdfResult); + } else { + throw new Error("Unexpected PDF status: " + pdfResult.status); + } + } else { + // Handle error response + const errorMessage = + pdfResult?.error || + pdfResult?.message || + "PDF generation failed with unknown error"; + throw new Error(errorMessage); + } + } catch (error) { + this.showError("PDF generation failed: " + error.message); + } finally { + this.isLoading = false; + this.hideProgress(); } - // Create complete template HTML with property data - createCompleteTemplateHTML() { - try { - - if (!this.selectedProperty) { - throw new Error('No property data available'); - } - - // Create a professional property brochure HTML with proper page breaks - const html = ` + } + + // Helper method to convert base64 to blob (from first prompt) + base64ToBlob(base64, mimeType) { + const byteCharacters = atob(base64); + const byteNumbers = new Array(byteCharacters.length); + for (let i = 0; i < byteCharacters.length; i++) { + byteNumbers[i] = byteCharacters.charCodeAt(i); + } + const byteArray = new Uint8Array(byteNumbers); + return new Blob([byteArray], { type: mimeType }); + } + // Create complete template HTML with property data + createCompleteTemplateHTML() { + try { + if (!this.selectedProperty) { + throw new Error("No property data available"); + } + + // Create a professional property brochure HTML with proper page breaks + const html = ` @@ -1575,7 +1815,11 @@ export default class PropertyTemplateSelector extends LightningElement {
-

${this.selectedProperty?.Name || this.propertyData?.propertyName || 'Property Details'}

+

${ + this.selectedProperty?.Name || + this.propertyData?.propertyName || + "Property Details" + }

Exclusive Property Brochure

@@ -1592,17 +1836,27 @@ export default class PropertyTemplateSelector extends LightningElement {
Property Type: - ${this.selectedProperty.Property_Type__c || 'N/A'} + ${ + this.selectedProperty.Property_Type__c || + "N/A" + }
Location: - ${this.selectedProperty.Location__c || 'N/A'} + ${ + this.selectedProperty.Location__c || "N/A" + }
Price: - ${this.selectedProperty.Price__c ? '$' + this.selectedProperty.Price__c.toLocaleString() : 'N/A'} + ${ + this.selectedProperty.Price__c + ? "$" + + this.selectedProperty.Price__c.toLocaleString() + : "N/A" + }
@@ -1611,17 +1865,26 @@ export default class PropertyTemplateSelector extends LightningElement {
Bedrooms: - ${this.selectedProperty.Bedrooms__c || 'N/A'} + ${ + this.selectedProperty.Bedrooms__c || "N/A" + }
Bathrooms: - ${this.selectedProperty.Bathrooms__c || 'N/A'} + ${ + this.selectedProperty.Bathrooms__c || "N/A" + }
Square Feet: - ${this.selectedProperty.Square_Feet__c ? this.selectedProperty.Square_Feet__c.toLocaleString() + ' sq ft' : 'N/A'} + ${ + this.selectedProperty.Square_Feet__c + ? this.selectedProperty.Square_Feet__c.toLocaleString() + + " sq ft" + : "N/A" + }
@@ -1630,7 +1893,10 @@ export default class PropertyTemplateSelector extends LightningElement {

Description

- ${this.selectedProperty.Description__c || 'This beautiful property offers exceptional value and is located in a prime area. Contact us for more details and to schedule a viewing.'} + ${ + this.selectedProperty.Description__c || + "This beautiful property offers exceptional value and is located in a prime area. Contact us for more details and to schedule a viewing." + }

@@ -1654,114 +1920,118 @@ export default class PropertyTemplateSelector extends LightningElement { `; - - return html; - - } catch (error) { - // Return a fallback HTML if there's an error - return ` + + return html; + } catch (error) { + // Return a fallback HTML if there's an error + return `

Property Brochure

-

Property: ${this.selectedProperty?.Name || 'Selected Property'}

-

Template: ${this.selectedTemplateId || 'Selected Template'}

+

Property: ${ + this.selectedProperty?.Name || "Selected Property" + }

+

Template: ${ + this.selectedTemplateId || "Selected Template" + }

Generated on: ${new Date().toLocaleDateString()}

`; + } + } + + // Progress and message methods (from first prompt) + showProgress(message) { + this.isLoading = true; + this.progressMessage = message; + } + + hideProgress() { + this.isLoading = false; + this.progressMessage = ""; + } + + showSuccess(message) { + this.progressMessage = message; + this.error = ""; + } + + showError(message) { + this.error = message; + this.progressMessage = ""; + } + + // Handle PDF response using the working approach from first prompt + handlePdfResponse(result) { + try { + if (result.pdfUrl) { + // For the working approach, we need to create a proper download + this.downloadPDF(result.pdfUrl); + } else { + this.error = "PDF generated but no download URL received"; + } + } catch (error) { + this.error = "Error handling PDF response: " + error.message; + } + } + // Download PDF method + downloadPDF(pdfUrl) { + try { + // Create a temporary link element + const link = document.createElement("a"); + link.href = pdfUrl; + link.download = `property-brochure-${ + this.selectedTemplateId + }-${Date.now()}.pdf`; + + // Append to body, click, and remove + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + this.progressMessage = "PDF download started!"; + + // Fallback: If download doesn't work, show instructions + setTimeout(() => { + if (this.template.querySelector(".progress-message")) { + const progressMsg = this.template.querySelector(".progress-message"); + progressMsg.innerHTML += + '
💡 If download didn\'t start, right-click the PDF link below and select "Save as..."'; + + // Add a visible download link as fallback + const fallbackLink = document.createElement("a"); + fallbackLink.href = pdfUrl; + fallbackLink.textContent = "📄 Click here to download PDF"; + fallbackLink.className = "slds-button slds-button_brand"; + fallbackLink.style.marginTop = "10px"; + fallbackLink.style.display = "inline-block"; + + progressMsg.appendChild(fallbackLink); } + }, 2000); + } catch (error) { + this.error = "Error downloading PDF: " + error.message; } + } + // Handle large PDF responses + async handleLargePDFResponse(decodedResponse) { + try { + const responseData = JSON.parse(decodedResponse); - // Progress and message methods (from first prompt) - showProgress(message) { - this.isLoading = true; - this.progressMessage = message; - } + this.hideProgress(); + this.isLoading = false; - hideProgress() { - this.isLoading = false; - this.progressMessage = ''; + if (responseData.status === "large_pdf") { + // Show options for large PDFs + this.showLargePDFOptions(responseData); + } + } catch (error) { + this.showError("Error handling large PDF response: " + error.message); } + } - showSuccess(message) { - this.progressMessage = message; - this.error = ''; - } - - showError(message) { - this.error = message; - this.progressMessage = ''; - } - - // Handle PDF response using the working approach from first prompt - handlePdfResponse(result) { - try { - - if (result.pdfUrl) { - // For the working approach, we need to create a proper download - this.downloadPDF(result.pdfUrl); - } else { - this.error = 'PDF generated but no download URL received'; - } - } catch (error) { - this.error = 'Error handling PDF response: ' + error.message; - } - } - // Download PDF method - downloadPDF(pdfUrl) { - try { - // Create a temporary link element - const link = document.createElement('a'); - link.href = pdfUrl; - link.download = `property-brochure-${this.selectedTemplateId}-${Date.now()}.pdf`; - - // Append to body, click, and remove - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - - this.progressMessage = 'PDF download started!'; - - // Fallback: If download doesn't work, show instructions - setTimeout(() => { - if (this.template.querySelector('.progress-message')) { - const progressMsg = this.template.querySelector('.progress-message'); - progressMsg.innerHTML += '
💡 If download didn\'t start, right-click the PDF link below and select "Save as..."'; - - // Add a visible download link as fallback - const fallbackLink = document.createElement('a'); - fallbackLink.href = pdfUrl; - fallbackLink.textContent = '📄 Click here to download PDF'; - fallbackLink.className = 'slds-button slds-button_brand'; - fallbackLink.style.marginTop = '10px'; - fallbackLink.style.display = 'inline-block'; - - progressMsg.appendChild(fallbackLink); - } - }, 2000); - } catch (error) { - this.error = 'Error downloading PDF: ' + error.message; - } - } - - // Handle large PDF responses - async handleLargePDFResponse(decodedResponse) { - try { - const responseData = JSON.parse(decodedResponse); - - this.hideProgress(); - this.isLoading = false; - - if (responseData.status === 'large_pdf') { - // Show options for large PDFs - this.showLargePDFOptions(responseData); - } - } catch (error) { - this.showError('Error handling large PDF response: ' + error.message); - } - } - - // Show options for large PDFs - showLargePDFOptions(responseData) { - const message = ` + // Show options for large PDFs + showLargePDFOptions(responseData) { + const message = `

📄 PDF Generated Successfully!

Size: ${responseData.size_mb} MB

@@ -1787,263 +2057,297 @@ export default class PropertyTemplateSelector extends LightningElement {
`; - - this.showSuccess(message); + + this.showSuccess(message); + } + + // Generate compressed PDF to stay under Salesforce limits + async generateCompressedPDF() { + try { + this.showProgress("Generating compressed PDF..."); + + // Get current editor content + const editorFrame = this.template.querySelector( + ".enhanced-editor-content" + ); + let htmlContent = ""; + + if (editorFrame && editorFrame.innerHTML) { + htmlContent = editorFrame.innerHTML; + } else { + htmlContent = this.createCompleteTemplateHTML(); + } + + // Call the compressed PDF generation method + const compressedPDF = await generateCompressedPDF({ + htmlContent: htmlContent, + pageSize: this.selectedPageSize, + }); + + if (compressedPDF) { + // Process the compressed PDF + const pdfBlob = this.base64ToBlob(compressedPDF, "application/pdf"); + const pdfUrl = window.URL.createObjectURL(pdfBlob); + + // Download the compressed PDF + const downloadLink = document.createElement("a"); + downloadLink.href = pdfUrl; + downloadLink.download = `${ + this.selectedProperty?.Name || "Property" + }_Brochure_Compressed_${this.selectedPageSize}.pdf`; + downloadLink.style.display = "none"; + + document.body.appendChild(downloadLink); + downloadLink.click(); + document.body.removeChild(downloadLink); + + // Clean up + setTimeout(() => { + window.URL.revokeObjectURL(pdfUrl); + }, 1000); + + this.hideProgress(); + this.showSuccess( + "Compressed PDF generated and downloaded successfully!" + ); + } + } catch (error) { + this.showError("Failed to generate compressed PDF: " + error.message); + } + } + + // Handle PDF download ready response + async handlePDFDownloadReady(pdfResult) { + try { + // Hide loading state + this.isLoading = false; + this.hideProgress(); + + // Set the download info for the modal + this.downloadInfo = { + filename: pdfResult.filename || "Unknown", + fileSize: pdfResult.file_size_mb + ? pdfResult.file_size_mb + " MB" + : "Unknown", + generatedAt: this.formatDate(pdfResult.generated_at), + expiresAt: this.formatDate(pdfResult.expires_at), + downloadUrl: pdfResult.download_url, + }; + // Automatically open download URL in new tab + window.open(pdfResult.download_url, "_blank"); + + // Show simple success message + this.showSuccess( + `✅ PDF generated successfully! File: ${pdfResult.filename} (${pdfResult.file_size_mb} MB) - Download opened in new tab.` + ); + this.showSuccess( + `✅ PDF generated successfully! File: ${pdfResult.filename} (${pdfResult.file_size_mb} MB)` + ); + } catch (error) { + this.showError("Error handling PDF download: " + error.message); + } + } + + // Modal control methods + closeDownloadModal() { + this.showDownloadModal = false; + } + + stopPropagation(event) { + event.stopPropagation(); + } + + copyDownloadLink() { + if (navigator.clipboard && this.downloadInfo) { + navigator.clipboard + .writeText(this.downloadInfo.downloadUrl) + .then(() => { + // Show feedback + const copyBtn = this.template.querySelector(".copy-btn"); + if (copyBtn) { + const originalText = copyBtn.textContent; + copyBtn.textContent = "✅ Copied!"; + copyBtn.classList.add("copied"); + + setTimeout(() => { + copyBtn.textContent = originalText; + copyBtn.classList.remove("copied"); + }, 2000); + } + }) + .catch((err) => { + alert("Failed to copy link to clipboard"); + }); + } + } + + openInNewTab() { + if (this.downloadInfo && this.downloadInfo.downloadUrl) { + window.open(this.downloadInfo.downloadUrl, "_blank"); + } + } + // Helper method to format dates + formatDate(dateString) { + if (!dateString) return "Unknown"; + const date = new Date(dateString); + return date.toLocaleString(); + } + // Create template HTML based on selection + createTemplateHTML() { + switch (this.selectedTemplateId) { + case "blank-template": + return this.createBlankTemplate(); + case "everkind-template": + return this.createEverkindTemplate(); + case "shift-template": + return this.createShiftTemplate(); + case "saintbarts-template": + return this.createSaintbartsTemplate(); + case "learnoy-template": + return this.createLearnoyTemplate(); + case "leafamp-template": + return this.createLeafampTemplate(); + case "coreshift-template": + return this.createCoreshiftTemplate(); + case "modern-home-template": + return this.createModernHomeTemplate(); + case "grand-oak-villa-template": + // Grand Oak Villa (black theme with gold accents) + return this.createGrandOakVillaTemplate(); + case "sample-template": + return this.createSampleTemplate(); + case "serenity-house-template": + return this.createSerenityHouseTemplate(); + case "luxury-mansion-template": + return this.createLuxuryMansionTemplate(); + default: + return this.createBlankTemplate(); + } + } + + // Format description for PDF generation + formatDescriptionForPDF(description) { + if (!description || description.trim() === "") { + return "Property description not available."; } - // Generate compressed PDF to stay under Salesforce limits - async generateCompressedPDF() { - try { - this.showProgress('Generating compressed PDF...'); - - // Get current editor content - const editorFrame = this.template.querySelector('.enhanced-editor-content'); - let htmlContent = ''; - - if (editorFrame && editorFrame.innerHTML) { - htmlContent = editorFrame.innerHTML; - } else { - htmlContent = this.createCompleteTemplateHTML(); - } - - // Call the compressed PDF generation method - const compressedPDF = await generateCompressedPDF({ - htmlContent: htmlContent, - pageSize: this.selectedPageSize - }); - - if (compressedPDF) { - // Process the compressed PDF - const pdfBlob = this.base64ToBlob(compressedPDF, 'application/pdf'); - const pdfUrl = window.URL.createObjectURL(pdfBlob); - - // Download the compressed PDF - const downloadLink = document.createElement('a'); - downloadLink.href = pdfUrl; - downloadLink.download = `${this.selectedProperty?.Name || 'Property'}_Brochure_Compressed_${this.selectedPageSize}.pdf`; - downloadLink.style.display = 'none'; - - document.body.appendChild(downloadLink); - downloadLink.click(); - document.body.removeChild(downloadLink); - - // Clean up - setTimeout(() => { - window.URL.revokeObjectURL(pdfUrl); - }, 1000); - - this.hideProgress(); - this.showSuccess('Compressed PDF generated and downloaded successfully!'); - } - - } catch (error) { - this.showError('Failed to generate compressed PDF: ' + error.message); - } + // Clean up the description + let formattedDescription = description.trim(); + + // Ensure proper paragraph breaks + formattedDescription = formattedDescription.replace(/\n\s*\n/g, "

"); + + // Wrap in paragraph tags if not already wrapped + if (!formattedDescription.startsWith("

")) { + formattedDescription = "

" + formattedDescription + "

"; } - // Handle PDF download ready response - async handlePDFDownloadReady(pdfResult) { - try { - - // Hide loading state - this.isLoading = false; - this.hideProgress(); - - // Set the download info for the modal - this.downloadInfo = { - filename: pdfResult.filename || "Unknown", - fileSize: pdfResult.file_size_mb ? pdfResult.file_size_mb + " MB" : "Unknown", - generatedAt: this.formatDate(pdfResult.generated_at), - expiresAt: this.formatDate(pdfResult.expires_at), - downloadUrl: pdfResult.download_url - }; - // Automatically open download URL in new tab - window.open(pdfResult.download_url, "_blank"); - - // Show simple success message - this.showSuccess(`✅ PDF generated successfully! File: ${pdfResult.filename} (${pdfResult.file_size_mb} MB) - Download opened in new tab.`); - this.showSuccess(`✅ PDF generated successfully! File: ${pdfResult.filename} (${pdfResult.file_size_mb} MB)`); - - } catch (error) { - this.showError("Error handling PDF download: " + error.message); + // Ensure proper spacing between paragraphs + formattedDescription = formattedDescription.replace(/<\/p>

/g, "

"); + + return formattedDescription; + } + + // Generate amenities HTML from property data + generateAmenitiesHTML(data) { + const amenities = []; + + // Check for common amenity fields in the property data + const amenityFields = [ + "amenities", + "features", + "facilities", + "amenitiesList", + "propertyAmenities", + "Amenities__c", + "Features__c", + "Facilities__c", + "Property_Amenities__c", + // Add the actual fields that are available in propertyData + "parkingSpaces", + "furnished", + "offeringType", + ]; + + // Try to find amenities in various field formats + for (const field of amenityFields) { + if (data[field] && data[field] !== "N/A") { + if (Array.isArray(data[field])) { + amenities.push(...data[field]); + } else if (typeof data[field] === "string") { + // For specific fields, format them properly + if (field === "parkingSpaces") { + amenities.push(`Parking: ${data[field]} spaces`); + } else if (field === "furnished") { + amenities.push(`Furnished: ${data[field]}`); + } else if (field === "offeringType") { + amenities.push(`Offering Type: ${data[field]}`); + } else { + // Split by common delimiters for other fields + const amenityList = data[field] + .split(/[,;|\n]/) + .map((a) => a.trim()) + .filter((a) => a); + amenities.push(...amenityList); + } } + } } - // Modal control methods - closeDownloadModal() { - this.showDownloadModal = false; + // If no amenities found, return empty string + if (amenities.length === 0) { + return '

No amenities specified
'; } - stopPropagation(event) { - event.stopPropagation(); - } + // Generate HTML for amenities + return amenities + .map( + (amenity) => + `
${amenity}
` + ) + .join(""); + } - copyDownloadLink() { - if (navigator.clipboard && this.downloadInfo) { - navigator.clipboard.writeText(this.downloadInfo.downloadUrl).then(() => { - // Show feedback - const copyBtn = this.template.querySelector(".copy-btn"); - if (copyBtn) { - const originalText = copyBtn.textContent; - copyBtn.textContent = "✅ Copied!"; - copyBtn.classList.add("copied"); - - setTimeout(() => { - copyBtn.textContent = originalText; - copyBtn.classList.remove("copied"); - }, 2000); - } - }).catch(err => { - alert("Failed to copy link to clipboard"); - }); - } - } + // Template methods + createBlankTemplate() { + const data = this.propertyData || {}; + const propertyName = data.Name || data.propertyName || "Property Name"; + const location = data.Address__c || data.location || "Location"; + const price = data.Price__c || data.price || "Price"; + const bedrooms = data.Bedrooms__c || data.bedrooms || "N/A"; + const bathrooms = data.Bathrooms__c || data.bathrooms || "N/A"; + const size = data.Square_Feet__c || data.size || "N/A"; + const sizeUnit = data.sizeUnit || "sq ft"; + const status = data.Status__c || data.status || "Available"; + const propertyType = + data.Property_Type__c || data.propertyType || "Property Type"; + const city = data.City__c || data.city || "City"; + const community = data.Community__c || data.community || "Community"; + const subCommunity = + data.Sub_Community__c || data.subCommunity || "Sub Community"; + const furnished = data.Furnished__c || data.furnished || "N/A"; + const parkingSpaces = data.Parking_Spaces__c || data.parkingSpaces || "N/A"; + const buildYear = data.Build_Year__c || data.buildYear || "N/A"; + const titleEnglish = + data.Title_English__c || data.titleEnglish || "Property Title"; + const descriptionEnglish = + data.Description_English__c || + data.descriptionEnglish || + "Property Description"; + const rentPriceMin = data.Rent_Price_Min__c || data.rentPriceMin || "N/A"; + const salePriceMin = data.Sale_Price_Min__c || data.salePriceMin || "N/A"; - openInNewTab() { - if (this.downloadInfo && this.downloadInfo.downloadUrl) { - window.open(this.downloadInfo.downloadUrl, "_blank"); - } - } - // Helper method to format dates - formatDate(dateString) { - if (!dateString) return "Unknown"; - const date = new Date(dateString); - return date.toLocaleString(); - } - // Create template HTML based on selection - createTemplateHTML() { - - switch (this.selectedTemplateId) { - case 'blank-template': - return this.createBlankTemplate(); - case 'everkind-template': - return this.createEverkindTemplate(); - case 'shift-template': - return this.createShiftTemplate(); - case 'saintbarts-template': - return this.createSaintbartsTemplate(); - case 'learnoy-template': - return this.createLearnoyTemplate(); - case 'leafamp-template': - return this.createLeafampTemplate(); - case 'coreshift-template': - return this.createCoreshiftTemplate(); - case 'modern-home-template': - return this.createModernHomeTemplate(); - case 'grand-oak-villa-template': - // Grand Oak Villa (black theme with gold accents) - return this.createGrandOakVillaTemplate(); - case 'sample-template': - return this.createSampleTemplate(); - case 'serenity-house-template': - return this.createSerenityHouseTemplate(); - case 'luxury-mansion-template': - return this.createLuxuryMansionTemplate(); - default: - return this.createBlankTemplate(); - } - } - - // Format description for PDF generation - formatDescriptionForPDF(description) { - if (!description || description.trim() === '') { - return 'Property description not available.'; - } - - // Clean up the description - let formattedDescription = description.trim(); - - // Ensure proper paragraph breaks - formattedDescription = formattedDescription.replace(/\n\s*\n/g, '

'); - - // Wrap in paragraph tags if not already wrapped - if (!formattedDescription.startsWith('

')) { - formattedDescription = '

' + formattedDescription + '

'; - } - - // Ensure proper spacing between paragraphs - formattedDescription = formattedDescription.replace(/<\/p>

/g, '

'); - - return formattedDescription; - } - - // Generate amenities HTML from property data - generateAmenitiesHTML(data) { - const amenities = []; - - // Check for common amenity fields in the property data - const amenityFields = [ - 'amenities', 'features', 'facilities', 'amenitiesList', 'propertyAmenities', - 'Amenities__c', 'Features__c', 'Facilities__c', 'Property_Amenities__c', - // Add the actual fields that are available in propertyData - 'parkingSpaces', 'furnished', 'offeringType' - ]; - - // Try to find amenities in various field formats - for (const field of amenityFields) { - if (data[field] && data[field] !== 'N/A') { - if (Array.isArray(data[field])) { - amenities.push(...data[field]); - } else if (typeof data[field] === 'string') { - // For specific fields, format them properly - if (field === 'parkingSpaces') { - amenities.push(`Parking: ${data[field]} spaces`); - } else if (field === 'furnished') { - amenities.push(`Furnished: ${data[field]}`); - } else if (field === 'offeringType') { - amenities.push(`Offering Type: ${data[field]}`); - } else { - // Split by common delimiters for other fields - const amenityList = data[field].split(/[,;|\n]/).map(a => a.trim()).filter(a => a); - amenities.push(...amenityList); - } - } - } - } - - // If no amenities found, return empty string - if (amenities.length === 0) { - return '

No amenities specified
'; - } - - // Generate HTML for amenities - return amenities.map(amenity => - `
${amenity}
` - ).join(''); - } - - // Template methods - createBlankTemplate() { - const data = this.propertyData || {}; - const propertyName = data.Name || data.propertyName || "Property Name"; - const location = data.Address__c || data.location || "Location"; - const price = data.Price__c || data.price || "Price"; - const bedrooms = data.Bedrooms__c || data.bedrooms || "N/A"; - const bathrooms = data.Bathrooms__c || data.bathrooms || "N/A"; - const size = data.Square_Feet__c || data.size || "N/A"; - const sizeUnit = data.sizeUnit || "sq ft"; - const status = data.Status__c || data.status || "Available"; - const propertyType = data.Property_Type__c || data.propertyType || "Property Type"; - const city = data.City__c || data.city || "City"; - const community = data.Community__c || data.community || "Community"; - const subCommunity = data.Sub_Community__c || data.subCommunity || "Sub Community"; - const furnished = data.Furnished__c || data.furnished || "N/A"; - const parkingSpaces = data.Parking_Spaces__c || data.parkingSpaces || "N/A"; - const buildYear = data.Build_Year__c || data.buildYear || "N/A"; - const titleEnglish = data.Title_English__c || data.titleEnglish || "Property Title"; - const descriptionEnglish = data.Description_English__c || data.descriptionEnglish || "Property Description"; - const rentPriceMin = data.Rent_Price_Min__c || data.rentPriceMin || "N/A"; - const salePriceMin = data.Sale_Price_Min__c || data.salePriceMin || "N/A"; - - // Build gallery pages so ALL images render in the empty template - const allImages = Array.isArray(this.realPropertyImages) ? this.realPropertyImages : []; - const imagesPerPage = 8; - const firstChunk = allImages.slice(0, imagesPerPage); - let additionalGalleryPagesHTML = ''; - if (allImages.length > imagesPerPage) { - for (let i = imagesPerPage; i < allImages.length; i += imagesPerPage) { - const chunk = allImages.slice(i, i + imagesPerPage); - additionalGalleryPagesHTML += ` + // Build gallery pages so ALL images render in the empty template + const allImages = Array.isArray(this.realPropertyImages) + ? this.realPropertyImages + : []; + const imagesPerPage = 8; + const firstChunk = allImages.slice(0, imagesPerPage); + let additionalGalleryPagesHTML = ""; + if (allImages.length > imagesPerPage) { + for (let i = imagesPerPage; i < allImages.length; i += imagesPerPage) { + const chunk = allImages.slice(i, i + imagesPerPage); + additionalGalleryPagesHTML += `

Property Gallery

@@ -2052,10 +2356,9 @@ export default class PropertyTemplateSelector extends LightningElement {
`; - } - } - - return ` + } + } + return `
@@ -2086,9 +2389,15 @@ export default class PropertyTemplateSelector extends LightningElement {

Contact Details

-
Name: ${data.contactName || 'N/A'}
-
Email: ${data.contactEmail || 'N/A'}
-
Phone: ${data.contactPhone || 'N/A'}
+
Name: ${ + data.contactName || "N/A" + }
+
Email: ${ + data.contactEmail || "N/A" + }
+
Phone: ${ + data.contactPhone || "N/A" + }
@@ -2096,14 +2405,30 @@ export default class PropertyTemplateSelector extends LightningElement {

Location Details

-
City (Bayut): ${data.cityBayut || 'N/A'}
-
City (Propertyfinder): ${data.cityPropertyfinder || 'N/A'}
-
Community (Bayut): ${data.communityBayut || 'N/A'}
-
Sub Community (Bayut): ${data.subCommunityBayut || 'N/A'}
-
Locality (Bayut): ${data.localityBayut || 'N/A'}
-
Sub Locality (Bayut): ${data.subLocalityBayut || 'N/A'}
-
Tower (Bayut): ${data.towerBayut || 'N/A'}
-
Unit Number: ${data.unitNumber || 'N/A'}
+
City (Bayut): ${ + data.cityBayut || "N/A" + }
+
City (Propertyfinder): ${ + data.cityPropertyfinder || "N/A" + }
+
Community (Bayut): ${ + data.communityBayut || "N/A" + }
+
Sub Community (Bayut): ${ + data.subCommunityBayut || "N/A" + }
+
Locality (Bayut): ${ + data.localityBayut || "N/A" + }
+
Sub Locality (Bayut): ${ + data.subLocalityBayut || "N/A" + }
+
Tower (Bayut): ${ + data.towerBayut || "N/A" + }
+
Unit Number: ${ + data.unitNumber || "N/A" + }
@@ -2116,7 +2441,9 @@ export default class PropertyTemplateSelector extends LightningElement {
Size: ${size} ${sizeUnit}
Parking Spaces: ${parkingSpaces}
Build Year: ${buildYear}
-
Floor: ${data.floor || 'N/A'}
+
Floor: ${ + data.floor || "N/A" + }
@@ -2125,8 +2452,12 @@ export default class PropertyTemplateSelector extends LightningElement {
Rent Price: ${rentPriceMin}
Sale Price: ${salePriceMin}
-
Rent Price (Max): ${data.rentPriceMax || 'N/A'}
-
Sale Price (Max): ${data.salePriceMax || 'N/A'}
+
Rent Price (Max): ${ + data.rentPriceMax || "N/A" + }
+
Sale Price (Max): ${ + data.salePriceMax || "N/A" + }
@@ -2134,8 +2465,12 @@ export default class PropertyTemplateSelector extends LightningElement {

Rent Availability

-
Rent Available From: ${data.rentAvailableFrom || 'N/A'}
-
Rent Available To: ${data.rentAvailableTo || 'N/A'}
+
Rent Available From: ${ + data.rentAvailableFrom || "N/A" + }
+
Rent Available To: ${ + data.rentAvailableTo || "N/A" + }
@@ -2162,350 +2497,564 @@ export default class PropertyTemplateSelector extends LightningElement {

Amenities & Features

-
Parking Spaces: ${data.parkingSpaces || 'N/A'}
-
Furnished: ${data.furnished || 'N/A'}
-
Offering Type: ${data.offeringType || 'N/A'}
-
Build Year: ${data.buildYear || 'N/A'}
+
Parking Spaces: ${ + data.parkingSpaces || "N/A" + }
+
Furnished: ${ + data.furnished || "N/A" + }
+
Offering Type: ${ + data.offeringType || "N/A" + }
+
Build Year: ${ + data.buildYear || "N/A" + }

Property Gallery

${additionalGalleryPagesHTML} `; - } + } - createEverkindTemplate() { - return `Luxury Villa

Luxury Villa Template

Elegance. Precision. Luxury.

`; - } + createEverkindTemplate() { + return `Luxury Villa

Luxury Villa Template

Elegance. Precision. Luxury.

`; + } + createShiftTemplate() { + const data = this.propertyData || {}; + const propertyName = data.Name || data.propertyName || "SHIFT PROPERTY"; + const location = data.Address__c || data.location || "Modern Living"; + const price = data.Price__c || data.price || "Starting from $1,500,000"; + const referenceId = + data.pcrm__Title_English__c || data.Name || data.propertyName || ""; + const bedrooms = data.Bedrooms__c || data.bedrooms || "N/A"; + const bathrooms = data.Bathrooms__c || data.bathrooms || "N/A"; + const size = data.Square_Feet__c || data.size || "N/A"; + const propertyType = + data.Property_Type__c || data.propertyType || "Property Type"; + const description = + data.Description_English__c || + data.descriptionEnglish || + "Modern living at its finest."; - createShiftTemplate() { - - const data = this.propertyData || {}; - const propertyName = data.Name || data.propertyName || "SHIFT PROPERTY"; - const location = data.Address__c || data.location || "Modern Living"; - const price = data.Price__c || data.price || "Starting from $1,500,000"; - const referenceId = data.pcrm__Title_English__c || data.Name || data.propertyName || ""; - const bedrooms = data.Bedrooms__c || data.bedrooms || "N/A"; - const bathrooms = data.Bathrooms__c || data.bathrooms || "N/A"; - const size = data.Square_Feet__c || data.size || "N/A"; - const propertyType = data.Property_Type__c || data.propertyType || "Property Type"; - const description = data.Description_English__c || data.descriptionEnglish || "Modern living at its finest."; - - // Get smart images - use direct method for exterior - const exteriorImage = this.getExteriorImageUrl(); - // Gallery HTML generated once in each template scope when needed - const allGalleryImages = this.realPropertyImages || []; - const firstGalleryCount = Math.min(4, allGalleryImages.length); - const firstPageImages = allGalleryImages.slice(0, firstGalleryCount); - const propertyGallery = this.generatePropertyGalleryHTML(); - const interiorImage = this.getSmartImageForSection('interior', 'https://images.unsplash.com/photo-1616486338812-3dadae4b4ace?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); - const kitchenImage = this.getSmartImageForSection('kitchen', 'https://images.unsplash.com/photo-1556909114-f6e7ad7d3136?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); - - // Debug logging - - // Generate property gallery for uncategorized images (already declared above) - - return `Shift Property - Modern Living
Property Exterior

${propertyName}

${location}

${price}

EXTERIOR IMAGE TEST: ${exteriorImage}

About Shift Property

Experience the future of living with Shift Property, where innovation meets comfort in perfect harmony.

Interior View

Modern Features

Innovation
Smart Design
Eco-Friendly
Connected Living
Kitchen View

Contact Information

Reference ID: ${referenceId}

Agent: ${data.agentName || "Innovation Specialist"}

Phone: ${data.agentPhone || "(555) 789-0123"}

${propertyGallery}
`; - } + // Get smart images - use direct method for exterior + const exteriorImage = this.getExteriorImageUrl(); + // Gallery HTML generated once in each template scope when needed + const allGalleryImages = this.realPropertyImages || []; + const firstGalleryCount = Math.min(4, allGalleryImages.length); + const firstPageImages = allGalleryImages.slice(0, firstGalleryCount); + const propertyGallery = this.generatePropertyGalleryHTML(); + const interiorImage = this.getSmartImageForSection( + "interior", + "" + ); + const kitchenImage = this.getSmartImageForSection( + "kitchen", + "" + ); - createSaintbartsTemplate() { - const data = this.propertyData || {}; - const propertyName = data.Name || data.propertyName || "SAINT BARTS VILLA"; - const location = data.Address__c || data.location || "Caribbean Paradise"; - const price = data.Price__c || data.price || "Starting from $3,200,000"; - const referenceId = data.pcrm__Title_English__c || data.Name || data.propertyName || ""; - const bedrooms = data.Bedrooms__c || data.bedrooms || "N/A"; - const bathrooms = data.Bathrooms__c || data.bathrooms || "N/A"; - const size = data.Square_Feet__c || data.size || "N/A"; - const propertyType = data.Property_Type__c || data.propertyType || "Property Type"; - const description = data.Description_English__c || data.descriptionEnglish || "Caribbean paradise awaits."; - - // Get smart images - use direct method for exterior - const exteriorImage = this.getExteriorImageUrl(); - const interiorImage = this.getSmartImageForSection('interior', 'https://images.unsplash.com/photo-1616486338812-3dadae4b4ace?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); - const bedroomImage = this.getSmartImageForSection('bedroom', 'https://images.unsplash.com/photo-1586023492125-27b2c045efd7?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); - - // Generate property gallery for uncategorized images - const propertyGallery = this.generatePropertyGalleryHTML(); - - return `Saint Barts Villa - Caribbean Paradise
Property Exterior

${propertyName}

${location}

${price}

About Saint Barts Villa

Discover paradise at Saint Barts Villa, where tropical luxury meets Caribbean charm in an idyllic setting.

Interior View

Tropical Features

Beach Access
Tropical Gardens
Ocean Views
Luxury Amenities
Bedroom View

Contact Information

Reference ID: ${referenceId}

Agent: ${data.agentName || "Caribbean Specialist"}

Phone: ${data.agentPhone || "(555) 456-7890"}

${propertyGallery}
`; - } + // Debug logging - createLearnoyTemplate() { - const data = this.propertyData || {}; - const propertyName = data.Name || data.propertyName || "Property Name"; - const location = data.Address__c || data.location || "Location"; - const price = data.Price__c || data.price || "Price"; - const bedrooms = data.Bedrooms__c || data.bedrooms || "N/A"; - const bathrooms = data.Bathrooms__c || data.bathrooms || "N/A"; - const size = data.Square_Feet__c || data.size || "N/A"; - const propertyType = data.Property_Type__c || data.propertyType || "N/A"; - const description = data.Description_English__c || data.descriptionEnglish || "Property description not available."; - const referenceId = data.pcrm__Title_English__c || data.Name || data.propertyName || ""; - - // Get smart images - use direct method for exterior - const exteriorImage = this.getExteriorImageUrl(); - const interiorImage = this.getSmartImageForSection('interior', 'https://images.unsplash.com/photo-1616486338812-3dadae4b4ace?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); - const livingImage = this.getSmartImageForSection('living', 'https://images.unsplash.com/photo-1586023492125-27b2c045efd7?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); - - // Generate property gallery for uncategorized images - const propertyGallery = this.generatePropertyGalleryHTML(); - - // Generate amenities from property data - const amenitiesHTML = this.generateAmenitiesHTML(data); - - return `Learnoy Estate - Heritage Collection
Property Exterior

${propertyName}

${location}

${price}

About ${propertyName}

${description}

Property Type: ${propertyType}
Bedrooms: ${bedrooms}
Bathrooms: ${bathrooms}
Size: ${size} sq ft
Status: ${data.Status__c || data.status || "N/A"}
Year Built: ${data.Build_Year__c || data.yearBuilt || "N/A"}
Furnished: ${data.Furnished__c || data.furnished || "N/A"}
Parking: ${data.Parking_Spaces__c || data.parking || "N/A"}
Interior View

Property Features

${amenitiesHTML}
Living Area View

Contact Information

Reference ID: ${data.Reference_Number__c || data.referenceNumber || "N/A"}

Agent: ${data.Agent_Name__c || data.agentName || "N/A"}

Phone: ${data.Agent_Phone__c || data.agentPhone || "N/A"}

Email: ${data.Agent_Email__c || data.agentEmail || "N/A"}

Property Gallery

`; - } + // Generate property gallery for uncategorized images (already declared above) - createLeafampTemplate() { - const data = this.propertyData || {}; - const propertyName = data.Name || data.propertyName || "LEAFAMP URBAN"; - const location = data.Address__c || data.location || "City Living Experience"; - const price = data.Price__c || data.price || "Starting from $1,200,000"; - const referenceId = data.pcrm__Title_English__c || data.Name || data.propertyName || ""; - const bedrooms = data.Bedrooms__c || data.bedrooms || "N/A"; - const bathrooms = data.Bathrooms__c || data.bathrooms || "N/A"; - const size = data.Square_Feet__c || data.size || "N/A"; - const propertyType = data.Property_Type__c || data.propertyType || "Property Type"; - const description = data.Description_English__c || data.descriptionEnglish || "City living experience."; - - // Get smart images - use direct method for exterior - const exteriorImage = this.getExteriorImageUrl(); - const interiorImage = this.getSmartImageForSection('interior', 'https://images.unsplash.com/photo-1616486338812-3dadae4b4ace?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); - const bathroomImage = this.getSmartImageForSection('bathroom', 'https://images.unsplash.com/photo-1584622650111-993a426fbf0a?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); - const mapsImage = this.getMapsImageUrl(); - - // Debug logging for maps image - - // Generate property gallery for uncategorized images - const propertyGallery = this.generatePropertyGalleryHTML(); - - return `Leafamp Urban - City Living Experience
Property Exterior

${propertyName}

${location}

${price}

About Leafamp Urban

Experience the pulse of city life at Leafamp Urban, where modern design meets urban convenience in the heart of the metropolis.

Interior View

Urban Features

City Views
Transit Access
Urban Amenities
Smart Living
Bathroom View

Floor Plan & Location

Discover the strategic location and layout of Leafamp Urban, perfectly positioned for modern urban living with easy access to all city amenities.

MAPS IMAGE TEST: ${mapsImage}
Floor Plan & Location Map

Contact Information

Reference ID: ${referenceId}

Agent: ${data.agentName || "Urban Specialist"}

Phone: ${data.agentPhone || "(555) 987-6543"}

${propertyGallery}
`; - } + return `Shift Property - Modern Living
Property Exterior

${propertyName}

${location}

${price}

EXTERIOR IMAGE TEST: ${exteriorImage}

About Shift Property

Experience the future of living with Shift Property, where innovation meets comfort in perfect harmony.

Interior View

Modern Features

Innovation
Smart Design
Eco-Friendly
Connected Living
Kitchen View

Contact Information

Reference ID: ${referenceId}

Agent: ${ + data.agentName || "Innovation Specialist" + }

Phone: ${ + data.agentPhone || "(555) 789-0123" + }

${propertyGallery}
`; + } - createCoreshiftTemplate() { - return `Commercial Properties

Commercial Properties Template

Unlock Your Business Potential

`; - } - createModernHomeTemplate() { - - const data = this.propertyData || {}; - const propertyName = data.Name || data.propertyName || "Property Name"; - const propertyType = data.Property_Type__c || data.propertyType || "Property Type"; - const location = data.Address__c || data.location || "Location"; - const price = data.Price__c || data.price || "Price on Request"; - const bedrooms = data.Bedrooms__c || data.bedrooms || "N/A"; - const bathrooms = data.Bathrooms__c || data.bathrooms || "N/A"; - const area = data.Square_Feet__c || data.area || "N/A"; - const description = this.formatDescriptionForPDF(data.Description_English__c || data.description || "This beautiful property offers exceptional value and modern amenities. Located in a prime area, it represents an excellent investment opportunity."); - const referenceId = data.pcrm__Title_English__c || data.Name || data.propertyName || ""; - - // Contact information - const agentName = data.contactName || data.Agent_Name__c || data.agentName || "N/A"; - const agentPhone = data.contactPhone || data.Agent_Phone__c || data.agentPhone || "N/A"; - const agentEmail = data.contactEmail || data.Agent_Email__c || data.agentEmail || "N/A"; - - // Get smart images - const exteriorImage = this.getExteriorImageUrl(); - const interiorImage = this.getSmartImageForSection('interior', 'https://images.unsplash.com/photo-1616486338812-3dadae4b4ace?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); - const kitchenImage = this.getSmartImageForSection('kitchen', 'https://images.unsplash.com/photo-1556909114-f6e7ad7d3136?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); - - // Generate property gallery - const propertyGallery = this.generatePropertyGalleryHTML(); - - // Generate amenities from property data - const amenitiesHTML = this.generateAmenitiesHTML(data); - - return `Property Brochure

${propertyName}

${location}

${price}
${bedrooms} Beds ${bathrooms} Baths ${area} sq. ft.

Description

${data.descriptionEnglish || data.description || "This beautiful property offers exceptional value and modern amenities. Located in a prime area, it represents an excellent investment opportunity."}

Reference ID: ${data.Reference_Number__c || data.referenceNumber || "REF-001"}

In-depth Details

A closer look at the property's features and specifications.

Specifications

Status: ${data.status || "Available"}
Type: ${propertyType}
Floor: ${data.Floor__c || data.floor || "N/A"}
Parking: ${data.Parking_Spaces__c || data.parking || "N/A"}
Year Built: ${data.Build_Year__c || data.yearBuilt || "N/A"}
Furnishing: ${data.Furnished__c || data.furnishing || "N/A"}
Maintenance Fee: ${data.Maintenance_Fee__c || data.maintenanceFee || "N/A"}
Service Charge: ${data.Service_Charge__c || data.serviceCharge || "N/A"}

Amenities & Features

${amenitiesHTML}
Reference ID: ${data.Reference_Number__c || data.referenceNumber || "REF-001"}

Location & Nearby

Landmarks: ${data.Landmarks__c || data.nearbyLandmarks || "N/A"}
Transportation: ${data.Transportation__c || data.transportation || "N/A"}
Schools: ${data.Schools__c || data.schools || "N/A"}
Hospitals: ${data.Hospitals__c || data.hospitals || "N/A"}
Shopping: ${data.Shopping_Centers__c || data.shoppingCenters || "N/A"}
Airport: ${data.Airport_Distance__c || data.airportDistance || "N/A"}
City: ${data.City__c || data.cityBayut || data.cityPropertyfinder || "N/A"}
Community: ${data.Community__c || data.communityBayut || "N/A"}
Sub Community: ${data.Sub_Community__c || data.subCommunityBayut || "N/A"}
Locality: ${data.Locality__c || data.localityBayut || "N/A"}
Sub Locality: ${data.Sub_Locality__c || data.subLocalityBayut || "N/A"}
Tower: ${data.Tower__c || data.towerBayut || "N/A"}
Beach Distance: ${data.Beach_Distance__c || data.beachDistance || "N/A"}
Metro Distance: ${data.Metro_Distance__c || data.metroDistance || "N/A"}
Country Club: ${data.Country_Club__c || data.countryClub || "N/A"}
Property Location Map

Additional Information

Pet Friendly: ${data.petFriendly || "Pet Friendly Status"}
Smoking: ${data.smokingAllowed || "Smoking Allowed"}
Available From: ${data.availableFrom || "Available From Date"}
Minimum Contract: ${data.minimumContract || "Minimum Contract Duration"}
Security Deposit: ${data.securityDeposit || "Security Deposit"}
Reference ID: ${data.Reference_Number__c || data.referenceNumber || "REF-001"}
`; - } + createSaintbartsTemplate() { + const data = this.propertyData || {}; + const propertyName = data.Name || data.propertyName || "SAINT BARTS VILLA"; + const location = data.Address__c || data.location || "Caribbean Paradise"; + const price = data.Price__c || data.price || "Starting from $3,200,000"; + const referenceId = + data.pcrm__Title_English__c || data.Name || data.propertyName || ""; + const bedrooms = data.Bedrooms__c || data.bedrooms || "N/A"; + const bathrooms = data.Bathrooms__c || data.bathrooms || "N/A"; + const size = data.Square_Feet__c || data.size || "N/A"; + const propertyType = + data.Property_Type__c || data.propertyType || "Property Type"; + const description = + data.Description_English__c || + data.descriptionEnglish || + "Caribbean paradise awaits."; - createAsgar1Template() { - const data = this.propertyData || {}; - - // Basic property information - const propertyName = data.Name || data.propertyName || "Property Name"; - const location = data.Address__c || data.location || "Location"; - const price = data.Price__c || data.price || "Price"; - const referenceId = data.pcrm__Title_English__c || data.Name || data.propertyName || ""; - const bedrooms = data.Bedrooms__c || data.bedrooms || "N/A"; - const bathrooms = data.Bathrooms__c || data.bathrooms || "N/A"; - const area = data.Square_Feet__c || data.area || "N/A"; - const squareFeet = data.Square_Feet__c || data.area || "N/A"; - const sizeUnit = data.sizeUnit || "sq ft"; - const propertyType = data.Property_Type__c || data.propertyType || "N/A"; - const description = data.Description_English__c || data.description || "Property description not available."; - - // Get smart images - const exteriorImage = this.getExteriorImageUrl(); - const interiorImage = this.getSmartImageForSection('interior', 'https://images.unsplash.com/photo-1616486338812-3dadae4b4ace?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); - const bedroomImage = this.getSmartImageForSection('bedroom', 'https://images.unsplash.com/photo-1586023492125-27b2c045efd7?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); - - // Generate property gallery - const propertyGallery = this.generatePropertyGalleryHTML(); - - // Generate amenities from property data - const amenitiesHTML = this.generateAmenitiesHTML(data); - - // Contact information - const contactName = data.Agent_Name__c || data.contactName || "N/A"; - const contactPhone = data.Agent_Phone__c || data.contactPhone || "N/A"; - const contactEmail = data.Agent_Email__c || data.contactEmail || "N/A"; - const ownerName = data.Owner_Name__c || data.ownerName || "N/A"; - const ownerPhone = data.Owner_Phone__c || data.ownerPhone || "N/A"; - const ownerEmail = data.Owner_Email__c || data.ownerEmail || "N/A"; - - // Property specifications - const status = data.Status__c || data.status || "N/A"; - const yearBuilt = data.Build_Year__c || data.yearBuilt || "N/A"; - const floor = data.Floor__c || data.floor || "N/A"; - const parking = data.Parking_Spaces__c || data.parking || "N/A"; - const furnishing = data.Furnished__c || data.furnishing || "N/A"; - const maintenanceFee = data.Maintenance_Fee__c || data.maintenanceFee || "N/A"; - const serviceCharge = data.Service_Charge__c || data.serviceCharge || "N/A"; - - // Property details - const acres = data.acres || "0.75"; - - // Location details - const cityBayut = data.City__c || data.cityBayut || "N/A"; - const cityPropertyfinder = data.City__c || data.cityPropertyfinder || "N/A"; - const communityBayut = data.Community__c || data.communityBayut || "N/A"; - const subCommunityBayut = data.Sub_Community__c || data.subCommunityBayut || "N/A"; - const localityBayut = data.Locality__c || data.localityBayut || "N/A"; - const subLocalityBayut = data.Sub_Locality__c || data.subLocalityBayut || "N/A"; - const towerBayut = data.Tower__c || data.towerBayut || "N/A"; - - // Nearby amenities - const schools = data.Schools__c || data.schools || "N/A"; - const shoppingCenters = data.Shopping_Centers__c || data.shoppingCenters || "N/A"; - const hospitals = data.Hospitals__c || data.hospitals || "N/A"; - const countryClub = data.Country_Club__c || data.countryClub || "N/A"; - const airportDistance = data.Airport_Distance__c || data.airportDistance || "N/A"; - const nearbyLandmarks = data.Landmarks__c || data.nearbyLandmarks || "N/A"; - const transportation = data.Transportation__c || data.transportation || "N/A"; - const beachDistance = data.Beach_Distance__c || data.beachDistance || "N/A"; - const metroDistance = data.Metro_Distance__c || data.metroDistance || "N/A"; - - // Additional information - const petFriendly = data.Pet_Friendly__c || data.petFriendly || "N/A"; - const smokingAllowed = data.Smoking_Allowed__c || data.smokingAllowed || "N/A"; - const availableFrom = data.Available_From__c || data.availableFrom || "N/A"; - const utilitiesIncluded = data.Utilities_Included__c || data.utilitiesIncluded || "N/A"; - const internetIncluded = data.Internet_Included__c || data.internetIncluded || "N/A"; - const cableIncluded = data.Cable_Included__c || data.cableIncluded || "N/A"; - - // Additional property fields - const titleEnglish = data.Title_English__c || data.titleEnglish || "N/A"; - const descriptionEnglish = data.Description_English__c || data.descriptionEnglish || "N/A"; - const amenities = data.Amenities__c || data.amenities || "N/A"; - const features = data.Features__c || data.features || "N/A"; - const size = data.Square_Feet__c || data.size || "N/A"; - const parkingSpaces = data.Parking_Spaces__c || data.parkingSpaces || "N/A"; - const buildYear = data.Build_Year__c || data.buildYear || "N/A"; - const offeringType = data.Offering_Type__c || data.offeringType || "N/A"; - - // Financial and availability fields - const rentPriceMin = data.Rent_Price_Min__c || data.rentPriceMin || "N/A"; - const salePriceMin = data.Sale_Price_Min__c || data.salePriceMin || "N/A"; - const rentAvailableFrom = data.Rent_Available_From__c || data.rentAvailableFrom || "N/A"; - const rentAvailableTo = data.Rent_Available_To__c || data.rentAvailableTo || "N/A"; - const minimumContract = data.Minimum_Contract__c || data.minimumContract || "N/A"; - const securityDeposit = data.Security_Deposit__c || data.securityDeposit || "N/A"; - - return `Prestige Real Estate Brochure - ${propertyName}
${status.toUpperCase()}

${propertyName}

${location}

${bedrooms}
Bedrooms
${bathrooms}
Bathrooms
${squareFeet}
Sq. Ft.
${price}
Price

Description

${description}

Specifications

Reference ID: ${referenceId}
Status: ${status}
Type: ${propertyType}
Year Built: ${yearBuilt}
Floor: ${floor}
Parking: ${parking}
Furnishing: ${furnishing}

Amenities & Features

    ${amenitiesHTML}
Agent: ${contactName} | ${contactPhone} | ${contactEmail}
Owner: ${ownerName} | ${ownerPhone}
`; - } + // Get smart images - use direct method for exterior + const exteriorImage = this.getExteriorImageUrl(); + const interiorImage = this.getSmartImageForSection("interior", ""); + const bedroomImage = this.getSmartImageForSection("bedroom", ""); - createGrandOakVillaTemplate() { - const data = this.propertyData || {}; - - // Enhanced property data extraction with better fallbacks - const propertyName = data.Name || data.propertyName || data.pcrm__Title_English__c || "The Grand Oak Villa"; - const location = data.Address__c || data.location || "123 Luxury Lane, Prestige City, PC 45678"; - const price = data.Sale_Price_Min__c || data.Rent_Price_Min__c || data.Price__c || data.price || "$4,500,000"; - const referenceId = data.pcrm__Title_English__c || data.Name || data.propertyName || ""; - const bedrooms = data.Bedrooms__c || data.bedrooms || "5"; - const bathrooms = data.Bathrooms__c || data.bathrooms || "6"; - const squareFeet = data.Square_Feet__c || data.squareFeet || data.area || "6,200"; - const status = (data.Status__c || data.status || "FOR SALE").toString(); - - // Enhanced property details - const propertyType = data.Property_Type__c || data.propertyType || "Villa"; - const yearBuilt = data.Build_Year__c || data.yearBuilt || "2020"; - const furnishing = data.Furnished__c || data.furnishing || "Fully Furnished"; - const parking = data.Parking_Spaces__c || data.parking || "2"; - const description = this.formatDescriptionForPDF(data.Description_English__c || data.descriptionEnglish || data.description || "An exquisite villa offering unparalleled luxury and sophistication in one of the most prestigious locations."); - const floor = data.Floor__c || data.floor || "Ground Floor"; - const maintenanceFee = data.Maintenance_Fee__c || data.maintenanceFee || "N/A"; - const serviceCharge = data.Service_Charge__c || data.serviceCharge || "N/A"; - - // Additional property details - const lotSize = data.Lot_Size__c || data.lotSize || "0.5 acres"; - const heating = data.Heating__c || data.heating || "Central Air"; - const cooling = data.Cooling__c || data.cooling || "Central Air"; - const roof = data.Roof__c || data.roof || "Tile"; - const exterior = data.Exterior__c || data.exterior || "Stone & Brick"; - const foundation = data.Foundation__c || data.foundation || "Concrete"; - const utilities = data.Utilities__c || data.utilities || "All Connected"; - const zoning = data.Zoning__c || data.zoning || "Residential"; - const hoa = data.HOA__c || data.hoa || "Yes"; - const hoaFee = data.HOA_Fee__c || data.hoaFee || "$500/month"; - const taxYear = data.Tax_Year__c || data.taxYear || "2024"; - const taxAmount = data.Tax_Amount__c || data.taxAmount || "$12,000/year"; - const lastSold = data.Last_Sold__c || data.lastSold || "2020"; - const lastSoldPrice = data.Last_Sold_Price__c || data.lastSoldPrice || "$3,200,000"; - - // Location and POI data - const schools = data.Schools__c || data.schools || "5 min drive"; - const shoppingCenters = data.Shopping_Centers__c || data.shoppingCenters || "10 min drive"; - const airportDistance = data.Airport_Distance__c || data.airportDistance || "25 min drive"; - const nearbyLandmarks = data.Nearby_Landmarks__c || data.nearbyLandmarks || "City Center 15 min"; - const transportation = data.Transportation__c || data.transportation || "Metro 5 min walk"; - const hospitals = data.Hospitals__c || data.hospitals || "10 min drive"; - const beachDistance = data.Beach_Distance__c || data.beachDistance || "30 min drive"; - const metroDistance = data.Metro_Distance__c || data.metroDistance || "5 min walk"; - - // Additional information - const petFriendly = data.Pet_Friendly__c || data.petFriendly || "Yes"; - const smokingAllowed = data.Smoking_Allowed__c || data.smokingAllowed || "No"; - const availableFrom = data.Available_From__c || data.availableFrom || "Immediate"; - const minimumContract = data.Minimum_Contract__c || data.minimumContract || "12 months"; - const securityDeposit = data.Security_Deposit__c || data.securityDeposit || "2 months rent"; - const utilitiesIncluded = data.Utilities_Included__c || data.utilitiesIncluded || "Water, Electricity"; - const internetIncluded = data.Internet_Included__c || data.internetIncluded || "Yes"; - const cableIncluded = data.Cable_Included__c || data.cableIncluded || "Yes"; + // Generate property gallery for uncategorized images + const propertyGallery = this.generatePropertyGalleryHTML(); - // Agent and owner information - const agentName = data.Agent_Name__c || data.agentName || "Olivia Sterling"; - const agentPhone = data.Agent_Phone__c || data.agentPhone || "(555) 987-6543"; - const agentEmail = data.Agent_Email__c || data.agentEmail || "olivia@elysianestates.com"; - const ownerName = data.Owner_Name__c || data.ownerName || "John & Jane Doe"; - const ownerPhone = data.Owner_Phone__c || data.ownerPhone || "(555) 111-2222"; - const ownerEmail = data.Owner_Email__c || data.ownerEmail || "owner@email.com"; - // Get smart images - const exteriorImage = this.getExteriorImageUrl(); - const interiorImage1 = this.getSmartImageForSection('interior', 'https://images.unsplash.com/photo-1616486338812-3dadae4b4ace?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); - const interiorImage2 = this.getSmartImageForSection('living', 'https://images.unsplash.com/photo-1586023492125-27b2c045efd7?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); - const kitchenImage = this.getSmartImageForSection('kitchen', 'https://images.unsplash.com/photo-1600585152225-3579fe9d7ae2?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); - const bedroomImage = this.getSmartImageForSection('bedroom', 'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); + return `Saint Barts Villa - Caribbean Paradise
Property Exterior

${propertyName}

${location}

${price}

About Saint Barts Villa

Discover paradise at Saint Barts Villa, where tropical luxury meets Caribbean charm in an idyllic setting.

Interior View

Tropical Features

Beach Access
Tropical Gardens
Ocean Views
Luxury Amenities
Bedroom View

Contact Information

Reference ID: ${referenceId}

Agent: ${ + data.agentName || "Caribbean Specialist" + }

Phone: ${ + data.agentPhone || "(555) 456-7890" + }

${propertyGallery}
`; + } - // Generate amenities HTML - const amenitiesHTML = this.generateAmenitiesHTML(data); + createLearnoyTemplate() { + const data = this.propertyData || {}; + const propertyName = data.Name || data.propertyName || "Property Name"; + const location = data.Address__c || data.location || "Location"; + const price = data.Price__c || data.price || "Price"; + const bedrooms = data.Bedrooms__c || data.bedrooms || "N/A"; + const bathrooms = data.Bathrooms__c || data.bathrooms || "N/A"; + const size = data.Square_Feet__c || data.size || "N/A"; + const propertyType = data.Property_Type__c || data.propertyType || "N/A"; + const description = + data.Description_English__c || + data.descriptionEnglish || + "Property description not available."; + const referenceId = + data.pcrm__Title_English__c || data.Name || data.propertyName || ""; - // Generate property gallery HTML - const propertyGalleryHTML = this.generatePropertyGalleryHTML(data); + // Get smart images - use direct method for exterior + const exteriorImage = this.getExteriorImageUrl(); + const interiorImage = this.getSmartImageForSection( + "interior", + "https://images.unsplash.com/photo-1616486338812-3dadae4b4ace?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200" + ); + const livingImage = this.getSmartImageForSection( + "living", + "https://images.unsplash.com/photo-1586023492125-27b2c045efd7?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200" + ); - // Return the complete Grand Oak Villa template with all dynamic data - return `Grand Oak Villa - ${propertyName}
${status.toUpperCase()}

${propertyName}

${location}

${bedrooms}
Bedrooms
${bathrooms}
Bathrooms
${squareFeet}
Sq. Ft.
${price}
Price

Description

${description}

Specifications

Reference ID: ${referenceId}
Status: ${status}
Type: ${propertyType}
Year Built: ${yearBuilt}
Floor: ${floor}
Parking: ${parking}
Furnishing: ${furnishing}
Lot Size: ${lotSize}
Heating: ${heating}
Cooling: ${cooling}
Roof: ${roof}
Exterior: ${exterior}
Foundation: ${foundation}
Utilities: ${utilities}
Zoning: ${zoning}
HOA: ${hoa}
HOA Fee: ${hoaFee}

Amenities & Features

    ${amenitiesHTML}
Agent: ${agentName} | ${agentPhone} | ${agentEmail}
Owner: ${ownerName} | ${ownerPhone}

Nearby Locations

Schools: ${schools}
Shopping Centers: ${shoppingCenters}
Airport: ${airportDistance}
Landmarks: ${nearbyLandmarks}
Transportation: ${transportation}
Hospitals: ${hospitals}
Beach: ${beachDistance}
Metro: ${metroDistance}

Financial Information

Tax Year: ${taxYear}
Tax Amount: ${taxAmount}
Last Sold: ${lastSold}
Last Sold Price: ${lastSoldPrice}
Maintenance Fee: ${maintenanceFee}
Service Charge: ${serviceCharge}

Additional Information

Pet Friendly: ${petFriendly}
Smoking Allowed: ${smokingAllowed}
Available From: ${availableFrom}
Minimum Contract: ${minimumContract}
Security Deposit: ${securityDeposit}
Utilities Included: ${utilitiesIncluded}
Internet Included: ${internetIncluded}
Cable Included: ${cableIncluded}
Agent: ${agentName} | ${agentPhone} | ${agentEmail}
Owner: ${ownerName} | ${ownerPhone}

Image Gallery

Agent: ${agentName} | ${agentPhone} | ${agentEmail}
Owner: ${ownerName} | ${ownerPhone}
`; - } + // Generate property gallery for uncategorized images + const propertyGallery = this.generatePropertyGalleryHTML(); - createSampleTemplate() { - // Return the template HTML with all dynamic data - return `
+ // Generate amenities from property data + const amenitiesHTML = this.generateAmenitiesHTML(data); + + return `Learnoy Estate - Heritage Collection
Property Exterior

${propertyName}

${location}

${price}

About ${propertyName}

${description}

Property Type: ${propertyType}
Bedrooms: ${bedrooms}
Bathrooms: ${bathrooms}
Size: ${size} sq ft
Status: ${ + data.Status__c || data.status || "N/A" + }
Year Built: ${ + data.Build_Year__c || data.yearBuilt || "N/A" + }
Furnished: ${ + data.Furnished__c || data.furnished || "N/A" + }
Parking: ${ + data.Parking_Spaces__c || data.parking || "N/A" + }
Interior View

Property Features

${amenitiesHTML}
Living Area View

Contact Information

Reference ID: ${ + data.Reference_Number__c || data.referenceNumber || "N/A" + }

Agent: ${ + data.Agent_Name__c || data.agentName || "N/A" + }

Phone: ${ + data.Agent_Phone__c || data.agentPhone || "N/A" + }

Email: ${ + data.Agent_Email__c || data.agentEmail || "N/A" + }

Property Gallery

`; + } + + createLeafampTemplate() { + const data = this.propertyData || {}; + const propertyName = data.Name || data.propertyName || "LEAFAMP URBAN"; + const location = + data.Address__c || data.location || "City Living Experience"; + const price = data.Price__c || data.price || "Starting from $1,200,000"; + const referenceId = + data.pcrm__Title_English__c || data.Name || data.propertyName || ""; + const bedrooms = data.Bedrooms__c || data.bedrooms || "N/A"; + const bathrooms = data.Bathrooms__c || data.bathrooms || "N/A"; + const size = data.Square_Feet__c || data.size || "N/A"; + const propertyType = + data.Property_Type__c || data.propertyType || "Property Type"; + const description = + data.Description_English__c || + data.descriptionEnglish || + "City living experience."; + + // Get smart images - use direct method for exterior + const exteriorImage = this.getExteriorImageUrl(); + const interiorImage = this.getSmartImageForSection( + "interior", + "https://images.unsplash.com/photo-1616486338812-3dadae4b4ace?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200" + ); + const bathroomImage = this.getSmartImageForSection( + "bathroom", + "https://images.unsplash.com/photo-1584622650111-993a426fbf0a?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200" + ); + const mapsImage = this.getMapsImageUrl(); + + // Debug logging for maps image + + // Generate property gallery for uncategorized images + const propertyGallery = this.generatePropertyGalleryHTML(); + + return `Leafamp Urban - City Living Experience
Property Exterior

${propertyName}

${location}

${price}

About Leafamp Urban

Experience the pulse of city life at Leafamp Urban, where modern design meets urban convenience in the heart of the metropolis.

Interior View

Urban Features

City Views
Transit Access
Urban Amenities
Smart Living
Bathroom View

Floor Plan & Location

Discover the strategic location and layout of Leafamp Urban, perfectly positioned for modern urban living with easy access to all city amenities.

MAPS IMAGE TEST: ${mapsImage}
Floor Plan & Location Map

Contact Information

Reference ID: ${referenceId}

Agent: ${ + data.agentName || "Urban Specialist" + }

Phone: ${ + data.agentPhone || "(555) 987-6543" + }

${propertyGallery}
`; + } + + createCoreshiftTemplate() { + return `Commercial Properties

Commercial Properties Template

Unlock Your Business Potential

`; + } + createModernHomeTemplate() { + const data = this.propertyData || {}; + const propertyName = data.Name || data.propertyName || "Property Name"; + const propertyType = + data.Property_Type__c || data.propertyType || "Property Type"; + const location = data.Address__c || data.location || "Location"; + const price = data.Price__c || data.price || "Price on Request"; + const bedrooms = data.Bedrooms__c || data.bedrooms || "N/A"; + const bathrooms = data.Bathrooms__c || data.bathrooms || "N/A"; + const area = data.Square_Feet__c || data.area || "N/A"; + const description = this.formatDescriptionForPDF( + data.Description_English__c || + data.description || + "This beautiful property offers exceptional value and modern amenities. Located in a prime area, it represents an excellent investment opportunity." + ); + const referenceId = + data.pcrm__Title_English__c || data.Name || data.propertyName || ""; + + // Contact information + const agentName = + data.contactName || data.Agent_Name__c || data.agentName || "N/A"; + const agentPhone = + data.contactPhone || data.Agent_Phone__c || data.agentPhone || "N/A"; + const agentEmail = + data.contactEmail || data.Agent_Email__c || data.agentEmail || "N/A"; + + // Dynamic gallery and amenities + const propertyGallery = this.generatePropertyGalleryHTML(); + const amenitiesHTML = this.generateAmenitiesHTML(data); + + return `Property Brochure

${propertyName}

${location}

${price}
${bedrooms} Beds ${bathrooms} Baths ${area} sq. ft.

Description

${description}
Reference ID: ${ + data.Reference_Number__c || data.referenceNumber || "REF-001" + }

In-depth Details

A closer look at the property's features and specifications.

Specifications

Status: ${ + data.status || "Available" + }
Type: ${propertyType}
Floor: ${ + data.Floor__c || data.floor || "N/A" + }
Parking: ${ + data.Parking_Spaces__c || data.parking || "N/A" + }
Year Built: ${ + data.Build_Year__c || data.yearBuilt || "N/A" + }
Furnishing: ${ + data.Furnished__c || data.furnishing || "N/A" + }
Maintenance Fee: ${ + data.Maintenance_Fee__c || data.maintenanceFee || "N/A" + }
Service Charge: ${ + data.Service_Charge__c || data.serviceCharge || "N/A" + }

Amenities & Features

${amenitiesHTML}
Reference ID: ${ + data.Reference_Number__c || data.referenceNumber || "REF-001" + }

Location & Nearby

Landmarks: ${ + data.Landmarks__c || data.nearbyLandmarks || "N/A" + }
Transportation: ${ + data.Transportation__c || data.transportation || "N/A" + }
Schools: ${ + data.Schools__c || data.schools || "N/A" + }
Hospitals: ${ + data.Hospitals__c || data.hospitals || "N/A" + }
Shopping: ${ + data.Shopping_Centers__c || data.shoppingCenters || "N/A" + }
Airport: ${ + data.Airport_Distance__c || data.airportDistance || "N/A" + }
City: ${ + data.City__c || data.cityBayut || data.cityPropertyfinder || "N/A" + }
Community: ${ + data.Community__c || data.communityBayut || "N/A" + }
Sub Community: ${ + data.Sub_Community__c || data.subCommunityBayut || "N/A" + }
Locality: ${ + data.Locality__c || data.localityBayut || "N/A" + }
Sub Locality: ${ + data.Sub_Locality__c || data.subLocalityBayut || "N/A" + }
Tower: ${ + data.Tower__c || data.towerBayut || "N/A" + }
Beach Distance: ${ + data.Beach_Distance__c || data.beachDistance || "N/A" + }
Metro Distance: ${ + data.Metro_Distance__c || data.metroDistance || "N/A" + }
Country Club: ${ + data.Country_Club__c || data.countryClub || "N/A" + }
Property Location Map

Additional Information

Pet Friendly: ${ + data.petFriendly || "Pet Friendly Status" + }
Smoking: ${ + data.smokingAllowed || "Smoking Allowed" + }
Available From: ${ + data.availableFrom || "Available From Date" + }
Minimum Contract: ${ + data.minimumContract || "Minimum Contract Duration" + }
Security Deposit: ${ + data.securityDeposit || "Security Deposit" + }
Reference ID: ${ + data.Reference_Number__c || data.referenceNumber || "REF-001" + }
`; + } + + createAsgar1Template() { + const data = this.propertyData || {}; + + // Basic property information + const propertyName = data.Name || data.propertyName || "Property Name"; + const location = data.Address__c || data.location || "Location"; + const price = data.Price__c || data.price || "Price"; + const referenceId = + data.pcrm__Title_English__c || data.Name || data.propertyName || ""; + const bedrooms = data.Bedrooms__c || data.bedrooms || "N/A"; + const bathrooms = data.Bathrooms__c || data.bathrooms || "N/A"; + const area = data.Square_Feet__c || data.area || "N/A"; + const squareFeet = data.Square_Feet__c || data.area || "N/A"; + const sizeUnit = data.sizeUnit || "sq ft"; + const propertyType = data.Property_Type__c || data.propertyType || "N/A"; + const description = + data.Description_English__c || + data.description || + "Property description not available."; + + // Get smart images + const exteriorImage = this.getExteriorImageUrl(); + const interiorImage = this.getSmartImageForSection( + "interior", + "https://images.unsplash.com/photo-1616486338812-3dadae4b4ace?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200" + ); + const bedroomImage = this.getSmartImageForSection( + "bedroom", + "https://images.unsplash.com/photo-1586023492125-27b2c045efd7?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200" + ); + + // Generate property gallery + const propertyGallery = this.generatePropertyGalleryHTML(); + + // Generate amenities from property data + const amenitiesHTML = this.generateAmenitiesHTML(data); + + // Contact information + const contactName = data.Agent_Name__c || data.contactName || "N/A"; + const contactPhone = data.Agent_Phone__c || data.contactPhone || "N/A"; + const contactEmail = data.Agent_Email__c || data.contactEmail || "N/A"; + const ownerName = data.Owner_Name__c || data.ownerName || "N/A"; + const ownerPhone = data.Owner_Phone__c || data.ownerPhone || "N/A"; + const ownerEmail = data.Owner_Email__c || data.ownerEmail || "N/A"; + + // Property specifications + const status = data.Status__c || data.status || "N/A"; + const yearBuilt = data.Build_Year__c || data.yearBuilt || "N/A"; + const floor = data.Floor__c || data.floor || "N/A"; + const parking = data.Parking_Spaces__c || data.parking || "N/A"; + const furnishing = data.Furnished__c || data.furnishing || "N/A"; + const maintenanceFee = + data.Maintenance_Fee__c || data.maintenanceFee || "N/A"; + const serviceCharge = data.Service_Charge__c || data.serviceCharge || "N/A"; + + // Property details + const acres = data.acres || "0.75"; + + // Location details + const cityBayut = data.City__c || data.cityBayut || "N/A"; + const cityPropertyfinder = data.City__c || data.cityPropertyfinder || "N/A"; + const communityBayut = data.Community__c || data.communityBayut || "N/A"; + const subCommunityBayut = + data.Sub_Community__c || data.subCommunityBayut || "N/A"; + const localityBayut = data.Locality__c || data.localityBayut || "N/A"; + const subLocalityBayut = + data.Sub_Locality__c || data.subLocalityBayut || "N/A"; + const towerBayut = data.Tower__c || data.towerBayut || "N/A"; + + // Nearby amenities + const schools = data.Schools__c || data.schools || "N/A"; + const shoppingCenters = + data.Shopping_Centers__c || data.shoppingCenters || "N/A"; + const hospitals = data.Hospitals__c || data.hospitals || "N/A"; + const countryClub = data.Country_Club__c || data.countryClub || "N/A"; + const airportDistance = + data.Airport_Distance__c || data.airportDistance || "N/A"; + const nearbyLandmarks = data.Landmarks__c || data.nearbyLandmarks || "N/A"; + const transportation = + data.Transportation__c || data.transportation || "N/A"; + const beachDistance = data.Beach_Distance__c || data.beachDistance || "N/A"; + const metroDistance = data.Metro_Distance__c || data.metroDistance || "N/A"; + + // Additional information + const petFriendly = data.Pet_Friendly__c || data.petFriendly || "N/A"; + const smokingAllowed = + data.Smoking_Allowed__c || data.smokingAllowed || "N/A"; + const availableFrom = data.Available_From__c || data.availableFrom || "N/A"; + const utilitiesIncluded = + data.Utilities_Included__c || data.utilitiesIncluded || "N/A"; + const internetIncluded = + data.Internet_Included__c || data.internetIncluded || "N/A"; + const cableIncluded = data.Cable_Included__c || data.cableIncluded || "N/A"; + + // Additional property fields + const titleEnglish = data.Title_English__c || data.titleEnglish || "N/A"; + const descriptionEnglish = + data.Description_English__c || data.descriptionEnglish || "N/A"; + const amenities = data.Amenities__c || data.amenities || "N/A"; + const features = data.Features__c || data.features || "N/A"; + const size = data.Square_Feet__c || data.size || "N/A"; + const parkingSpaces = data.Parking_Spaces__c || data.parkingSpaces || "N/A"; + const buildYear = data.Build_Year__c || data.buildYear || "N/A"; + const offeringType = data.Offering_Type__c || data.offeringType || "N/A"; + // Financial and availability fields + const rentPriceMin = data.Rent_Price_Min__c || data.rentPriceMin || "N/A"; + const salePriceMin = data.Sale_Price_Min__c || data.salePriceMin || "N/A"; + const rentAvailableFrom = + data.Rent_Available_From__c || data.rentAvailableFrom || "N/A"; + const rentAvailableTo = + data.Rent_Available_To__c || data.rentAvailableTo || "N/A"; + const minimumContract = + data.Minimum_Contract__c || data.minimumContract || "N/A"; + const securityDeposit = + data.Security_Deposit__c || data.securityDeposit || "N/A"; + + return `Prestige Real Estate Brochure - ${propertyName}
${status.toUpperCase()}

${propertyName}

${location}

${bedrooms}
Bedrooms
${bathrooms}
Bathrooms
${squareFeet}
Sq. Ft.
${price}
Price

Description

${description}

Specifications

Reference ID: ${referenceId}
Status: ${status}
Type: ${propertyType}
Year Built: ${yearBuilt}
Floor: ${floor}
Parking: ${parking}
Furnishing: ${furnishing}

Amenities & Features

    ${amenitiesHTML}
Agent: ${contactName} | ${contactPhone} | ${contactEmail}
Owner: ${ownerName} | ${ownerPhone}
`; + } + + createGrandOakVillaTemplate() { + const data = this.propertyData || {}; + + // Enhanced property data extraction with better fallbacks + const propertyName = + data.Name || + data.propertyName || + data.pcrm__Title_English__c || + "The Grand Oak Villa"; + const location = + data.Address__c || + data.location || + "123 Luxury Lane, Prestige City, PC 45678"; + const price = + data.Sale_Price_Min__c || + data.Rent_Price_Min__c || + data.Price__c || + data.price || + "$4,500,000"; + const referenceId = + data.pcrm__Title_English__c || data.Name || data.propertyName || ""; + const bedrooms = data.Bedrooms__c || data.bedrooms || "5"; + const bathrooms = data.Bathrooms__c || data.bathrooms || "6"; + const squareFeet = + data.Square_Feet__c || data.squareFeet || data.area || "6,200"; + const status = (data.Status__c || data.status || "FOR SALE").toString(); + + // Enhanced property details + const propertyType = data.Property_Type__c || data.propertyType || "Villa"; + const yearBuilt = data.Build_Year__c || data.yearBuilt || "2020"; + const furnishing = + data.Furnished__c || data.furnishing || "Fully Furnished"; + const parking = data.Parking_Spaces__c || data.parking || "2"; + const description = this.formatDescriptionForPDF( + data.Description_English__c || + data.descriptionEnglish || + data.description || + "An exquisite villa offering unparalleled luxury and sophistication in one of the most prestigious locations." + ); + const floor = data.Floor__c || data.floor || "Ground Floor"; + const maintenanceFee = + data.Maintenance_Fee__c || data.maintenanceFee || "N/A"; + const serviceCharge = data.Service_Charge__c || data.serviceCharge || "N/A"; + + // Additional property details + const lotSize = data.Lot_Size__c || data.lotSize || "0.5 acres"; + const heating = data.Heating__c || data.heating || "Central Air"; + const cooling = data.Cooling__c || data.cooling || "Central Air"; + const roof = data.Roof__c || data.roof || "Tile"; + const exterior = data.Exterior__c || data.exterior || "Stone & Brick"; + const foundation = data.Foundation__c || data.foundation || "Concrete"; + const utilities = data.Utilities__c || data.utilities || "All Connected"; + const zoning = data.Zoning__c || data.zoning || "Residential"; + const hoa = data.HOA__c || data.hoa || "Yes"; + const hoaFee = data.HOA_Fee__c || data.hoaFee || "$500/month"; + const taxYear = data.Tax_Year__c || data.taxYear || "2024"; + const taxAmount = data.Tax_Amount__c || data.taxAmount || "$12,000/year"; + const lastSold = data.Last_Sold__c || data.lastSold || "2020"; + const lastSoldPrice = + data.Last_Sold_Price__c || data.lastSoldPrice || "$3,200,000"; + + // Location and POI data + const schools = data.Schools__c || data.schools || "5 min drive"; + const shoppingCenters = + data.Shopping_Centers__c || data.shoppingCenters || "10 min drive"; + const airportDistance = + data.Airport_Distance__c || data.airportDistance || "25 min drive"; + const nearbyLandmarks = + data.Nearby_Landmarks__c || data.nearbyLandmarks || "City Center 15 min"; + const transportation = + data.Transportation__c || data.transportation || "Metro 5 min walk"; + const hospitals = data.Hospitals__c || data.hospitals || "10 min drive"; + const beachDistance = + data.Beach_Distance__c || data.beachDistance || "30 min drive"; + const metroDistance = + data.Metro_Distance__c || data.metroDistance || "5 min walk"; + + // Additional information + const petFriendly = data.Pet_Friendly__c || data.petFriendly || "Yes"; + const smokingAllowed = + data.Smoking_Allowed__c || data.smokingAllowed || "No"; + const availableFrom = + data.Available_From__c || data.availableFrom || "Immediate"; + const minimumContract = + data.Minimum_Contract__c || data.minimumContract || "12 months"; + const securityDeposit = + data.Security_Deposit__c || data.securityDeposit || "2 months rent"; + const utilitiesIncluded = + data.Utilities_Included__c || + data.utilitiesIncluded || + "Water, Electricity"; + const internetIncluded = + data.Internet_Included__c || data.internetIncluded || "Yes"; + const cableIncluded = data.Cable_Included__c || data.cableIncluded || "Yes"; + + // Agent and owner information + const agentName = data.Agent_Name__c || data.agentName || "Olivia Sterling"; + const agentPhone = + data.Agent_Phone__c || data.agentPhone || "(555) 987-6543"; + const agentEmail = + data.Agent_Email__c || data.agentEmail || "olivia@elysianestates.com"; + const ownerName = data.Owner_Name__c || data.ownerName || "John & Jane Doe"; + const ownerPhone = + data.Owner_Phone__c || data.ownerPhone || "(555) 111-2222"; + const ownerEmail = + data.Owner_Email__c || data.ownerEmail || "owner@email.com"; + // Get smart images + const exteriorImage = this.getExteriorImageUrl(); + const interiorImage1 = this.getSmartImageForSection( + "interior", + "https://images.unsplash.com/photo-1616486338812-3dadae4b4ace?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200" + ); + const interiorImage2 = this.getSmartImageForSection( + "living", + "https://images.unsplash.com/photo-1586023492125-27b2c045efd7?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200" + ); + const kitchenImage = this.getSmartImageForSection( + "kitchen", + "https://images.unsplash.com/photo-1600585152225-3579fe9d7ae2?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200" + ); + const bedroomImage = this.getSmartImageForSection( + "bedroom", + "https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200" + ); + + // Generate amenities HTML + const amenitiesHTML = this.generateAmenitiesHTML(data); + + // Generate property gallery HTML + const propertyGalleryHTML = this.generatePropertyGalleryHTML(data); + + // Return the complete Grand Oak Villa template with all dynamic data + return `Grand Oak Villa - ${propertyName}
${status.toUpperCase()}

${propertyName}

${location}

${bedrooms}
Bedrooms
${bathrooms}
Bathrooms
${squareFeet}
Sq. Ft.
${price}
Price

Description

${description}

Specifications

Reference ID: ${referenceId}
Status: ${status}
Type: ${propertyType}
Year Built: ${yearBuilt}
Floor: ${floor}
Parking: ${parking}
Furnishing: ${furnishing}
Lot Size: ${lotSize}
Heating: ${heating}
Cooling: ${cooling}
Roof: ${roof}
Exterior: ${exterior}
Foundation: ${foundation}
Utilities: ${utilities}
Zoning: ${zoning}
HOA: ${hoa}
HOA Fee: ${hoaFee}

Amenities & Features

    ${amenitiesHTML}
Agent: ${agentName} | ${agentPhone} | ${agentEmail}
Owner: ${ownerName} | ${ownerPhone}

Nearby Locations

Schools: ${schools}
Shopping Centers: ${shoppingCenters}
Airport: ${airportDistance}
Landmarks: ${nearbyLandmarks}
Transportation: ${transportation}
Hospitals: ${hospitals}
Beach: ${beachDistance}
Metro: ${metroDistance}

Financial Information

Tax Year: ${taxYear}
Tax Amount: ${taxAmount}
Last Sold: ${lastSold}
Last Sold Price: ${lastSoldPrice}
Maintenance Fee: ${maintenanceFee}
Service Charge: ${serviceCharge}

Additional Information

Pet Friendly: ${petFriendly}
Smoking Allowed: ${smokingAllowed}
Available From: ${availableFrom}
Minimum Contract: ${minimumContract}
Security Deposit: ${securityDeposit}
Utilities Included: ${utilitiesIncluded}
Internet Included: ${internetIncluded}
Cable Included: ${cableIncluded}
Agent: ${agentName} | ${agentPhone} | ${agentEmail}
Owner: ${ownerName} | ${ownerPhone}

Image Gallery

Agent: ${agentName} | ${agentPhone} | ${agentEmail}
Owner: ${ownerName} | ${ownerPhone}
`; + } + + createSampleTemplate() { + // Return the template HTML with all dynamic data + return `
@@ -2540,23 +3089,23 @@ export default class PropertyTemplateSelector extends LightningElement {
`; - } + } + createSampleTemplate() { + const data = this.propertyData || {}; + const propertyName = data.propertyName || "The Serenity House"; + const location = + data.location || "123 Luxury Lane, Prestige City, PC 45678"; + const price = data.price || "$4,500,000"; + const referenceId = data.referenceNumber || "ES-8821"; + const agentName = data.agentName || "Olivia Sterling"; + const agentPhone = data.agentPhone || "(555) 987-6543"; + const agentEmail = data.agentEmail || "olivia@elysianestates.com"; + const ownerName = data.ownerName || "John & Jane Doe"; + const ownerPhone = data.ownerPhone || "(555) 111-2222"; + const ownerEmail = data.ownerEmail || "owner.serenity@email.com"; - createSampleTemplate() { - const data = this.propertyData || {}; - const propertyName = data.propertyName || "The Serenity House"; - const location = data.location || "123 Luxury Lane, Prestige City, PC 45678"; - const price = data.price || "$4,500,000"; - const referenceId = data.referenceNumber || "ES-8821"; - const agentName = data.agentName || "Olivia Sterling"; - const agentPhone = data.agentPhone || "(555) 987-6543"; - const agentEmail = data.agentEmail || "olivia@elysianestates.com"; - const ownerName = data.ownerName || "John & Jane Doe"; - const ownerPhone = data.ownerPhone || "(555) 111-2222"; - const ownerEmail = data.ownerEmail || "owner.serenity@email.com"; - - // Return the complete Grand Oak Villa template with all dynamic data - return `
+ // Return the complete Grand Oak Villa template with all dynamic data + return `
Cover
An Urban Oasis

${propertyName}

${location}

Where Design Meets Desire.

${propertyName} is not just a building; it's a bold statement on modern urban living. ${description}

Every residence is a testament to quality, featuring panoramic city views from floor-to-ceiling windows, intelligent home systems, and finishes selected from the finest materials around the globe. This is more than a home; it's a new perspective.

Interior
${propertyName}Page 02 / 07
${propertyName}Page 03 / 07

An unrivaled collection of amenities offers residents a resort-style living experience. From the serene rooftop pool to the state-of-the-art wellness center, every detail is crafted for comfort, convenience, and luxury.

Feature

Lifestyle Amenities

    ${amenitiesHTML}

Key Specifications

Status ${status}
Property Type ${propertyType}
Year Built ${yearBuilt}
Floor ${data.Floor__c || "N/A"}
Maintenance Fee ${data.Maintenance_Fee__c || "N/A"}
Bedrooms ${bedrooms}
Bathrooms ${bathrooms}
Square Feet ${squareFeet}
${propertyName}Page 04 / 07
Residence Plan

${bedrooms}-Bedroom Residence

${squareFeet}
SQ. FT.
${bedrooms}
BEDROOMS
${bathrooms}
BATHROOMS
${parking}
PARKING

A thoughtfully designed space perfect for urban professionals or small families, combining comfort with panoramic city views.

Penthouse Plan

Premium ${propertyType}

${squareFeet}
SQ. FT.
${bedrooms}
BEDROOMS
${bathrooms}
BATHROOMS
${furnishing}
FURNISHING

The pinnacle of luxury living, this residence offers expansive spaces, premium finishes, and exclusive access to premium amenities.

Additional Information

Pets
${petFriendly}
Smoking
${smoking}
Availability
${availability}
Parking
${parking}
Furnishing
${furnishing}
Utilities
${utilities}
${propertyName}Page 05 / 07

Schedule a Private Viewing

Experience ${propertyName} firsthand. Contact our sales executive to arrange an exclusive tour of the property and available residences.

${agentName}
Sales Executive, Elysian Properties
${agentPhone}
${agentEmail}

Neighborhood Highlights

  • Landmarks: ${landmarks}
  • Transportation: ${transportation}
  • Schools: ${schools}
  • Shopping: ${shopping}
  • Airport: ${airport}
${propertyName}Page 06 / 07
${propertyGallery}
${propertyName}Page 07 / 07
`; - } + const propertyName = data.Name || data.propertyName || "Property Name"; + const propertyType = data.Property_Type__c || data.propertyType || "N/A"; + const location = data.Address__c || data.location || "Location"; + const price = + data.Sale_Price_Min__c || + data.Rent_Price_Min__c || + data.Price__c || + data.salePriceMin || + data.rentPriceMin || + data.price || + "Price on Request"; + const referenceId = + data.pcrm__Title_English__c || data.Name || data.propertyName || "N/A"; - createSerenityHouseTemplate() { - const data = this.propertyData || {}; - - // Extract all available property data with fallbacks - const propertyName = data.Name || data.propertyName || "Property Name"; - const location = data.Address__c || data.location || "Location"; - const referenceId = data.pcrm__Title_English__c || data.Name || data.propertyName || ""; - const agentName = data.contactName || data.Agent_Name__c || data.agentName || "N/A"; - const agentPhone = data.contactPhone || data.Agent_Phone__c || data.agentPhone || "N/A"; - const agentEmail = data.contactEmail || data.Agent_Email__c || data.agentEmail || "N/A"; - const ownerName = data.Owner_Name__c || data.ownerName || "N/A"; - const ownerPhone = data.Owner_Phone__c || data.ownerPhone || "N/A"; - const ownerEmail = data.Owner_Email__c || data.ownerEmail || "N/A"; - - // Dynamic pricing with fallbacks - const price = data.Sale_Price_Min__c || data.Rent_Price_Min__c || data.Price__c || data.salePriceMin || data.rentPriceMin || data.price || "Price on Request"; - const priceDisplay = price !== "Price on Request" ? `Offered at ${price}` : "Price on Request"; - - // Dynamic property details - const bedrooms = data.Bedrooms__c || data.bedrooms || "N/A"; - const bathrooms = data.Bathrooms__c || data.bathrooms || "N/A"; - const squareFeet = data.Square_Feet__c || data.squareFeet || data.area || "N/A"; - const propertyType = data.Property_Type__c || data.propertyType || "N/A"; - const status = data.Status__c || data.status || data.offeringType || "N/A"; - const yearBuilt = data.Build_Year__c || data.yearBuilt || data.buildYear || "N/A"; - const furnishing = data.Furnished__c || data.furnishing || "N/A"; - const parking = data.Parking_Spaces__c || data.parking || "N/A"; - - // Dynamic description - const description = this.formatDescriptionForPDF(data.Description_English__c || data.descriptionEnglish || data.description || "Property description not available."); - - // Get smart images - const exteriorImage = this.getExteriorImageUrl(); - const interiorImage = this.getSmartImageForSection('interior', 'https://images.unsplash.com/photo-1616486338812-3dadae4b4ace?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); - const bedroomImage = this.getSmartImageForSection('bedroom', 'https://images.unsplash.com/photo-1586023492125-27b2c045efd7?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); - - // Generate property gallery - const propertyGallery = this.generatePropertyGalleryHTML(); - - // Generate amenities from property data - const amenitiesHTML = this.generateAmenitiesHTML(data); - - // Dynamic location details - const schools = data.Schools__c || data.schools || "N/A"; - const shopping = data.Shopping_Centers__c || data.shoppingCenters || "N/A"; - const hospitals = data.Hospitals__c || data.hospitals || "N/A"; - const countryClub = data.Country_Club__c || data.countryClub || "N/A"; - const airport = data.Airport_Distance__c || data.airportDistance || "N/A"; - - // Dynamic additional info - const petFriendly = data.Pet_Friendly__c || data.petFriendly || "N/A"; - const smoking = data.Smoking_Allowed__c || data.smokingAllowed || "N/A"; - const availability = data.Available_From__c || data.availableFrom || "N/A"; - const utilities = data.Utilities_Included__c || data.utilitiesIncluded || "N/A"; - - return ` + const description = this.formatDescriptionForPDF( + data.Description_English__c || + data.descriptionEnglish || + data.description || + "Property description not available." + ); + + const propertyGallery = this.generatePropertyGalleryHTML(); + + return `${propertyName} - Luxury Brochure
Cover

${propertyName}

${location}
${price}
Ref ID: ${referenceId}
${description}
${propertyGallery}
`; + } +/* + // Dynamic additional info + const petFriendly = data.Pet_Friendly__c || data.petFriendly || "N/A"; + const smoking = data.Smoking_Allowed__c || data.smokingAllowed || "N/A"; + const availability = data.Available_From__c || data.availableFrom || "N/A"; + const utilities = + data.Utilities_Included__c || data.utilitiesIncluded || "N/A"; + + return `${propertyName} - Luxury Brochure
Cover

${propertyName}

${location}
${description}
${propertyName}
${this.generatePropertyGalleryHTML()}
${propertyName}
`; + interiorImage || exteriorImage + }" alt="Interior"/>
${propertyName}Page 02 / 07
${propertyGallery}
${propertyName}Page 03 / 07
Feature

Lifestyle Amenities

    ${amenitiesHTML}

Key Specifications

Status ${status}
Property Type ${propertyType}
Year Built ${yearBuilt}
Floor ${ + data.Floor__c || "N/A" + }
Maintenance Fee ${ + data.Maintenance_Fee__c || "N/A" + }
Bedrooms ${bedrooms}
Bathrooms ${bathrooms}
Square Feet ${squareFeet}
${propertyName}Page 04 / 07
Residence Plan

${bedrooms}-Bedroom Residence

${squareFeet}
SQ. FT.
${bedrooms}
BEDROOMS
${bathrooms}
BATHROOMS
${parking}
PARKING

A thoughtfully designed space perfect for urban professionals or small families, combining comfort with panoramic city views.

Penthouse Plan

Premium ${propertyType}

${squareFeet}
SQ. FT.
${bedrooms}
BEDROOMS
${bathrooms}
BATHROOMS
${furnishing}
FURNISHING

The pinnacle of luxury living, this residence offers expansive spaces, premium finishes, and exclusive access to premium amenities.

Additional Information

Pets
${petFriendly}
Smoking
${smoking}
Availability
${availability}
Parking
${parking}
Furnishing
${furnishing}
Utilities
${utilities}
${propertyName}Page 05 / 07

Schedule a Private Viewing

Experience ${propertyName} firsthand. Contact our sales executive to arrange an exclusive tour of the property and available residences.

${agentName}
Sales Executive, Elysian Properties
${agentPhone}
${agentEmail}

Neighborhood Highlights

  • Landmarks: ${landmarks}
  • Transportation: ${transportation}
  • Schools: ${schools}
  • Shopping: ${shopping}
  • Airport: ${airport}
${propertyName}Page 06 / 07
${propertyGallery}
${propertyName}Page 07 / 07
`; + }*/ + + createSerenityHouseTemplate() { + const data = this.propertyData || {}; + + // Extract all available property data with fallbacks + const propertyName = data.Name || data.propertyName || "Property Name"; + const location = data.Address__c || data.location || "Location"; + const referenceId = + data.pcrm__Title_English__c || data.Name || data.propertyName || ""; + const agentName = + data.contactName || data.Agent_Name__c || data.agentName || "N/A"; + const agentPhone = + data.contactPhone || data.Agent_Phone__c || data.agentPhone || "N/A"; + const agentEmail = + data.contactEmail || data.Agent_Email__c || data.agentEmail || "N/A"; + const ownerName = data.Owner_Name__c || data.ownerName || "N/A"; + const ownerPhone = data.Owner_Phone__c || data.ownerPhone || "N/A"; + const ownerEmail = data.Owner_Email__c || data.ownerEmail || "N/A"; + + // Dynamic pricing with fallbacks + const price = + data.Sale_Price_Min__c || + data.Rent_Price_Min__c || + data.Price__c || + data.salePriceMin || + data.rentPriceMin || + data.price || + "Price on Request"; + const priceDisplay = + price !== "Price on Request" ? `Offered at ${price}` : "Price on Request"; + + // Dynamic property details + const bedrooms = data.Bedrooms__c || data.bedrooms || "N/A"; + const bathrooms = data.Bathrooms__c || data.bathrooms || "N/A"; + const squareFeet = + data.Square_Feet__c || data.squareFeet || data.area || "N/A"; + const propertyType = data.Property_Type__c || data.propertyType || "N/A"; + const status = data.Status__c || data.status || data.offeringType || "N/A"; + const yearBuilt = + data.Build_Year__c || data.yearBuilt || data.buildYear || "N/A"; + const furnishing = data.Furnished__c || data.furnishing || "N/A"; + const parking = data.Parking_Spaces__c || data.parking || "N/A"; + + // Dynamic description + const description = this.formatDescriptionForPDF( + data.Description_English__c || + data.descriptionEnglish || + data.description || + "Property description not available." + ); + + // Get smart images + const exteriorImage = this.getExteriorImageUrl(); + const interiorImage = this.getSmartImageForSection( + "interior", + "https://images.unsplash.com/photo-1616486338812-3dadae4b4ace?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200" + ); + const bedroomImage = this.getSmartImageForSection( + "bedroom", + "https://images.unsplash.com/photo-1586023492125-27b2c045efd7?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200" + ); + + // Generate property gallery + const propertyGallery = this.generatePropertyGalleryHTML(); + + // Generate amenities from property data + const amenitiesHTML = this.generateAmenitiesHTML(data); + + // Dynamic location details + const schools = data.Schools__c || data.schools || "N/A"; + const shopping = data.Shopping_Centers__c || data.shoppingCenters || "N/A"; + const hospitals = data.Hospitals__c || data.hospitals || "N/A"; + const countryClub = data.Country_Club__c || data.countryClub || "N/A"; + const airport = data.Airport_Distance__c || data.airportDistance || "N/A"; + + // Dynamic additional info + const petFriendly = data.Pet_Friendly__c || data.petFriendly || "N/A"; + const smoking = data.Smoking_Allowed__c || data.smokingAllowed || "N/A"; + const availability = data.Available_From__c || data.availableFrom || "N/A"; + const utilities = + data.Utilities_Included__c || data.utilitiesIncluded || "N/A"; + + return ` @@ -3213,7 +3833,6 @@ export default class PropertyTemplateSelector extends LightningElement { font-size: 0.9rem; color: var(--color-text-secondary); } - .p1-footer strong { color: var(--color-text-primary); } @@ -3409,7 +4028,6 @@ export default class PropertyTemplateSelector extends LightningElement { color: var(--color-accent-beige); margin-right: 12px; } - .p4-section-title { font-size: 1rem; font-weight: 700; @@ -3501,7 +4119,9 @@ export default class PropertyTemplateSelector extends LightningElement {
-
${data.collection || "Elysian Estates Collection"}
+
${ + data.collection || "Elysian Estates Collection" + }

${propertyName}

${location}

Property: ${propertyName}

@@ -3583,11 +4203,17 @@ export default class PropertyTemplateSelector extends LightningElement {
Floor - ${data.Floor__c || data.floor || "N/A"} + ${ + data.Floor__c || data.floor || "N/A" + }
Maintenance Fee - ${data.Maintenance_Fee__c || data.maintenanceFee || "N/A"} + ${ + data.Maintenance_Fee__c || + data.maintenanceFee || + "N/A" + }
Parking @@ -3595,10 +4221,11 @@ export default class PropertyTemplateSelector extends LightningElement {
Service Charge - ${data.Service_Charge__c || data.serviceCharge || "N/A"} + ${ + data.Service_Charge__c || data.serviceCharge || "N/A" + }
-

Amenities & Features

@@ -3686,448 +4313,330 @@ export default class PropertyTemplateSelector extends LightningElement { `; - } + } - // Error handling methods - clearError() { - this.error = ''; - } + // Error handling methods + clearError() { + this.error = ""; + } - // Development mode properties - @track debugMode = false; + // Development mode properties + @track debugMode = false; - // Development page event handlers - handleClearData() { - this.currentStep = 1; - this.selectedTemplateId = ''; - this.selectedPropertyId = ''; - this.propertyData = {}; - this.htmlContent = ''; - this.editorContent = ''; - this.error = ''; - this.showPdfPreview = false; - this.showImageReview = false; - this.showImageReplacement = false; - this.showSaveDialog = false; - this.undoStack = []; - this.redoStack = []; - this.showSuccess('All data cleared'); - } + // Development page event handlers + handleClearData() { + this.currentStep = 1; + this.selectedTemplateId = ""; + this.selectedPropertyId = ""; + this.propertyData = {}; + this.htmlContent = ""; + this.editorContent = ""; + this.error = ""; + this.showPdfPreview = false; + this.showImageReview = false; + this.showImageReplacement = false; + this.showSaveDialog = false; + this.undoStack = []; + this.redoStack = []; + this.showSuccess("All data cleared"); + } - handleResetTemplates() { - this.currentStep = 1; - this.selectedTemplateId = ''; - this.selectedPropertyId = ''; - this.propertyData = {}; - this.htmlContent = ''; - this.editorContent = ''; - this.showSuccess('Templates reset to default'); - } + handleResetTemplates() { + this.currentStep = 1; + this.selectedTemplateId = ""; + this.selectedPropertyId = ""; + this.propertyData = {}; + this.htmlContent = ""; + this.editorContent = ""; + this.showSuccess("Templates reset to default"); + } - handleTestPdf() { - if (this.selectedTemplateId && this.selectedPropertyId) { - this.generatePdfViaExternalApi(); - } else { - this.showError('Please select a template and property first'); - } + handleTestPdf() { + if (this.selectedTemplateId && this.selectedPropertyId) { + this.generatePdfViaExternalApi(); + } else { + this.showError("Please select a template and property first"); } + } - handleToggleDebug(event) { - this.debugMode = event.detail.debugMode; - if (this.debugMode) { - this.showSuccess('Debug mode enabled - check console for detailed logs'); - } + handleToggleDebug(event) { + this.debugMode = event.detail.debugMode; + if (this.debugMode) { + this.showSuccess("Debug mode enabled - check console for detailed logs"); } + } - // PDF Preview methods - closePdfPreview() { - this.showPdfPreview = false; - } + // PDF Preview methods + closePdfPreview() { + this.showPdfPreview = false; + } - // Editor methods (placeholder implementations) - handleSave() { - const editorContent = this.template.querySelector('.enhanced-editor-content'); - if (editorContent) { - const content = editorContent.innerHTML; - const blob = new Blob([content], { type: 'text/html' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = 'template.html'; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - this.showSuccess('Template saved successfully'); - } - } - handleReset() { - // Reload the template - this.loadTemplateInStep3(); + // Editor methods (placeholder implementations) + handleSave() { + const editorContent = this.template.querySelector( + ".enhanced-editor-content" + ); + if (editorContent) { + const content = editorContent.innerHTML; + const blob = new Blob([content], { type: "text/html" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = "template.html"; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + this.showSuccess("Template saved successfully"); } + } + handleReset() { + // Reload the template + this.loadTemplateInStep3(); + } - handleLoad() { - // Create a file input to load template - const input = document.createElement('input'); - input.type = 'file'; - input.accept = '.html,.txt'; - input.onchange = (event) => { - const file = event.target.files[0]; - if (file) { - const reader = new FileReader(); - reader.onload = (e) => { - const content = e.target.result; - const editorContent = this.template.querySelector('.enhanced-editor-content'); - if (editorContent) { - editorContent.innerHTML = content; - this.htmlContent = content; - this.showSuccess('Template loaded successfully'); - } - }; - reader.readAsText(file); - } + handleLoad() { + // Create a file input to load template + const input = document.createElement("input"); + input.type = "file"; + input.accept = ".html,.txt"; + input.onchange = (event) => { + const file = event.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (e) => { + const content = e.target.result; + const editorContent = this.template.querySelector( + ".enhanced-editor-content" + ); + if (editorContent) { + editorContent.innerHTML = content; + this.htmlContent = content; + this.showSuccess("Template loaded successfully"); + } }; - input.click(); + reader.readAsText(file); + } + }; + input.click(); + } + handleFontFamilyChange(event) {} + + handleFontSizeChange(event) { + const fontSize = event.target.value; + const selection = window.getSelection(); + if (selection.rangeCount > 0) { + document.execCommand("fontSize", false, fontSize); + this.showSuccess(`Font size changed to ${fontSize}`); + } else { + this.showError("Please select text first"); + } + } + + handleBold() { + const selection = window.getSelection(); + if (selection.rangeCount > 0) { + document.execCommand("bold", false, null); + this.showSuccess("Text made bold"); + } else { + this.showError("Please select text first"); + } + } + + handleItalic() { + const selection = window.getSelection(); + if (selection.rangeCount > 0) { + document.execCommand("italic", false, null); + this.showSuccess("Text made italic"); + } else { + this.showError("Please select text first"); + } + } + + handleUnderline() { + const selection = window.getSelection(); + if (selection.rangeCount > 0) { + document.execCommand("underline", false, null); + this.showSuccess("Text underlined"); + } else { + this.showError("Please select text first"); + } + } + + handleHighlight() { + const selection = window.getSelection(); + if (selection.rangeCount > 0) { + document.execCommand("hiliteColor", false, "#ffff00"); + this.showSuccess("Text highlighted"); + } else { + this.showError("Please select text first"); + } + } + + // Helper function to ensure editor is properly focused and editable + ensureEditorFocus() { + const editorContent = this.template.querySelector( + ".enhanced-editor-content" + ); + if (!editorContent) { + return false; } - handleFontFamilyChange(event) { - } + // Ensure contenteditable is enabled + editorContent.setAttribute("contenteditable", "true"); + editorContent.style.userSelect = "text"; + editorContent.style.webkitUserSelect = "text"; + editorContent.style.cursor = "text"; - handleFontSizeChange(event) { - const fontSize = event.target.value; - const selection = window.getSelection(); - if (selection.rangeCount > 0) { - document.execCommand('fontSize', false, fontSize); - this.showSuccess(`Font size changed to ${fontSize}`); - } else { - this.showError('Please select text first'); - } - } + // Focus the editor + editorContent.focus(); - handleBold() { - const selection = window.getSelection(); - if (selection.rangeCount > 0) { - document.execCommand('bold', false, null); - this.showSuccess('Text made bold'); - } else { - this.showError('Please select text first'); - } - } - - handleItalic() { - const selection = window.getSelection(); - if (selection.rangeCount > 0) { - document.execCommand('italic', false, null); - this.showSuccess('Text made italic'); - } else { - this.showError('Please select text first'); - } - } - - handleUnderline() { - const selection = window.getSelection(); - if (selection.rangeCount > 0) { - document.execCommand('underline', false, null); - this.showSuccess('Text underlined'); - } else { - this.showError('Please select text first'); - } - } - - handleHighlight() { - const selection = window.getSelection(); - if (selection.rangeCount > 0) { - document.execCommand('hiliteColor', false, '#ffff00'); - this.showSuccess('Text highlighted'); - } else { - this.showError('Please select text first'); - } - } - - - - - - - - // Helper function to ensure editor is properly focused and editable - ensureEditorFocus() { - const editorContent = this.template.querySelector('.enhanced-editor-content'); - if (!editorContent) { - return false; - } - - // Ensure contenteditable is enabled - editorContent.setAttribute('contenteditable', 'true'); - editorContent.style.userSelect = 'text'; - editorContent.style.webkitUserSelect = 'text'; - editorContent.style.cursor = 'text'; - - // Focus the editor + // Ensure the editor is in the document's active element chain + if (document.activeElement !== editorContent) { + // Try to focus a child element if the parent won't focus + const focusableChild = editorContent.querySelector( + "p, div, span, h1, h2, h3, h4, h5, h6" + ); + if (focusableChild) { + focusableChild.focus(); + } else { editorContent.focus(); - - // Ensure the editor is in the document's active element chain - if (document.activeElement !== editorContent) { - // Try to focus a child element if the parent won't focus - const focusableChild = editorContent.querySelector('p, div, span, h1, h2, h3, h4, h5, h6'); - if (focusableChild) { - focusableChild.focus(); - } else { - editorContent.focus(); - } - } - - return true; + } } - // ENHANCED BULLET FUNCTION - handleBulletList() { - const editorContent = this.template.querySelector('.enhanced-editor-content'); - if (!editorContent) { - this.showError('Editor not found'); - return; - } - - editorContent.focus(); - - const selection = window.getSelection(); - if (!selection.rangeCount) { - // No selection, create a new list at the end - const ul = document.createElement('ul'); - const li = document.createElement('li'); - li.innerHTML = ' '; - ul.appendChild(li); - editorContent.appendChild(ul); - - // Place cursor in the new list item - const newRange = document.createRange(); - newRange.selectNodeContents(li); - newRange.collapse(true); - selection.removeAllRanges(); - selection.addRange(newRange); - } else { - const range = selection.getRangeAt(0); - - // Check if we're already in a list - const listItem = range.commonAncestorContainer.closest('li'); - if (listItem) { - // Already in a list, create new list item - const newLi = document.createElement('li'); - newLi.innerHTML = ' '; - listItem.parentNode.insertBefore(newLi, listItem.nextSibling); - - // Place cursor in new list item - const newRange = document.createRange(); - newRange.selectNodeContents(newLi); - newRange.collapse(true); - selection.removeAllRanges(); - selection.addRange(newRange); - } else { - // Not in a list, create new list - if (range.collapsed) { - // No text selected, create new list - const ul = document.createElement('ul'); - const li = document.createElement('li'); - li.innerHTML = ' '; - ul.appendChild(li); - range.insertNode(ul); - - // Place cursor in list item - const newRange = document.createRange(); - newRange.selectNodeContents(li); - newRange.collapse(true); - selection.removeAllRanges(); - selection.addRange(newRange); - } else { - // Text selected, wrap in list - const selectedContent = range.extractContents(); - const ul = document.createElement('ul'); - const li = document.createElement('li'); - li.appendChild(selectedContent); - ul.appendChild(li); - range.insertNode(ul); - - // Place cursor after the list - const newRange = document.createRange(); - newRange.setStartAfter(ul); - newRange.collapse(true); - selection.removeAllRanges(); - selection.addRange(newRange); - } - } - } - - editorContent.dispatchEvent(new Event('input', { bubbles: true })); - } - // ENHANCED NUMBERED LIST FUNCTION - handleNumberedList() { - const editorContent = this.template.querySelector('.enhanced-editor-content'); - if (!editorContent) { - this.showError('Editor not found'); - return; - } - - editorContent.focus(); - - const selection = window.getSelection(); - if (!selection.rangeCount) { - // No selection, create a new list at the end - const ol = document.createElement('ol'); - const li = document.createElement('li'); - li.innerHTML = ' '; - ol.appendChild(li); - editorContent.appendChild(ol); - - // Place cursor in the new list item - const newRange = document.createRange(); - newRange.selectNodeContents(li); - newRange.collapse(true); - selection.removeAllRanges(); - selection.addRange(newRange); - } else { - const range = selection.getRangeAt(0); - - // Check if we're already in a list - const listItem = range.commonAncestorContainer.closest('li'); - if (listItem) { - // Already in a list, create new list item - const newLi = document.createElement('li'); - newLi.innerHTML = ' '; - listItem.parentNode.insertBefore(newLi, listItem.nextSibling); - - // Place cursor in new list item - const newRange = document.createRange(); - newRange.selectNodeContents(newLi); - newRange.collapse(true); - selection.removeAllRanges(); - selection.addRange(newRange); - } else { - // Not in a list, create new list - if (range.collapsed) { - // No text selected, create new list - const ol = document.createElement('ol'); - const li = document.createElement('li'); - li.innerHTML = ' '; - ol.appendChild(li); - range.insertNode(ol); - - // Place cursor in list item - const newRange = document.createRange(); - newRange.selectNodeContents(li); - newRange.collapse(true); - selection.removeAllRanges(); - selection.addRange(newRange); - } else { - // Text selected, wrap in list - const selectedContent = range.extractContents(); - const ol = document.createElement('ol'); - const li = document.createElement('li'); - li.appendChild(selectedContent); - ol.appendChild(li); - range.insertNode(ol); - - // Place cursor after the list - const newRange = document.createRange(); - newRange.setStartAfter(ol); - newRange.collapse(true); - selection.removeAllRanges(); - selection.addRange(newRange); - } - } - } - - editorContent.dispatchEvent(new Event('input', { bubbles: true })); + return true; + } + + // ENHANCED BULLET FUNCTION + handleBulletList() { + const editorContent = this.template.querySelector( + ".enhanced-editor-content" + ); + if (!editorContent) { + this.showError("Editor not found"); + return; } - // Alias for numbered list - handleNumberList() { - this.handleNumberedList(); + editorContent.focus(); + // Use native command so selection toggles bullet list and supports nesting + document.execCommand("insertUnorderedList", false, null); + editorContent.dispatchEvent(new Event("input", { bubbles: true })); + } + // ENHANCED NUMBERED LIST FUNCTION + handleNumberedList() { + const editorContent = this.template.querySelector( + ".enhanced-editor-content" + ); + if (!editorContent) { + this.showError("Editor not found"); + return; } - // Toggle selector mode - toggleSelectorMode() { - this.selectorMode = !this.selectorMode; - const button = this.template.querySelector('.selector-mode-text'); - const controls = this.template.querySelector('.selector-controls'); - - if (button) { - button.textContent = this.selectorMode ? 'Exit Selector' : 'Selector Mode'; - } - - if (controls) { - controls.style.display = this.selectorMode ? 'flex' : 'none'; - } - - if (this.selectorMode) { - this.addSelectorModeListeners(); - } else { - this.removeSelectorModeListeners(); - this.clearSelection(); - } + editorContent.focus(); + // Use native command so selection toggles ordered list and supports nesting + document.execCommand("insertOrderedList", false, null); + editorContent.dispatchEvent(new Event("input", { bubbles: true })); + } + + // Alias for numbered list + handleNumberList() { + this.handleNumberedList(); + } + + // Toggle selector mode + toggleSelectorMode() { + this.selectorMode = !this.selectorMode; + const button = this.template.querySelector(".selector-mode-text"); + const controls = this.template.querySelector(".selector-controls"); + + if (button) { + button.textContent = this.selectorMode + ? "Exit Selector" + : "Selector Mode"; } - // Add selector mode event listeners - addSelectorModeListeners() { - const editor = this.template.querySelector('.enhanced-editor-content'); - if (editor) { - editor.addEventListener('click', this.handleSelectorClick.bind(this)); - editor.style.cursor = 'crosshair'; - } + if (controls) { + controls.style.display = this.selectorMode ? "flex" : "none"; } - // Remove selector mode event listeners - removeSelectorModeListeners() { - const editor = this.template.querySelector('.enhanced-editor-content'); - if (editor) { - editor.removeEventListener('click', this.handleSelectorClick.bind(this)); - editor.style.cursor = 'default'; - } + if (this.selectorMode) { + this.addSelectorModeListeners(); + } else { + this.removeSelectorModeListeners(); + this.clearSelection(); } + } - // Handle selector click - handleSelectorClick(event) { - if (!this.selectorMode) return; - - event.preventDefault(); - event.stopPropagation(); - - this.clearSelection(); - - const element = event.target; - if (element && element !== this.template.querySelector('.enhanced-editor-content')) { - this.selectedElement = element; - this.highlightSelectedElement(element); - // Don't show floating panel - controls are now in toolbar - } + // Add selector mode event listeners + addSelectorModeListeners() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (editor) { + editor.addEventListener("click", this.handleSelectorClick.bind(this)); + editor.style.cursor = "crosshair"; } + } - // Highlight selected element - highlightSelectedElement(element) { - element.style.outline = '2px solid #6b7280'; - element.style.outlineOffset = '2px'; - // Reflect current z-index in toolbox - const target = element.classList && element.classList.contains('draggable-image-container') ? element : (element.closest && element.closest('.draggable-image-container')) || element; - const currentZ = (target && target.style && target.style.zIndex) ? target.style.zIndex : ''; - this.zIndexInput = currentZ; + // Remove selector mode event listeners + removeSelectorModeListeners() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (editor) { + editor.removeEventListener("click", this.handleSelectorClick.bind(this)); + editor.style.cursor = "default"; } + } - // Clear selection - clearSelection() { - if (this.selectedElement) { - this.selectedElement.style.outline = ''; - this.selectedElement.style.outlineOffset = ''; - this.selectedElement = null; - } - // Don't hide floating panel since we're not using it + // Handle selector click + handleSelectorClick(event) { + if (!this.selectorMode) return; + + event.preventDefault(); + event.stopPropagation(); + + this.clearSelection(); + + const element = event.target; + if ( + element && + element !== this.template.querySelector(".enhanced-editor-content") + ) { + this.selectedElement = element; + this.highlightSelectedElement(element); + // Don't show floating panel - controls are now in toolbar } - // Show selector options - showSelectorOptions(element) { - // Create or update selector options panel - let optionsPanel = this.template.querySelector('.selector-options-panel'); - if (!optionsPanel) { - optionsPanel = document.createElement('div'); - optionsPanel.className = 'selector-options-panel'; - optionsPanel.style.cssText = ` + } + + // Highlight selected element + highlightSelectedElement(element) { + element.style.outline = "2px solid #6b7280"; + element.style.outlineOffset = "2px"; + // Reflect current z-index in toolbox + const target = + element.classList && + element.classList.contains("draggable-image-container") + ? element + : (element.closest && element.closest(".draggable-image-container")) || + element; + const currentZ = + target && target.style && target.style.zIndex ? target.style.zIndex : ""; + this.zIndexInput = currentZ; + } + // Clear selection + clearSelection() { + if (this.selectedElement) { + this.selectedElement.style.outline = ""; + this.selectedElement.style.outlineOffset = ""; + this.selectedElement = null; + } + // Don't hide floating panel since we're not using it + } + // Show selector options + showSelectorOptions(element) { + // Create or update selector options panel + let optionsPanel = this.template.querySelector(".selector-options-panel"); + if (!optionsPanel) { + optionsPanel = document.createElement("div"); + optionsPanel.className = "selector-options-panel"; + optionsPanel.style.cssText = ` position: fixed; top: 10px; right: 10px; @@ -4140,136 +4649,170 @@ export default class PropertyTemplateSelector extends LightningElement { min-width: 200px; max-width: 250px; `; - document.body.appendChild(optionsPanel); - } + document.body.appendChild(optionsPanel); + } - optionsPanel.innerHTML = ` + optionsPanel.innerHTML = `
Element Options
- - + +
- - + +
- - + +
- + `; + } + + // Hide selector options + hideSelectorOptions() { + const optionsPanel = this.template.querySelector(".selector-options-panel"); + if (optionsPanel) { + optionsPanel.remove(); + } + } + + // Insert content at selected position + insertAtSelection(type) { + if (!this.selectedElement) return; + + let content; + switch (type) { + case "text": + content = document.createElement("p"); + content.textContent = "New Text"; + content.contentEditable = true; + break; + case "image": + content = document.createElement("img"); + content.src = "https://via.placeholder.com/200x150"; + content.style.maxWidth = "200px"; + content.style.height = "auto"; + content.draggable = true; + content.addEventListener( + "dragstart", + this.handleImageDragStart.bind(this) + ); + break; + case "table": + content = this.createTableElement(); + // Make table draggable + content.draggable = true; + content.addEventListener( + "dragstart", + this.handleTableDragStart.bind(this) + ); + break; } - // Hide selector options - hideSelectorOptions() { - const optionsPanel = this.template.querySelector('.selector-options-panel'); - if (optionsPanel) { - optionsPanel.remove(); - } + if (content) { + this.selectedElement.parentNode.insertBefore( + content, + this.selectedElement.nextSibling + ); + this.clearSelection(); } + } - // Insert content at selected position - insertAtSelection(type) { - if (!this.selectedElement) return; - - let content; - switch (type) { - case 'text': - content = document.createElement('p'); - content.textContent = 'New Text'; - content.contentEditable = true; - break; - case 'image': - content = document.createElement('img'); - content.src = 'https://via.placeholder.com/200x150'; - content.style.maxWidth = '200px'; - content.style.height = 'auto'; - content.draggable = true; - content.addEventListener('dragstart', this.handleImageDragStart.bind(this)); - break; - case 'table': - content = this.createTableElement(); - // Make table draggable - content.draggable = true; - content.addEventListener('dragstart', this.handleTableDragStart.bind(this)); - break; - } - - if (content) { - this.selectedElement.parentNode.insertBefore(content, this.selectedElement.nextSibling); - this.clearSelection(); - } + // Remove selected element + removeSelectedElement() { + if (this.selectedElement) { + this.selectedElement.remove(); + this.clearSelection(); } + } - // Remove selected element - removeSelectedElement() { - if (this.selectedElement) { - this.selectedElement.remove(); - this.clearSelection(); - } + // Move element up + moveElementUp() { + if (this.selectedElement && this.selectedElement.previousElementSibling) { + this.selectedElement.parentNode.insertBefore( + this.selectedElement, + this.selectedElement.previousElementSibling + ); } + } - // Move element up - moveElementUp() { - if (this.selectedElement && this.selectedElement.previousElementSibling) { - this.selectedElement.parentNode.insertBefore(this.selectedElement, this.selectedElement.previousElementSibling); - } + // Move element down + moveElementDown() { + if (this.selectedElement && this.selectedElement.nextElementSibling) { + this.selectedElement.parentNode.insertBefore( + this.selectedElement.nextElementSibling, + this.selectedElement + ); } + } - // Move element down - moveElementDown() { - if (this.selectedElement && this.selectedElement.nextElementSibling) { - this.selectedElement.parentNode.insertBefore(this.selectedElement.nextElementSibling, this.selectedElement); - } - } + // Insert property image + insertPropertyImage() { + if (!this.selectedElement) return; - // Insert property image - insertPropertyImage() { - if (!this.selectedElement) return; + // Show property image selection popup + this.showPropertyImagePopup(); + } - // Show property image selection popup - this.showPropertyImagePopup(); - } + // Insert local image + insertLocalImage() { + if (!this.selectedElement) return; - // Insert local image - insertLocalImage() { - if (!this.selectedElement) return; + const input = document.createElement("input"); + input.type = "file"; + input.accept = "image/*"; + input.onchange = (e) => { + const file = e.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (e) => { + const img = document.createElement("img"); + img.src = e.target.result; + img.style.maxWidth = "200px"; + img.style.height = "auto"; + img.draggable = true; + img.addEventListener( + "dragstart", + this.handleImageDragStart.bind(this) + ); - const input = document.createElement('input'); - input.type = 'file'; - input.accept = 'image/*'; - input.onchange = (e) => { - const file = e.target.files[0]; - if (file) { - const reader = new FileReader(); - reader.onload = (e) => { - const img = document.createElement('img'); - img.src = e.target.result; - img.style.maxWidth = '200px'; - img.style.height = 'auto'; - img.draggable = true; - img.addEventListener('dragstart', this.handleImageDragStart.bind(this)); - - this.selectedElement.parentNode.insertBefore(img, this.selectedElement.nextSibling); - this.clearSelection(); - }; - reader.readAsDataURL(file); - } + this.selectedElement.parentNode.insertBefore( + img, + this.selectedElement.nextSibling + ); + this.clearSelection(); }; - input.click(); - } - - // Show property image popup - showPropertyImagePopup() { - // Create property image selection popup - let popup = this.template.querySelector('.property-image-popup'); - if (!popup) { - popup = document.createElement('div'); - popup.className = 'property-image-popup'; - popup.style.cssText = ` + reader.readAsDataURL(file); + } + }; + input.click(); + } + // Show property image popup + showPropertyImagePopup() { + // Create property image selection popup + let popup = this.template.querySelector(".property-image-popup"); + if (!popup) { + popup = document.createElement("div"); + popup.className = "property-image-popup"; + popup.style.cssText = ` position: fixed; top: 50%; left: 50%; @@ -4284,59 +4827,72 @@ export default class PropertyTemplateSelector extends LightningElement { max-height: 500px; overflow-y: auto; `; - document.body.appendChild(popup); - } + document.body.appendChild(popup); + } - // Get property images - const images = this.realPropertyImages || []; - const imageGrid = images.map(img => ` + // Get property images + const images = this.realPropertyImages || []; + const imageGrid = images + .map( + (img) => `
- -
${img.category || 'Uncategorized'}
+ +
${ + img.category || "Uncategorized" + }
- `).join(''); + ` + ) + .join(""); - popup.innerHTML = ` + popup.innerHTML = `
Select Property Image
${imageGrid}
- + `; - } - // Select property image - selectPropertyImage(imageUrl) { - if (this.selectedElement) { - const img = document.createElement('img'); - img.src = imageUrl; - img.style.maxWidth = '200px'; - img.style.height = 'auto'; - img.draggable = true; - img.addEventListener('dragstart', this.handleImageDragStart.bind(this)); - - this.selectedElement.parentNode.insertBefore(img, this.selectedElement.nextSibling); - this.clearSelection(); - } - this.closePropertyImagePopup(); - } + } + // Select property image + selectPropertyImage(imageUrl) { + if (this.selectedElement) { + const img = document.createElement("img"); + img.src = imageUrl; + img.style.maxWidth = "200px"; + img.style.height = "auto"; + img.draggable = true; + img.addEventListener("dragstart", this.handleImageDragStart.bind(this)); - // Close property image popup - closePropertyImagePopup() { - const popup = this.template.querySelector('.property-image-popup'); - if (popup) { - popup.remove(); - } + this.selectedElement.parentNode.insertBefore( + img, + this.selectedElement.nextSibling + ); + this.clearSelection(); } + this.closePropertyImagePopup(); + } - // Create table element with enhanced drag and resize functionality - createTableElement() { - // Create the main table container with absolute positioning for drag/resize - const tableContainer = document.createElement('div'); - tableContainer.className = 'draggable-table-container'; - tableContainer.style.cssText = ` + // Close property image popup + closePropertyImagePopup() { + const popup = this.template.querySelector(".property-image-popup"); + if (popup) { + popup.remove(); + } + } + + // Create table element with enhanced drag and resize functionality + createTableElement() { + // Create the main table container with absolute positioning for drag/resize + const tableContainer = document.createElement("div"); + tableContainer.className = "draggable-table-container"; + tableContainer.style.cssText = ` position: absolute; left: 50px; top: 50px; @@ -4352,88 +4908,87 @@ export default class PropertyTemplateSelector extends LightningElement { border-radius: 8px; overflow: hidden; `; - - // Create the actual table - const table = document.createElement('table'); - table.style.cssText = ` + + // Create the actual table + const table = document.createElement("table"); + table.style.cssText = ` width: 100%; height: 100%; border-collapse: collapse; margin: 0; background: white; `; - - // Create header row - const headerRow = document.createElement('tr'); - for (let i = 0; i < this.tableCols; i++) { - const th = document.createElement('th'); - th.textContent = `Header ${i + 1}`; - th.style.cssText = ` + + // Create header row + const headerRow = document.createElement("tr"); + for (let i = 0; i < this.tableCols; i++) { + const th = document.createElement("th"); + th.textContent = `Header ${i + 1}`; + th.style.cssText = ` border: 1px solid #ddd; padding: 8px; background: #f8f9fa; font-weight: 600; text-align: left; `; - headerRow.appendChild(th); - } - table.appendChild(headerRow); - - // Create data rows - const startRow = this.includeHeader ? 1 : 0; - for (let i = startRow; i < this.tableRows; i++) { - const row = document.createElement('tr'); - for (let j = 0; j < this.tableCols; j++) { - const td = document.createElement('td'); - td.textContent = `Cell ${i + 1},${j + 1}`; - td.style.cssText = ` + headerRow.appendChild(th); + } + table.appendChild(headerRow); + + // Create data rows + const startRow = this.includeHeader ? 1 : 0; + for (let i = startRow; i < this.tableRows; i++) { + const row = document.createElement("tr"); + for (let j = 0; j < this.tableCols; j++) { + const td = document.createElement("td"); + td.textContent = `Cell ${i + 1},${j + 1}`; + td.style.cssText = ` border: 1px solid #ddd; padding: 8px; background: white; `; - // Make cells editable - td.contentEditable = true; - td.addEventListener('blur', () => { - // Save changes when cell loses focus - }); - row.appendChild(td); - } - table.appendChild(row); - } - - tableContainer.appendChild(table); - - // Add resize handles (same as images) - this.addResizeHandles(tableContainer); - - // Add delete handle (same as images) - this.addDeleteHandle(tableContainer); - - // Add drag functionality (same as images) - this.makeDraggable(tableContainer); - - // Add click to select functionality - tableContainer.addEventListener('click', (e) => { - e.stopPropagation(); - this.selectDraggableElement(tableContainer); + // Make cells editable + td.contentEditable = true; + td.addEventListener("blur", () => { + // Save changes when cell loses focus }); - - // Add table controls overlay - this.addTableControls(tableContainer, table); - - // Select the table after a short delay - setTimeout(() => { - this.selectDraggableElement(tableContainer); - }, 100); - - return tableContainer; + row.appendChild(td); + } + table.appendChild(row); } - + + tableContainer.appendChild(table); + + // Add resize handles (same as images) + this.addResizeHandles(tableContainer); + + // Add delete handle (same as images) + this.addDeleteHandle(tableContainer); + + // Add drag functionality (same as images) + this.makeDraggable(tableContainer); + + // Add click to select functionality + tableContainer.addEventListener("click", (e) => { + e.stopPropagation(); + this.selectDraggableElement(tableContainer); + }); + // Add table controls overlay - addTableControls(container, table) { - const controls = document.createElement('div'); - controls.className = 'table-controls-overlay'; - controls.style.cssText = ` + this.addTableControls(tableContainer, table); + + // Select the table after a short delay + setTimeout(() => { + this.selectDraggableElement(tableContainer); + }, 100); + + return tableContainer; + } + // Add table controls overlay + addTableControls(container, table) { + const controls = document.createElement("div"); + controls.className = "table-controls-overlay"; + controls.style.cssText = ` position: absolute; top: -40px; left: 0; @@ -4447,11 +5002,11 @@ export default class PropertyTemplateSelector extends LightningElement { gap: 4px; z-index: 1002; `; - - // Add Row button - const addRowBtn = document.createElement('button'); - addRowBtn.innerHTML = '+ Row'; - addRowBtn.style.cssText = ` + + // Add Row button + const addRowBtn = document.createElement("button"); + addRowBtn.innerHTML = "+ Row"; + addRowBtn.style.cssText = ` padding: 4px 8px; font-size: 12px; background: #28a745; @@ -4460,15 +5015,15 @@ export default class PropertyTemplateSelector extends LightningElement { border-radius: 4px; cursor: pointer; `; - addRowBtn.onclick = (e) => { - e.stopPropagation(); - this.addTableRow(table); - }; - - // Add Column button - const addColBtn = document.createElement('button'); - addColBtn.innerHTML = '+ Col'; - addColBtn.style.cssText = ` + addRowBtn.onclick = (e) => { + e.stopPropagation(); + this.addTableRow(table); + }; + + // Add Column button + const addColBtn = document.createElement("button"); + addColBtn.innerHTML = "+ Col"; + addColBtn.style.cssText = ` padding: 4px 8px; font-size: 12px; background: #17a2b8; @@ -4477,15 +5032,15 @@ export default class PropertyTemplateSelector extends LightningElement { border-radius: 4px; cursor: pointer; `; - addColBtn.onclick = (e) => { - e.stopPropagation(); - this.addTableColumn(table); - }; - - // Delete Row button - const delRowBtn = document.createElement('button'); - delRowBtn.innerHTML = '- Row'; - delRowBtn.style.cssText = ` + addColBtn.onclick = (e) => { + e.stopPropagation(); + this.addTableColumn(table); + }; + + // Delete Row button + const delRowBtn = document.createElement("button"); + delRowBtn.innerHTML = "- Row"; + delRowBtn.style.cssText = ` padding: 4px 8px; font-size: 12px; background: #ffc107; @@ -4494,14 +5049,14 @@ export default class PropertyTemplateSelector extends LightningElement { border-radius: 4px; cursor: pointer; `; - delRowBtn.onclick = (e) => { - e.stopPropagation(); - this.deleteTableRow(table); - }; - // Delete Column button - const delColBtn = document.createElement('button'); - delColBtn.innerHTML = '- Col'; - delColBtn.style.cssText = ` + delRowBtn.onclick = (e) => { + e.stopPropagation(); + this.deleteTableRow(table); + }; + // Delete Column button + const delColBtn = document.createElement("button"); + delColBtn.innerHTML = "- Col"; + delColBtn.style.cssText = ` padding: 4px 8px; font-size: 12px; background: #fd7e14; @@ -4510,185 +5065,185 @@ export default class PropertyTemplateSelector extends LightningElement { border-radius: 4px; cursor: pointer; `; - delColBtn.onclick = (e) => { - e.stopPropagation(); - this.deleteTableColumn(table); - }; - - controls.appendChild(addRowBtn); - controls.appendChild(addColBtn); - controls.appendChild(delRowBtn); - controls.appendChild(delColBtn); - - container.appendChild(controls); - - // Show/hide controls on hover - container.addEventListener('mouseenter', () => { - controls.style.opacity = '1'; - }); - - container.addEventListener('mouseleave', () => { - controls.style.opacity = '0'; - }); - } + delColBtn.onclick = (e) => { + e.stopPropagation(); + this.deleteTableColumn(table); + }; - // Table manipulation methods (updated for new structure) - addTableRow(table) { - const newRow = document.createElement('tr'); - const colCount = table.rows[0].cells.length; - - for (let i = 0; i < colCount; i++) { - const td = document.createElement('td'); - td.textContent = `New Cell`; - td.style.cssText = ` + controls.appendChild(addRowBtn); + controls.appendChild(addColBtn); + controls.appendChild(delRowBtn); + controls.appendChild(delColBtn); + + container.appendChild(controls); + + // Show/hide controls on hover + container.addEventListener("mouseenter", () => { + controls.style.opacity = "1"; + }); + + container.addEventListener("mouseleave", () => { + controls.style.opacity = "0"; + }); + } + + // Table manipulation methods (updated for new structure) + addTableRow(table) { + const newRow = document.createElement("tr"); + const colCount = table.rows[0].cells.length; + + for (let i = 0; i < colCount; i++) { + const td = document.createElement("td"); + td.textContent = `New Cell`; + td.style.cssText = ` border: 1px solid #ddd; padding: 8px; background: white; `; - td.contentEditable = true; - newRow.appendChild(td); - } - - table.appendChild(newRow); + td.contentEditable = true; + newRow.appendChild(td); } - addTableColumn(table) { - const rows = table.rows; - - for (let i = 0; i < rows.length; i++) { - const cell = document.createElement(i === 0 ? 'th' : 'td'); - cell.textContent = i === 0 ? `Header ${rows[i].cells.length + 1}` : `New Cell`; - cell.style.cssText = ` + table.appendChild(newRow); + } + + addTableColumn(table) { + const rows = table.rows; + + for (let i = 0; i < rows.length; i++) { + const cell = document.createElement(i === 0 ? "th" : "td"); + cell.textContent = + i === 0 ? `Header ${rows[i].cells.length + 1}` : `New Cell`; + cell.style.cssText = ` border: 1px solid #ddd; padding: 8px; - background: ${i === 0 ? '#f8f9fa' : 'white'}; - font-weight: ${i === 0 ? '600' : 'normal'}; + background: ${i === 0 ? "#f8f9fa" : "white"}; + font-weight: ${i === 0 ? "600" : "normal"}; `; - if (i > 0) { - cell.contentEditable = true; - } - rows[i].appendChild(cell); - } + if (i > 0) { + cell.contentEditable = true; + } + rows[i].appendChild(cell); + } + } + + deleteTableRow(table) { + if (table.rows.length > 1) { + table.deleteRow(-1); + } + } + + deleteTableColumn(table) { + const rows = table.rows; + if (rows[0].cells.length > 1) { + for (let i = 0; i < rows.length; i++) { + rows[i].deleteCell(-1); + } + } + } + + deleteTable(event) { + const tableContainer = event.target.closest("div"); + tableContainer.remove(); + } + + // Make images draggable and resizable + makeImagesDraggableAndResizable() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) return; + + const images = editor.querySelectorAll("img"); + images.forEach((img) => { + // Prevent position changes on click + img.style.position = "relative"; + img.style.zIndex = "1000"; + img.style.transition = "none"; // Disable transitions during drag + + // Add resize handles + this.addResizeHandles(img); + + // Add smooth drag event listeners + img.addEventListener("mousedown", this.handleImageMouseDown.bind(this)); + img.addEventListener("mousemove", this.handleImageMouseMove.bind(this)); + img.addEventListener("mouseup", this.handleImageMouseUp.bind(this)); + img.addEventListener("mouseleave", this.handleImageMouseUp.bind(this)); + }); + } + // Smooth drag handlers for images + handleImageMouseDown(e) { + if (e.target.tagName !== "IMG") return; + + e.preventDefault(); + this.isDraggingImage = true; + this.dragStartX = e.clientX; + this.dragStartY = e.clientY; + this.dragElement = e.target; + this.dragInitiated = false; // will flip to true only after threshold is exceeded + + // Store initial position + const rect = this.dragElement.getBoundingClientRect(); + const editor = this.template.querySelector(".enhanced-editor-content"); + const editorRect = editor.getBoundingClientRect(); + + this.initialLeft = rect.left - editorRect.left; + this.initialTop = rect.top - editorRect.top; + + // Add dragging class for visual feedback + this.dragElement.style.cursor = "grabbing"; + + // Prevent text selection during drag + document.body.style.userSelect = "none"; + } + + handleImageMouseMove(e) { + if (!this.isDraggingImage || !this.dragElement) return; + + e.preventDefault(); + + const deltaX = e.clientX - this.dragStartX; + const deltaY = e.clientY - this.dragStartY; + const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); + + // Only start moving the image if the cursor moved beyond a small threshold + if (!this.dragInitiated && distance > 5) { + this.dragInitiated = true; + this.dragElement.style.opacity = "0.85"; + this.dragElement.style.position = "absolute"; + } + if (!this.dragInitiated) return; + + // Update position smoothly after drag actually begins + this.dragElement.style.left = this.initialLeft + deltaX + "px"; + this.dragElement.style.top = this.initialTop + deltaY + "px"; + } + + handleImageMouseUp(e) { + if (!this.isDraggingImage || !this.dragElement) return; + + this.isDraggingImage = false; + + // Restore cursor and opacity + this.dragElement.style.cursor = "grab"; + this.dragElement.style.opacity = ""; + + // Re-enable text selection + document.body.style.userSelect = ""; + + // Save undo state after drag + if (this.dragInitiated) { + this.saveUndoState(); } - deleteTableRow(table) { - if (table.rows.length > 1) { - table.deleteRow(-1); - } - } - - deleteTableColumn(table) { - const rows = table.rows; - if (rows[0].cells.length > 1) { - for (let i = 0; i < rows.length; i++) { - rows[i].deleteCell(-1); - } - } - } - - deleteTable(event) { - const tableContainer = event.target.closest('div'); - tableContainer.remove(); - } - - // Make images draggable and resizable - makeImagesDraggableAndResizable() { - const editor = this.template.querySelector('.enhanced-editor-content'); - if (!editor) return; - - const images = editor.querySelectorAll('img'); - images.forEach(img => { - // Prevent position changes on click - img.style.position = 'relative'; - img.style.zIndex = '1000'; - img.style.transition = 'none'; // Disable transitions during drag - - // Add resize handles - this.addResizeHandles(img); - - // Add smooth drag event listeners - img.addEventListener('mousedown', this.handleImageMouseDown.bind(this)); - img.addEventListener('mousemove', this.handleImageMouseMove.bind(this)); - img.addEventListener('mouseup', this.handleImageMouseUp.bind(this)); - img.addEventListener('mouseleave', this.handleImageMouseUp.bind(this)); - }); - } - - // Smooth drag handlers for images - handleImageMouseDown(e) { - if (e.target.tagName !== 'IMG') return; - - e.preventDefault(); - this.isDraggingImage = true; - this.dragStartX = e.clientX; - this.dragStartY = e.clientY; - this.dragElement = e.target; - this.dragInitiated = false; // will flip to true only after threshold is exceeded - - // Store initial position - const rect = this.dragElement.getBoundingClientRect(); - const editor = this.template.querySelector('.enhanced-editor-content'); - const editorRect = editor.getBoundingClientRect(); - - this.initialLeft = rect.left - editorRect.left; - this.initialTop = rect.top - editorRect.top; - - // Add dragging class for visual feedback - this.dragElement.style.cursor = 'grabbing'; - - // Prevent text selection during drag - document.body.style.userSelect = 'none'; - } - - handleImageMouseMove(e) { - if (!this.isDraggingImage || !this.dragElement) return; - - e.preventDefault(); - - const deltaX = e.clientX - this.dragStartX; - const deltaY = e.clientY - this.dragStartY; - const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); - - // Only start moving the image if the cursor moved beyond a small threshold - if (!this.dragInitiated && distance > 5) { - this.dragInitiated = true; - this.dragElement.style.opacity = '0.85'; - this.dragElement.style.position = 'absolute'; - } - if (!this.dragInitiated) return; - - // Update position smoothly after drag actually begins - this.dragElement.style.left = (this.initialLeft + deltaX) + 'px'; - this.dragElement.style.top = (this.initialTop + deltaY) + 'px'; - } - - handleImageMouseUp(e) { - if (!this.isDraggingImage || !this.dragElement) return; - - this.isDraggingImage = false; - - // Restore cursor and opacity - this.dragElement.style.cursor = 'grab'; - this.dragElement.style.opacity = ''; - - // Re-enable text selection - document.body.style.userSelect = ''; - - // Save undo state after drag - if (this.dragInitiated) { - this.saveUndoState(); - } - - this.dragElement = null; - this.dragInitiated = false; - } - // Add resize handles to image - addResizeHandles(img) { - const handles = ['nw', 'ne', 'sw', 'se']; - handles.forEach(handle => { - const resizeHandle = document.createElement('div'); - resizeHandle.className = `resize-handle resize-${handle}`; - resizeHandle.style.cssText = ` + this.dragElement = null; + this.dragInitiated = false; + } + // Add resize handles to image + addResizeHandles(img) { + const handles = ["nw", "ne", "sw", "se"]; + handles.forEach((handle) => { + const resizeHandle = document.createElement("div"); + resizeHandle.className = `resize-handle resize-${handle}`; + resizeHandle.style.cssText = ` position: absolute; width: 8px; height: 8px; @@ -4697,689 +5252,831 @@ export default class PropertyTemplateSelector extends LightningElement { cursor: ${handle}-resize; z-index: 1001; `; - - // Position handles - switch (handle) { - case 'nw': - resizeHandle.style.top = '-4px'; - resizeHandle.style.left = '-4px'; - break; - case 'ne': - resizeHandle.style.top = '-4px'; - resizeHandle.style.right = '-4px'; - break; - case 'sw': - resizeHandle.style.bottom = '-4px'; - resizeHandle.style.left = '-4px'; - break; - case 'se': - resizeHandle.style.bottom = '-4px'; - resizeHandle.style.right = '-4px'; - break; + + // Position handles + switch (handle) { + case "nw": + resizeHandle.style.top = "-4px"; + resizeHandle.style.left = "-4px"; + break; + case "ne": + resizeHandle.style.top = "-4px"; + resizeHandle.style.right = "-4px"; + break; + case "sw": + resizeHandle.style.bottom = "-4px"; + resizeHandle.style.left = "-4px"; + break; + case "se": + resizeHandle.style.bottom = "-4px"; + resizeHandle.style.right = "-4px"; + break; + } + + img.appendChild(resizeHandle); + + // Add resize functionality + resizeHandle.addEventListener("mousedown", (e) => { + e.preventDefault(); + this.startResize(e, img, handle); + }); + }); + } + + // Handle image drag start + handleImageDragStart(event) { + event.dataTransfer.setData("text/plain", "image"); + event.dataTransfer.effectAllowed = "move"; + } + + // Handle image drag end + handleImageDragEnd(event) { + // Remove any drag feedback + } + + // Start resize operation + startResize(event, target, handle) { + const container = target.classList.contains("draggable-image-container") + ? target + : target.parentElement; + const startX = event.clientX; + const startY = event.clientY; + const startWidth = container.offsetWidth; + const startHeight = container.offsetHeight; + const startLeft = container.offsetLeft; + const startTop = container.offsetTop; + + const handleMouseMove = (e) => { + const deltaX = e.clientX - startX; + const deltaY = e.clientY - startY; + + let newWidth = startWidth; + let newHeight = startHeight; + let newLeft = startLeft; + let newTop = startTop; + + switch (handle) { + case "se": + newWidth = startWidth + deltaX; + newHeight = startHeight + deltaY; + break; + case "sw": + newWidth = startWidth - deltaX; + newHeight = startHeight + deltaY; + newLeft = startLeft + deltaX; + break; + case "ne": + newWidth = startWidth + deltaX; + newHeight = startHeight - deltaY; + newTop = startTop + deltaY; + break; + case "nw": + newWidth = startWidth - deltaX; + newHeight = startHeight - deltaY; + newLeft = startLeft + deltaX; + newTop = startTop + deltaY; + break; + } + + container.style.width = Math.max(50, newWidth) + "px"; + container.style.height = Math.max(50, newHeight) + "px"; + container.style.left = newLeft + "px"; + container.style.top = newTop + "px"; + }; + + const handleMouseUp = () => { + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + }; + + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + } + + handleAlignLeft() { + const selection = window.getSelection(); + if (selection.rangeCount > 0) { + document.execCommand("justifyLeft", false, null); + this.showSuccess("Text aligned left"); + } else { + this.showError("Please select text first"); + } + } + handleAlignCenter() { + const selection = window.getSelection(); + if (selection.rangeCount > 0) { + document.execCommand("justifyCenter", false, null); + this.showSuccess("Text aligned center"); + } else { + this.showError("Please select text first"); + } + } + + handleAlignRight() { + const selection = window.getSelection(); + if (selection.rangeCount > 0) { + document.execCommand("justifyRight", false, null); + this.showSuccess("Text aligned right"); + } else { + this.showError("Please select text first"); + } + } + + handleTextColorChange(event) { + const color = event.target.value; + const selection = window.getSelection(); + if (selection.rangeCount > 0) { + document.execCommand("foreColor", false, color); + this.showSuccess(`Text color changed to ${color}`); + } else { + this.showError("Please select text first"); + } + } + + handleBackgroundColorChange(event) { + const color = event.target.value; + const selection = window.getSelection(); + if (selection.rangeCount > 0) { + document.execCommand("hiliteColor", false, color); + this.showSuccess(`Background color changed to ${color}`); + } else { + this.showError("Please select text first"); + } + } + + handleIndent() { + const editorContent = this.template.querySelector( + ".enhanced-editor-content" + ); + if (!editorContent) { + this.showError("Editor not found"); + return; + } + editorContent.focus(); + // For list contexts, execCommand handles nesting properly + document.execCommand("indent", false, null); + editorContent.dispatchEvent(new Event("input", { bubbles: true })); + } + + handleOutdent() { + const editorContent = this.template.querySelector( + ".enhanced-editor-content" + ); + if (!editorContent) { + this.showError("Editor not found"); + return; + } + editorContent.focus(); + document.execCommand("outdent", false, null); + editorContent.dispatchEvent(new Event("input", { bubbles: true })); + } + + handleFontFamilyChange(event) { + const fontFamily = event.target.value; + const selection = window.getSelection(); + if (selection.rangeCount > 0) { + document.execCommand("fontName", false, fontFamily); + this.showSuccess(`Font family changed to ${fontFamily}`); + } else { + this.showError("Please select text first"); + } + } + handleContentChange() { + // Update the HTML content when user types in the editor + const editorContent = this.template.querySelector( + ".enhanced-editor-content" + ); + if (editorContent) { + this.htmlContent = editorContent.innerHTML; + } + } + + openPdfPreview() { + // Get current content from editor + const editorContent = this.template.querySelector( + ".enhanced-editor-content" + ); + if (editorContent) { + this.htmlContent = editorContent.innerHTML; + } + this.showPdfPreview = true; + } + + closePdfPreview() { + this.showPdfPreview = false; + } + + generatePdfFromPreview() { + // Close preview and generate PDF + this.showPdfPreview = false; + this.generatePdfSimple(); + } + + // Property insertion functions + insertPropertyName() { + const propertyName = this.propertyData.Name || "Property Name"; + this.insertTextAtCursor(propertyName); + } + + insertPropertyPrice() { + const price = this.propertyData.Price__c || "$0"; + this.insertTextAtCursor(price); + } + + insertPropertyType() { + const type = this.propertyData.Property_Type__c || "Property Type"; + this.insertTextAtCursor(type); + } + + insertPropertyBedrooms() { + const bedrooms = this.propertyData.Bedrooms__c || "0"; + this.insertTextAtCursor(bedrooms + " Bedrooms"); + } + + insertPropertyBathrooms() { + const bathrooms = this.propertyData.Bathrooms__c || "0"; + this.insertTextAtCursor(bathrooms + " Bathrooms"); + } + + insertPropertySqft() { + const sqft = this.propertyData.Square_Footage__c || "0"; + this.insertTextAtCursor(sqft + " sq ft"); + } + + insertPropertyAddress() { + const address = this.propertyData.Location__c || "Property Address"; + this.insertTextAtCursor(address); + } + + insertPropertyDescription() { + const description = + this.propertyData.Description_English__c || + this.propertyData.pcrm__Description_English__c || + this.propertyData.Description__c || + "Property Description"; + // Wrap into paragraphs and basic formatting + const lines = String(description) + .split(/\n+/) + .map((l) => l.trim()) + .filter(Boolean); + const html = lines.map((l) => `

${l}

`).join(""); + this.insertHtmlAtCursor(html); + } + + // Helper function to insert text at cursor position + insertTextAtCursor(text) { + const selection = window.getSelection(); + if (selection.rangeCount > 0) { + const range = selection.getRangeAt(0); + range.deleteContents(); + const textNode = document.createTextNode(text); + range.insertNode(textNode); + range.setStartAfter(textNode); + range.setEndAfter(textNode); + selection.removeAllRanges(); + selection.addRange(range); + this.showSuccess(`Inserted: ${text}`); + } else { + this.showError("Please place cursor in the editor first"); + } + } + // Helper to insert HTML at cursor + insertHtmlAtCursor(html) { + const selection = window.getSelection(); + if (!selection.rangeCount) { + this.showError("Please place cursor in the editor first"); + return; + } + const range = selection.getRangeAt(0); + range.deleteContents(); + const temp = document.createElement("div"); + temp.innerHTML = html; + const fragment = document.createDocumentFragment(); + while (temp.firstChild) { + fragment.appendChild(temp.firstChild); + } + range.insertNode(fragment); + // Move caret to end of inserted content + range.collapse(false); + selection.removeAllRanges(); + selection.addRange(range); + const editorContent = this.template.querySelector( + ".enhanced-editor-content" + ); + if (editorContent) + editorContent.dispatchEvent(new Event("input", { bubbles: true })); + } + + // Setup editor click handler to deselect elements + setupEditorClickHandler() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (editor && !editor.hasClickHandler) { + editor.addEventListener("click", (e) => { + // Enhanced image detection - check multiple ways to find images + let clickedImage = null; + + // Method 1: Direct image click + if ( + e.target.tagName === "IMG" && + e.target.src && + e.target.src.trim() !== "" + ) { + clickedImage = e.target; + } + + // Method 2: Click on element containing an image (children) + if (!clickedImage && e.target.querySelector) { + const containedImg = e.target.querySelector("img"); + if ( + containedImg && + containedImg.src && + containedImg.src.trim() !== "" + ) { + clickedImage = containedImg; + } + } + + // Method 3: Click on element that is inside a container with an image (parent traversal) + if (!clickedImage) { + let currentElement = e.target; + while (currentElement && currentElement !== editor) { + // Check if current element is an IMG + if ( + currentElement.tagName === "IMG" && + currentElement.src && + currentElement.src.trim() !== "" + ) { + clickedImage = currentElement; + break; } - - img.appendChild(resizeHandle); - - // Add resize functionality - resizeHandle.addEventListener('mousedown', (e) => { - e.preventDefault(); - this.startResize(e, img, handle); - }); - }); - } + // Check if current element contains an IMG + if ( + currentElement.querySelector && + currentElement.querySelector("img") + ) { + const img = currentElement.querySelector("img"); + if (img && img.src && img.src.trim() !== "") { + clickedImage = img; + break; + } + } + // Check siblings for IMG elements only if current element is positioned + if ( + currentElement.parentElement && + (currentElement.style.position === "absolute" || + currentElement.style.position === "relative" || + currentElement.classList.contains("draggable-element")) + ) { + const siblingImg = + currentElement.parentElement.querySelector("img"); + if ( + siblingImg && + siblingImg.src && + siblingImg.src.trim() !== "" + ) { + clickedImage = siblingImg; + break; + } + } + currentElement = currentElement.parentElement; + } + } + // Method 4: Check for background images in the element hierarchy (enhanced for property cards) + if (!clickedImage) { + let currentElement = e.target; + while (currentElement && currentElement !== editor) { + // Check for background images on any element (not just positioned ones) + const computedStyle = window.getComputedStyle(currentElement); + const backgroundImage = computedStyle.backgroundImage; - // Handle image drag start - handleImageDragStart(event) { - event.dataTransfer.setData('text/plain', 'image'); - event.dataTransfer.effectAllowed = 'move'; - } - - // Handle image drag end - handleImageDragEnd(event) { - // Remove any drag feedback - } - - // Start resize operation - startResize(event, target, handle) { - const container = target.classList.contains('draggable-image-container') ? target : target.parentElement; - const startX = event.clientX; - const startY = event.clientY; - const startWidth = container.offsetWidth; - const startHeight = container.offsetHeight; - const startLeft = container.offsetLeft; - const startTop = container.offsetTop; - - const handleMouseMove = (e) => { - const deltaX = e.clientX - startX; - const deltaY = e.clientY - startY; - - let newWidth = startWidth; - let newHeight = startHeight; - let newLeft = startLeft; - let newTop = startTop; - - switch (handle) { - case 'se': - newWidth = startWidth + deltaX; - newHeight = startHeight + deltaY; - break; - case 'sw': - newWidth = startWidth - deltaX; - newHeight = startHeight + deltaY; - newLeft = startLeft + deltaX; - break; - case 'ne': - newWidth = startWidth + deltaX; - newHeight = startHeight - deltaY; - newTop = startTop + deltaY; - break; - case 'nw': - newWidth = startWidth - deltaX; - newHeight = startHeight - deltaY; - newLeft = startLeft + deltaX; - newTop = startTop + deltaY; - break; + if ( + backgroundImage && + backgroundImage !== "none" && + backgroundImage !== "initial" + ) { + // Create a virtual IMG element for background images + const virtualImg = document.createElement("img"); + virtualImg.src = backgroundImage.replace( + /url\(['"]?(.+?)['"]?\)/, + "$1" + ); + virtualImg.isBackgroundImage = true; + virtualImg.style.backgroundImage = backgroundImage; + virtualImg.originalElement = currentElement; // Store reference to original element + clickedImage = virtualImg; + break; } - container.style.width = Math.max(50, newWidth) + 'px'; - container.style.height = Math.max(50, newHeight) + 'px'; - container.style.left = newLeft + 'px'; - container.style.top = newTop + 'px'; - }; - - const handleMouseUp = () => { - document.removeEventListener('mousemove', handleMouseMove); - document.removeEventListener('mouseup', handleMouseUp); - }; - - document.addEventListener('mousemove', handleMouseMove); - document.addEventListener('mouseup', handleMouseUp); - } - - handleAlignLeft() { - const selection = window.getSelection(); - if (selection.rangeCount > 0) { - document.execCommand('justifyLeft', false, null); - this.showSuccess('Text aligned left'); - } else { - this.showError('Please select text first'); - } - } - - handleAlignCenter() { - const selection = window.getSelection(); - if (selection.rangeCount > 0) { - document.execCommand('justifyCenter', false, null); - this.showSuccess('Text aligned center'); - } else { - this.showError('Please select text first'); - } - } - - handleAlignRight() { - const selection = window.getSelection(); - if (selection.rangeCount > 0) { - document.execCommand('justifyRight', false, null); - this.showSuccess('Text aligned right'); - } else { - this.showError('Please select text first'); - } - } - - handleTextColorChange(event) { - const color = event.target.value; - const selection = window.getSelection(); - if (selection.rangeCount > 0) { - document.execCommand('foreColor', false, color); - this.showSuccess(`Text color changed to ${color}`); - } else { - this.showError('Please select text first'); - } - } - - handleBackgroundColorChange(event) { - const color = event.target.value; - const selection = window.getSelection(); - if (selection.rangeCount > 0) { - document.execCommand('hiliteColor', false, color); - this.showSuccess(`Background color changed to ${color}`); - } else { - this.showError('Please select text first'); - } - } - - - - handleIndent() { - const editorContent = this.template.querySelector('.enhanced-editor-content'); - if (!editorContent) { this.showError('Editor not found'); return; } - editorContent.focus(); - // For list contexts, execCommand handles nesting properly - document.execCommand('indent', false, null); - editorContent.dispatchEvent(new Event('input', { bubbles: true })); - } - - handleOutdent() { - const editorContent = this.template.querySelector('.enhanced-editor-content'); - if (!editorContent) { this.showError('Editor not found'); return; } - editorContent.focus(); - document.execCommand('outdent', false, null); - editorContent.dispatchEvent(new Event('input', { bubbles: true })); - } - - handleFontFamilyChange(event) { - const fontFamily = event.target.value; - const selection = window.getSelection(); - if (selection.rangeCount > 0) { - document.execCommand('fontName', false, fontFamily); - this.showSuccess(`Font family changed to ${fontFamily}`); - } else { - this.showError('Please select text first'); - } - } - handleContentChange() { - // Update the HTML content when user types in the editor - const editorContent = this.template.querySelector('.enhanced-editor-content'); - if (editorContent) { - this.htmlContent = editorContent.innerHTML; - } - } - - openPdfPreview() { - // Get current content from editor - const editorContent = this.template.querySelector('.enhanced-editor-content'); - if (editorContent) { - this.htmlContent = editorContent.innerHTML; - } - this.showPdfPreview = true; - } - - closePdfPreview() { - this.showPdfPreview = false; - } - - generatePdfFromPreview() { - // Close preview and generate PDF - this.showPdfPreview = false; - this.generatePdfSimple(); - } - - // Property insertion functions - insertPropertyName() { - const propertyName = this.propertyData.Name || 'Property Name'; - this.insertTextAtCursor(propertyName); - } - - insertPropertyPrice() { - const price = this.propertyData.Price__c || '$0'; - this.insertTextAtCursor(price); - } - - insertPropertyType() { - const type = this.propertyData.Property_Type__c || 'Property Type'; - this.insertTextAtCursor(type); - } - - insertPropertyBedrooms() { - const bedrooms = this.propertyData.Bedrooms__c || '0'; - this.insertTextAtCursor(bedrooms + ' Bedrooms'); - } - - insertPropertyBathrooms() { - const bathrooms = this.propertyData.Bathrooms__c || '0'; - this.insertTextAtCursor(bathrooms + ' Bathrooms'); - } - - insertPropertySqft() { - const sqft = this.propertyData.Square_Footage__c || '0'; - this.insertTextAtCursor(sqft + ' sq ft'); - } - - insertPropertyAddress() { - const address = this.propertyData.Location__c || 'Property Address'; - this.insertTextAtCursor(address); - } - - insertPropertyDescription() { - const description = this.propertyData.Description_English__c || this.propertyData.pcrm__Description_English__c || this.propertyData.Description__c || 'Property Description'; - // Wrap into paragraphs and basic formatting - const lines = String(description).split(/\n+/).map(l => l.trim()).filter(Boolean); - const html = lines.map(l => `

${l}

`).join(''); - this.insertHtmlAtCursor(html); - } - - // Helper function to insert text at cursor position - insertTextAtCursor(text) { - const selection = window.getSelection(); - if (selection.rangeCount > 0) { - const range = selection.getRangeAt(0); - range.deleteContents(); - const textNode = document.createTextNode(text); - range.insertNode(textNode); - range.setStartAfter(textNode); - range.setEndAfter(textNode); - selection.removeAllRanges(); - selection.addRange(range); - this.showSuccess(`Inserted: ${text}`); - } else { - this.showError('Please place cursor in the editor first'); - } - } - - // Helper to insert HTML at cursor - insertHtmlAtCursor(html) { - const selection = window.getSelection(); - if (!selection.rangeCount) { this.showError('Please place cursor in the editor first'); return; } - const range = selection.getRangeAt(0); - range.deleteContents(); - const temp = document.createElement('div'); - temp.innerHTML = html; - const fragment = document.createDocumentFragment(); - while (temp.firstChild) { - fragment.appendChild(temp.firstChild); - } - range.insertNode(fragment); - // Move caret to end of inserted content - range.collapse(false); - selection.removeAllRanges(); - selection.addRange(range); - const editorContent = this.template.querySelector('.enhanced-editor-content'); - if (editorContent) editorContent.dispatchEvent(new Event('input', { bubbles: true })); - } - - // Setup editor click handler to deselect elements - setupEditorClickHandler() { - const editor = this.template.querySelector('.enhanced-editor-content'); - if (editor && !editor.hasClickHandler) { - editor.addEventListener('click', (e) => { - // Enhanced image detection - check multiple ways to find images - let clickedImage = null; - - // Method 1: Direct image click - if (e.target.tagName === 'IMG' && e.target.src && e.target.src.trim() !== '') { - clickedImage = e.target; + // Also check if this element has a background image set via CSS classes + if (currentElement.className) { + const classList = currentElement.className.split(" "); + for (let className of classList) { + // Look for common background image class patterns + if ( + className.includes("bg-") || + className.includes("background") || + className.includes("hero") || + className.includes("banner") || + className.includes("card") || + className.includes("property") + ) { + const classStyle = window.getComputedStyle(currentElement); + const classBgImage = classStyle.backgroundImage; + if ( + classBgImage && + classBgImage !== "none" && + classBgImage !== "initial" + ) { + const virtualImg = document.createElement("img"); + virtualImg.src = classBgImage.replace( + /url\(['"]?(.+?)['"]?\)/, + "$1" + ); + virtualImg.isBackgroundImage = true; + virtualImg.style.backgroundImage = classBgImage; + virtualImg.originalElement = currentElement; + clickedImage = virtualImg; + break; + } } - - // Method 2: Click on element containing an image (children) - if (!clickedImage && e.target.querySelector) { - const containedImg = e.target.querySelector('img'); - if (containedImg && containedImg.src && containedImg.src.trim() !== '') { - clickedImage = containedImg; - } - } - - // Method 3: Click on element that is inside a container with an image (parent traversal) - if (!clickedImage) { - let currentElement = e.target; - while (currentElement && currentElement !== editor) { - // Check if current element is an IMG - if (currentElement.tagName === 'IMG' && currentElement.src && currentElement.src.trim() !== '') { - clickedImage = currentElement; - break; - } - // Check if current element contains an IMG - if (currentElement.querySelector && currentElement.querySelector('img')) { - const img = currentElement.querySelector('img'); - if (img && img.src && img.src.trim() !== '') { - clickedImage = img; - break; - } - } - // Check siblings for IMG elements only if current element is positioned - if (currentElement.parentElement && - (currentElement.style.position === 'absolute' || - currentElement.style.position === 'relative' || - currentElement.classList.contains('draggable-element'))) { - const siblingImg = currentElement.parentElement.querySelector('img'); - if (siblingImg && siblingImg.src && siblingImg.src.trim() !== '') { - clickedImage = siblingImg; - break; - } - } - currentElement = currentElement.parentElement; - } - } - // Method 4: Check for background images in the element hierarchy (enhanced for property cards) - if (!clickedImage) { - let currentElement = e.target; - while (currentElement && currentElement !== editor) { - // Check for background images on any element (not just positioned ones) - const computedStyle = window.getComputedStyle(currentElement); - const backgroundImage = computedStyle.backgroundImage; - - if (backgroundImage && backgroundImage !== 'none' && backgroundImage !== 'initial') { - // Create a virtual IMG element for background images - const virtualImg = document.createElement('img'); - virtualImg.src = backgroundImage.replace(/url\(['"]?(.+?)['"]?\)/, '$1'); - virtualImg.isBackgroundImage = true; - virtualImg.style.backgroundImage = backgroundImage; - virtualImg.originalElement = currentElement; // Store reference to original element - clickedImage = virtualImg; - break; - } - - // Also check if this element has a background image set via CSS classes - if (currentElement.className) { - const classList = currentElement.className.split(' '); - for (let className of classList) { - // Look for common background image class patterns - if (className.includes('bg-') || className.includes('background') || - className.includes('hero') || className.includes('banner') || - className.includes('card') || className.includes('property')) { - - const classStyle = window.getComputedStyle(currentElement); - const classBgImage = classStyle.backgroundImage; - if (classBgImage && classBgImage !== 'none' && classBgImage !== 'initial') { - const virtualImg = document.createElement('img'); - virtualImg.src = classBgImage.replace(/url\(['"]?(.+?)['"]?\)/, '$1'); - virtualImg.isBackgroundImage = true; - virtualImg.style.backgroundImage = classBgImage; - virtualImg.originalElement = currentElement; - clickedImage = virtualImg; - break; - } - } - } - } - - currentElement = currentElement.parentElement; - } - } - - if (clickedImage) { - // Additional validation to ensure we have a valid image - if (clickedImage.tagName === 'IMG' || clickedImage.isBackgroundImage) { - this.handleImageClick(clickedImage, e); - return; - } else { - } - } - - // Reset image click tracking when clicking on non-image areas - this.resetImageClickTracking(); - - // Only deselect if clicking on the editor background or non-editable content - if (e.target === editor || (!e.target.classList.contains('draggable-element') && - !e.target.closest('.draggable-element'))) { - // Remove selection from all draggable elements - const allDraggable = editor.querySelectorAll('.draggable-element, .draggable-image-container, .draggable-table-container'); - allDraggable.forEach(el => { - el.classList.remove('selected'); - // Remove any resize handles - const resizeHandles = el.querySelectorAll('.resize-handle'); - resizeHandles.forEach(handle => handle.remove()); - // Remove any delete buttons - const deleteButtons = el.querySelectorAll('.delete-handle, .delete-image-btn'); - deleteButtons.forEach(btn => btn.remove()); - }); - - // Clear the selected element reference - this.clearSelection(); - } - }); - - // Ensure contenteditable is always enabled - editor.setAttribute('contenteditable', 'true'); - - // Prevent default scroll behavior when selecting draggable elements - editor.addEventListener('selectstart', (e) => { - if (e.target.classList.contains('draggable-element') && - !e.target.classList.contains('draggable-text')) { - e.preventDefault(); - } - }); - - // Prevent focus from jumping to top - editor.addEventListener('focus', (e) => { - e.preventDefault(); - }, true); - - // Add keyboard event handling for undo/redo - editor.addEventListener('keydown', (e) => { - if (e.ctrlKey || e.metaKey) { - if (e.key === 'z' && !e.shiftKey) { - e.preventDefault(); - this.undo(); - } else if (e.key === 'y' || (e.key === 'z' && e.shiftKey)) { - e.preventDefault(); - this.redo(); - } - } - }); - - editor.hasClickHandler = true; - } - } - - // Insert draggable text element - insertDraggableText() { - const editor = this.template.querySelector('.enhanced-editor-content'); - if (editor) { - this.setupEditorClickHandler(); - const textElement = document.createElement('div'); - textElement.className = 'draggable-element draggable-text'; - textElement.contentEditable = true; - textElement.innerHTML = 'Click to edit text'; - textElement.style.left = '50px'; - textElement.style.top = '50px'; - textElement.style.width = '200px'; - textElement.style.height = '40px'; - textElement.style.zIndex = '1000'; - textElement.style.position = 'absolute'; - - // Add resize handles - this.addResizeHandles(textElement); - - // Add drag functionality - this.makeDraggable(textElement); - - // Focus on the text element after a short delay - setTimeout(() => { - textElement.focus(); - textElement.classList.add('selected'); - }, 100); - - editor.appendChild(textElement); - } - } - - // Show image insertion modal - showImageInsertModal() { - this.showImageModal = true; - this.selectedImageUrl = ''; - this.selectedImageName = ''; - this.uploadedImageData = ''; - this.selectedImageCategory = 'all'; - this.insertButtonDisabled = true; - - // Populate property images from the existing data - this.populatePropertyImages(); - } - - // Populate property images array - populatePropertyImages() { - this.propertyImages = []; - - // Add images from imagesByCategory - Object.keys(this.imagesByCategory).forEach(category => { - this.imagesByCategory[category].forEach(image => { - this.propertyImages.push({ - url: image.url, - name: image.title || image.name || `${category} Image`, - category: category.toLowerCase() - }); - }); - }); - - // Add real property images if available - if (this.realPropertyImages && this.realPropertyImages.length > 0) { - this.realPropertyImages.forEach(image => { - this.propertyImages.push({ - url: image.url || image.Url__c, - name: image.name || image.Name || 'Property Image', - category: (image.category || image.Category__c || 'none').toLowerCase() - }); - }); - } - - } - - // Close image insertion modal - closeImageModal() { - this.showImageModal = false; - this.selectedImageUrl = ''; - this.selectedImageName = ''; - this.uploadedImageData = ''; - this.insertButtonDisabled = true; - - // Clear any selections - document.querySelectorAll('.property-image-item').forEach(item => { - item.classList.remove('selected'); - }); - - // Reset upload area - this.resetUploadArea(); - } - // Set image source (property or local) - setImageSource(event) { - const source = event.target.dataset.source; - this.imageSource = source; - this.selectedImageUrl = ''; - this.selectedImageName = ''; - this.uploadedImageData = ''; - this.insertButtonDisabled = true; - - // Clear any selections - document.querySelectorAll('.property-image-item').forEach(item => { - item.classList.remove('selected'); - }); - - // Reset upload area - this.resetUploadArea(); - } - - // Select image category - selectImageCategory(event) { - const category = event.target.dataset.category; - this.selectedImageCategory = category; - - // Update button states - document.querySelectorAll('.category-btn').forEach(btn => { - btn.classList.remove('active'); - if (btn.dataset.category === category) { - btn.classList.add('active'); + } } - }); - } - - // Select property image - selectPropertyImage(event) { - // Get the image URL from the closest element with data-image-url - const imageItem = event.target.closest('[data-image-url]'); - const imageUrl = imageItem ? imageItem.dataset.imageUrl : null; - const imageName = event.target.alt || event.target.textContent || 'Property Image'; - - - if (!imageUrl) { + + currentElement = currentElement.parentElement; + } + } + + if (clickedImage) { + // Additional validation to ensure we have a valid image + if ( + clickedImage.tagName === "IMG" || + clickedImage.isBackgroundImage + ) { + this.handleImageClick(clickedImage, e); return; + } else { + } } - - // Remove previous selection - document.querySelectorAll('.property-image-item').forEach(item => { - item.classList.remove('selected'); + + // Reset image click tracking when clicking on non-image areas + this.resetImageClickTracking(); + // Only deselect if clicking on the editor background or non-editable content + if ( + e.target === editor || + (!e.target.classList.contains("draggable-element") && + !e.target.closest(".draggable-element")) + ) { + // Remove selection from all draggable elements + const allDraggable = editor.querySelectorAll( + ".draggable-element, .draggable-image-container, .draggable-table-container" + ); + allDraggable.forEach((el) => { + el.classList.remove("selected"); + // Remove any resize handles + const resizeHandles = el.querySelectorAll(".resize-handle"); + resizeHandles.forEach((handle) => handle.remove()); + // Remove any delete buttons + const deleteButtons = el.querySelectorAll( + ".delete-handle, .delete-image-btn" + ); + deleteButtons.forEach((btn) => btn.remove()); + }); + + // Clear the selected element reference + this.clearSelection(); + } + }); + + // Ensure contenteditable is always enabled + editor.setAttribute("contenteditable", "true"); + + // Prevent default scroll behavior when selecting draggable elements + editor.addEventListener("selectstart", (e) => { + if ( + e.target.classList.contains("draggable-element") && + !e.target.classList.contains("draggable-text") + ) { + e.preventDefault(); + } + }); + + // Prevent focus from jumping to top + editor.addEventListener( + "focus", + (e) => { + e.preventDefault(); + }, + true + ); + + // Add keyboard event handling for undo/redo + editor.addEventListener("keydown", (e) => { + if (e.ctrlKey || e.metaKey) { + if (e.key === "z" && !e.shiftKey) { + e.preventDefault(); + this.undo(); + } else if (e.key === "y" || (e.key === "z" && e.shiftKey)) { + e.preventDefault(); + this.redo(); + } + } + }); + + editor.hasClickHandler = true; + } + } + + addDeselectFunctionality() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor || editor.hasDeselectHandler) return; + + editor.addEventListener( + "click", + (e) => { + // Only deselect if we're NOT clicking on: + // 1. Images or image containers + // 2. Resize handles + // 3. Delete buttons + // 4. Any draggable elements + + const isImageClick = + e.target.tagName === "IMG" || + e.target.closest(".draggable-image-container") || + e.target.closest(".draggable-table-container") || + e.target.classList.contains("resize-handle") || + e.target.classList.contains("delete-handle") || + e.target.closest(".resize-handle") || + e.target.closest(".delete-handle"); + + if (!isImageClick) { + this.deselectAllElements(); + } + }, + true + ); // Use capture phase to run before your existing handlers + + editor.hasDeselectHandler = true; + } + + // Keep the deselectAllElements method as I suggested + deselectAllElements() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) return; + + const allDraggable = editor.querySelectorAll( + ".draggable-element, .draggable-image-container, .draggable-table-container" + ); + + allDraggable.forEach((el) => { + el.classList.remove("selected"); + el.style.border = ""; + el.style.boxShadow = ""; + + const resizeHandles = el.querySelectorAll(".resize-handle"); + resizeHandles.forEach((handle) => handle.remove()); + + const deleteButtons = el.querySelectorAll( + ".delete-handle, .delete-image-btn" + ); + deleteButtons.forEach((btn) => btn.remove()); + }); + + this.selectedElement = null; + } + + // Insert draggable text element + insertDraggableText() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (editor) { + this.setupEditorClickHandler(); + const textElement = document.createElement("div"); + textElement.className = "draggable-element draggable-text"; + textElement.contentEditable = true; + textElement.innerHTML = "Click to edit text"; + textElement.style.left = "50px"; + textElement.style.top = "50px"; + textElement.style.width = "200px"; + textElement.style.height = "40px"; + textElement.style.zIndex = "1000"; + textElement.style.position = "absolute"; + + // Add resize handles + this.addResizeHandles(textElement); + + // Add drag functionality + this.makeDraggable(textElement); + + // Focus on the text element after a short delay + setTimeout(() => { + textElement.focus(); + textElement.classList.add("selected"); + }, 100); + + editor.appendChild(textElement); + } + } + + // Show image insertion modal + showImageInsertModal() { + this.showImageModal = true; + this.selectedImageUrl = ""; + this.selectedImageName = ""; + this.uploadedImageData = ""; + this.selectedImageCategory = "all"; + this.insertButtonDisabled = true; + + // Populate property images from the existing data + this.populatePropertyImages(); + } + + // Populate property images array + populatePropertyImages() { + this.propertyImages = []; + + // Add images from imagesByCategory + Object.keys(this.imagesByCategory).forEach((category) => { + this.imagesByCategory[category].forEach((image) => { + this.propertyImages.push({ + url: image.url, + name: image.title || image.name || `${category} Image`, + category: category.toLowerCase(), }); - - // Add selection to clicked item - const targetItem = event.target.closest('.property-image-item'); - if (targetItem) { - targetItem.classList.add('selected'); - } - - // Force reactivity by creating new objects - this.selectedImageUrl = imageUrl; - this.selectedImageName = imageName; - this.uploadedImageData = ''; + }); + }); + + // Add real property images if available + if (this.realPropertyImages && this.realPropertyImages.length > 0) { + this.realPropertyImages.forEach((image) => { + this.propertyImages.push({ + url: image.url || image.Url__c, + name: image.name || image.Name || "Property Image", + category: ( + image.category || + image.Category__c || + "none" + ).toLowerCase(), + }); + }); + } + } + // Close image insertion modal + closeImageModal() { + this.showImageModal = false; + this.selectedImageUrl = ""; + this.selectedImageName = ""; + this.uploadedImageData = ""; + this.insertButtonDisabled = true; + + // Clear any selections + document.querySelectorAll(".property-image-item").forEach((item) => { + item.classList.remove("selected"); + }); + + // Reset upload area + this.resetUploadArea(); + } + // Set image source (property or local) + setImageSource(event) { + const source = event.target.dataset.source; + this.imageSource = source; + this.selectedImageUrl = ""; + this.selectedImageName = ""; + this.uploadedImageData = ""; + this.insertButtonDisabled = true; + + // Clear any selections + document.querySelectorAll(".property-image-item").forEach((item) => { + item.classList.remove("selected"); + }); + + // Reset upload area + this.resetUploadArea(); + } + + // Select image category + selectImageCategory(event) { + const category = event.target.dataset.category; + this.selectedImageCategory = category; + + // Update button states + document.querySelectorAll(".category-btn").forEach((btn) => { + btn.classList.remove("active"); + if (btn.dataset.category === category) { + btn.classList.add("active"); + } + }); + } + + // Select property image + selectPropertyImage(event) { + // Get the image URL from the closest element with data-image-url + const imageItem = event.target.closest("[data-image-url]"); + const imageUrl = imageItem ? imageItem.dataset.imageUrl : null; + const imageName = + event.target.alt || event.target.textContent || "Property Image"; + + if (!imageUrl) { + return; + } + + // Remove previous selection + document.querySelectorAll(".property-image-item").forEach((item) => { + item.classList.remove("selected"); + }); + + // Add selection to clicked item + const targetItem = event.target.closest(".property-image-item"); + if (targetItem) { + targetItem.classList.add("selected"); + } + + // Force reactivity by creating new objects + this.selectedImageUrl = imageUrl; + this.selectedImageName = imageName; + this.uploadedImageData = ""; + this.insertButtonDisabled = false; + + // Log current state for debugging + this.logCurrentState(); + + // Reset upload area if we're on local tab + if (this.imageSource === "local") { + this.resetUploadArea(); + } + + // Force a re-render by updating a tracked property + this.forceRerender(); + } + + // Reset upload area to default state + resetUploadArea() { + const uploadArea = this.template.querySelector(".upload-area"); + if (uploadArea) { + // Remove existing preview if any + const existingPreview = uploadArea.querySelector( + ".uploaded-image-preview" + ); + if (existingPreview) { + existingPreview.remove(); + } + + // Show upload content again + const uploadContent = uploadArea.querySelector(".upload-content"); + if (uploadContent) { + uploadContent.style.display = "flex"; + } + } + } + + // Trigger file upload for main image modal + triggerFileUpload() { + const fileInput = this.template.querySelector(".file-input"); + if (fileInput) { + fileInput.click(); + } else { + } + } + + // Handle file upload + handleFileUpload(event) { + const file = event.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (e) => { + this.uploadedImageData = e.target.result; + this.selectedImageUrl = e.target.result; + this.selectedImageName = file.name; this.insertButtonDisabled = false; - - + // Log current state for debugging this.logCurrentState(); - - // Reset upload area if we're on local tab - if (this.imageSource === 'local') { - this.resetUploadArea(); - } - + + // Update the upload area to show selected image + this.updateUploadAreaWithSelectedImage(e.target.result, file.name); + // Force a re-render by updating a tracked property this.forceRerender(); + }; + reader.readAsDataURL(file); } - - // Reset upload area to default state - resetUploadArea() { - const uploadArea = this.template.querySelector('.upload-area'); - if (uploadArea) { - // Remove existing preview if any - const existingPreview = uploadArea.querySelector('.uploaded-image-preview'); - if (existingPreview) { - existingPreview.remove(); - } - - // Show upload content again - const uploadContent = uploadArea.querySelector('.upload-content'); - if (uploadContent) { - uploadContent.style.display = 'flex'; - } - } - } - - // Trigger file upload for main image modal - triggerFileUpload() { - const fileInput = this.template.querySelector('.file-input'); - if (fileInput) { - fileInput.click(); - } else { - } - } - - // Handle file upload - handleFileUpload(event) { - const file = event.target.files[0]; - if (file) { - const reader = new FileReader(); - reader.onload = (e) => { - this.uploadedImageData = e.target.result; - this.selectedImageUrl = e.target.result; - this.selectedImageName = file.name; - this.insertButtonDisabled = false; - - - // Log current state for debugging - this.logCurrentState(); - - // Update the upload area to show selected image - this.updateUploadAreaWithSelectedImage(e.target.result, file.name); - - // Force a re-render by updating a tracked property - this.forceRerender(); - }; - reader.readAsDataURL(file); - } - } - - // Update upload area to show selected image - updateUploadAreaWithSelectedImage(imageUrl, fileName) { - const uploadArea = this.template.querySelector('.upload-area'); - if (uploadArea) { - // Remove existing preview if any - const existingPreview = uploadArea.querySelector('.uploaded-image-preview'); - if (existingPreview) { - existingPreview.remove(); - } - - // Create preview container - const previewContainer = document.createElement('div'); - previewContainer.className = 'uploaded-image-preview'; - previewContainer.style.cssText = ` + } + + // Update upload area to show selected image + updateUploadAreaWithSelectedImage(imageUrl, fileName) { + const uploadArea = this.template.querySelector(".upload-area"); + if (uploadArea) { + // Remove existing preview if any + const existingPreview = uploadArea.querySelector( + ".uploaded-image-preview" + ); + if (existingPreview) { + existingPreview.remove(); + } + + // Create preview container + const previewContainer = document.createElement("div"); + previewContainer.className = "uploaded-image-preview"; + previewContainer.style.cssText = ` position: relative; width: 100%; max-width: 200px; @@ -5389,22 +6086,22 @@ export default class PropertyTemplateSelector extends LightningElement { border: 2px solid #4f46e5; box-shadow: 0 4px 12px rgba(79, 70, 229, 0.2); `; - - // Create image element - const img = document.createElement('img'); - img.src = imageUrl; - img.alt = fileName; - img.style.cssText = ` + + // Create image element + const img = document.createElement("img"); + img.src = imageUrl; + img.alt = fileName; + img.style.cssText = ` width: 100%; height: auto; display: block; max-height: 150px; object-fit: cover; `; - - // Create file name overlay - const fileNameOverlay = document.createElement('div'); - fileNameOverlay.style.cssText = ` + + // Create file name overlay + const fileNameOverlay = document.createElement("div"); + fileNameOverlay.style.cssText = ` position: absolute; bottom: 0; left: 0; @@ -5415,831 +6112,932 @@ export default class PropertyTemplateSelector extends LightningElement { font-size: 12px; font-weight: 500; `; - fileNameOverlay.textContent = fileName; - - previewContainer.appendChild(img); - previewContainer.appendChild(fileNameOverlay); - - // Replace upload content with preview - const uploadContent = uploadArea.querySelector('.upload-content'); - if (uploadContent) { - uploadContent.style.display = 'none'; - } - - uploadArea.appendChild(previewContainer); - - // Add click handler to change image - uploadArea.onclick = () => { - this.triggerFileUpload(); - }; - } + fileNameOverlay.textContent = fileName; + + previewContainer.appendChild(img); + previewContainer.appendChild(fileNameOverlay); + // Replace upload content with preview + const uploadContent = uploadArea.querySelector(".upload-content"); + if (uploadContent) { + uploadContent.style.display = "none"; + } + + uploadArea.appendChild(previewContainer); + + // Add click handler to change image + uploadArea.onclick = () => { + this.triggerFileUpload(); + }; } - - // Handle insert button click with debugging - handleInsertButtonClick() { - this.logCurrentState(); - this.insertSelectedImage(); + } + + // Handle insert button click with debugging + handleInsertButtonClick() { + this.logCurrentState(); + this.insertSelectedImage(); + } + // Insert selected image + insertSelectedImage() { + // Check if we have a valid image URL + const imageUrl = this.selectedImageUrl || this.uploadedImageData; + const imageName = this.selectedImageName || "Uploaded Image"; + + if (this.insertButtonDisabled || !imageUrl) { + alert("Please select an image first"); + return; } - // Insert selected image - insertSelectedImage() { - - // Check if we have a valid image URL - const imageUrl = this.selectedImageUrl || this.uploadedImageData; - const imageName = this.selectedImageName || 'Uploaded Image'; - - if (this.insertButtonDisabled || !imageUrl) { - alert('Please select an image first'); - return; - } - - const editor = this.template.querySelector('.enhanced-editor-content'); - if (editor) { + + const editor = this.template.querySelector(".enhanced-editor-content"); + if (editor) { + // Save undo state before making changes + this.saveUndoState(); + this.setupEditorClickHandler(); + + // Create draggable image container + const imageContainer = document.createElement("div"); + imageContainer.className = "draggable-image-container"; + imageContainer.style.left = "50px"; + imageContainer.style.top = "50px"; + imageContainer.style.width = "200px"; + imageContainer.style.height = "150px"; + imageContainer.style.zIndex = "1000"; + imageContainer.style.position = "absolute"; + imageContainer.style.overflow = "hidden"; + imageContainer.style.border = "2px solid transparent"; + imageContainer.style.cursor = "move"; + imageContainer.style.userSelect = "none"; + + // Create image element + const img = document.createElement("img"); + img.src = imageUrl; + img.alt = imageName; + img.style.width = "100%"; + img.style.height = "100%"; + img.style.objectFit = "cover"; + img.style.display = "block"; + + imageContainer.appendChild(img); + + // Add resize handles + this.addResizeHandles(imageContainer); + + // Add delete handle + this.addDeleteHandle(imageContainer); + + // Add drag functionality + this.makeDraggable(imageContainer); + + // Add click to select functionality + imageContainer.addEventListener("click", (e) => { + e.stopPropagation(); + this.selectDraggableElement(imageContainer); + }); + + // Select the image after a short delay + setTimeout(() => { + this.selectDraggableElement(imageContainer); + }, 100); + + editor.appendChild(imageContainer); + + // Close modal + this.closeImageModal(); + } + } + + // Insert draggable image element + insertDraggableImage() { + const input = document.createElement("input"); + input.type = "file"; + input.accept = "image/*"; + input.onchange = (e) => { + const file = e.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (event) => { + const editor = this.template.querySelector( + ".enhanced-editor-content" + ); + if (editor) { // Save undo state before making changes this.saveUndoState(); this.setupEditorClickHandler(); - - // Create draggable image container - const imageContainer = document.createElement('div'); - imageContainer.className = 'draggable-image-container'; - imageContainer.style.left = '50px'; - imageContainer.style.top = '50px'; - imageContainer.style.width = '200px'; - imageContainer.style.height = '150px'; - imageContainer.style.zIndex = '1000'; - imageContainer.style.position = 'absolute'; - imageContainer.style.overflow = 'hidden'; - imageContainer.style.border = '2px solid transparent'; - imageContainer.style.cursor = 'move'; - imageContainer.style.userSelect = 'none'; - - // Create image element - const img = document.createElement('img'); - img.src = imageUrl; - img.alt = imageName; - img.style.width = '100%'; - img.style.height = '100%'; - img.style.objectFit = 'cover'; - img.style.display = 'block'; - + const imageContainer = document.createElement("div"); + imageContainer.className = "draggable-element"; + imageContainer.style.left = "50px"; + imageContainer.style.top = "50px"; + imageContainer.style.width = "200px"; + imageContainer.style.height = "150px"; + imageContainer.style.zIndex = "1000"; + imageContainer.style.position = "absolute"; + imageContainer.style.overflow = "hidden"; + + const img = document.createElement("img"); + img.src = event.target.result; + img.className = "draggable-image"; + img.alt = "Inserted Image"; + img.style.width = "100%"; + img.style.height = "100%"; + img.style.objectFit = "cover"; + imageContainer.appendChild(img); - + // Add resize handles this.addResizeHandles(imageContainer); - - // Add delete handle - this.addDeleteHandle(imageContainer); - + // Add drag functionality this.makeDraggable(imageContainer); - - // Add click to select functionality - imageContainer.addEventListener('click', (e) => { - e.stopPropagation(); - this.selectDraggableElement(imageContainer); - }); - + // Select the image after a short delay setTimeout(() => { - this.selectDraggableElement(imageContainer); + imageContainer.classList.add("selected"); }, 100); - + editor.appendChild(imageContainer); - - // Close modal - this.closeImageModal(); - } - } - - // Insert draggable image element - insertDraggableImage() { - const input = document.createElement('input'); - input.type = 'file'; - input.accept = 'image/*'; - input.onchange = (e) => { - const file = e.target.files[0]; - if (file) { - const reader = new FileReader(); - reader.onload = (event) => { - const editor = this.template.querySelector('.enhanced-editor-content'); - if (editor) { - // Save undo state before making changes - this.saveUndoState(); - this.setupEditorClickHandler(); - const imageContainer = document.createElement('div'); - imageContainer.className = 'draggable-element'; - imageContainer.style.left = '50px'; - imageContainer.style.top = '50px'; - imageContainer.style.width = '200px'; - imageContainer.style.height = '150px'; - imageContainer.style.zIndex = '1000'; - imageContainer.style.position = 'absolute'; - imageContainer.style.overflow = 'hidden'; - - const img = document.createElement('img'); - img.src = event.target.result; - img.className = 'draggable-image'; - img.alt = 'Inserted Image'; - img.style.width = '100%'; - img.style.height = '100%'; - img.style.objectFit = 'cover'; - - imageContainer.appendChild(img); - - // Add resize handles - this.addResizeHandles(imageContainer); - - // Add drag functionality - this.makeDraggable(imageContainer); - - // Select the image after a short delay - setTimeout(() => { - imageContainer.classList.add('selected'); - }, 100); - - editor.appendChild(imageContainer); - } - }; - reader.readAsDataURL(file); - } + } }; - input.click(); - } + reader.readAsDataURL(file); + } + }; + input.click(); + } - // Add resize handles to element - addResizeHandles(element) { - // Avoid duplicate handles - const existing = element.querySelectorAll('.resize-handle'); - if (existing && existing.length > 0) return; + // Add resize handles to element + addResizeHandles(element) { + // Avoid duplicate handles + const existing = element.querySelectorAll(".resize-handle"); + if (existing && existing.length > 0) return; - const handles = ['nw', 'ne', 'sw', 'se', 'n', 's', 'w', 'e']; - handles.forEach(direction => { - const handle = document.createElement('div'); - handle.className = `resize-handle ${direction}`; - handle.style.position = 'absolute'; - handle.style.width = '8px'; - handle.style.height = '8px'; - handle.style.background = '#6c63ff'; - handle.style.border = '2px solid white'; - handle.style.borderRadius = '50%'; - handle.style.zIndex = '1001'; - handle.addEventListener('mousedown', (e) => this.startResize(e, element, direction)); - element.appendChild(handle); + const handles = ["nw", "ne", "sw", "se", "n", "s", "w", "e"]; + handles.forEach((direction) => { + const handle = document.createElement("div"); + handle.className = `resize-handle ${direction}`; + handle.style.position = "absolute"; + handle.style.width = "8px"; + handle.style.height = "8px"; + handle.style.background = "#6c63ff"; + handle.style.border = "2px solid white"; + handle.style.borderRadius = "50%"; + handle.style.zIndex = "1001"; + handle.addEventListener("mousedown", (e) => + this.startResize(e, element, direction) + ); + element.appendChild(handle); + }); + } + + // Make element draggable + makeDraggable(element) { + let isDragging = false; + let startX, startY, startLeft, startTop; + let dragStarted = false; + + // Handle mousedown on the element (not on resize handles) + const handleMouseDown = (e) => { + if (e.target.classList.contains("resize-handle")) return; + + isDragging = true; + dragStarted = false; + element.classList.add("selected"); + + // Remove selection from other elements + const editor = element.closest(".enhanced-editor-content"); + if (editor) { + const allDraggable = editor.querySelectorAll(".draggable-element"); + allDraggable.forEach((el) => { + if (el !== element) el.classList.remove("selected"); }); - } + } - // Make element draggable - makeDraggable(element) { - let isDragging = false; - let startX, startY, startLeft, startTop; - let dragStarted = false; + startX = e.clientX; + startY = e.clientY; + startLeft = parseInt(element.style.left) || 0; + startTop = parseInt(element.style.top) || 0; - // Handle mousedown on the element (not on resize handles) - const handleMouseDown = (e) => { - if (e.target.classList.contains('resize-handle')) return; - - isDragging = true; - dragStarted = false; - element.classList.add('selected'); - - // Remove selection from other elements - const editor = element.closest('.enhanced-editor-content'); - if (editor) { - const allDraggable = editor.querySelectorAll('.draggable-element'); - allDraggable.forEach(el => { - if (el !== element) el.classList.remove('selected'); - }); - } - - startX = e.clientX; - startY = e.clientY; - startLeft = parseInt(element.style.left) || 0; - startTop = parseInt(element.style.top) || 0; - - e.preventDefault(); - e.stopPropagation(); - - // Prevent scrolling while dragging - document.body.style.overflow = 'hidden'; - }; - const handleMouseMove = (e) => { - if (!isDragging) return; - - const deltaX = e.clientX - startX; - const deltaY = e.clientY - startY; - - // Only start dragging if mouse moved more than 5px - if (!dragStarted && (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5)) { - dragStarted = true; - element.classList.add('dragging'); - } - - if (dragStarted) { - const editor = element.closest('.enhanced-editor-content'); - const editorRect = editor ? editor.getBoundingClientRect() : { left: 0, top: 0, width: window.innerWidth, height: window.innerHeight }; - - // Calculate new position relative to editor - let newLeft = startLeft + deltaX; - let newTop = startTop + deltaY; - - // Keep element within editor bounds - use scrollHeight for full template height - const maxWidth = editor ? editor.clientWidth : editorRect.width; - const maxHeight = editor ? editor.scrollHeight : editorRect.height; - - newLeft = Math.max(0, Math.min(newLeft, maxWidth - element.offsetWidth)); - newTop = Math.max(0, Math.min(newTop, maxHeight - element.offsetHeight)); - - element.style.left = newLeft + 'px'; - element.style.top = newTop + 'px'; - element.style.position = 'absolute'; - } - - e.preventDefault(); - e.stopPropagation(); - }; + if (editor) { +editor.initialScrollLeft = editor.scrollLeft; +editor.initialScrollTop = editor.scrollTop; +} - const handleMouseUp = () => { - if (isDragging) { - isDragging = false; - dragStarted = false; - element.classList.remove('dragging'); - - // Restore scrolling - document.body.style.overflow = ''; - } - }; + e.preventDefault(); + e.stopPropagation(); - element.addEventListener('mousedown', handleMouseDown); - document.addEventListener('mousemove', handleMouseMove); - document.addEventListener('mouseup', handleMouseUp); + // Prevent scrolling while dragging + document.body.style.overflow = "hidden"; + }; + const handleMouseMove = (e) => { + if (!isDragging) return; - // Handle click to select without dragging - element.addEventListener('click', (e) => { - if (!dragStarted) { - e.stopPropagation(); - element.classList.add('selected'); - - // Remove selection from other elements - const editor = element.closest('.enhanced-editor-content'); - if (editor) { - const allDraggable = editor.querySelectorAll('.draggable-element'); - allDraggable.forEach(el => { - if (el !== element) el.classList.remove('selected'); - }); - } - } - }); + const deltaX = e.clientX - startX; + const deltaY = e.clientY - startY; - // Handle text editing for text elements - if (element.classList.contains('draggable-text')) { - element.addEventListener('dblclick', (e) => { - if (!dragStarted) { - e.stopPropagation(); - element.focus(); - element.style.cursor = 'text'; - } - }); + // Only start dragging if mouse moved more than 5px + if (!dragStarted && (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5)) { + dragStarted = true; + element.classList.add("dragging"); + } - element.addEventListener('input', (e) => { - e.stopPropagation(); - }); + if (dragStarted) { + const editor = element.closest(".enhanced-editor-content"); + const editorRect = editor + ? editor.getBoundingClientRect() + : { + left: 0, + top: 0, + width: window.innerWidth, + height: window.innerHeight, + }; - element.addEventListener('keydown', (e) => { - e.stopPropagation(); - }); - } - } + // Calculate new position relative to editor + // let newLeft = startLeft + deltaX; + // let newTop = startTop + deltaY; - // Start resize operation - startResize(e, element, direction) { - e.preventDefault(); + // Calculate new position relative to editor (FIXED for smooth scroll) + // Use existing 'editor' and 'editorRect' variables declared above to avoid redeclaration + let newLeft = startLeft + deltaX; + let newTop = startTop + deltaY; + +// Account for editor scroll position for smooth dragging +if (editor) { +const currentScrollLeft = editor.scrollLeft; +const currentScrollTop = editor.scrollTop; + +// Adjust position based on scroll changes since drag started +const scrollDeltaX = currentScrollLeft - (editor.initialScrollLeft || 0); +const scrollDeltaY = currentScrollTop - (editor.initialScrollTop || 0); + +newLeft -= scrollDeltaX; +newTop -= scrollDeltaY; +} + + + + // Keep element within editor bounds - use scrollHeight for full template height + const maxWidth = editor ? editor.clientWidth : editorRect.width; + const maxHeight = editor ? editor.scrollHeight : editorRect.height; + + newLeft = Math.max( + 0, + Math.min(newLeft, maxWidth - element.offsetWidth) + ); + newTop = Math.max( + 0, + Math.min(newTop, maxHeight - element.offsetHeight) + ); + + element.style.left = newLeft + "px"; + element.style.top = newTop + "px"; + element.style.position = "absolute"; + } + + e.preventDefault(); + e.stopPropagation(); + }; + + const handleMouseUp = () => { + if (isDragging) { + isDragging = false; + dragStarted = false; + element.classList.remove("dragging"); + + // Restore scrolling + document.body.style.overflow = ""; + } + }; + + element.addEventListener("mousedown", handleMouseDown); + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + + // Handle click to select without dragging + element.addEventListener("click", (e) => { + if (!dragStarted) { e.stopPropagation(); - - const startX = e.clientX; - const startY = e.clientY; - const startWidth = parseInt(element.style.width) || element.offsetWidth; - const startHeight = parseInt(element.style.height) || element.offsetHeight; - const startLeft = parseInt(element.style.left) || 0; - const startTop = parseInt(element.style.top) || 0; + element.classList.add("selected"); - // Add resizing class and prevent scrolling - element.classList.add('resizing'); - document.body.style.overflow = 'hidden'; - - const handleMouseMove = (e) => { - e.preventDefault(); - e.stopPropagation(); - - const deltaX = e.clientX - startX; - const deltaY = e.clientY - startY; - - let newWidth = startWidth; - let newHeight = startHeight; - let newLeft = startLeft; - let newTop = startTop; - - const editor = element.closest('.enhanced-editor-content'); - const editorRect = editor ? editor.getBoundingClientRect() : { width: window.innerWidth, height: window.innerHeight }; - - // Use scrollHeight for full template height - const maxWidth = editor ? editor.clientWidth : editorRect.width; - const maxHeight = editor ? editor.scrollHeight : editorRect.height; - - switch(direction) { - case 'se': - newWidth = Math.max(50, Math.min(startWidth + deltaX, maxWidth - startLeft)); - newHeight = Math.max(20, Math.min(startHeight + deltaY, maxHeight - startTop)); - break; - case 'sw': - newWidth = Math.max(50, startWidth - deltaX); - newHeight = Math.max(20, Math.min(startHeight + deltaY, maxHeight - startTop)); - if (newWidth >= 50) { - newLeft = Math.max(0, startLeft + deltaX); - } - break; - case 'ne': - newWidth = Math.max(50, Math.min(startWidth + deltaX, maxWidth - startLeft)); - newHeight = Math.max(20, startHeight - deltaY); - if (newHeight >= 20) { - newTop = Math.max(0, startTop + deltaY); - } - break; - case 'nw': - newWidth = Math.max(50, startWidth - deltaX); - newHeight = Math.max(20, startHeight - deltaY); - if (newWidth >= 50) { - newLeft = Math.max(0, startLeft + deltaX); - } - if (newHeight >= 20) { - newTop = Math.max(0, startTop + deltaY); - } - break; - case 'e': - newWidth = Math.max(50, Math.min(startWidth + deltaX, maxWidth - startLeft)); - break; - case 'w': - newWidth = Math.max(50, startWidth - deltaX); - if (newWidth >= 50) { - newLeft = Math.max(0, startLeft + deltaX); - } - break; - case 's': - newHeight = Math.max(20, Math.min(startHeight + deltaY, maxHeight - startTop)); - break; - case 'n': - newHeight = Math.max(20, startHeight - deltaY); - if (newHeight >= 20) { - newTop = Math.max(0, startTop + deltaY); - } - break; - } - - // Apply the new dimensions and position - element.style.width = newWidth + 'px'; - element.style.height = newHeight + 'px'; - element.style.left = newLeft + 'px'; - element.style.top = newTop + 'px'; - element.style.position = 'absolute'; - }; - - const handleMouseUp = () => { - element.classList.remove('resizing'); - document.body.style.overflow = ''; - document.removeEventListener('mousemove', handleMouseMove); - document.removeEventListener('mouseup', handleMouseUp); - }; - - document.addEventListener('mousemove', handleMouseMove); - document.addEventListener('mouseup', handleMouseUp); - } - handleBringForward() { - const selection = window.getSelection(); - if (selection.rangeCount > 0) { - const range = selection.getRangeAt(0); - const element = range.commonAncestorContainer.nodeType === Node.ELEMENT_NODE - ? range.commonAncestorContainer - : range.commonAncestorContainer.parentElement; - - if (element && element.style) { - const currentZIndex = parseInt(element.style.zIndex) || 0; - element.style.zIndex = currentZIndex + 1; - this.showSuccess(`Z-index increased to ${currentZIndex + 1}`); - } - } else { - this.showError('Please select an element first'); + // Remove selection from other elements + const editor = element.closest(".enhanced-editor-content"); + if (editor) { + const allDraggable = editor.querySelectorAll(".draggable-element"); + allDraggable.forEach((el) => { + if (el !== element) el.classList.remove("selected"); + }); } - } + } + }); - handleSendBackward() { - const selection = window.getSelection(); - if (selection.rangeCount > 0) { - const range = selection.getRangeAt(0); - const element = range.commonAncestorContainer.nodeType === Node.ELEMENT_NODE - ? range.commonAncestorContainer - : range.commonAncestorContainer.parentElement; - - if (element && element.style) { - const currentZIndex = parseInt(element.style.zIndex) || 0; - element.style.zIndex = Math.max(0, currentZIndex - 1); - this.showSuccess(`Z-index decreased to ${Math.max(0, currentZIndex - 1)}`); - } - } else { - this.showError('Please select an element first'); + // Handle text editing for text elements + if (element.classList.contains("draggable-text")) { + element.addEventListener("dblclick", (e) => { + if (!dragStarted) { + e.stopPropagation(); + element.focus(); + element.style.cursor = "text"; } - } + }); - setZIndex() { - const zIndexInput = this.template.querySelector('#zIndexInput'); - const zIndex = parseInt(zIndexInput.value) || 0; - - const selection = window.getSelection(); - if (selection.rangeCount > 0) { - const range = selection.getRangeAt(0); - const element = range.commonAncestorContainer.nodeType === Node.ELEMENT_NODE - ? range.commonAncestorContainer - : range.commonAncestorContainer.parentElement; - - if (element && element.style) { - element.style.zIndex = zIndex; - this.showSuccess(`Z-index set to ${zIndex}`); - } - } else { - this.showError('Please select an element first'); + element.addEventListener("input", (e) => { + e.stopPropagation(); + }); + + element.addEventListener("keydown", (e) => { + e.stopPropagation(); + }); + } + } + + // Start resize operation + startResize(e, element, direction) { + e.preventDefault(); + e.stopPropagation(); + + const startX = e.clientX; + const startY = e.clientY; + const startWidth = parseInt(element.style.width) || element.offsetWidth; + const startHeight = parseInt(element.style.height) || element.offsetHeight; + const startLeft = parseInt(element.style.left) || 0; + const startTop = parseInt(element.style.top) || 0; + + // Add resizing class and prevent scrolling + element.classList.add("resizing"); + document.body.style.overflow = "hidden"; + + const handleMouseMove = (e) => { + e.preventDefault(); + e.stopPropagation(); + + const deltaX = e.clientX - startX; + const deltaY = e.clientY - startY; + + let newWidth = startWidth; + let newHeight = startHeight; + let newLeft = startLeft; + let newTop = startTop; + + const editor = element.closest(".enhanced-editor-content"); + const editorRect = editor + ? editor.getBoundingClientRect() + : { width: window.innerWidth, height: window.innerHeight }; + // Use scrollHeight for full template height + const maxWidth = editor ? editor.clientWidth : editorRect.width; + const maxHeight = editor ? editor.scrollHeight : editorRect.height; + + switch (direction) { + case "se": + newWidth = Math.max( + 50, + Math.min(startWidth + deltaX, maxWidth - startLeft) + ); + newHeight = Math.max( + 20, + Math.min(startHeight + deltaY, maxHeight - startTop) + ); + break; + case "sw": + newWidth = Math.max(50, startWidth - deltaX); + newHeight = Math.max( + 20, + Math.min(startHeight + deltaY, maxHeight - startTop) + ); + if (newWidth >= 50) { + newLeft = Math.max(0, startLeft + deltaX); + } + break; + case "ne": + newWidth = Math.max( + 50, + Math.min(startWidth + deltaX, maxWidth - startLeft) + ); + newHeight = Math.max(20, startHeight - deltaY); + if (newHeight >= 20) { + newTop = Math.max(0, startTop + deltaY); + } + break; + case "nw": + newWidth = Math.max(50, startWidth - deltaX); + newHeight = Math.max(20, startHeight - deltaY); + if (newWidth >= 50) { + newLeft = Math.max(0, startLeft + deltaX); + } + if (newHeight >= 20) { + newTop = Math.max(0, startTop + deltaY); + } + break; + case "e": + newWidth = Math.max( + 50, + Math.min(startWidth + deltaX, maxWidth - startLeft) + ); + break; + case "w": + newWidth = Math.max(50, startWidth - deltaX); + if (newWidth >= 50) { + newLeft = Math.max(0, startLeft + deltaX); + } + break; + case "s": + newHeight = Math.max( + 20, + Math.min(startHeight + deltaY, maxHeight - startTop) + ); + break; + case "n": + newHeight = Math.max(20, startHeight - deltaY); + if (newHeight >= 20) { + newTop = Math.max(0, startTop + deltaY); + } + break; + } + + // Apply the new dimensions and position + element.style.width = newWidth + "px"; + element.style.height = newHeight + "px"; + element.style.left = newLeft + "px"; + element.style.top = newTop + "px"; + element.style.position = "absolute"; + }; + + const handleMouseUp = () => { + element.classList.remove("resizing"); + document.body.style.overflow = ""; + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + }; + + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + } + handleBringForward() { + const selection = window.getSelection(); + if (selection.rangeCount > 0) { + const range = selection.getRangeAt(0); + const element = + range.commonAncestorContainer.nodeType === Node.ELEMENT_NODE + ? range.commonAncestorContainer + : range.commonAncestorContainer.parentElement; + + if (element && element.style) { + const currentZIndex = parseInt(element.style.zIndex) || 0; + element.style.zIndex = currentZIndex + 1; + this.showSuccess(`Z-index increased to ${currentZIndex + 1}`); + } + } else { + this.showError("Please select an element first"); + } + } + + handleSendBackward() { + const selection = window.getSelection(); + if (selection.rangeCount > 0) { + const range = selection.getRangeAt(0); + const element = + range.commonAncestorContainer.nodeType === Node.ELEMENT_NODE + ? range.commonAncestorContainer + : range.commonAncestorContainer.parentElement; + + if (element && element.style) { + const currentZIndex = parseInt(element.style.zIndex) || 0; + element.style.zIndex = Math.max(0, currentZIndex - 1); + this.showSuccess( + `Z-index decreased to ${Math.max(0, currentZIndex - 1)}` + ); + } + } else { + this.showError("Please select an element first"); + } + } + + setZIndex() { + const zIndexInput = this.template.querySelector("#zIndexInput"); + const zIndex = parseInt(zIndexInput.value) || 0; + + const selection = window.getSelection(); + if (selection.rangeCount > 0) { + const range = selection.getRangeAt(0); + const element = + range.commonAncestorContainer.nodeType === Node.ELEMENT_NODE + ? range.commonAncestorContainer + : range.commonAncestorContainer.parentElement; + + if (element && element.style) { + element.style.zIndex = zIndex; + this.showSuccess(`Z-index set to ${zIndex}`); + } + } else { + this.showError("Please select an element first"); + } + } + + // Helper method to make elements draggable + makeDraggable(element) { + let isDragging = false; + let currentX; + let currentY; + let initialX; + let initialY; + let xOffset = 0; + let yOffset = 0; + + element.addEventListener("mousedown", (e) => { + // Only start dragging if clicking on the element itself (not on text inside) + if ( + e.target === element || + (element.classList.contains("draggable-text-box") && + e.target.parentNode === element) + ) { + initialX = e.clientX - xOffset; + initialY = e.clientY - yOffset; + isDragging = true; + element.style.cursor = "grabbing"; + } + }); + + document.addEventListener("mousemove", (e) => { + if (isDragging) { + e.preventDefault(); + currentX = e.clientX - initialX; + currentY = e.clientY - initialY; + xOffset = currentX; + yOffset = currentY; + + element.style.left = currentX + "px"; + element.style.top = currentY + "px"; + } + }); + + document.addEventListener("mouseup", () => { + if (isDragging) { + isDragging = false; + element.style.cursor = element.classList.contains("draggable-text-box") + ? "text" + : "move"; + } + }); + } + // Helper method to make elements resizable + makeResizable(element) { + const resizer = document.createElement("div"); + resizer.className = "resizer"; + resizer.style.position = "absolute"; + resizer.style.width = "10px"; + resizer.style.height = "10px"; + resizer.style.background = "#667eea"; + resizer.style.borderRadius = "50%"; + resizer.style.bottom = "-5px"; + resizer.style.right = "-5px"; + resizer.style.cursor = "se-resize"; + resizer.style.zIndex = "1001"; + + element.appendChild(resizer); + + let isResizing = false; + let startWidth, startHeight, startX, startY; + + resizer.addEventListener("mousedown", (e) => { + isResizing = true; + startX = e.clientX; + startY = e.clientY; + startWidth = parseInt(element.style.width) || element.offsetWidth; + startHeight = parseInt(element.style.height) || element.offsetHeight; + e.stopPropagation(); + }); + + document.addEventListener("mousemove", (e) => { + if (isResizing) { + const newWidth = startWidth + (e.clientX - startX); + const newHeight = startHeight + (e.clientY - startY); + + if (newWidth > 50) element.style.width = newWidth + "px"; + if (newHeight > 30) element.style.height = newHeight + "px"; + } + }); + + document.addEventListener("mouseup", () => { + isResizing = false; + }); + } + + insertText() { + const previewFrame = this.template.querySelector( + ".enhanced-editor-content" + ); + if (previewFrame) { + // Create draggable and resizable text box + const textBox = document.createElement("div"); + textBox.className = "draggable-text-box"; + textBox.contentEditable = true; + textBox.textContent = "Double-click to edit text"; + textBox.style.position = "absolute"; + textBox.style.left = "50px"; + textBox.style.top = "50px"; + textBox.style.width = "150px"; + textBox.style.height = "40px"; + textBox.style.minWidth = "100px"; + textBox.style.minHeight = "30px"; + textBox.style.padding = "8px"; + textBox.style.border = "2px solid #ddd"; + textBox.style.borderRadius = "4px"; + textBox.style.backgroundColor = "white"; + textBox.style.cursor = "text"; + textBox.style.zIndex = "1000"; + textBox.style.fontSize = "14px"; + textBox.style.fontFamily = "Arial, sans-serif"; + textBox.style.color = "#333"; + textBox.style.boxSizing = "border-box"; + textBox.style.outline = "none"; + + // Handle Enter key to keep text in place + textBox.addEventListener("keydown", (e) => { + if (e.key === "Enter") { + e.preventDefault(); + // Insert a line break instead of creating new element + document.execCommand("insertLineBreak", false); } + }); + + // Handle selection like Word/Google Docs + textBox.addEventListener("click", (e) => { + e.stopPropagation(); + this.selectElement(textBox); + }); + + // Make text box draggable + this.makeDraggable(textBox); + + // Make text box resizable + this.makeResizable(textBox); + + previewFrame.appendChild(textBox); + textBox.focus(); + + // Select the text for easy editing + const range = document.createRange(); + range.selectNodeContents(textBox); + const selection = window.getSelection(); + selection.removeAllRanges(); + selection.addRange(range); } + } + insertImage() { + // Create file input for local image upload + const fileInput = document.createElement("input"); + fileInput.type = "file"; + fileInput.accept = "image/*"; + fileInput.style.display = "none"; - // Helper method to make elements draggable - makeDraggable(element) { - let isDragging = false; - let currentX; - let currentY; - let initialX; - let initialY; - let xOffset = 0; - let yOffset = 0; + fileInput.onchange = (event) => { + const file = event.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (e) => { + const previewFrame = this.template.querySelector( + ".enhanced-editor-content" + ); + if (previewFrame) { + // Create draggable and resizable image container + const imageContainer = document.createElement("div"); + imageContainer.className = "draggable-image-container"; + imageContainer.style.position = "absolute"; + imageContainer.style.left = "50px"; + imageContainer.style.top = "50px"; + imageContainer.style.width = "300px"; + imageContainer.style.height = "200px"; + imageContainer.style.cursor = "move"; + imageContainer.style.zIndex = "1000"; + imageContainer.style.border = "2px dashed #667eea"; + imageContainer.style.borderRadius = "4px"; + imageContainer.style.overflow = "hidden"; - element.addEventListener('mousedown', (e) => { - // Only start dragging if clicking on the element itself (not on text inside) - if (e.target === element || (element.classList.contains('draggable-text-box') && e.target.parentNode === element)) { - initialX = e.clientX - xOffset; - initialY = e.clientY - yOffset; - isDragging = true; - element.style.cursor = 'grabbing'; - } - }); + const img = document.createElement("img"); + img.src = e.target.result; + img.alt = "Inserted Image"; + img.style.width = "100%"; + img.style.height = "100%"; + img.style.objectFit = "cover"; + img.style.borderRadius = "4px"; + img.style.boxShadow = "0 2px 8px rgba(0,0,0,0.1)"; - document.addEventListener('mousemove', (e) => { - if (isDragging) { - e.preventDefault(); - currentX = e.clientX - initialX; - currentY = e.clientY - initialY; - xOffset = currentX; - yOffset = currentY; - - element.style.left = currentX + 'px'; - element.style.top = currentY + 'px'; - } - }); - - document.addEventListener('mouseup', () => { - if (isDragging) { - isDragging = false; - element.style.cursor = element.classList.contains('draggable-text-box') ? 'text' : 'move'; - } - }); - } - - // Helper method to make elements resizable - makeResizable(element) { - const resizer = document.createElement('div'); - resizer.className = 'resizer'; - resizer.style.position = 'absolute'; - resizer.style.width = '10px'; - resizer.style.height = '10px'; - resizer.style.background = '#667eea'; - resizer.style.borderRadius = '50%'; - resizer.style.bottom = '-5px'; - resizer.style.right = '-5px'; - resizer.style.cursor = 'se-resize'; - resizer.style.zIndex = '1001'; - - element.appendChild(resizer); - - let isResizing = false; - let startWidth, startHeight, startX, startY; - - resizer.addEventListener('mousedown', (e) => { - isResizing = true; - startX = e.clientX; - startY = e.clientY; - startWidth = parseInt(element.style.width) || element.offsetWidth; - startHeight = parseInt(element.style.height) || element.offsetHeight; - e.stopPropagation(); - }); - - document.addEventListener('mousemove', (e) => { - if (isResizing) { - const newWidth = startWidth + (e.clientX - startX); - const newHeight = startHeight + (e.clientY - startY); - - if (newWidth > 50) element.style.width = newWidth + 'px'; - if (newHeight > 30) element.style.height = newHeight + 'px'; - } - }); - - document.addEventListener('mouseup', () => { - isResizing = false; - }); - } - - insertText() { - const previewFrame = this.template.querySelector('.enhanced-editor-content'); - if (previewFrame) { - // Create draggable and resizable text box - const textBox = document.createElement('div'); - textBox.className = 'draggable-text-box'; - textBox.contentEditable = true; - textBox.textContent = 'Double-click to edit text'; - textBox.style.position = 'absolute'; - textBox.style.left = '50px'; - textBox.style.top = '50px'; - textBox.style.width = '150px'; - textBox.style.height = '40px'; - textBox.style.minWidth = '100px'; - textBox.style.minHeight = '30px'; - textBox.style.padding = '8px'; - textBox.style.border = '2px solid #ddd'; - textBox.style.borderRadius = '4px'; - textBox.style.backgroundColor = 'white'; - textBox.style.cursor = 'text'; - textBox.style.zIndex = '1000'; - textBox.style.fontSize = '14px'; - textBox.style.fontFamily = 'Arial, sans-serif'; - textBox.style.color = '#333'; - textBox.style.boxSizing = 'border-box'; - textBox.style.outline = 'none'; - - // Handle Enter key to keep text in place - textBox.addEventListener('keydown', (e) => { - if (e.key === 'Enter') { - e.preventDefault(); - // Insert a line break instead of creating new element - document.execCommand('insertLineBreak', false); - } - }); - // Handle selection like Word/Google Docs - textBox.addEventListener('click', (e) => { - e.stopPropagation(); - this.selectElement(textBox); + imageContainer.addEventListener("click", (e) => { + e.stopPropagation(); + this.selectElement(imageContainer); }); - - // Make text box draggable - this.makeDraggable(textBox); - - // Make text box resizable - this.makeResizable(textBox); - - previewFrame.appendChild(textBox); - textBox.focus(); - - // Select the text for easy editing - const range = document.createRange(); - range.selectNodeContents(textBox); - const selection = window.getSelection(); - selection.removeAllRanges(); - selection.addRange(range); - } - } - insertImage() { - // Create file input for local image upload - const fileInput = document.createElement('input'); - fileInput.type = 'file'; - fileInput.accept = 'image/*'; - fileInput.style.display = 'none'; - - fileInput.onchange = (event) => { - const file = event.target.files[0]; - if (file) { - const reader = new FileReader(); - reader.onload = (e) => { - const previewFrame = this.template.querySelector('.enhanced-editor-content'); - if (previewFrame) { - // Create draggable and resizable image container - const imageContainer = document.createElement('div'); - imageContainer.className = 'draggable-image-container'; - imageContainer.style.position = 'absolute'; - imageContainer.style.left = '50px'; - imageContainer.style.top = '50px'; - imageContainer.style.width = '300px'; - imageContainer.style.height = '200px'; - imageContainer.style.cursor = 'move'; - imageContainer.style.zIndex = '1000'; - imageContainer.style.border = '2px dashed #667eea'; - imageContainer.style.borderRadius = '4px'; - imageContainer.style.overflow = 'hidden'; - - const img = document.createElement('img'); - img.src = e.target.result; - img.alt = 'Inserted Image'; - img.style.width = '100%'; - img.style.height = '100%'; - img.style.objectFit = 'cover'; - img.style.borderRadius = '4px'; - img.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)'; - - // Handle selection like Word/Google Docs - imageContainer.addEventListener('click', (e) => { - e.stopPropagation(); - this.selectElement(imageContainer); - }); - - // Add delete button (only visible when selected) - const deleteBtn = document.createElement('button'); - deleteBtn.className = 'delete-btn'; - deleteBtn.innerHTML = '×'; - deleteBtn.style.position = 'absolute'; - deleteBtn.style.top = '-10px'; - deleteBtn.style.right = '-10px'; - deleteBtn.style.width = '20px'; - deleteBtn.style.height = '20px'; - deleteBtn.style.borderRadius = '50%'; - deleteBtn.style.background = '#ff4757'; - deleteBtn.style.color = 'white'; - deleteBtn.style.border = 'none'; - deleteBtn.style.cursor = 'pointer'; - deleteBtn.style.fontSize = '16px'; - deleteBtn.style.fontWeight = 'bold'; - deleteBtn.style.zIndex = '1002'; - deleteBtn.style.opacity = '0'; - deleteBtn.style.transition = 'opacity 0.2s ease'; - - deleteBtn.onclick = (e) => { - e.stopPropagation(); - imageContainer.remove(); - }; - - imageContainer.appendChild(deleteBtn); - - // Make image container draggable - this.makeDraggable(imageContainer); - - // Make image container resizable - this.makeResizable(imageContainer); - - imageContainer.appendChild(img); - previewFrame.appendChild(imageContainer); - } - }; - reader.readAsDataURL(file); - } + + // Add delete button (only visible when selected) + const deleteBtn = document.createElement("button"); + deleteBtn.className = "delete-btn"; + deleteBtn.innerHTML = "×"; + deleteBtn.style.position = "absolute"; + deleteBtn.style.top = "-10px"; + deleteBtn.style.right = "-10px"; + deleteBtn.style.width = "20px"; + deleteBtn.style.height = "20px"; + deleteBtn.style.borderRadius = "50%"; + deleteBtn.style.background = "#ff4757"; + deleteBtn.style.color = "white"; + deleteBtn.style.border = "none"; + deleteBtn.style.cursor = "pointer"; + deleteBtn.style.fontSize = "16px"; + deleteBtn.style.fontWeight = "bold"; + deleteBtn.style.zIndex = "1002"; + deleteBtn.style.opacity = "0"; + deleteBtn.style.transition = "opacity 0.2s ease"; + + deleteBtn.onclick = (e) => { + e.stopPropagation(); + imageContainer.remove(); + }; + + imageContainer.appendChild(deleteBtn); + + // Make image container draggable + this.makeDraggable(imageContainer); + + // Make image container resizable + this.makeResizable(imageContainer); + + imageContainer.appendChild(img); + previewFrame.appendChild(imageContainer); + } }; - - // Trigger file selection - document.body.appendChild(fileInput); - fileInput.click(); - document.body.removeChild(fileInput); - } + reader.readAsDataURL(file); + } + }; - // Helper method to duplicate an image - duplicateImage(originalContainer) { - const previewFrame = this.template.querySelector('.enhanced-editor-content'); - if (previewFrame) { - const newContainer = originalContainer.cloneNode(true); - newContainer.style.left = (parseInt(originalContainer.style.left) + 20) + 'px'; - newContainer.style.top = (parseInt(originalContainer.style.top) + 20) + 'px'; - newContainer.style.zIndex = parseInt(originalContainer.style.zIndex) + 1; - - // Reattach event listeners - this.makeDraggable(newContainer); - this.makeResizable(newContainer); - - // Update control panel event listeners - const controlPanel = newContainer.querySelector('.image-control-panel'); - if (controlPanel) { - controlPanel.addEventListener('mouseenter', () => { - controlPanel.style.opacity = '1'; - }); - - controlPanel.addEventListener('mouseleave', () => { - controlPanel.style.opacity = '0'; - }); - } - - previewFrame.appendChild(newContainer); - this.showSuccess('Image duplicated successfully!'); - } - } + // Trigger file selection + document.body.appendChild(fileInput); + fileInput.click(); + document.body.removeChild(fileInput); + } + // Helper method to duplicate an image + duplicateImage(originalContainer) { + const previewFrame = this.template.querySelector( + ".enhanced-editor-content" + ); + if (previewFrame) { + const newContainer = originalContainer.cloneNode(true); + newContainer.style.left = + parseInt(originalContainer.style.left) + 20 + "px"; + newContainer.style.top = + parseInt(originalContainer.style.top) + 20 + "px"; + newContainer.style.zIndex = parseInt(originalContainer.style.zIndex) + 1; + // Reattach event listeners + this.makeDraggable(newContainer); + this.makeResizable(newContainer); - - // Select element like Word/Google Docs - selectElement(element) { - // Remove selection from all other elements - const allElements = this.template.querySelectorAll('.draggable-text-box, .draggable-image-container'); - allElements.forEach(el => { - el.classList.remove('selected'); - el.style.border = el.classList.contains('draggable-text-box') ? '2px solid #ddd' : '2px solid #ddd'; - - // Hide delete buttons - const deleteBtn = el.querySelector('.delete-btn'); - if (deleteBtn) { - deleteBtn.style.opacity = '0'; - } + // Update control panel event listeners + const controlPanel = newContainer.querySelector(".image-control-panel"); + if (controlPanel) { + controlPanel.addEventListener("mouseenter", () => { + controlPanel.style.opacity = "1"; }); - - // Select current element - element.classList.add('selected'); - element.style.border = '2px solid #667eea'; - - // Show delete button - const deleteBtn = element.querySelector('.delete-btn'); - if (deleteBtn) { - deleteBtn.style.opacity = '1'; - } - - // Show selection handles - this.showSelectionHandles(element); - } - // Show selection handles like Word/Google Docs - showSelectionHandles(element) { - // Remove existing handles - const existingHandles = element.querySelectorAll('.selection-handle'); - existingHandles.forEach(handle => handle.remove()); - - // Create selection handles - const handles = [ - { position: 'top-left', cursor: 'nw-resize' }, - { position: 'top-right', cursor: 'ne-resize' }, - { position: 'bottom-left', cursor: 'sw-resize' }, - { position: 'bottom-right', cursor: 'se-resize' } - ]; - - handles.forEach(handle => { - const handleElement = document.createElement('div'); - handleElement.className = 'selection-handle'; - handleElement.style.position = 'absolute'; - handleElement.style.width = '8px'; - handleElement.style.height = '8px'; - handleElement.style.background = '#667eea'; - handleElement.style.border = '1px solid white'; - handleElement.style.borderRadius = '50%'; - handleElement.style.cursor = handle.cursor; - handleElement.style.zIndex = '1003'; - - // Position handles - switch(handle.position) { - case 'top-left': - handleElement.style.top = '-4px'; - handleElement.style.left = '-4px'; - break; - case 'top-right': - handleElement.style.top = '-4px'; - handleElement.style.right = '-4px'; - break; - case 'bottom-left': - handleElement.style.bottom = '-4px'; - handleElement.style.left = '-4px'; - break; - case 'bottom-right': - handleElement.style.bottom = '-4px'; - handleElement.style.right = '-4px'; - break; - } - - element.appendChild(handleElement); + controlPanel.addEventListener("mouseleave", () => { + controlPanel.style.opacity = "0"; }); + } + + previewFrame.appendChild(newContainer); + this.showSuccess("Image duplicated successfully!"); } - addShape() { + } + + // Select element like Word/Google Docs + selectElement(element) { + // Remove selection from all other elements + const allElements = this.template.querySelectorAll( + ".draggable-text-box, .draggable-image-container" + ); + allElements.forEach((el) => { + el.classList.remove("selected"); + el.style.border = el.classList.contains("draggable-text-box") + ? "2px solid #ddd" + : "2px solid #ddd"; + + // Hide delete buttons + const deleteBtn = el.querySelector(".delete-btn"); + if (deleteBtn) { + deleteBtn.style.opacity = "0"; + } + }); + + // Select current element + element.classList.add("selected"); + element.style.border = "2px solid #667eea"; + + // Show delete button + const deleteBtn = element.querySelector(".delete-btn"); + if (deleteBtn) { + deleteBtn.style.opacity = "1"; } + // Show selection handles + this.showSelectionHandles(element); + } - // Helper method to build amenities list dynamically - buildAmenitiesList(data) { - let amenitiesList = ''; - - // First priority: Use amenities array if available - if (data.amenities && Array.isArray(data.amenities) && data.amenities.length > 0) { - amenitiesList = data.amenities.map(amenity => - `
  • ${amenity}
  • ` - ).join(''); - } - // Second priority: Use individual amenity fields if available - else if (data.amenity1 || data.amenity2 || data.amenity3 || data.amenity4 || data.amenity5 || - data.amenity6 || data.amenity7 || data.amenity8 || data.amenity9 || data.amenity10) { - - const individualAmenities = [ - data.amenity1, data.amenity2, data.amenity3, data.amenity4, data.amenity5, - data.amenity6, data.amenity7, data.amenity8, data.amenity9, data.amenity10 - ].filter(amenity => amenity && amenity.trim() !== ''); - - amenitiesList = individualAmenities.map(amenity => - `
  • ${amenity}
  • ` - ).join(''); - } - // Fallback: Use default luxury amenities - else { - amenitiesList = ` + // Show selection handles like Word/Google Docs + showSelectionHandles(element) { + // Remove existing handles + const existingHandles = element.querySelectorAll(".selection-handle"); + existingHandles.forEach((handle) => handle.remove()); + + // Create selection handles + const handles = [ + { position: "top-left", cursor: "nw-resize" }, + { position: "top-right", cursor: "ne-resize" }, + { position: "bottom-left", cursor: "sw-resize" }, + { position: "bottom-right", cursor: "se-resize" }, + ]; + + handles.forEach((handle) => { + const handleElement = document.createElement("div"); + handleElement.className = "selection-handle"; + handleElement.style.position = "absolute"; + handleElement.style.width = "8px"; + handleElement.style.height = "8px"; + handleElement.style.background = "#667eea"; + handleElement.style.border = "1px solid white"; + handleElement.style.borderRadius = "50%"; + handleElement.style.cursor = handle.cursor; + handleElement.style.zIndex = "1003"; + + // Position handles + switch (handle.position) { + case "top-left": + handleElement.style.top = "-4px"; + handleElement.style.left = "-4px"; + break; + case "top-right": + handleElement.style.top = "-4px"; + handleElement.style.right = "-4px"; + break; + case "bottom-left": + handleElement.style.bottom = "-4px"; + handleElement.style.left = "-4px"; + break; + case "bottom-right": + handleElement.style.bottom = "-4px"; + handleElement.style.right = "-4px"; + break; + } + + element.appendChild(handleElement); + }); + } + addShape() {} + + // Helper method to build amenities list dynamically + buildAmenitiesList(data) { + let amenitiesList = ""; + + // First priority: Use amenities array if available + if ( + data.amenities && + Array.isArray(data.amenities) && + data.amenities.length > 0 + ) { + amenitiesList = data.amenities + .map( + (amenity) => `
  • ${amenity}
  • ` + ) + .join(""); + } + // Second priority: Use individual amenity fields if available + else if ( + data.amenity1 || + data.amenity2 || + data.amenity3 || + data.amenity4 || + data.amenity5 || + data.amenity6 || + data.amenity7 || + data.amenity8 || + data.amenity9 || + data.amenity10 + ) { + const individualAmenities = [ + data.amenity1, + data.amenity2, + data.amenity3, + data.amenity4, + data.amenity5, + data.amenity6, + data.amenity7, + data.amenity8, + data.amenity9, + data.amenity10, + ].filter((amenity) => amenity && amenity.trim() !== ""); + + amenitiesList = individualAmenities + .map( + (amenity) => `
  • ${amenity}
  • ` + ) + .join(""); + } + // Fallback: Use default luxury amenities + else { + amenitiesList = `
  • Primary Suite with Spa-Bath
  • Radiant Heated Flooring
  • Custom Walk-in Closets
  • @@ -6251,37 +7049,63 @@ export default class PropertyTemplateSelector extends LightningElement {
  • Gourmet Chef's Kitchen
  • Floor-to-Ceiling Glass Walls
  • `; - } - - return amenitiesList; } - // Helper method to build amenities list for THE VERTICE template - buildAmenitiesListForVertice(data) { - let amenitiesList = ''; - - // First priority: Use amenities array if available - if (data.amenities && Array.isArray(data.amenities) && data.amenities.length > 0) { - amenitiesList = data.amenities.map(amenity => - `
  • ${amenity}
  • ` - ).join(''); - } - // Second priority: Use individual amenity fields if available - else if (data.amenity1 || data.amenity2 || data.amenity3 || data.amenity4 || data.amenity5 || - data.amenity6 || data.amenity7 || data.amenity8 || data.amenity9 || data.amenity10) { - - const individualAmenities = [ - data.amenity1, data.amenity2, data.amenity3, data.amenity4, data.amenity5, - data.amenity6, data.amenity7, data.amenity8, data.amenity9, data.amenity10 - ].filter(amenity => amenity && amenity.trim() !== ''); - - amenitiesList = individualAmenities.map(amenity => - `
  • ${amenity}
  • ` - ).join(''); - } - // Fallback: Use default luxury amenities - else { - amenitiesList = ` + return amenitiesList; + } + // Helper method to build amenities list for THE VERTICE template + buildAmenitiesListForVertice(data) { + let amenitiesList = ""; + + // First priority: Use amenities array if available + if ( + data.amenities && + Array.isArray(data.amenities) && + data.amenities.length > 0 + ) { + amenitiesList = data.amenities + .map( + (amenity) => + `
  • ${amenity}
  • ` + ) + .join(""); + } + // Second priority: Use individual amenity fields if available + else if ( + data.amenity1 || + data.amenity2 || + data.amenity3 || + data.amenity4 || + data.amenity5 || + data.amenity6 || + data.amenity7 || + data.amenity8 || + data.amenity9 || + data.amenity10 + ) { + const individualAmenities = [ + data.amenity1, + data.amenity2, + data.amenity3, + data.amenity4, + data.amenity5, + data.amenity6, + data.amenity7, + data.amenity8, + data.amenity9, + data.amenity10, + ].filter((amenity) => amenity && amenity.trim() !== ""); + + amenitiesList = individualAmenities + .map( + (amenity) => + `
  • ${amenity}
  • ` + ) + .join(""); + } + // Fallback: Use default luxury amenities + else { + amenitiesList = `
  • Rooftop Infinity Pool
  • Fitness Center
  • Residents' Sky Lounge
  • @@ -6291,1004 +7115,1093 @@ export default class PropertyTemplateSelector extends LightningElement {
  • 24/7 Concierge
  • Secure Parking
  • `; - } - - return amenitiesList; } - // Image Review Methods - openImageReview() { - this.showImageReview = true; - // Auto-select category will be handled in loadPropertyImages // Default to Interior category + return amenitiesList; + } + + // Image Review Methods + openImageReview() { + this.showImageReview = true; + // Auto-select category will be handled in loadPropertyImages // Default to Interior category + } + + closeImageReview() { + this.showImageReview = false; + this.currentImageIndex = 0; + this.currentImage = null; + } + + selectCategory(event) { + let category; + + // Handle both event and direct category parameter + if (typeof event === "string") { + category = event; + } else if (event && event.currentTarget && event.currentTarget.dataset) { + category = event.currentTarget.dataset.category; + + // Update active category button + this.template.querySelectorAll(".category-btn-step2").forEach((btn) => { + btn.classList.remove("active"); + }); + event.currentTarget.classList.add("active"); + } else { + return; } - closeImageReview() { - this.showImageReview = false; - this.currentImageIndex = 0; - this.currentImage = null; + this.selectedCategory = category; + + // Filter real property images by category + this.filterImagesByCategory(category); + } + + // Add new method to show all images (no filtering) + filterImagesByCategory(category) { + if (!this.realPropertyImages || this.realPropertyImages.length === 0) { + this.currentImage = null; + this.totalImages = 0; + this.currentImageIndex = 0; + return; } - selectCategory(event) { - let category; - - // Handle both event and direct category parameter - if (typeof event === 'string') { - category = event; - } else if (event && event.currentTarget && event.currentTarget.dataset) { - category = event.currentTarget.dataset.category; - - // Update active category button - this.template.querySelectorAll('.category-btn-step2').forEach(btn => { - btn.classList.remove('active'); - }); - event.currentTarget.classList.add('active'); - } else { - return; - } - - - this.selectedCategory = category; - - // Filter real property images by category - this.filterImagesByCategory(category); + // Show all images instead of filtering by category + this.propertyImages = this.realPropertyImages; + this.totalImages = this.realPropertyImages.length; + + if (this.realPropertyImages.length > 0) { + this.currentImage = this.realPropertyImages[0]; + this.currentImageIndex = 0; + } else { + this.currentImage = null; + this.totalImages = 0; + this.currentImageIndex = 0; + } + } + + getImagesForCategory(category) { + // First try to get real images from Salesforce + if (this.realPropertyImages && this.realPropertyImages.length > 0) { + // Filter images by category + const categoryImages = this.realPropertyImages + .filter((img) => { + // Handle case-insensitive matching and variations + const imgCategory = img.category ? img.category.toLowerCase() : ""; + const searchCategory = category.toLowerCase(); + + // Direct match + if (imgCategory === searchCategory) { + return true; + } + + // Category mapping for common variations + const categoryMappings = { + interior: ["interior", "inside", "indoor"], + exterior: ["exterior", "outside", "outdoor", "facade"], + kitchen: ["kitchen", "dining"], + bedroom: ["bedroom", "bed", "room"], + "living area": ["living", "lounge", "sitting"], + parking: ["parking", "garage"], + anchor: ["anchor", "main", "hero"], + maps: ["map", "location", "area"], + }; + + const mappings = categoryMappings[searchCategory] || [searchCategory]; + return mappings.some((mapping) => imgCategory.includes(mapping)); + }) + .map((img) => ({ + url: img.url || `/servlet/FileDownload?file=${img.id}`, + id: img.id, + title: img.name || `${category} Image`, + category: category, + })); + + if (categoryImages.length > 0) { + return categoryImages; + } } - // Add new method to show all images (no filtering) - filterImagesByCategory(category) { - - if (!this.realPropertyImages || this.realPropertyImages.length === 0) { - this.currentImage = null; - this.totalImages = 0; - this.currentImageIndex = 0; - return; - } - - // Show all images instead of filtering by category - this.propertyImages = this.realPropertyImages; - this.totalImages = this.realPropertyImages.length; - - if (this.realPropertyImages.length > 0) { - this.currentImage = this.realPropertyImages[0]; - this.currentImageIndex = 0; - } else { - this.currentImage = null; - this.totalImages = 0; - this.currentImageIndex = 0; - } + // Get images based on the selected template and property + if (!this.selectedTemplateId || !this.propertyData) { + return []; } - getImagesForCategory(category) { - // First try to get real images from Salesforce - if (this.realPropertyImages && this.realPropertyImages.length > 0) { - // Filter images by category - const categoryImages = this.realPropertyImages.filter(img => { - // Handle case-insensitive matching and variations - const imgCategory = img.category ? img.category.toLowerCase() : ''; - const searchCategory = category.toLowerCase(); - - // Direct match - if (imgCategory === searchCategory) { - return true; - } - - // Category mapping for common variations - const categoryMappings = { - 'interior': ['interior', 'inside', 'indoor'], - 'exterior': ['exterior', 'outside', 'outdoor', 'facade'], - 'kitchen': ['kitchen', 'dining'], - 'bedroom': ['bedroom', 'bed', 'room'], - 'living area': ['living', 'lounge', 'sitting'], - 'parking': ['parking', 'garage'], - 'anchor': ['anchor', 'main', 'hero'], - 'maps': ['map', 'location', 'area'] - }; - - const mappings = categoryMappings[searchCategory] || [searchCategory]; - return mappings.some(mapping => imgCategory.includes(mapping)); - }).map(img => ({ - url: img.url || `/servlet/FileDownload?file=${img.id}`, - id: img.id, - title: img.name || `${category} Image`, - category: category - })); - - if (categoryImages.length > 0) { - return categoryImages; - } - } - - // Get images based on the selected template and property - if (!this.selectedTemplateId || !this.propertyData) { - return []; - } - - // Template-specific image mapping - const templateImages = this.getTemplateSpecificImages(category); - if (templateImages && templateImages.length > 0) { - return templateImages; - } - // No images found - return []; + // Template-specific image mapping + const templateImages = this.getTemplateSpecificImages(category); + if (templateImages && templateImages.length > 0) { + return templateImages; } + // No images found + return []; + } + getTemplateSpecificImages(category) { + const templateId = this.selectedTemplateId; + const propertyData = this.propertyData; - getTemplateSpecificImages(category) { - const templateId = this.selectedTemplateId; - const propertyData = this.propertyData; + // Map category names to property fields + const categoryFieldMap = { + Interior: ["interiorImage1", "interiorImage2", "interiorImage3"], + Exterior: ["exteriorImage1", "exteriorImage2", "exteriorImage3"], + Kitchen: ["kitchenImage1", "kitchenImage2", "kitchenImage3"], + Bedroom: ["bedroomImage1", "bedroomImage2", "bedroomImage3"], + "Living Area": [ + "livingAreaImage1", + "livingAreaImage2", + "livingAreaImage3", + ], + Parking: ["parkingImage1", "parkingImage2"], + Anchor: ["anchorImage1", "anchorImage2"], + Maps: ["mapImage1", "mapImage2"], + }; - // Map category names to property fields - const categoryFieldMap = { - 'Interior': ['interiorImage1', 'interiorImage2', 'interiorImage3'], - 'Exterior': ['exteriorImage1', 'exteriorImage2', 'exteriorImage3'], - 'Kitchen': ['kitchenImage1', 'kitchenImage2', 'kitchenImage3'], - 'Bedroom': ['bedroomImage1', 'bedroomImage2', 'bedroomImage3'], - 'Living Area': ['livingAreaImage1', 'livingAreaImage2', 'livingAreaImage3'], - 'Parking': ['parkingImage1', 'parkingImage2'], - 'Anchor': ['anchorImage1', 'anchorImage2'], - 'Maps': ['mapImage1', 'mapImage2'] - }; + const fields = categoryFieldMap[category] || []; + const images = []; - const fields = categoryFieldMap[category] || []; - const images = []; - - // Check if property has images for this category - fields.forEach(field => { - if (propertyData[field] && propertyData[field].trim() !== '') { - images.push({ - url: propertyData[field], - title: `${category} - ${field.replace('Image', ' View ')}`, - category: category - }); - } + // Check if property has images for this category + fields.forEach((field) => { + if (propertyData[field] && propertyData[field].trim() !== "") { + images.push({ + url: propertyData[field], + title: `${category} - ${field.replace("Image", " View ")}`, + category: category, }); + } + }); - return images; + return images; + } + + generateImagesFromPropertyData(category, propertyData) { + const images = []; + + // Generate placeholder images based on property type and category + const propertyType = propertyData.propertyType || "Property"; + const location = propertyData.city || propertyData.community || "Location"; + + // Create sample images based on category and property data + const sampleImages = { + Interior: [ + "https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200", + "https://images.unsplash.com/photo-1618221195710-dd6b41faaea6?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=800", + ], + Exterior: [ + "https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200", + "https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200", + ], + Kitchen: [ + "https://images.unsplash.com/photo-1556909114-f6e7ad7d3136?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200", + ], + Bedroom: [ + "https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200", + ], + "Living Area": [ + "https://images.unsplash.com/photo-1618221195710-dd6b41faaea6?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=800", + ], + Parking: [ + "https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200", + ], + Anchor: [ + "https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200", + ], + Maps: [ + "https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200", + ], + }; + + const urls = sampleImages[category] || []; + urls.forEach((url, index) => { + images.push({ + url: url, + title: `${propertyType} - ${category} View ${index + 1}`, + category: category, + }); + }); + + return images; + } + + nextImage() { + if (this.currentImageIndex < this.totalImages - 1) { + this.currentImageIndex++; + this.updateCurrentImage(); + } else { + } + } + + previousImage() { + if (this.currentImageIndex > 0) { + this.currentImageIndex--; + this.updateCurrentImage(); + } else { + } + } + + // Add new method to update current image + updateCurrentImage() { + if (!this.realPropertyImages || this.realPropertyImages.length === 0) { + return; } - generateImagesFromPropertyData(category, propertyData) { - const images = []; - - // Generate placeholder images based on property type and category - const propertyType = propertyData.propertyType || 'Property'; - const location = propertyData.city || propertyData.community || 'Location'; - - // Create sample images based on category and property data - const sampleImages = { - 'Interior': [ - 'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200', - 'https://images.unsplash.com/photo-1618221195710-dd6b41faaea6?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=800' - ], - 'Exterior': [ - 'https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200', - 'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200' - ], - 'Kitchen': [ - 'https://images.unsplash.com/photo-1556909114-f6e7ad7d3136?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200' - ], - 'Bedroom': [ - 'https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200' - ], - 'Living Area': [ - 'https://images.unsplash.com/photo-1618221195710-dd6b41faaea6?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=800' - ], - 'Parking': [ - 'https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200' - ], - 'Anchor': [ - 'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200' - ], - 'Maps': [ - 'https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200' - ] + // Use all images instead of filtering by category + if ( + this.realPropertyImages.length > 0 && + this.currentImageIndex < this.realPropertyImages.length + ) { + this.currentImage = this.realPropertyImages[this.currentImageIndex]; + // Revert: only enable drag & drop; no auto-wrap on click + const imgEl = this.template.querySelector( + ".property-image-step2, .review-image" + ); + if (imgEl) { + imgEl.setAttribute("draggable", "true"); + imgEl.addEventListener( + "dragstart", + this.handleImageDragStart.bind(this) + ); + imgEl.style.cursor = "zoom-in"; + imgEl.onclick = () => { + const w = window.open(); + if (w && w.document) { + w.document.write( + `` + ); + } }; + } + } + } - const urls = sampleImages[category] || []; - urls.forEach((url, index) => { - images.push({ - url: url, - title: `${propertyType} - ${category} View ${index + 1}`, - category: category - }); + // Ensure editor is always editable + ensureEditorEditable() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (editor) { + editor.setAttribute("contenteditable", "true"); + editor.style.userSelect = "text"; + editor.style.webkitUserSelect = "text"; + editor.style.cursor = "text"; + + // Remove any potential pointer-events restrictions + editor.style.pointerEvents = "auto"; + + // Add event listeners to ensure editing works + if (!editor.hasEditListeners) { + editor.addEventListener("input", this.handleContentChange.bind(this)); + editor.addEventListener("keydown", (e) => { + // Handle undo/redo and other special keys + this.handleEditorKeydown(e); + // Allow all key presses for editing + e.stopPropagation(); }); - - return images; - } - - nextImage() { - if (this.currentImageIndex < this.totalImages - 1) { - this.currentImageIndex++; - this.updateCurrentImage(); - } else { - } - } - - previousImage() { - if (this.currentImageIndex > 0) { - this.currentImageIndex--; - this.updateCurrentImage(); - } else { - } - } - - // Add new method to update current image - updateCurrentImage() { - if (!this.realPropertyImages || this.realPropertyImages.length === 0) { - return; - } - - // Use all images instead of filtering by category - if (this.realPropertyImages.length > 0 && this.currentImageIndex < this.realPropertyImages.length) { - this.currentImage = this.realPropertyImages[this.currentImageIndex]; - // Revert: only enable drag & drop; no auto-wrap on click - const imgEl = this.template.querySelector('.property-image-step2, .review-image'); - if (imgEl) { - imgEl.setAttribute('draggable', 'true'); - imgEl.addEventListener('dragstart', this.handleImageDragStart.bind(this)); - imgEl.style.cursor = 'zoom-in'; - imgEl.onclick = () => { - const w = window.open(); - if (w && w.document) { - w.document.write(``); - } - }; + editor.addEventListener("keyup", this.handleContentChange.bind(this)); + editor.addEventListener("paste", this.handleContentChange.bind(this)); + // NEW: single-click any image to show resize controls + editor.addEventListener( + "click", + (e) => { + const target = e.target; + if ( + target && + target.tagName && + target.tagName.toLowerCase() === "img" + ) { + e.preventDefault(); + e.stopPropagation(); + this.selectDraggableElement(target); } - } - } - - // Ensure editor is always editable - ensureEditorEditable() { - const editor = this.template.querySelector('.enhanced-editor-content'); - if (editor) { - editor.setAttribute('contenteditable', 'true'); - editor.style.userSelect = 'text'; - editor.style.webkitUserSelect = 'text'; - editor.style.cursor = 'text'; - - // Remove any potential pointer-events restrictions - editor.style.pointerEvents = 'auto'; - - // Add event listeners to ensure editing works - if (!editor.hasEditListeners) { - editor.addEventListener('input', this.handleContentChange.bind(this)); - editor.addEventListener('keydown', (e) => { - // Handle undo/redo and other special keys - this.handleEditorKeydown(e); - // Allow all key presses for editing - e.stopPropagation(); - }); - editor.addEventListener('keyup', this.handleContentChange.bind(this)); - editor.addEventListener('paste', this.handleContentChange.bind(this)); - // NEW: single-click any image to show resize controls - editor.addEventListener('click', (e) => { - const target = e.target; - if (target && target.tagName && target.tagName.toLowerCase() === 'img') { - e.preventDefault(); - e.stopPropagation(); - this.selectDraggableElement(target); - } - }, true); - - editor.hasEditListeners = true; - } - - } - } - - // Connected callback to initialize - connectedCallback() { - - // Ensure editor is editable after component loads - setTimeout(() => { - this.ensureEditorEditable(); - }, 1000); + }, + true + ); - // Keyboard shortcuts for Word-like experience - this._keyHandler = (e) => { - if (this.currentStep !== 3) return; - const isMac = navigator.platform.toUpperCase().indexOf('MAC')>=0; - const mod = isMac ? e.metaKey : e.ctrlKey; - if (!mod) return; - switch (e.key.toLowerCase()) { - case 'b': e.preventDefault(); this.handleBold(); break; - case 'i': e.preventDefault(); this.handleItalic(); break; - case 'u': e.preventDefault(); this.handleUnderline(); break; - case 'z': e.preventDefault(); this.undo(); break; - case 'y': e.preventDefault(); this.redo(); break; - } + editor.hasEditListeners = true; + } + } + } + // Connected callback to initialize + connectedCallback() { + // Ensure editor is editable after component loads + setTimeout(() => { + this.ensureEditorEditable(); + }, 1000); + + // Keyboard shortcuts for Word-like experience + this._keyHandler = (e) => { + if (this.currentStep !== 3) return; + const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0; + const mod = isMac ? e.metaKey : e.ctrlKey; + if (!mod) return; + switch (e.key.toLowerCase()) { + case "b": + e.preventDefault(); + this.handleBold(); + break; + case "i": + e.preventDefault(); + this.handleItalic(); + break; + case "u": + e.preventDefault(); + this.handleUnderline(); + break; + case "z": + e.preventDefault(); + this.undo(); + break; + case "y": + e.preventDefault(); + this.redo(); + break; + } + }; + window.addEventListener("keydown", this._keyHandler); + + // Auto-fit when window resizes in Step 3 + this._resizeHandler = () => { + if (this.currentStep === 3 && this.fitToWidth) this.fitToWidth(); + }; + window.addEventListener("resize", this._resizeHandler); + } + // Called after template loads + renderedCallback() { + this.ensureEditorEditable(); + this.setupEditorClickHandler(); + this.addDeselectFunctionality(); + + // Save initial state for undo functionality + setTimeout(() => { + this.saveUndoState(); + }, 100); + // Ensure initial fit + if (this.currentStep === 3 && this.fitToWidth) { + setTimeout(() => this.fitToWidth(), 0); + } + } + + // Test editor functionality - can be called from toolbar + testEditor() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (editor) { + editor.focus(); + this.ensureEditorEditable(); + } + } + + // Helper method to determine if element is likely positioned over an image + isElementLikelyOverImage(element) { + if (!element || element.tagName === "IMG") return false; + + // Be much more restrictive - only trigger for elements that are clearly over images + const style = window.getComputedStyle(element); + + // Only check for images underneath if the element has strong indicators + const isTransparentText = this.isTransparentTextElement(element, style); + const isPositionedOverlay = this.isPositionedOverlay(element, style); + const hasImageParent = this.hasDirectImageParent(element); + + // Only return true if there are very specific indicators + return ( + (isTransparentText && hasImageParent) || + (isPositionedOverlay && this.checkBackgroundImages(element)) + ); + } + + isTransparentTextElement(element, style) { + // Text elements that are likely overlays + const textTags = [ + "P", + "SPAN", + "H1", + "H2", + "H3", + "H4", + "H5", + "H6", + "A", + "STRONG", + "EM", + "B", + "I", + "DIV", + ]; + const isTextElement = textTags.includes(element.tagName); + + // Check if background is transparent or semi-transparent + const bg = style.backgroundColor; + const isTransparent = + bg === "rgba(0, 0, 0, 0)" || + bg === "transparent" || + bg === "" || + (bg.includes("rgba") && + (bg.includes(", 0)") || + (bg.includes(", 0.") && parseFloat(bg.split(",")[3]) < 0.5))); + + return isTextElement && isTransparent; + } + + isPositionedOverlay(element, style) { + const isPositioned = ["absolute", "relative", "fixed"].includes( + style.position + ); + const hasLowOpacity = parseFloat(style.opacity) < 1; + const hasTransformOrZ = + style.transform !== "none" || parseInt(style.zIndex) > 0; + + return isPositioned && (hasLowOpacity || hasTransformOrZ); + } + + hasDirectImageParent(element) { + // Only check immediate parent and grandparent + let current = element.parentElement; + let depth = 0; + + while (current && depth < 2) { + if (current.querySelector("img")) return true; + current = current.parentElement; + depth++; + } + return false; + } + + checkBackgroundImages(element) { + let current = element; + let depth = 0; + + while (current && depth < 5) { + const style = window.getComputedStyle(current); + if (style.backgroundImage && style.backgroundImage !== "none") { + return true; + } + current = current.parentElement; + depth++; + } + return false; + } + + // Helper method to check if there are actual image indicators before expensive search + hasImageIndicators(clickedElement, x, y, editor) { + // Quick check: if the element or its parents have background images + let current = clickedElement; + let depth = 0; + + while (current && current !== editor && depth < 3) { + const style = window.getComputedStyle(current); + if (style.backgroundImage && style.backgroundImage !== "none") { + return true; + } + current = current.parentElement; + depth++; + } + + // Quick check: if there are any img elements in the nearby area + const allElementsAtPoint = document.elementsFromPoint + ? document.elementsFromPoint(x, y) + : []; + const hasDirectImage = allElementsAtPoint.some((el) => { + return ( + el.tagName === "IMG" || + (el.querySelector && el.querySelector("img")) || + window.getComputedStyle(el).backgroundImage !== "none" + ); + }); + + if (!hasDirectImage) { + // Final check: look for images in the clicked element's container + const container = clickedElement.closest("div, section, article"); + if (container && container !== editor) { + return container.querySelector("img") !== null; + } + } + + return hasDirectImage; + } + // Helper method to find images under click coordinates - including low z-index images + findImageUnderClick(x, y, editor) { + // Check for images directly at the click point + const allElementsAtPoint = document.elementsFromPoint + ? document.elementsFromPoint(x, y) + : []; + + for (const element of allElementsAtPoint) { + // Skip if it's part of the UI (toolbar, navigation, etc.) + if ( + element.closest(".editor-left") || + element.closest(".toolbar-section") || + element.closest(".step-navigation") || + element.closest(".page-controls") + ) { + continue; + } + + // Direct image - highest priority + if (element.tagName === "IMG") { + return element; + } + + // Check for contained images + const containedImg = element.querySelector("img"); + if (containedImg) { + return containedImg; + } + + // Check for background images + const style = window.getComputedStyle(element); + if (style.backgroundImage && style.backgroundImage !== "none") { + return { + src: style.backgroundImage.slice(5, -2), + element: element, + isBackgroundImage: true, }; - window.addEventListener('keydown', this._keyHandler); - - // Auto-fit when window resizes in Step 3 - this._resizeHandler = () => { if (this.currentStep === 3 && this.fitToWidth) this.fitToWidth(); }; - window.addEventListener('resize', this._resizeHandler); + } } - // Called after template loads - renderedCallback() { - this.ensureEditorEditable(); - this.setupEditorClickHandler(); - - // Save initial state for undo functionality - setTimeout(() => { - this.saveUndoState(); - }, 100); - // Ensure initial fit - if (this.currentStep === 3 && this.fitToWidth) { - setTimeout(() => this.fitToWidth(), 0); + + return null; + } + // Triple click handler for image replacement + handleImageClick(clickedImage, event) { + // Clear any existing timeout + if (this.clickTimeout) { + clearTimeout(this.clickTimeout); + } + + // Debug logging for image detection + const debugInfo = { + tagName: clickedImage.tagName, + isBackgroundImage: clickedImage.isBackgroundImage, + src: clickedImage.src, + backgroundImage: clickedImage.style.backgroundImage, + originalElement: clickedImage.originalElement, + }; + + // Check if this is the same image as the last click + const isSameImage = + this.lastClickedImage && + ((this.lastClickedImage.src && + clickedImage.src && + this.lastClickedImage.src === clickedImage.src) || + (this.lastClickedImage.isBackgroundImage && + clickedImage.isBackgroundImage && + this.lastClickedImage.style.backgroundImage === + clickedImage.style.backgroundImage)); + + if (isSameImage) { + // Same image clicked, increment counter + this.imageClickCount++; + } else { + // Different image clicked, reset counter + this.imageClickCount = 1; + this.lastClickedImage = clickedImage; + } + + // Set timeout to reset counter after 1 second + this.clickTimeout = setTimeout(() => { + this.imageClickCount = 0; + this.lastClickedImage = null; + }, 1000); + + // Check if we've reached exactly 3 clicks + if (this.imageClickCount === 3) { + event.preventDefault(); + event.stopPropagation(); + this.openImageReplacement(clickedImage); + + // Reset counter after opening popup + this.imageClickCount = 0; + this.lastClickedImage = null; + if (this.clickTimeout) { + clearTimeout(this.clickTimeout); + this.clickTimeout = null; + } + } else { + // Show feedback for clicks 1 and 2, but don't open popup + if (this.imageClickCount === 1) { + this.showSuccess("Click 2 more times on the same image to replace it"); + } else if (this.imageClickCount === 2) { + this.showSuccess("Click 1 more time on the same image to replace it"); + } + + // Prevent any default behavior for clicks 1 and 2 + event.preventDefault(); + event.stopPropagation(); + } + } + + // Image Replacement Methods + openImageReplacement(imageElement) { + if (!imageElement) { + return; + } + + this.selectedImageElement = imageElement; + this.showImageReplacement = true; + this.replacementActiveTab = "property"; + + // Use smart category selection like Step 2 + this.replacementSelectedCategory = this.findFirstAvailableCategory(); + + this.uploadedImagePreview = null; + this.filterReplacementImages(); + + // Update category button states after filtering + setTimeout(() => { + const categoryButtons = this.template.querySelectorAll( + ".category-btn-step2" + ); + categoryButtons.forEach((btn) => { + btn.classList.remove("active"); + if (btn.dataset.category === this.replacementSelectedCategory) { + btn.classList.add("active"); } + }); + }, 100); + // Prevent body scrolling + document.body.style.overflow = "hidden"; + + // Log the selected image details for debugging + if (imageElement.isBackgroundImage) { + } else if (imageElement.tagName === "IMG") { + } else { } - - // Test editor functionality - can be called from toolbar - testEditor() { - const editor = this.template.querySelector('.enhanced-editor-content'); - if (editor) { - editor.focus(); - this.ensureEditorEditable(); + } + + closeImageReplacement() { + this.showImageReplacement = false; + this.selectedImageElement = null; + this.uploadedImagePreview = null; + + // Clear click tracking + this.resetImageClickTracking(); + + // Restore body scrolling + document.body.style.overflow = ""; + } + + resetImageClickTracking() { + this.imageClickCount = 0; + this.lastClickedImage = null; + if (this.clickTimeout) { + clearTimeout(this.clickTimeout); + this.clickTimeout = null; + } + } + + selectPropertyImagesTab() { + this.replacementActiveTab = "property"; + this.filterReplacementImages(); + } + + selectLocalUploadTab() { + this.replacementActiveTab = "upload"; + this.uploadedImagePreview = null; + + // Force re-render to ensure the upload area is visible + this.forceRerender(); + + // Add a small delay to ensure DOM is updated + setTimeout(() => { + const uploadDropzone = this.template.querySelector(".upload-dropzone"); + if (uploadDropzone) { + } else { + } + }, 100); + } + selectReplacementCategory(event) { + const category = event.target.dataset.category; + + this.replacementSelectedCategory = category; + this.filterReplacementImages(); + + // Update active state for category buttons + const categoryButtons = this.template.querySelectorAll( + ".category-btn-step2" + ); + categoryButtons.forEach((btn) => { + btn.classList.remove("active"); + if (btn.dataset.category === this.replacementSelectedCategory) { + btn.classList.add("active"); + } + }); + } + + filterReplacementImages() { + if (!this.realPropertyImages || this.realPropertyImages.length === 0) { + this.filteredReplacementImages = []; + return; + } + + // Filter images by category using the same logic as Step 2 + const filteredImages = this.realPropertyImages.filter((img) => { + const imgCategory = img.category || img.pcrm__Category__c; + + // Handle "None" category - show images with no category or empty category + if (this.replacementSelectedCategory === "None") { + return ( + !imgCategory || + imgCategory === "" || + imgCategory === null || + imgCategory === undefined || + imgCategory === "None" + ); + } + + return imgCategory === this.replacementSelectedCategory; + }); + + this.filteredReplacementImages = filteredImages.map((img, index) => ({ + id: `${this.replacementSelectedCategory}-${index}`, + url: img.url, + title: img.title || img.name || `Image ${index + 1}`, + category: img.category || img.pcrm__Category__c || "None", + })); + } + + selectReplacementImage(event) { + const imageUrl = event.currentTarget.dataset.imageUrl; + + if (!imageUrl) { + this.showError("Failed to get image URL. Please try again."); + return; + } + + this.replaceImageSrc(imageUrl); + this.closeImageReplacement(); + } + triggerImageReplacementFileUpload() { + // Try to find the image upload input in the replacement modal + const fileInput = this.template.querySelector(".image-upload-input"); + if (fileInput) { + // Reset the input to allow selecting the same file again + fileInput.value = ""; + fileInput.click(); + } else { + // Fallback: create a new input programmatically + const input = document.createElement("input"); + input.type = "file"; + input.accept = "image/*"; + input.style.display = "none"; + input.onchange = (e) => this.handleImageUpload(e); + document.body.appendChild(input); + input.click(); + // Don't remove immediately, let the handler process first + setTimeout(() => { + if (document.body.contains(input)) { + document.body.removeChild(input); } + }, 100); + } + } + + handleImageUpload(event) { + const file = event.target.files[0]; + + if (!file) { + return; } - // Helper method to determine if element is likely positioned over an image - isElementLikelyOverImage(element) { - if (!element || element.tagName === 'IMG') return false; - - // Be much more restrictive - only trigger for elements that are clearly over images - const style = window.getComputedStyle(element); - - // Only check for images underneath if the element has strong indicators - const isTransparentText = this.isTransparentTextElement(element, style); - const isPositionedOverlay = this.isPositionedOverlay(element, style); - const hasImageParent = this.hasDirectImageParent(element); - - // Only return true if there are very specific indicators - return (isTransparentText && hasImageParent) || - (isPositionedOverlay && this.checkBackgroundImages(element)); - } - - isTransparentTextElement(element, style) { - // Text elements that are likely overlays - const textTags = ['P', 'SPAN', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'A', 'STRONG', 'EM', 'B', 'I', 'DIV']; - const isTextElement = textTags.includes(element.tagName); - - // Check if background is transparent or semi-transparent - const bg = style.backgroundColor; - const isTransparent = bg === 'rgba(0, 0, 0, 0)' || - bg === 'transparent' || - bg === '' || - (bg.includes('rgba') && (bg.includes(', 0)') || bg.includes(', 0.') && parseFloat(bg.split(',')[3]) < 0.5)); - - return isTextElement && isTransparent; - } - - isPositionedOverlay(element, style) { - const isPositioned = ['absolute', 'relative', 'fixed'].includes(style.position); - const hasLowOpacity = parseFloat(style.opacity) < 1; - const hasTransformOrZ = style.transform !== 'none' || parseInt(style.zIndex) > 0; - - return isPositioned && (hasLowOpacity || hasTransformOrZ); - } - - hasDirectImageParent(element) { - // Only check immediate parent and grandparent - let current = element.parentElement; - let depth = 0; - - while (current && depth < 2) { - if (current.querySelector('img')) return true; - current = current.parentElement; - depth++; - } - return false; - } - - - - checkBackgroundImages(element) { - let current = element; - let depth = 0; - - while (current && depth < 5) { - const style = window.getComputedStyle(current); - if (style.backgroundImage && style.backgroundImage !== 'none') { - return true; - } - current = current.parentElement; - depth++; - } - return false; + // Validate file type + if (!file.type.startsWith("image/")) { + this.showError("Please select a valid image file (JPG, PNG, GIF, WebP)"); + return; } - // Helper method to check if there are actual image indicators before expensive search - hasImageIndicators(clickedElement, x, y, editor) { - // Quick check: if the element or its parents have background images - let current = clickedElement; - let depth = 0; - - while (current && current !== editor && depth < 3) { - const style = window.getComputedStyle(current); - if (style.backgroundImage && style.backgroundImage !== 'none') { - return true; - } - current = current.parentElement; - depth++; - } - - // Quick check: if there are any img elements in the nearby area - const allElementsAtPoint = document.elementsFromPoint ? document.elementsFromPoint(x, y) : []; - const hasDirectImage = allElementsAtPoint.some(el => { - return el.tagName === 'IMG' || - (el.querySelector && el.querySelector('img')) || - (window.getComputedStyle(el).backgroundImage !== 'none'); - }); - - if (!hasDirectImage) { - // Final check: look for images in the clicked element's container - const container = clickedElement.closest('div, section, article'); - if (container && container !== editor) { - return container.querySelector('img') !== null; - } - } - - return hasDirectImage; + // Validate file size (e.g., max 10MB) + const maxSize = 10 * 1024 * 1024; // 10MB + if (file.size > maxSize) { + this.showError("File size must be less than 10MB"); + return; } - // Helper method to find images under click coordinates - including low z-index images - findImageUnderClick(x, y, editor) { - // Check for images directly at the click point - const allElementsAtPoint = document.elementsFromPoint ? document.elementsFromPoint(x, y) : []; - - for (const element of allElementsAtPoint) { - // Skip if it's part of the UI (toolbar, navigation, etc.) - if (element.closest('.editor-left') || - element.closest('.toolbar-section') || - element.closest('.step-navigation') || - element.closest('.page-controls')) { - continue; - } - - // Direct image - highest priority - if (element.tagName === 'IMG') { - return element; - } - - // Check for contained images - const containedImg = element.querySelector('img'); - if (containedImg) { - return containedImg; - } - - // Check for background images - const style = window.getComputedStyle(element); - if (style.backgroundImage && style.backgroundImage !== 'none') { - return { - src: style.backgroundImage.slice(5, -2), - element: element, - isBackgroundImage: true - }; - } - } - - return null; + const reader = new FileReader(); + reader.onload = (e) => { + this.uploadedImagePreview = e.target.result; + + // Show success message + this.showSuccess( + '✅ Image uploaded successfully! Click "Use This Image" to apply it.' + ); + + // Force re-render to show the preview + this.forceRerender(); + }; + + reader.onerror = (e) => { + this.showError("Error reading the selected file. Please try again."); + }; + + reader.readAsDataURL(file); + } + + useUploadedImage() { + if (this.uploadedImagePreview) { + this.replaceImageSrc(this.uploadedImagePreview); + this.closeImageReplacement(); } - // Triple click handler for image replacement - handleImageClick(clickedImage, event) { - // Clear any existing timeout - if (this.clickTimeout) { - clearTimeout(this.clickTimeout); - } + } - // Debug logging for image detection - const debugInfo = { - tagName: clickedImage.tagName, - isBackgroundImage: clickedImage.isBackgroundImage, - src: clickedImage.src, - backgroundImage: clickedImage.style.backgroundImage, - originalElement: clickedImage.originalElement - }; + // Drag and drop handlers for image upload + handleDragOver(event) { + event.preventDefault(); + event.stopPropagation(); + const dropzone = event.currentTarget; + dropzone.classList.add("drag-over"); + } - // Check if this is the same image as the last click - const isSameImage = this.lastClickedImage && - ((this.lastClickedImage.src && clickedImage.src && this.lastClickedImage.src === clickedImage.src) || - (this.lastClickedImage.isBackgroundImage && clickedImage.isBackgroundImage && - this.lastClickedImage.style.backgroundImage === clickedImage.style.backgroundImage)); + handleDragLeave(event) { + event.preventDefault(); + event.stopPropagation(); + const dropzone = event.currentTarget; + dropzone.classList.remove("drag-over"); + } - if (isSameImage) { - // Same image clicked, increment counter - this.imageClickCount++; - } else { - // Different image clicked, reset counter - this.imageClickCount = 1; - this.lastClickedImage = clickedImage; - } + handleDrop(event) { + event.preventDefault(); + event.stopPropagation(); - // Set timeout to reset counter after 1 second - this.clickTimeout = setTimeout(() => { - this.imageClickCount = 0; - this.lastClickedImage = null; - }, 1000); + const dropzone = event.currentTarget; + dropzone.classList.remove("drag-over"); - // Check if we've reached exactly 3 clicks - if (this.imageClickCount === 3) { - event.preventDefault(); - event.stopPropagation(); - this.openImageReplacement(clickedImage); - - // Reset counter after opening popup - this.imageClickCount = 0; - this.lastClickedImage = null; - if (this.clickTimeout) { - clearTimeout(this.clickTimeout); - this.clickTimeout = null; - } - } else { - // Show feedback for clicks 1 and 2, but don't open popup - if (this.imageClickCount === 1) { - this.showSuccess('Click 2 more times on the same image to replace it'); - } else if (this.imageClickCount === 2) { - this.showSuccess('Click 1 more time on the same image to replace it'); - } - - // Prevent any default behavior for clicks 1 and 2 - event.preventDefault(); - event.stopPropagation(); - } - } + const files = event.dataTransfer.files; + if (files.length > 0) { + const file = files[0]; - // Image Replacement Methods - openImageReplacement(imageElement) { - if (!imageElement) { - return; - } + // Validate file type + if (!file.type.startsWith("image/")) { + this.showError("Please drop a valid image file (JPG, PNG, GIF, WebP)"); + return; + } - - this.selectedImageElement = imageElement; - this.showImageReplacement = true; - this.replacementActiveTab = 'property'; - - // Use smart category selection like Step 2 - this.replacementSelectedCategory = this.findFirstAvailableCategory(); - - this.uploadedImagePreview = null; - this.filterReplacementImages(); - - // Update category button states after filtering - setTimeout(() => { - const categoryButtons = this.template.querySelectorAll('.category-btn-step2'); - categoryButtons.forEach(btn => { - btn.classList.remove('active'); - if (btn.dataset.category === this.replacementSelectedCategory) { - btn.classList.add('active'); - } - }); - }, 100); - - // Prevent body scrolling - document.body.style.overflow = 'hidden'; - - // Log the selected image details for debugging - if (imageElement.isBackgroundImage) { - } else if (imageElement.tagName === 'IMG') { - } else { - } - } + // Validate file size (e.g., max 10MB) + const maxSize = 10 * 1024 * 1024; // 10MB + if (file.size > maxSize) { + this.showError("File size must be less than 10MB"); + return; + } - closeImageReplacement() { - this.showImageReplacement = false; - this.selectedImageElement = null; - this.uploadedImagePreview = null; - - // Clear click tracking - this.resetImageClickTracking(); - - // Restore body scrolling - document.body.style.overflow = ''; - } + // Process the dropped file + const reader = new FileReader(); + reader.onload = (e) => { + this.uploadedImagePreview = e.target.result; - resetImageClickTracking() { - this.imageClickCount = 0; - this.lastClickedImage = null; - if (this.clickTimeout) { - clearTimeout(this.clickTimeout); - this.clickTimeout = null; - } - } + // Show success message + this.showSuccess( + '✅ Image uploaded successfully! Click "Use This Image" to apply it.' + ); - selectPropertyImagesTab() { - this.replacementActiveTab = 'property'; - this.filterReplacementImages(); - } - - selectLocalUploadTab() { - this.replacementActiveTab = 'upload'; - this.uploadedImagePreview = null; - - // Force re-render to ensure the upload area is visible + // Force re-render to show the preview this.forceRerender(); - - // Add a small delay to ensure DOM is updated + }; + + reader.onerror = (e) => { + this.showError("Error reading the dropped file. Please try again."); + }; + + reader.readAsDataURL(file); + } + } + replaceImageSrc(newImageUrl) { + if (!this.selectedImageElement || !newImageUrl) { + return; + } + + try { + // Save undo state before making changes + this.saveUndoState(); + + // Handle background images + if (this.selectedImageElement.isBackgroundImage) { + // Use the stored original element reference if available + if (this.selectedImageElement.originalElement) { + this.selectedImageElement.originalElement.style.backgroundImage = `url("${newImageUrl}")`; + this.showSuccess("Background image updated successfully!"); + return; + } + + // Fallback: Find the actual DOM element that has the background image + const editor = this.template.querySelector(".enhanced-editor-content"); + if (editor) { + // Find all elements with background images and update the one that matches + const allElements = editor.querySelectorAll("*"); + for (let element of allElements) { + const computedStyle = window.getComputedStyle(element); + const currentBgImage = computedStyle.backgroundImage; + if (currentBgImage && currentBgImage !== "none") { + // Check if this is the element we want to update + const currentBgUrl = currentBgImage.replace( + /url\(['"]?(.+?)['"]?\)/, + "$1" + ); + if (currentBgUrl === this.selectedImageElement.src) { + element.style.backgroundImage = `url("${newImageUrl}")`; + this.showSuccess("Background image updated successfully!"); + return; + } + } + } + } + this.showError("Failed to update background image. Please try again."); + return; + } + + // Handle regular img elements + if (this.selectedImageElement.tagName === "IMG") { + this.selectedImageElement.src = newImageUrl; + + // If the image is inside a draggable container, ensure it maintains proper styling + const draggableContainer = + this.selectedImageElement.closest(".draggable-element"); + if (draggableContainer) { + // Reset any max-width/max-height constraints that might interfere + this.selectedImageElement.style.width = "100%"; + this.selectedImageElement.style.height = "100%"; + this.selectedImageElement.style.objectFit = "cover"; + } + + this.showSuccess("Image updated successfully!"); + } else { + this.showError("Failed to update image: Invalid element type"); + } + } catch (error) { + this.showError("Failed to update image. Please try again."); + } + } + + // Template Save/Load/Export Methods + openSaveDialog() { + this.showSaveDialog = true; + this.saveTemplateName = ""; + document.body.style.overflow = "hidden"; + } + closeSaveDialog() { + this.showSaveDialog = false; + document.body.style.overflow = ""; + } + + handleSaveNameChange(event) { + this.saveTemplateName = event.target.value; + } + + saveTemplate() { + if (!this.saveTemplateName.trim()) { + this.showError("Please enter a template name"); + return; + } + + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) { + this.showError("No template content to save"); + return; + } + + const templateData = { + id: Date.now().toString(), + name: this.saveTemplateName.trim(), + content: editor.innerHTML, + pageSize: this.selectedPageSize, + baseTemplateId: this.selectedTemplateId, + propertyId: this.selectedPropertyId, + savedAt: new Date().toISOString(), + thumbnail: this.generateThumbnail(editor), + }; + + // Get existing saved templates from localStorage + const savedTemplates = JSON.parse( + localStorage.getItem("savedTemplates") || "[]" + ); + savedTemplates.push(templateData); + localStorage.setItem("savedTemplates", JSON.stringify(savedTemplates)); + + this.loadSavedTemplates(); + this.closeSaveDialog(); + this.showSuccess(`Template "${this.saveTemplateName}" saved successfully!`); + } + + generateThumbnail(editor) { + // Create a simple text preview of the template + const textContent = editor.textContent || editor.innerText || ""; + return ( + textContent.substring(0, 100) + (textContent.length > 100 ? "..." : "") + ); + } + + openLoadDialog() { + this.loadSavedTemplates(); + this.showLoadDialog = true; + document.body.style.overflow = "hidden"; + } + + closeLoadDialog() { + this.showLoadDialog = false; + document.body.style.overflow = ""; + } + + loadSavedTemplates() { + const saved = JSON.parse(localStorage.getItem("savedTemplates") || "[]"); + this.savedTemplates = saved.map((template) => ({ + ...template, + formattedDate: new Date(template.savedAt).toLocaleDateString(), + })); + } + + loadTemplate(event) { + const templateId = event.currentTarget.dataset.templateId; + const template = this.savedTemplates.find((t) => t.id === templateId); + + if (template) { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (editor) { + editor.innerHTML = template.content; + this.selectedPageSize = template.pageSize || "A4"; + this.htmlContent = template.content; + + // Update page size radio buttons + const pageRadios = this.template.querySelectorAll( + 'input[name="pageSize"]' + ); + pageRadios.forEach((radio) => { + radio.checked = radio.value === this.selectedPageSize; + }); + + this.closeLoadDialog(); + this.showSuccess(`Template "${template.name}" loaded successfully!`); + + // Re-setup editor functionality setTimeout(() => { - const uploadDropzone = this.template.querySelector('.upload-dropzone'); - if (uploadDropzone) { - } else { - } + this.ensureEditorEditable(); + this.setupEditorClickHandler(); }, 100); + } + } + } + + deleteTemplate(event) { + event.stopPropagation(); + const templateId = event.currentTarget.dataset.templateId; + const template = this.savedTemplates.find((t) => t.id === templateId); + + if ( + template && + confirm(`Are you sure you want to delete "${template.name}"?`) + ) { + const savedTemplates = JSON.parse( + localStorage.getItem("savedTemplates") || "[]" + ); + const filtered = savedTemplates.filter((t) => t.id !== templateId); + localStorage.setItem("savedTemplates", JSON.stringify(filtered)); + this.loadSavedTemplates(); + this.showSuccess("Template deleted successfully"); + } + } + exportHtml() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) { + this.showError("No template content to export"); + return; } - selectReplacementCategory(event) { - const category = event.target.dataset.category; - - this.replacementSelectedCategory = category; - this.filterReplacementImages(); - - // Update active state for category buttons - const categoryButtons = this.template.querySelectorAll('.category-btn-step2'); - categoryButtons.forEach(btn => { - btn.classList.remove('active'); - if (btn.dataset.category === this.replacementSelectedCategory) { - btn.classList.add('active'); - } - }); - } + // Use the raw HTML content + const htmlContent = editor.innerHTML; - filterReplacementImages() { - - if (!this.realPropertyImages || this.realPropertyImages.length === 0) { - this.filteredReplacementImages = []; - return; - } - - // Filter images by category using the same logic as Step 2 - const filteredImages = this.realPropertyImages.filter(img => { - const imgCategory = img.category || img.pcrm__Category__c; - - // Handle "None" category - show images with no category or empty category - if (this.replacementSelectedCategory === 'None') { - return !imgCategory || imgCategory === '' || imgCategory === null || imgCategory === undefined || imgCategory === 'None'; - } - - return imgCategory === this.replacementSelectedCategory; - }); - - - this.filteredReplacementImages = filteredImages.map((img, index) => ({ - id: `${this.replacementSelectedCategory}-${index}`, - url: img.url, - title: img.title || img.name || `Image ${index + 1}`, - category: img.category || img.pcrm__Category__c || 'None' - })); - } - - selectReplacementImage(event) { - const imageUrl = event.currentTarget.dataset.imageUrl; - - if (!imageUrl) { - this.showError('Failed to get image URL. Please try again.'); - return; - } - - this.replaceImageSrc(imageUrl); - this.closeImageReplacement(); - } - triggerImageReplacementFileUpload() { - - // Try to find the image upload input in the replacement modal - const fileInput = this.template.querySelector('.image-upload-input'); - if (fileInput) { - // Reset the input to allow selecting the same file again - fileInput.value = ''; - fileInput.click(); - } else { - // Fallback: create a new input programmatically - const input = document.createElement('input'); - input.type = 'file'; - input.accept = 'image/*'; - input.style.display = 'none'; - input.onchange = (e) => this.handleImageUpload(e); - document.body.appendChild(input); - input.click(); - // Don't remove immediately, let the handler process first - setTimeout(() => { - if (document.body.contains(input)) { - document.body.removeChild(input); - } - }, 100); - } - } - - handleImageUpload(event) { - const file = event.target.files[0]; - - if (!file) { - return; - } - - - // Validate file type - if (!file.type.startsWith('image/')) { - this.showError('Please select a valid image file (JPG, PNG, GIF, WebP)'); - return; - } - - // Validate file size (e.g., max 10MB) - const maxSize = 10 * 1024 * 1024; // 10MB - if (file.size > maxSize) { - this.showError('File size must be less than 10MB'); - return; - } - - const reader = new FileReader(); - reader.onload = (e) => { - this.uploadedImagePreview = e.target.result; - - // Show success message - this.showSuccess('✅ Image uploaded successfully! Click "Use This Image" to apply it.'); - - // Force re-render to show the preview - this.forceRerender(); - }; - - reader.onerror = (e) => { - this.showError('Error reading the selected file. Please try again.'); - }; - - reader.readAsDataURL(file); - } - - useUploadedImage() { - if (this.uploadedImagePreview) { - this.replaceImageSrc(this.uploadedImagePreview); - this.closeImageReplacement(); - } - } - - // Drag and drop handlers for image upload - handleDragOver(event) { - event.preventDefault(); - event.stopPropagation(); - const dropzone = event.currentTarget; - dropzone.classList.add('drag-over'); - } - - handleDragLeave(event) { - event.preventDefault(); - event.stopPropagation(); - const dropzone = event.currentTarget; - dropzone.classList.remove('drag-over'); - } - - handleDrop(event) { - event.preventDefault(); - event.stopPropagation(); - - const dropzone = event.currentTarget; - dropzone.classList.remove('drag-over'); - - const files = event.dataTransfer.files; - if (files.length > 0) { - const file = files[0]; - - // Validate file type - if (!file.type.startsWith('image/')) { - this.showError('Please drop a valid image file (JPG, PNG, GIF, WebP)'); - return; - } - - // Validate file size (e.g., max 10MB) - const maxSize = 10 * 1024 * 1024; // 10MB - if (file.size > maxSize) { - this.showError('File size must be less than 10MB'); - return; - } - - // Process the dropped file - const reader = new FileReader(); - reader.onload = (e) => { - this.uploadedImagePreview = e.target.result; - - // Show success message - this.showSuccess('✅ Image uploaded successfully! Click "Use This Image" to apply it.'); - - // Force re-render to show the preview - this.forceRerender(); - }; - - reader.onerror = (e) => { - this.showError('Error reading the dropped file. Please try again.'); - }; - - reader.readAsDataURL(file); - } - } - - replaceImageSrc(newImageUrl) { - if (!this.selectedImageElement || !newImageUrl) { - return; - } - - try { - // Save undo state before making changes - this.saveUndoState(); - - // Handle background images - if (this.selectedImageElement.isBackgroundImage) { - // Use the stored original element reference if available - if (this.selectedImageElement.originalElement) { - this.selectedImageElement.originalElement.style.backgroundImage = `url("${newImageUrl}")`; - this.showSuccess('Background image updated successfully!'); - return; - } - - // Fallback: Find the actual DOM element that has the background image - const editor = this.template.querySelector('.enhanced-editor-content'); - if (editor) { - // Find all elements with background images and update the one that matches - const allElements = editor.querySelectorAll('*'); - for (let element of allElements) { - const computedStyle = window.getComputedStyle(element); - const currentBgImage = computedStyle.backgroundImage; - if (currentBgImage && currentBgImage !== 'none') { - // Check if this is the element we want to update - const currentBgUrl = currentBgImage.replace(/url\(['"]?(.+?)['"]?\)/, '$1'); - if (currentBgUrl === this.selectedImageElement.src) { - element.style.backgroundImage = `url("${newImageUrl}")`; - this.showSuccess('Background image updated successfully!'); - return; - } - } - } - } - this.showError('Failed to update background image. Please try again.'); - return; - } - - // Handle regular img elements - if (this.selectedImageElement.tagName === 'IMG') { - this.selectedImageElement.src = newImageUrl; - - // If the image is inside a draggable container, ensure it maintains proper styling - const draggableContainer = this.selectedImageElement.closest('.draggable-element'); - if (draggableContainer) { - // Reset any max-width/max-height constraints that might interfere - this.selectedImageElement.style.width = '100%'; - this.selectedImageElement.style.height = '100%'; - this.selectedImageElement.style.objectFit = 'cover'; - } - - this.showSuccess('Image updated successfully!'); - } else { - this.showError('Failed to update image: Invalid element type'); - } - } catch (error) { - this.showError('Failed to update image. Please try again.'); - } - } - - // Template Save/Load/Export Methods - openSaveDialog() { - this.showSaveDialog = true; - this.saveTemplateName = ''; - document.body.style.overflow = 'hidden'; - } - closeSaveDialog() { - this.showSaveDialog = false; - document.body.style.overflow = ''; - } - - handleSaveNameChange(event) { - this.saveTemplateName = event.target.value; - } - - saveTemplate() { - if (!this.saveTemplateName.trim()) { - this.showError('Please enter a template name'); - return; - } - - const editor = this.template.querySelector('.enhanced-editor-content'); - if (!editor) { - this.showError('No template content to save'); - return; - } - - const templateData = { - id: Date.now().toString(), - name: this.saveTemplateName.trim(), - content: editor.innerHTML, - pageSize: this.selectedPageSize, - baseTemplateId: this.selectedTemplateId, - propertyId: this.selectedPropertyId, - savedAt: new Date().toISOString(), - thumbnail: this.generateThumbnail(editor) - }; - - // Get existing saved templates from localStorage - const savedTemplates = JSON.parse(localStorage.getItem('savedTemplates') || '[]'); - savedTemplates.push(templateData); - localStorage.setItem('savedTemplates', JSON.stringify(savedTemplates)); - - this.loadSavedTemplates(); - this.closeSaveDialog(); - this.showSuccess(`Template "${this.saveTemplateName}" saved successfully!`); - } - - generateThumbnail(editor) { - // Create a simple text preview of the template - const textContent = editor.textContent || editor.innerText || ''; - return textContent.substring(0, 100) + (textContent.length > 100 ? '...' : ''); - } - - openLoadDialog() { - this.loadSavedTemplates(); - this.showLoadDialog = true; - document.body.style.overflow = 'hidden'; - } - - closeLoadDialog() { - this.showLoadDialog = false; - document.body.style.overflow = ''; - } - - loadSavedTemplates() { - const saved = JSON.parse(localStorage.getItem('savedTemplates') || '[]'); - this.savedTemplates = saved.map(template => ({ - ...template, - formattedDate: new Date(template.savedAt).toLocaleDateString() - })); - } - - loadTemplate(event) { - const templateId = event.currentTarget.dataset.templateId; - const template = this.savedTemplates.find(t => t.id === templateId); - - if (template) { - const editor = this.template.querySelector('.enhanced-editor-content'); - if (editor) { - editor.innerHTML = template.content; - this.selectedPageSize = template.pageSize || 'A4'; - this.htmlContent = template.content; - - // Update page size radio buttons - const pageRadios = this.template.querySelectorAll('input[name="pageSize"]'); - pageRadios.forEach(radio => { - radio.checked = radio.value === this.selectedPageSize; - }); - - this.closeLoadDialog(); - this.showSuccess(`Template "${template.name}" loaded successfully!`); - - // Re-setup editor functionality - setTimeout(() => { - this.ensureEditorEditable(); - this.setupEditorClickHandler(); - }, 100); - } - } - } - - deleteTemplate(event) { - event.stopPropagation(); - const templateId = event.currentTarget.dataset.templateId; - const template = this.savedTemplates.find(t => t.id === templateId); - - if (template && confirm(`Are you sure you want to delete "${template.name}"?`)) { - const savedTemplates = JSON.parse(localStorage.getItem('savedTemplates') || '[]'); - const filtered = savedTemplates.filter(t => t.id !== templateId); - localStorage.setItem('savedTemplates', JSON.stringify(filtered)); - this.loadSavedTemplates(); - this.showSuccess('Template deleted successfully'); - } - } - - exportHtml() { - const editor = this.template.querySelector('.enhanced-editor-content'); - if (!editor) { - this.showError('No template content to export'); - return; - } - - // Use the raw HTML content - const htmlContent = editor.innerHTML; - - // Create a complete HTML document - const fullHtml = ` + // Create a complete HTML document + const fullHtml = ` @@ -7304,7 +8217,7 @@ export default class PropertyTemplateSelector extends LightningElement { line-height: 1.6; } .template-content { - max-width: ${this.selectedPageSize === 'A3' ? '297mm' : '210mm'}; + max-width: ${this.selectedPageSize === "A3" ? "297mm" : "210mm"}; margin: 0 auto; background: white; box-shadow: 0 0 20px rgba(0,0,0,0.1); @@ -7339,1253 +8252,1394 @@ export default class PropertyTemplateSelector extends LightningElement { `; - this.exportedHtml = fullHtml; - this.showHtmlDialog = true; - document.body.style.overflow = 'hidden'; + this.exportedHtml = fullHtml; + this.showHtmlDialog = true; + document.body.style.overflow = "hidden"; + } + + closeHtmlDialog() { + this.showHtmlDialog = false; + document.body.style.overflow = ""; + } + + copyHtmlToClipboard() { + if (navigator.clipboard) { + navigator.clipboard + .writeText(this.exportedHtml) + .then(() => { + this.showSuccess("HTML copied to clipboard!"); + }) + .catch(() => { + this.fallbackCopyToClipboard(); + }); + } else { + this.fallbackCopyToClipboard(); + } + } + + fallbackCopyToClipboard() { + const textArea = document.createElement("textarea"); + textArea.value = this.exportedHtml; + textArea.style.position = "fixed"; + textArea.style.left = "-999999px"; + textArea.style.top = "-999999px"; + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + + try { + document.execCommand("copy"); + this.showSuccess("HTML copied to clipboard!"); + } catch (err) { + this.showError("Failed to copy HTML"); } - closeHtmlDialog() { - this.showHtmlDialog = false; - document.body.style.overflow = ''; + document.body.removeChild(textArea); + } + + downloadHtml() { + const blob = new Blob([this.exportedHtml], { type: "text/html" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `property-brochure-${Date.now()}.html`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + this.showSuccess("HTML file downloaded!"); + } + + // Table Dialog Methods + openTableDialog() { + this.showTableDialog = true; + document.body.style.overflow = "hidden"; + } + + closeTableDialog() { + this.showTableDialog = false; + document.body.style.overflow = ""; + } + + handleTableRowsChange(event) { + this.tableRows = parseInt(event.target.value) || 3; + } + + handleTableColsChange(event) { + this.tableCols = parseInt(event.target.value) || 3; + } + + handleHeaderChange(event) { + this.includeHeader = event.target.checked; + } + + insertTable() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) { + this.showError("Editor not found"); + return; } - copyHtmlToClipboard() { - if (navigator.clipboard) { - navigator.clipboard.writeText(this.exportedHtml).then(() => { - this.showSuccess('HTML copied to clipboard!'); - }).catch(() => { - this.fallbackCopyToClipboard(); - }); - } else { - this.fallbackCopyToClipboard(); - } + // Save undo state + this.saveUndoState(); + + // Create table element using our new method (draggable/resizeable container like images) + const tableContainer = this.createTableElement(); + editor.appendChild(tableContainer); + // Default placement similar to images + tableContainer.style.left = "50px"; + tableContainer.style.top = "50px"; + // Enable drag + resize + this.addTableResizeHandles(tableContainer); + this.makeDraggable(tableContainer); + this.setupTableEventListeners(tableContainer); + + this.closeTableDialog(); + this.showSuccess("Table inserted successfully!"); + } + + // ===== DYNAMIC IMAGE REPLACEMENT UTILITIES ===== + + // Get first image from a specific category + getFirstImageByCategory(category) { + if (!this.realPropertyImages || this.realPropertyImages.length === 0) { + return null; } - fallbackCopyToClipboard() { - const textArea = document.createElement('textarea'); - textArea.value = this.exportedHtml; - textArea.style.position = 'fixed'; - textArea.style.left = '-999999px'; - textArea.style.top = '-999999px'; - document.body.appendChild(textArea); - textArea.focus(); - textArea.select(); - - try { - document.execCommand('copy'); - this.showSuccess('HTML copied to clipboard!'); - } catch (err) { - this.showError('Failed to copy HTML'); - } - - document.body.removeChild(textArea); + const categoryImages = this.realPropertyImages.filter((img) => { + const imgCategory = img.category || img.pcrm__Category__c; + return ( + imgCategory && imgCategory.toLowerCase() === category.toLowerCase() + ); + }); + + return categoryImages.length > 0 ? categoryImages[0] : null; + } + // Direct method to get exterior image URL + getExteriorImageUrl() { + if (!this.realPropertyImages || this.realPropertyImages.length === 0) { + return ""; } - downloadHtml() { - const blob = new Blob([this.exportedHtml], { type: 'text/html' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `property-brochure-${Date.now()}.html`; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - this.showSuccess('HTML file downloaded!'); + // Look for exterior images first + const exteriorImages = this.realPropertyImages.filter((img) => { + const category = img.category || img.pcrm__Category__c; + return category && category.toLowerCase().includes("exterior"); + }); + + if (exteriorImages.length > 0) { + return exteriorImages[0].url; } - // Table Dialog Methods - openTableDialog() { - this.showTableDialog = true; - document.body.style.overflow = 'hidden'; + // If no exterior, use first available image + if (this.realPropertyImages.length > 0) { + return this.realPropertyImages[0].url; } - closeTableDialog() { - this.showTableDialog = false; - document.body.style.overflow = ''; + return ""; + } + + // Direct method to get maps image URL + getMapsImageUrl() { + if (!this.realPropertyImages || this.realPropertyImages.length === 0) { + return ""; } - handleTableRowsChange(event) { - this.tableRows = parseInt(event.target.value) || 3; + // Look for maps images first - check both exact match and contains + const mapsImages = this.realPropertyImages.filter((img) => { + const category = img.category || img.pcrm__Category__c; + return ( + category && + (category.toLowerCase() === "maps" || + category.toLowerCase().includes("maps")) + ); + }); + + if (mapsImages.length > 0) { + return mapsImages[0].url; } - handleTableColsChange(event) { - this.tableCols = parseInt(event.target.value) || 3; + // Look for anchor images as fallback + const anchorImages = this.realPropertyImages.filter((img) => { + const category = img.category || img.pcrm__Category__c; + return ( + category && + (category.toLowerCase() === "anchor" || + category.toLowerCase().includes("anchor")) + ); + }); + + if (anchorImages.length > 0) { + return anchorImages[0].url; } - handleHeaderChange(event) { - this.includeHeader = event.target.checked; + return ""; + } + + // Method to replace background-image URLs in CSS at runtime + replaceBackgroundImagesInHTML(htmlContent) { + if (!this.realPropertyImages || this.realPropertyImages.length === 0) { + return htmlContent; } - insertTable() { - const editor = this.template.querySelector('.enhanced-editor-content'); - if (!editor) { - this.showError("Editor not found"); - return; + const exteriorImageUrl = this.getExteriorImageUrl(); + + // Replace any hardcoded background-image URLs with the property's exterior image + let updatedHTML = htmlContent; + + // Pattern to match background-image: url('...') or background-image: url("...") + const backgroundImagePattern = + /background-image\s*:\s*url\(['"][^'"]*['"]\)/gi; + + // Replace all background-image URLs with the property's exterior image + updatedHTML = updatedHTML.replace(backgroundImagePattern, (match) => { + return exteriorImageUrl ? `background-image: url('${exteriorImageUrl}')` : "background-image: none"; + }); + + return updatedHTML; + } + // Method to dynamically update CSS background-image rules after template loads + updateCSSBackgroundImages() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) return; + if (!this.realPropertyImages || this.realPropertyImages.length === 0) + return; + const exteriorImageUrl = this.getExteriorImageUrl(); + // Scope to styles inside the editor only + const styleElements = editor.querySelectorAll("style"); + styleElements.forEach((styleElement) => { + const cssText = styleElement.textContent || ""; + const backgroundImagePattern = + /background-image\s*:\s*url\(['"][^'"]*['"]\)/gi; + const updatedCSS = cssText.replace(backgroundImagePattern, (match) => { + return exteriorImageUrl + ? `background-image: url('${exteriorImageUrl}')` + : "background-image: none"; + }); + if (updatedCSS !== cssText) styleElement.textContent = updatedCSS; + }); + // Update inline background-image styles only within editor + const elementsWithBackground = editor.querySelectorAll( + '[style*="background-image"]' + ); + elementsWithBackground.forEach((element) => { + const currentStyle = element.getAttribute("style") || ""; + const backgroundImagePattern = + /background-image\s*:\s*url\(['"][^'"]*['"]\)/gi; + const updatedStyle = currentStyle.replace(backgroundImagePattern, (match) => { + return exteriorImageUrl + ? `background-image: url('${exteriorImageUrl}')` + : "background-image: none"; + }); + if (updatedStyle !== currentStyle) + element.setAttribute("style", updatedStyle); + }); + } + + // Get all images from a specific category + getAllImagesByCategory(category) { + if (!this.realPropertyImages || this.realPropertyImages.length === 0) { + return []; + } + + return this.realPropertyImages.filter( + (img) => + img.category && img.category.toLowerCase() === category.toLowerCase() + ); + } + + // Get uncategorized images (no category or category is null/empty) + getUncategorizedImages() { + if (!this.realPropertyImages || this.realPropertyImages.length === 0) { + return []; + } + + const uncategorized = this.realPropertyImages.filter( + (img) => + !img.category || + img.category.trim() === "" || + img.category.toLowerCase() === "none" + ); + + return uncategorized; + } + + // Smart image replacement - tries multiple categories in order of preference + getSmartImageForSection(sectionType, fallbackUrl) { + const categoryPriority = { + exterior: ["Exterior", "Anchor", "None"], + interior: ["Interior", "Living Area", "Kitchen", "Bedroom", "None"], + kitchen: ["Kitchen", "Interior", "Living Area", "None"], + bedroom: ["Bedroom", "Interior", "None"], + living: ["Living Area", "Interior", "Kitchen", "None"], + bathroom: ["Bathroom", "Interior", "None"], + parking: ["Parking", "Exterior", "None"], + maps: ["Maps", "Anchor", "Exterior", "None"], + gallery: [ + "Interior", + "Exterior", + "Kitchen", + "Bedroom", + "Living Area", + "None", + ], + }; + + const categories = categoryPriority[sectionType] || [ + "Interior", + "Exterior", + "None", + ]; + + for (const category of categories) { + const image = this.getFirstImageByCategory(category); + if (image && image.url) { + return image.url; + } + } + + return fallbackUrl; + } + + // Generate Property Gallery HTML for uncategorized images + generatePropertyGalleryHTML() { + if (!this.realPropertyImages || this.realPropertyImages.length === 0) { + return ""; + } + + let galleryHTML = ""; + this.realPropertyImages.forEach((image, index) => { + const title = + image.title || image.pcrm__Title__c || `Property Image ${index + 1}`; + galleryHTML += `${title}`; + }); + + return galleryHTML; + } + // Generate gallery HTML for a provided subset of images + generatePropertyGalleryHTMLForImages(imagesSubset) { + if (!imagesSubset || imagesSubset.length === 0) { + return ""; + } + let galleryHTML = ""; + imagesSubset.forEach((image, index) => { + const title = + image.title || image.pcrm__Title__c || `Property Image ${index + 1}`; + galleryHTML += `${title}`; + }); + return galleryHTML; + } + + // ===== TABLE DRAG AND DROP FUNCTIONALITY ===== + + // Handle table drag start + handleTableDragStart(event) { + this.isDraggingTable = true; + + // Store table configuration data + this.draggedTableData = { + rows: this.tableRows, + cols: this.tableCols, + includeHeader: this.includeHeader, + }; + + // Set drag data + event.dataTransfer.setData("text/plain", "table"); + event.dataTransfer.effectAllowed = "copy"; + + // Add visual feedback + event.currentTarget.classList.add("dragging"); + + // Add drag over class to editor + setTimeout(() => { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (editor) { + editor.classList.add("drag-over"); + } + }, 100); + } + + // Handle editor drag over + handleEditorDragOver(event) { + // Allow dropping tables and images + event.preventDefault(); + event.dataTransfer.dropEffect = "copy"; + } + + // Handle editor drop + handleEditorDrop(event) { + event.preventDefault(); + + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) { + this.showError("Editor not found"); + return; + } + + const dataType = event.dataTransfer.getData("text/plain"); + if (dataType === "image" && this.currentImage) { + // Insert draggable image at drop + const img = document.createElement("img"); + img.src = this.currentImage.url; + img.style.maxWidth = "300px"; + img.style.height = "auto"; + img.className = "draggable-image"; + + const container = document.createElement("div"); + container.className = "draggable-image-container"; + container.style.position = "absolute"; + container.style.left = + event.clientX - editor.getBoundingClientRect().left + "px"; + container.style.top = + event.clientY - editor.getBoundingClientRect().top + "px"; + container.appendChild(img); + editor.appendChild(container); + this.makeImagesDraggableAndResizable([img]); + this.showSuccess("Image inserted via drag and drop!"); + return; + } + + if (!this.isDraggingTable || !this.draggedTableData) { + return; + } + + // Remove visual feedback + this.removeTableDragFeedback(); + + // Get drop position + // editor already resolved above + + // Save undo state before making changes + this.saveUndoState(); + + // Insert table at drop position + this.insertTableAtPosition(editor, this.draggedTableData, event); + + // Reset drag state + this.isDraggingTable = false; + this.draggedTableData = null; + + this.showSuccess("Table inserted via drag and drop!"); + } + // Insert table at specific position + insertTableAtPosition(editor, tableData, event) { + // Get cursor position relative to editor + const rect = editor.getBoundingClientRect(); + const x = event.clientX - rect.left; + const y = event.clientY - rect.top; + + // Create table element directly using DOM methods (same as insertTable) + const tableId = `table-${Date.now()}-${Math.random() + .toString(36) + .substr(2, 9)}`; + + // Create container div (use draggable-table-container to get drag/resize behavior) + const container = document.createElement("div"); + container.className = "draggable-table-container"; + container.setAttribute("data-table-id", tableId); + container.style.cssText = + "position: absolute; left: 0; top: 0; width: 400px; min-width: 200px; min-height: 150px; z-index: 1000; border: 2px dashed #667eea; border-radius: 8px; background: white; box-shadow: 0 4px 12px rgba(0,0,0,0.1); overflow: hidden;"; + + // Create table controls + const controls = document.createElement("div"); + controls.className = "table-controls"; + controls.style.cssText = + "position: absolute; top: -40px; left: 0; background: white; padding: 5px; border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); opacity: 0; transition: opacity 0.2s;"; + + // Add control buttons (same as insertTable) + const controlGroup1 = document.createElement("div"); + controlGroup1.className = "table-control-group"; + controlGroup1.style.cssText = "display: flex; gap: 5px;"; + + const addRowBtn = document.createElement("button"); + addRowBtn.className = "table-control-btn"; + addRowBtn.setAttribute("data-table-id", tableId); + addRowBtn.textContent = "+ Row"; + addRowBtn.style.cssText = + "padding: 4px 8px; border: 1px solid #ddd; background: white; cursor: pointer;"; + + const addColBtn = document.createElement("button"); + addColBtn.className = "table-control-btn"; + addColBtn.setAttribute("data-table-id", tableId); + addColBtn.textContent = "+ Col"; + addColBtn.style.cssText = + "padding: 4px 8px; border: 1px solid #ddd; background: white; cursor: pointer;"; + + const delRowBtn = document.createElement("button"); + delRowBtn.className = "table-control-btn"; + delRowBtn.setAttribute("data-table-id", tableId); + delRowBtn.textContent = "- Row"; + delRowBtn.style.cssText = + "padding: 4px 8px; border: 1px solid #ddd; background: white; cursor: pointer;"; + + const delColBtn = document.createElement("button"); + delColBtn.className = "table-control-btn"; + delColBtn.setAttribute("data-table-id", tableId); + delColBtn.textContent = "- Col"; + delColBtn.style.cssText = + "padding: 4px 8px; border: 1px solid #ddd; background: white; cursor: pointer;"; + + const deleteBtn = document.createElement("button"); + deleteBtn.className = "table-control-btn delete"; + deleteBtn.setAttribute("data-table-id", tableId); + deleteBtn.textContent = "🗑️"; + deleteBtn.style.cssText = + "padding: 4px 8px; border: 1px solid #ff4444; background: #ff4444; color: white; cursor: pointer;"; + + controlGroup1.appendChild(addRowBtn); + controlGroup1.appendChild(addColBtn); + controlGroup1.appendChild(delRowBtn); + controlGroup1.appendChild(delColBtn); + + const controlGroup2 = document.createElement("div"); + controlGroup2.className = "table-control-group"; + controlGroup2.style.cssText = "display: flex; gap: 5px; margin-left: 10px;"; + controlGroup2.appendChild(deleteBtn); + + controls.appendChild(controlGroup1); + controls.appendChild(controlGroup2); + + // Create table + const table = document.createElement("table"); + table.className = "inserted-table"; + table.id = tableId; + table.style.cssText = + "border-collapse: collapse; width: 100%; margin: 1rem 0; border: 2px solid #333; background-color: white;"; + + // Create table body + const tbody = document.createElement("tbody"); + // Add header row if requested + if (tableData.includeHeader) { + const thead = document.createElement("thead"); + const headerRow = document.createElement("tr"); + + for (let col = 0; col < tableData.cols; col++) { + const th = document.createElement("th"); + th.style.cssText = + "border: 1px solid #333; padding: 12px; background-color: #4f46e5; color: white; font-weight: bold; text-align: center;"; + th.setAttribute("contenteditable", "true"); + th.textContent = `Header ${col + 1}`; + headerRow.appendChild(th); + } + + thead.appendChild(headerRow); + table.appendChild(thead); + } + + // Add body rows + for (let row = 0; row < tableData.rows; row++) { + const tr = document.createElement("tr"); + + for (let col = 0; col < tableData.cols; col++) { + const td = document.createElement("td"); + td.style.cssText = + "border: 1px solid #333; padding: 12px; background-color: #f8f9fa; min-width: 100px; min-height: 40px;"; + td.setAttribute("contenteditable", "true"); + td.textContent = `Cell ${row + 1}-${col + 1}`; + tr.appendChild(td); + } + + tbody.appendChild(tr); + } + + table.appendChild(tbody); + + // Assemble the container + container.appendChild(controls); + container.appendChild(table); + + const tableElement = container; + + // Try to find the best insertion point + const range = document.createRange(); + const walker = document.createTreeWalker( + editor, + NodeFilter.SHOW_TEXT, + null, + false + ); + + let bestNode = null; + let bestDistance = Infinity; + let node; + + // Find the closest text node to the drop position + while ((node = walker.nextNode())) { + const nodeRect = node.getBoundingClientRect(); + const nodeX = nodeRect.left - rect.left; + const nodeY = nodeRect.top - rect.top; + const distance = Math.sqrt((x - nodeX) ** 2 + (y - nodeY) ** 2); + + if (distance < bestDistance) { + bestDistance = distance; + bestNode = node; + } + } + + // Insert into editor (append at end for simplicity) + editor.appendChild(tableElement); + // Position at drop point + tableElement.style.left = + Math.max(0, Math.min(x, editor.clientWidth - tableElement.offsetWidth)) + + "px"; + tableElement.style.top = + Math.max( + 0, + Math.min(y, editor.scrollHeight - tableElement.offsetHeight) + ) + "px"; + + // Add drag/resize to the new table + this.addTableResizeHandles(tableElement); + this.makeDraggable(tableElement); + this.setupTableEventListeners(tableElement); + } + + // Remove table drag feedback + removeTableDragFeedback() { + // Remove dragging class from button + const tableBtn = this.template.querySelector(".draggable-table-btn"); + if (tableBtn) { + tableBtn.classList.remove("dragging"); + } + + // Remove drag-over class from editor + const editor = this.template.querySelector(".enhanced-editor-content"); + if (editor) { + editor.classList.remove("drag-over"); + } + } + + // ===== TABLE EDITING FUNCTIONALITY ===== + + // Add row to table + addTableRow(event) { + const tableId = event.currentTarget.dataset.tableId; + const table = document.getElementById(tableId); + if (!table) return; + + this.saveUndoState(); + + const tbody = table.querySelector("tbody"); + const firstRow = tbody.querySelector("tr"); + if (!firstRow) return; + + const newRow = firstRow.cloneNode(true); + const cells = newRow.querySelectorAll("td, th"); + cells.forEach((cell, index) => { + cell.textContent = `Cell ${tbody.children.length + 1}-${index + 1}`; + }); + + tbody.appendChild(newRow); + this.showSuccess("Row added successfully!"); + } + // Add column to table + addTableColumn(event) { + const tableId = event.currentTarget.dataset.tableId; + const table = document.getElementById(tableId); + if (!table) return; + + this.saveUndoState(); + + const rows = table.querySelectorAll("tr"); + rows.forEach((row, rowIndex) => { + const newCell = document.createElement( + row.cells[0].tagName.toLowerCase() + ); + newCell.style.border = "1px solid #ddd"; + newCell.style.padding = "8px"; + newCell.contentEditable = "true"; + + if (row.cells[0].tagName === "TH") { + newCell.style.backgroundColor = "#f2f2f2"; + newCell.style.fontWeight = "bold"; + newCell.textContent = `Header ${row.cells.length + 1}`; + } else { + newCell.textContent = `Cell ${rowIndex}-${row.cells.length + 1}`; + } + + row.appendChild(newCell); + }); + + this.showSuccess("Column added successfully!"); + } + + // Delete row from table + deleteTableRow(event) { + const tableId = event.currentTarget.dataset.tableId; + const table = document.getElementById(tableId); + if (!table) return; + + const tbody = table.querySelector("tbody"); + if (tbody.children.length <= 1) { + this.showError("Cannot delete the last row!"); + return; + } + + this.saveUndoState(); + tbody.removeChild(tbody.lastChild); + this.showSuccess("Row deleted successfully!"); + } + + // Delete column from table + deleteTableColumn(event) { + const tableId = event.currentTarget.dataset.tableId; + const table = document.getElementById(tableId); + if (!table) return; + + const firstRow = table.querySelector("tr"); + if (!firstRow || firstRow.cells.length <= 1) { + this.showError("Cannot delete the last column!"); + return; + } + + this.saveUndoState(); + + const rows = table.querySelectorAll("tr"); + rows.forEach((row) => { + if (row.cells.length > 0) { + row.removeChild(row.lastChild); + } + }); + + this.showSuccess("Column deleted successfully!"); + } + // Delete entire table + deleteTable(event) { + const tableId = event.currentTarget.dataset.tableId; + const tableContainer = document.querySelector( + `[data-table-id="${tableId}"]` + ); + if (!tableContainer) return; + + this.saveUndoState(); + tableContainer.remove(); + this.showSuccess("Table deleted successfully!"); + } + + // Handle table drag start (for moving tables) + handleTableContainerDragStart(event) { + if (event.target.classList.contains("table-control-btn")) { + event.preventDefault(); + return; + } + + const tableId = event.currentTarget.dataset.tableId; + + event.dataTransfer.setData("text/plain", "table-container"); + event.dataTransfer.setData("table-id", tableId); + event.dataTransfer.effectAllowed = "move"; + + // Add visual feedback + event.currentTarget.style.opacity = "0.5"; + event.currentTarget.style.transform = "rotate(2deg)"; + } + + // Handle table container drag end + handleTableContainerDragEnd(event) { + event.currentTarget.style.opacity = "1"; + event.currentTarget.style.transform = "rotate(0deg)"; + } + + // Setup event listeners for table controls + setupTableEventListeners(tableContainer) { + const tableId = tableContainer.dataset.tableId; + + // Add row button + const addRowBtn = tableContainer.querySelector( + '.table-control-btn[title="Add Row"]' + ); + if (addRowBtn) { + addRowBtn.addEventListener("click", (e) => { + e.stopPropagation(); + this.addTableRow(e); + }); + } + + // Add column button + const addColBtn = tableContainer.querySelector( + '.table-control-btn[title="Add Column"]' + ); + if (addColBtn) { + addColBtn.addEventListener("click", (e) => { + e.stopPropagation(); + this.addTableColumn(e); + }); + } + + // Delete row button + const deleteRowBtn = tableContainer.querySelector( + '.table-control-btn[title="Delete Row"]' + ); + if (deleteRowBtn) { + deleteRowBtn.addEventListener("click", (e) => { + e.stopPropagation(); + this.deleteTableRow(e); + }); + } + + // Delete column button + const deleteColBtn = tableContainer.querySelector( + '.table-control-btn[title="Delete Column"]' + ); + if (deleteColBtn) { + deleteColBtn.addEventListener("click", (e) => { + e.stopPropagation(); + this.deleteTableColumn(e); + }); + } + + // Delete table button + const deleteTableBtn = tableContainer.querySelector( + '.table-control-btn[title="Delete Table"]' + ); + if (deleteTableBtn) { + deleteTableBtn.addEventListener("click", (e) => { + e.stopPropagation(); + this.deleteTable(e); + }); + } + + // Drag and drop for table container + tableContainer.addEventListener("dragstart", (e) => { + this.handleTableContainerDragStart(e); + }); + + tableContainer.addEventListener("dragend", (e) => { + this.handleTableContainerDragEnd(e); + }); + + // Prevent drag on control buttons + const controlButtons = + tableContainer.querySelectorAll(".table-control-btn"); + controlButtons.forEach((btn) => { + btn.addEventListener("dragstart", (e) => { + e.preventDefault(); + }); + }); + } + // Improved text insertion that's draggable anywhere + insertDraggableText() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) { + this.showError("Editor not found"); + return; + } + + // Save undo state before making changes + this.saveUndoState(); + + // Create draggable text element + const textElement = document.createElement("div"); + textElement.className = "draggable-element draggable-text"; + textElement.contentEditable = true; + textElement.innerHTML = "Click to edit text"; + + // Position absolutely for free placement + textElement.style.position = "absolute"; + textElement.style.left = "20px"; + textElement.style.top = "20px"; + textElement.style.minWidth = "150px"; + textElement.style.minHeight = "30px"; + textElement.style.padding = "8px"; + textElement.style.border = "2px dashed #4f46e5"; + textElement.style.borderRadius = "4px"; + textElement.style.backgroundColor = "rgba(255, 255, 255, 0.9)"; + textElement.style.zIndex = "1000"; + textElement.style.cursor = "move"; + textElement.style.fontFamily = "Inter, sans-serif"; + textElement.style.fontSize = "14px"; + textElement.style.lineHeight = "1.4"; + + // Add to editor + editor.appendChild(textElement); + + // Make it draggable and resizable + this.addResizeHandles(textElement); + this.makeDraggable(textElement); + + // Select the text for immediate editing + setTimeout(() => { + textElement.focus(); + const selection = window.getSelection(); + const range = document.createRange(); + range.selectNodeContents(textElement); + selection.removeAllRanges(); + selection.addRange(range); + }, 100); + } + + // Undo/Redo functionality + saveUndoState() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) return; + + const currentState = { + content: editor.innerHTML, + timestamp: Date.now(), + }; + + this.undoStack.push(currentState); + + // Limit undo stack size + if (this.undoStack.length > this.maxUndoSteps) { + this.undoStack.shift(); + } + + // Clear redo stack when new action is performed + this.redoStack = []; + } + undo() { + if (this.undoStack.length === 0) return; + + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) return; + + // Save current state to redo stack + const currentState = { + content: editor.innerHTML, + timestamp: Date.now(), + }; + this.redoStack.push(currentState); + + // Restore previous state + const previousState = this.undoStack.pop(); + editor.innerHTML = previousState.content; + + // Re-setup event handlers for any dynamic elements + this.setupEditorEventHandlers(); + } + + redo() { + if (this.redoStack.length === 0) return; + + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) return; + + // Save current state to undo stack + const currentState = { + content: editor.innerHTML, + timestamp: Date.now(), + }; + this.undoStack.push(currentState); + + // Restore next state + const nextState = this.redoStack.pop(); + editor.innerHTML = nextState.content; + + // Re-setup event handlers for any dynamic elements + this.setupEditorEventHandlers(); + } + + // Setup editor event handlers after undo/redo + setupEditorEventHandlers() { + this.setupEditorClickHandler(); + this.ensureEditorEditable(); + } + + // Find the first available category that has images + findFirstAvailableCategory() { + if (!this.realPropertyImages || this.realPropertyImages.length === 0) { + return "None"; + } + + // Define the order of categories to check + const categoryOrder = [ + "Interior", + "Exterior", + "Kitchen", + "Bedroom", + "Living Area", + "Parking", + "Anchor", + "Maps", + "None", + ]; + + // Check each category in order + for (let category of categoryOrder) { + const hasImages = this.realPropertyImages.some((img) => { + const imgCategory = img.category || img.pcrm__Category__c; + + if (category === "None") { + return ( + !imgCategory || + imgCategory === "" || + imgCategory === null || + imgCategory === undefined || + imgCategory === "None" + ); } - // Save undo state + return imgCategory === category; + }); + + if (hasImages) { + return category; + } + } + + // Fallback to None if no specific category has images + return "None"; + } + + // Ensure smart category selection only on initial load + ensureSmartCategorySelection() { + // Only run if initial category selection hasn't been done yet + if ( + !this.initialCategorySelected && + this.realPropertyImages && + this.realPropertyImages.length > 0 + ) { + const firstAvailableCategory = this.findFirstAvailableCategory(); + this.selectedCategory = firstAvailableCategory; + this.filterImagesByCategory(firstAvailableCategory); + this.initialCategorySelected = true; + + // Update button states + const categoryButtons = this.template.querySelectorAll( + ".category-btn-step2" + ); + categoryButtons.forEach((btn) => { + btn.classList.remove("active"); + if (btn.dataset.category === firstAvailableCategory) { + btn.classList.add("active"); + } + }); + } + } + + // Enhanced keyboard event handler + handleEditorKeydown(event) { + // Check for Ctrl+Z (Undo) + if ( + (event.ctrlKey || event.metaKey) && + event.key === "z" && + !event.shiftKey + ) { + event.preventDefault(); + this.undo(); + return; + } + + // Check for Ctrl+Y or Ctrl+Shift+Z (Redo) + if ( + (event.ctrlKey || event.metaKey) && + (event.key === "y" || (event.key === "z" && event.shiftKey)) + ) { + event.preventDefault(); + this.redo(); + return; + } + + // Save state before modifications (with debouncing) + if (!this.pendingUndoSave) { + this.pendingUndoSave = true; + setTimeout(() => { this.saveUndoState(); + this.pendingUndoSave = false; + }, 500); + } + } + // Enhanced image manipulation methods + addResizeHandles(container) { + const handles = ["nw", "ne", "sw", "se", "n", "s", "w", "e"]; - // Create table element using our new method (draggable/resizeable container like images) - const tableContainer = this.createTableElement(); - editor.appendChild(tableContainer); - // Default placement similar to images - tableContainer.style.left = '50px'; - tableContainer.style.top = '50px'; - // Enable drag + resize - this.addTableResizeHandles(tableContainer); - this.makeDraggable(tableContainer); - this.setupTableEventListeners(tableContainer); - - this.closeTableDialog(); - this.showSuccess('Table inserted successfully!'); + handles.forEach((handle) => { + const resizeHandle = document.createElement("div"); + resizeHandle.className = `resize-handle ${handle}`; + resizeHandle.style.position = "absolute"; + resizeHandle.style.background = "#4f46e5"; + resizeHandle.style.border = "2px solid white"; + resizeHandle.style.borderRadius = "50%"; + resizeHandle.style.width = "12px"; + resizeHandle.style.height = "12px"; + resizeHandle.style.zIndex = "1001"; + + // Position handles + switch (handle) { + case "nw": + resizeHandle.style.top = "-6px"; + resizeHandle.style.left = "-6px"; + resizeHandle.style.cursor = "nw-resize"; + break; + case "ne": + resizeHandle.style.top = "-6px"; + resizeHandle.style.right = "-6px"; + resizeHandle.style.cursor = "ne-resize"; + break; + case "sw": + resizeHandle.style.bottom = "-6px"; + resizeHandle.style.left = "-6px"; + resizeHandle.style.cursor = "sw-resize"; + break; + case "se": + resizeHandle.style.bottom = "-6px"; + resizeHandle.style.right = "-6px"; + resizeHandle.style.cursor = "se-resize"; + break; + case "n": + resizeHandle.style.top = "-6px"; + resizeHandle.style.left = "50%"; + resizeHandle.style.transform = "translateX(-50%)"; + resizeHandle.style.cursor = "n-resize"; + break; + case "s": + resizeHandle.style.bottom = "-6px"; + resizeHandle.style.left = "50%"; + resizeHandle.style.transform = "translateX(-50%)"; + resizeHandle.style.cursor = "s-resize"; + break; + case "w": + resizeHandle.style.top = "50%"; + resizeHandle.style.left = "-6px"; + resizeHandle.style.transform = "translateY(-50%)"; + resizeHandle.style.cursor = "w-resize"; + break; + case "e": + resizeHandle.style.top = "50%"; + resizeHandle.style.right = "-6px"; + resizeHandle.style.transform = "translateY(-50%)"; + resizeHandle.style.cursor = "e-resize"; + break; + } + + // Add resize functionality + this.addResizeFunctionality(resizeHandle, container, handle); + + container.appendChild(resizeHandle); + }); + } + + // Add delete handle to image + addDeleteHandle(container) { + const deleteHandle = document.createElement("button"); + deleteHandle.className = "delete-handle"; + deleteHandle.innerHTML = "×"; + deleteHandle.style.position = "absolute"; + deleteHandle.style.top = "-8px"; + deleteHandle.style.right = "-8px"; + deleteHandle.style.background = "#ef4444"; + deleteHandle.style.color = "white"; + deleteHandle.style.border = "none"; + deleteHandle.style.borderRadius = "50%"; + deleteHandle.style.width = "20px"; + deleteHandle.style.height = "20px"; + deleteHandle.style.fontSize = "12px"; + deleteHandle.style.cursor = "pointer"; + deleteHandle.style.zIndex = "1002"; + deleteHandle.style.display = "flex"; + deleteHandle.style.alignItems = "center"; + deleteHandle.style.justifyContent = "center"; + deleteHandle.style.boxShadow = "0 2px 4px rgba(0, 0, 0, 0.2)"; + + deleteHandle.addEventListener("click", (e) => { + e.stopPropagation(); + this.saveUndoState(); + container.remove(); + }); + + deleteHandle.addEventListener("mouseenter", () => { + deleteHandle.style.background = "#dc2626"; + deleteHandle.style.transform = "scale(1.1)"; + }); + + deleteHandle.addEventListener("mouseleave", () => { + deleteHandle.style.background = "#ef4444"; + deleteHandle.style.transform = "scale(1)"; + }); + + container.appendChild(deleteHandle); + } + + // Add resize functionality to handle + addResizeFunctionality(handle, container, direction) { + let isResizing = false; + let startX, startY, startWidth, startHeight, startLeft, startTop; + + handle.addEventListener("mousedown", (e) => { + e.stopPropagation(); + isResizing = true; + + startX = e.clientX; + startY = e.clientY; + startWidth = parseInt(window.getComputedStyle(container).width, 10); + startHeight = parseInt(window.getComputedStyle(container).height, 10); + startLeft = parseInt(window.getComputedStyle(container).left, 10); + startTop = parseInt(window.getComputedStyle(container).top, 10); + + document.addEventListener("mousemove", handleResize); + document.addEventListener("mouseup", stopResize); + }); + + const handleResize = (e) => { + if (!isResizing) return; + + const deltaX = e.clientX - startX; + const deltaY = e.clientY - startY; + + let newWidth = startWidth; + let newHeight = startHeight; + let newLeft = startLeft; + let newTop = startTop; + + switch (direction) { + case "se": + newWidth = Math.max(50, startWidth + deltaX); + newHeight = Math.max(50, startHeight + deltaY); + break; + case "sw": + newWidth = Math.max(50, startWidth - deltaX); + newHeight = Math.max(50, startHeight + deltaY); + newLeft = startLeft + (startWidth - newWidth); + break; + case "ne": + newWidth = Math.max(50, startWidth + deltaX); + newHeight = Math.max(50, startHeight - deltaY); + newTop = startTop + (startHeight - newHeight); + break; + case "nw": + newWidth = Math.max(50, startWidth - deltaX); + newHeight = Math.max(50, startHeight - deltaY); + newLeft = startLeft + (startWidth - newWidth); + newTop = startTop + (startHeight - newHeight); + break; + case "e": + newWidth = Math.max(50, startWidth + deltaX); + break; + case "w": + newWidth = Math.max(50, startWidth - deltaX); + newLeft = startLeft + (startWidth - newWidth); + break; + case "s": + newHeight = Math.max(50, startHeight + deltaY); + break; + case "n": + newHeight = Math.max(50, startHeight - deltaY); + newTop = startTop + (startHeight - newHeight); + break; + } + + container.style.width = newWidth + "px"; + container.style.height = newHeight + "px"; + container.style.left = newLeft + "px"; + container.style.top = newTop + "px"; + }; + + const stopResize = () => { + isResizing = false; + document.removeEventListener("mousemove", handleResize); + document.removeEventListener("mouseup", stopResize); + }; + } + // Make element draggable (enhanced version) + makeDraggable(element) { + let isDragging = false; + let startX, startY, startLeft, startTop; + + element.addEventListener("mousedown", (e) => { + // Don't start drag if clicking on resize handles or delete button + if ( + e.target.classList.contains("resize-handle") || + e.target.classList.contains("delete-handle") + ) { + return; + } + + isDragging = true; + startX = e.clientX; + startY = e.clientY; + startLeft = parseInt(window.getComputedStyle(element).left, 10); + startTop = parseInt(window.getComputedStyle(element).top, 10); + + element.style.cursor = "grabbing"; + document.addEventListener("mousemove", handleDrag); + document.addEventListener("mouseup", stopDrag); + }); + + const handleDrag = (e) => { + if (!isDragging) return; + + const deltaX = e.clientX - startX; + const deltaY = e.clientY - startY; + + element.style.left = startLeft + deltaX + "px"; + element.style.top = startTop + deltaY + "px"; + }; + + const stopDrag = () => { + isDragging = false; + element.style.cursor = "move"; + document.removeEventListener("mousemove", handleDrag); + document.removeEventListener("mouseup", stopDrag); + }; + } + // Select draggable element + selectDraggableElement(element) { + // Remove selection from all draggable elements + const editor = this.template.querySelector(".enhanced-editor-content"); + if (editor) { + const allDraggable = editor.querySelectorAll( + ".draggable-element, .draggable-image-container, .draggable-table-container" + ); + allDraggable.forEach((el) => { + if (el !== element) { + el.classList.remove("selected"); + // Remove any resize handles + const resizeHandles = el.querySelectorAll(".resize-handle"); + resizeHandles.forEach((handle) => handle.remove()); + // Remove any delete buttons + const deleteButtons = el.querySelectorAll( + ".delete-handle, .delete-image-btn" + ); + deleteButtons.forEach((btn) => btn.remove()); + } + }); } - // ===== DYNAMIC IMAGE REPLACEMENT UTILITIES ===== - - // Get first image from a specific category - getFirstImageByCategory(category) { - - if (!this.realPropertyImages || this.realPropertyImages.length === 0) { - return null; - } - - const categoryImages = this.realPropertyImages.filter(img => { - const imgCategory = img.category || img.pcrm__Category__c; - return imgCategory && imgCategory.toLowerCase() === category.toLowerCase(); - }); - - - return categoryImages.length > 0 ? categoryImages[0] : null; + // Add selection to clicked element + element.classList.add("selected"); + + // Add resize handles and controls to the selected element + if (element.classList.contains("draggable-image-container")) { + const img = element.querySelector("img"); + if (img) { + this.addResizeHandles(img); + this.addDeleteButton(element); + } + } else if (element.classList.contains("draggable-table-container")) { + this.addTableResizeHandles(element); + this.addDeleteButton(element); + } else if (element.tagName && element.tagName.toLowerCase() === "img") { + // If already wrapped, ensure handles on container + if ( + element.parentElement && + element.parentElement.classList && + element.parentElement.classList.contains("draggable-image-container") + ) { + const container = element.parentElement; + this.addResizeHandles(container); + this.makeDraggable(container); + this.addDeleteButton(container); + this.highlightSelectedElement(container); + return; + } + // Wrap plain image and add handles; preserve on-screen size and position + const editor = this.template.querySelector(".enhanced-editor-content"); + const rect = element.getBoundingClientRect(); + const editorRect = editor + ? editor.getBoundingClientRect() + : { left: 0, top: 0 }; + const currentWidth = element.offsetWidth; + const currentHeight = element.offsetHeight; + + const container = document.createElement("div"); + container.className = "draggable-image-container"; + container.style.position = "absolute"; + container.style.left = + rect.left - editorRect.left + (editor ? editor.scrollLeft : 0) + "px"; + container.style.top = + rect.top - editorRect.top + (editor ? editor.scrollTop : 0) + "px"; + container.style.zIndex = + window.getComputedStyle(element).zIndex || "auto"; + container.style.display = "inline-block"; + + // Move the image into container and preserve size + // Set container and image sizing + container.style.width = currentWidth + "px"; + container.style.height = currentHeight + "px"; + element.style.width = "100%"; + element.style.height = "100%"; + element.style.display = "block"; + element.style.objectFit = "cover"; + + if (editor) { + editor.appendChild(container); + } else { + element.parentNode.insertBefore(container, element); + } + container.appendChild(element); + container.classList.add("no-frame"); + this.addResizeHandles(container); + this.makeDraggable(container); + this.addDeleteButton(container); + this.highlightSelectedElement(container); } - - // Direct method to get exterior image URL - getExteriorImageUrl() { - - if (!this.realPropertyImages || this.realPropertyImages.length === 0) { - return 'https://images.unsplash.com/photo-1568605114967-8130f3a36994?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'; - } - - // Look for exterior images first - const exteriorImages = this.realPropertyImages.filter(img => { - const category = img.category || img.pcrm__Category__c; - return category && category.toLowerCase().includes('exterior'); - }); - - if (exteriorImages.length > 0) { - return exteriorImages[0].url; - } - - // If no exterior, use first available image - if (this.realPropertyImages.length > 0) { - return this.realPropertyImages[0].url; - } - - return 'https://images.unsplash.com/photo-1568605114967-8130f3a36994?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'; - } - - // Direct method to get maps image URL - getMapsImageUrl() { - - if (!this.realPropertyImages || this.realPropertyImages.length === 0) { - return 'https://images.unsplash.com/photo-1524661135-423995f22d0b?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'; - } - - // Look for maps images first - check both exact match and contains - const mapsImages = this.realPropertyImages.filter(img => { - const category = img.category || img.pcrm__Category__c; - return category && (category.toLowerCase() === 'maps' || category.toLowerCase().includes('maps')); - }); - - - if (mapsImages.length > 0) { - return mapsImages[0].url; - } - - // Look for anchor images as fallback - const anchorImages = this.realPropertyImages.filter(img => { - const category = img.category || img.pcrm__Category__c; - return category && (category.toLowerCase() === 'anchor' || category.toLowerCase().includes('anchor')); - }); - - - if (anchorImages.length > 0) { - return anchorImages[0].url; - } - - return 'https://images.unsplash.com/photo-1524661135-423995f22d0b?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'; - } - - // Method to replace background-image URLs in CSS at runtime - replaceBackgroundImagesInHTML(htmlContent) { - - if (!this.realPropertyImages || this.realPropertyImages.length === 0) { - return htmlContent; - } - - const exteriorImageUrl = this.getExteriorImageUrl(); - - // Replace any hardcoded background-image URLs with the property's exterior image - let updatedHTML = htmlContent; - - // Pattern to match background-image: url('...') or background-image: url("...") - const backgroundImagePattern = /background-image\s*:\s*url\(['"][^'"]*['"]\)/gi; - - // Replace all background-image URLs with the property's exterior image - updatedHTML = updatedHTML.replace(backgroundImagePattern, `background-image: url('${exteriorImageUrl}')`); - - return updatedHTML; - } - // Method to dynamically update CSS background-image rules after template loads - updateCSSBackgroundImages() { - const editor = this.template.querySelector('.enhanced-editor-content'); - if (!editor) return; - if (!this.realPropertyImages || this.realPropertyImages.length === 0) return; - const exteriorImageUrl = this.getExteriorImageUrl(); - // Scope to styles inside the editor only - const styleElements = editor.querySelectorAll('style'); - styleElements.forEach(styleElement => { - const cssText = styleElement.textContent || ''; - const backgroundImagePattern = /background-image\s*:\s*url\(['"][^'"]*['"]\)/gi; - const updatedCSS = cssText.replace(backgroundImagePattern, `background-image: url('${exteriorImageUrl}')`); - if (updatedCSS !== cssText) styleElement.textContent = updatedCSS; - }); - // Update inline background-image styles only within editor - const elementsWithBackground = editor.querySelectorAll('[style*="background-image"]'); - elementsWithBackground.forEach(element => { - const currentStyle = element.getAttribute('style') || ''; - const backgroundImagePattern = /background-image\s*:\s*url\(['"][^'"]*['"]\)/gi; - const updatedStyle = currentStyle.replace(backgroundImagePattern, `background-image: url('${exteriorImageUrl}')`); - if (updatedStyle !== currentStyle) element.setAttribute('style', updatedStyle); - }); - } - - // Get all images from a specific category - getAllImagesByCategory(category) { - if (!this.realPropertyImages || this.realPropertyImages.length === 0) { - return []; - } - - return this.realPropertyImages.filter(img => - img.category && img.category.toLowerCase() === category.toLowerCase() - ); - } - - // Get uncategorized images (no category or category is null/empty) - getUncategorizedImages() { - - if (!this.realPropertyImages || this.realPropertyImages.length === 0) { - return []; - } - - const uncategorized = this.realPropertyImages.filter(img => - !img.category || img.category.trim() === '' || img.category.toLowerCase() === 'none' - ); - - - return uncategorized; - } - - // Smart image replacement - tries multiple categories in order of preference - getSmartImageForSection(sectionType, fallbackUrl) { - - const categoryPriority = { - 'exterior': ['Exterior', 'Anchor', 'None'], - 'interior': ['Interior', 'Living Area', 'Kitchen', 'Bedroom', 'None'], - 'kitchen': ['Kitchen', 'Interior', 'Living Area', 'None'], - 'bedroom': ['Bedroom', 'Interior', 'None'], - 'living': ['Living Area', 'Interior', 'Kitchen', 'None'], - 'bathroom': ['Bathroom', 'Interior', 'None'], - 'parking': ['Parking', 'Exterior', 'None'], - 'maps': ['Maps', 'Anchor', 'Exterior', 'None'], - 'gallery': ['Interior', 'Exterior', 'Kitchen', 'Bedroom', 'Living Area', 'None'] - }; - - const categories = categoryPriority[sectionType] || ['Interior', 'Exterior', 'None']; - - for (const category of categories) { - const image = this.getFirstImageByCategory(category); - if (image && image.url) { - return image.url; - } - } - - return fallbackUrl; - } - - // Generate Property Gallery HTML for uncategorized images - generatePropertyGalleryHTML() { - - if (!this.realPropertyImages || this.realPropertyImages.length === 0) { - return '
    No images available
    '; - } - - - let galleryHTML = ''; - this.realPropertyImages.forEach((image, index) => { - const title = image.title || image.pcrm__Title__c || `Property Image ${index + 1}`; - galleryHTML += `${title}`; - }); - - return galleryHTML; + } + + // Add delete button to element + addDeleteButton(element) { + // Remove existing delete button if any + const existingDelete = element.querySelector( + ".delete-handle, .delete-image-btn" + ); + if (existingDelete) { + existingDelete.remove(); } - - // Generate gallery HTML for a provided subset of images - generatePropertyGalleryHTMLForImages(imagesSubset) { - if (!imagesSubset || imagesSubset.length === 0) { - return '
    No images available
    '; - } - let galleryHTML = ''; - imagesSubset.forEach((image, index) => { - const title = image.title || image.pcrm__Title__c || `Property Image ${index + 1}`; - galleryHTML += `${title}`; - }); - return galleryHTML; - } - - - // ===== TABLE DRAG AND DROP FUNCTIONALITY ===== - - // Handle table drag start - handleTableDragStart(event) { - this.isDraggingTable = true; - - // Store table configuration data - this.draggedTableData = { - rows: this.tableRows, - cols: this.tableCols, - includeHeader: this.includeHeader - }; - - // Set drag data - event.dataTransfer.setData('text/plain', 'table'); - event.dataTransfer.effectAllowed = 'copy'; - - // Add visual feedback - event.currentTarget.classList.add('dragging'); - - // Add drag over class to editor - setTimeout(() => { - const editor = this.template.querySelector('.enhanced-editor-content'); - if (editor) { - editor.classList.add('drag-over'); - } - }, 100); - } - - // Handle editor drag over - handleEditorDragOver(event) { - // Allow dropping tables and images - event.preventDefault(); - event.dataTransfer.dropEffect = 'copy'; - } - - // Handle editor drop - handleEditorDrop(event) { - event.preventDefault(); - - const editor = this.template.querySelector('.enhanced-editor-content'); - if (!editor) { this.showError("Editor not found"); return; } - - const dataType = event.dataTransfer.getData('text/plain'); - if (dataType === 'image' && this.currentImage) { - // Insert draggable image at drop - const img = document.createElement('img'); - img.src = this.currentImage.url; - img.style.maxWidth = '300px'; - img.style.height = 'auto'; - img.className = 'draggable-image'; - - const container = document.createElement('div'); - container.className = 'draggable-image-container'; - container.style.position = 'absolute'; - container.style.left = (event.clientX - editor.getBoundingClientRect().left) + 'px'; - container.style.top = (event.clientY - editor.getBoundingClientRect().top) + 'px'; - container.appendChild(img); - editor.appendChild(container); - this.makeImagesDraggableAndResizable([img]); - this.showSuccess('Image inserted via drag and drop!'); - return; - } - - if (!this.isDraggingTable || !this.draggedTableData) { - return; - } - - - // Remove visual feedback - this.removeTableDragFeedback(); - - // Get drop position - // editor already resolved above - - // Save undo state before making changes - this.saveUndoState(); - - // Insert table at drop position - this.insertTableAtPosition(editor, this.draggedTableData, event); - - // Reset drag state - this.isDraggingTable = false; - this.draggedTableData = null; - - this.showSuccess('Table inserted via drag and drop!'); - } - // Insert table at specific position - insertTableAtPosition(editor, tableData, event) { - // Get cursor position relative to editor - const rect = editor.getBoundingClientRect(); - const x = event.clientX - rect.left; - const y = event.clientY - rect.top; - - // Create table element directly using DOM methods (same as insertTable) - const tableId = `table-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; - - // Create container div (use draggable-table-container to get drag/resize behavior) - const container = document.createElement('div'); - container.className = 'draggable-table-container'; - container.setAttribute('data-table-id', tableId); - container.style.cssText = 'position: absolute; left: 0; top: 0; width: 400px; min-width: 200px; min-height: 150px; z-index: 1000; border: 2px dashed #667eea; border-radius: 8px; background: white; box-shadow: 0 4px 12px rgba(0,0,0,0.1); overflow: hidden;'; - - // Create table controls - const controls = document.createElement('div'); - controls.className = 'table-controls'; - controls.style.cssText = 'position: absolute; top: -40px; left: 0; background: white; padding: 5px; border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); opacity: 0; transition: opacity 0.2s;'; - - // Add control buttons (same as insertTable) - const controlGroup1 = document.createElement('div'); - controlGroup1.className = 'table-control-group'; - controlGroup1.style.cssText = 'display: flex; gap: 5px;'; - - const addRowBtn = document.createElement('button'); - addRowBtn.className = 'table-control-btn'; - addRowBtn.setAttribute('data-table-id', tableId); - addRowBtn.textContent = '+ Row'; - addRowBtn.style.cssText = 'padding: 4px 8px; border: 1px solid #ddd; background: white; cursor: pointer;'; - - const addColBtn = document.createElement('button'); - addColBtn.className = 'table-control-btn'; - addColBtn.setAttribute('data-table-id', tableId); - addColBtn.textContent = '+ Col'; - addColBtn.style.cssText = 'padding: 4px 8px; border: 1px solid #ddd; background: white; cursor: pointer;'; - - const delRowBtn = document.createElement('button'); - delRowBtn.className = 'table-control-btn'; - delRowBtn.setAttribute('data-table-id', tableId); - delRowBtn.textContent = '- Row'; - delRowBtn.style.cssText = 'padding: 4px 8px; border: 1px solid #ddd; background: white; cursor: pointer;'; - - const delColBtn = document.createElement('button'); - delColBtn.className = 'table-control-btn'; - delColBtn.setAttribute('data-table-id', tableId); - delColBtn.textContent = '- Col'; - delColBtn.style.cssText = 'padding: 4px 8px; border: 1px solid #ddd; background: white; cursor: pointer;'; - - const deleteBtn = document.createElement('button'); - deleteBtn.className = 'table-control-btn delete'; - deleteBtn.setAttribute('data-table-id', tableId); - deleteBtn.textContent = '🗑️'; - deleteBtn.style.cssText = 'padding: 4px 8px; border: 1px solid #ff4444; background: #ff4444; color: white; cursor: pointer;'; - - controlGroup1.appendChild(addRowBtn); - controlGroup1.appendChild(addColBtn); - controlGroup1.appendChild(delRowBtn); - controlGroup1.appendChild(delColBtn); - - const controlGroup2 = document.createElement('div'); - controlGroup2.className = 'table-control-group'; - controlGroup2.style.cssText = 'display: flex; gap: 5px; margin-left: 10px;'; - controlGroup2.appendChild(deleteBtn); - - controls.appendChild(controlGroup1); - controls.appendChild(controlGroup2); - - // Create table - const table = document.createElement('table'); - table.className = 'inserted-table'; - table.id = tableId; - table.style.cssText = 'border-collapse: collapse; width: 100%; margin: 1rem 0; border: 2px solid #333; background-color: white;'; - - // Create table body - const tbody = document.createElement('tbody'); - - // Add header row if requested - if (tableData.includeHeader) { - const thead = document.createElement('thead'); - const headerRow = document.createElement('tr'); - - for (let col = 0; col < tableData.cols; col++) { - const th = document.createElement('th'); - th.style.cssText = 'border: 1px solid #333; padding: 12px; background-color: #4f46e5; color: white; font-weight: bold; text-align: center;'; - th.setAttribute('contenteditable', 'true'); - th.textContent = `Header ${col + 1}`; - headerRow.appendChild(th); - } - - thead.appendChild(headerRow); - table.appendChild(thead); - } - - // Add body rows - for (let row = 0; row < tableData.rows; row++) { - const tr = document.createElement('tr'); - - for (let col = 0; col < tableData.cols; col++) { - const td = document.createElement('td'); - td.style.cssText = 'border: 1px solid #333; padding: 12px; background-color: #f8f9fa; min-width: 100px; min-height: 40px;'; - td.setAttribute('contenteditable', 'true'); - td.textContent = `Cell ${row + 1}-${col + 1}`; - tr.appendChild(td); - } - - tbody.appendChild(tr); - } - - table.appendChild(tbody); - - // Assemble the container - container.appendChild(controls); - container.appendChild(table); - - const tableElement = container; - - // Try to find the best insertion point - const range = document.createRange(); - const walker = document.createTreeWalker( - editor, - NodeFilter.SHOW_TEXT, - null, - false - ); - - let bestNode = null; - let bestDistance = Infinity; - let node; - - // Find the closest text node to the drop position - while (node = walker.nextNode()) { - const nodeRect = node.getBoundingClientRect(); - const nodeX = nodeRect.left - rect.left; - const nodeY = nodeRect.top - rect.top; - const distance = Math.sqrt((x - nodeX) ** 2 + (y - nodeY) ** 2); - - if (distance < bestDistance) { - bestDistance = distance; - bestNode = node; - } - } - - // Insert into editor (append at end for simplicity) - editor.appendChild(tableElement); - // Position at drop point - tableElement.style.left = Math.max(0, Math.min(x, editor.clientWidth - tableElement.offsetWidth)) + 'px'; - tableElement.style.top = Math.max(0, Math.min(y, editor.scrollHeight - tableElement.offsetHeight)) + 'px'; - - // Add drag/resize to the new table - this.addTableResizeHandles(tableElement); - this.makeDraggable(tableElement); - this.setupTableEventListeners(tableElement); - } - - // Remove table drag feedback - removeTableDragFeedback() { - // Remove dragging class from button - const tableBtn = this.template.querySelector('.draggable-table-btn'); - if (tableBtn) { - tableBtn.classList.remove('dragging'); - } - - // Remove drag-over class from editor - const editor = this.template.querySelector('.enhanced-editor-content'); - if (editor) { - editor.classList.remove('drag-over'); - } - } - - // ===== TABLE EDITING FUNCTIONALITY ===== - - // Add row to table - addTableRow(event) { - const tableId = event.currentTarget.dataset.tableId; - const table = document.getElementById(tableId); - if (!table) return; - - this.saveUndoState(); - - const tbody = table.querySelector('tbody'); - const firstRow = tbody.querySelector('tr'); - if (!firstRow) return; - - const newRow = firstRow.cloneNode(true); - const cells = newRow.querySelectorAll('td, th'); - cells.forEach((cell, index) => { - cell.textContent = `Cell ${tbody.children.length + 1}-${index + 1}`; - }); - - tbody.appendChild(newRow); - this.showSuccess('Row added successfully!'); - } - // Add column to table - addTableColumn(event) { - const tableId = event.currentTarget.dataset.tableId; - const table = document.getElementById(tableId); - if (!table) return; - - this.saveUndoState(); - - const rows = table.querySelectorAll('tr'); - rows.forEach((row, rowIndex) => { - const newCell = document.createElement(row.cells[0].tagName.toLowerCase()); - newCell.style.border = '1px solid #ddd'; - newCell.style.padding = '8px'; - newCell.contentEditable = 'true'; - - if (row.cells[0].tagName === 'TH') { - newCell.style.backgroundColor = '#f2f2f2'; - newCell.style.fontWeight = 'bold'; - newCell.textContent = `Header ${row.cells.length + 1}`; - } else { - newCell.textContent = `Cell ${rowIndex}-${row.cells.length + 1}`; - } - - row.appendChild(newCell); - }); - - this.showSuccess('Column added successfully!'); - } - - // Delete row from table - deleteTableRow(event) { - const tableId = event.currentTarget.dataset.tableId; - const table = document.getElementById(tableId); - if (!table) return; - - const tbody = table.querySelector('tbody'); - if (tbody.children.length <= 1) { - this.showError('Cannot delete the last row!'); - return; - } - - this.saveUndoState(); - tbody.removeChild(tbody.lastChild); - this.showSuccess('Row deleted successfully!'); - } - - // Delete column from table - deleteTableColumn(event) { - const tableId = event.currentTarget.dataset.tableId; - const table = document.getElementById(tableId); - if (!table) return; - - const firstRow = table.querySelector('tr'); - if (!firstRow || firstRow.cells.length <= 1) { - this.showError('Cannot delete the last column!'); - return; - } - - this.saveUndoState(); - - const rows = table.querySelectorAll('tr'); - rows.forEach(row => { - if (row.cells.length > 0) { - row.removeChild(row.lastChild); - } - }); - - this.showSuccess('Column deleted successfully!'); - } - - // Delete entire table - deleteTable(event) { - const tableId = event.currentTarget.dataset.tableId; - const tableContainer = document.querySelector(`[data-table-id="${tableId}"]`); - if (!tableContainer) return; - - this.saveUndoState(); - tableContainer.remove(); - this.showSuccess('Table deleted successfully!'); - } - - // Handle table drag start (for moving tables) - handleTableContainerDragStart(event) { - if (event.target.classList.contains('table-control-btn')) { - event.preventDefault(); - return; - } - - const tableId = event.currentTarget.dataset.tableId; - - event.dataTransfer.setData('text/plain', 'table-container'); - event.dataTransfer.setData('table-id', tableId); - event.dataTransfer.effectAllowed = 'move'; - - // Add visual feedback - event.currentTarget.style.opacity = '0.5'; - event.currentTarget.style.transform = 'rotate(2deg)'; - } - - // Handle table container drag end - handleTableContainerDragEnd(event) { - event.currentTarget.style.opacity = '1'; - event.currentTarget.style.transform = 'rotate(0deg)'; - } - - // Setup event listeners for table controls - setupTableEventListeners(tableContainer) { - const tableId = tableContainer.dataset.tableId; - - // Add row button - const addRowBtn = tableContainer.querySelector('.table-control-btn[title="Add Row"]'); - if (addRowBtn) { - addRowBtn.addEventListener('click', (e) => { - e.stopPropagation(); - this.addTableRow(e); - }); - } - - // Add column button - const addColBtn = tableContainer.querySelector('.table-control-btn[title="Add Column"]'); - if (addColBtn) { - addColBtn.addEventListener('click', (e) => { - e.stopPropagation(); - this.addTableColumn(e); - }); - } - - // Delete row button - const deleteRowBtn = tableContainer.querySelector('.table-control-btn[title="Delete Row"]'); - if (deleteRowBtn) { - deleteRowBtn.addEventListener('click', (e) => { - e.stopPropagation(); - this.deleteTableRow(e); - }); - } - - // Delete column button - const deleteColBtn = tableContainer.querySelector('.table-control-btn[title="Delete Column"]'); - if (deleteColBtn) { - deleteColBtn.addEventListener('click', (e) => { - e.stopPropagation(); - this.deleteTableColumn(e); - }); - } - - // Delete table button - const deleteTableBtn = tableContainer.querySelector('.table-control-btn[title="Delete Table"]'); - if (deleteTableBtn) { - deleteTableBtn.addEventListener('click', (e) => { - e.stopPropagation(); - this.deleteTable(e); - }); - } - - // Drag and drop for table container - tableContainer.addEventListener('dragstart', (e) => { - this.handleTableContainerDragStart(e); - }); - - tableContainer.addEventListener('dragend', (e) => { - this.handleTableContainerDragEnd(e); - }); - - // Prevent drag on control buttons - const controlButtons = tableContainer.querySelectorAll('.table-control-btn'); - controlButtons.forEach(btn => { - btn.addEventListener('dragstart', (e) => { - e.preventDefault(); - }); - }); - } - // Improved text insertion that's draggable anywhere - insertDraggableText() { - const editor = this.template.querySelector('.enhanced-editor-content'); - if (!editor) { - this.showError('Editor not found'); - return; - } - - // Save undo state before making changes - this.saveUndoState(); - - // Create draggable text element - const textElement = document.createElement('div'); - textElement.className = 'draggable-element draggable-text'; - textElement.contentEditable = true; - textElement.innerHTML = 'Click to edit text'; - - // Position absolutely for free placement - textElement.style.position = 'absolute'; - textElement.style.left = '20px'; - textElement.style.top = '20px'; - textElement.style.minWidth = '150px'; - textElement.style.minHeight = '30px'; - textElement.style.padding = '8px'; - textElement.style.border = '2px dashed #4f46e5'; - textElement.style.borderRadius = '4px'; - textElement.style.backgroundColor = 'rgba(255, 255, 255, 0.9)'; - textElement.style.zIndex = '1000'; - textElement.style.cursor = 'move'; - textElement.style.fontFamily = 'Inter, sans-serif'; - textElement.style.fontSize = '14px'; - textElement.style.lineHeight = '1.4'; - - // Add to editor - editor.appendChild(textElement); - - // Make it draggable and resizable - this.addResizeHandles(textElement); - this.makeDraggable(textElement); - - // Select the text for immediate editing - setTimeout(() => { - textElement.focus(); - const selection = window.getSelection(); - const range = document.createRange(); - range.selectNodeContents(textElement); - selection.removeAllRanges(); - selection.addRange(range); - }, 100); - - } - - // Undo/Redo functionality - saveUndoState() { - const editor = this.template.querySelector('.enhanced-editor-content'); - if (!editor) return; - - const currentState = { - content: editor.innerHTML, - timestamp: Date.now() - }; - - this.undoStack.push(currentState); - - // Limit undo stack size - if (this.undoStack.length > this.maxUndoSteps) { - this.undoStack.shift(); - } - - // Clear redo stack when new action is performed - this.redoStack = []; - } - - undo() { - if (this.undoStack.length === 0) return; - - const editor = this.template.querySelector('.enhanced-editor-content'); - if (!editor) return; - - // Save current state to redo stack - const currentState = { - content: editor.innerHTML, - timestamp: Date.now() - }; - this.redoStack.push(currentState); - - // Restore previous state - const previousState = this.undoStack.pop(); - editor.innerHTML = previousState.content; - - // Re-setup event handlers for any dynamic elements - this.setupEditorEventHandlers(); - - } - - redo() { - if (this.redoStack.length === 0) return; - - const editor = this.template.querySelector('.enhanced-editor-content'); - if (!editor) return; - - // Save current state to undo stack - const currentState = { - content: editor.innerHTML, - timestamp: Date.now() - }; - this.undoStack.push(currentState); - - // Restore next state - const nextState = this.redoStack.pop(); - editor.innerHTML = nextState.content; - - // Re-setup event handlers for any dynamic elements - this.setupEditorEventHandlers(); - - } - - // Setup editor event handlers after undo/redo - setupEditorEventHandlers() { - this.setupEditorClickHandler(); - this.ensureEditorEditable(); - } - - // Find the first available category that has images - findFirstAvailableCategory() { - if (!this.realPropertyImages || this.realPropertyImages.length === 0) { - return 'None'; - } - - // Define the order of categories to check - const categoryOrder = ['Interior', 'Exterior', 'Kitchen', 'Bedroom', 'Living Area', 'Parking', 'Anchor', 'Maps', 'None']; - - // Check each category in order - for (let category of categoryOrder) { - const hasImages = this.realPropertyImages.some(img => { - const imgCategory = img.category || img.pcrm__Category__c; - - if (category === 'None') { - return !imgCategory || imgCategory === '' || imgCategory === null || imgCategory === undefined || imgCategory === 'None'; - } - - return imgCategory === category; - }); - - if (hasImages) { - return category; - } - } - - // Fallback to None if no specific category has images - return 'None'; - } - - // Ensure smart category selection only on initial load - ensureSmartCategorySelection() { - // Only run if initial category selection hasn't been done yet - if (!this.initialCategorySelected && this.realPropertyImages && this.realPropertyImages.length > 0) { - const firstAvailableCategory = this.findFirstAvailableCategory(); - this.selectedCategory = firstAvailableCategory; - this.filterImagesByCategory(firstAvailableCategory); - this.initialCategorySelected = true; - - // Update button states - const categoryButtons = this.template.querySelectorAll('.category-btn-step2'); - categoryButtons.forEach(btn => { - btn.classList.remove('active'); - if (btn.dataset.category === firstAvailableCategory) { - btn.classList.add('active'); - } - }); - } - } - - - - // Enhanced keyboard event handler - handleEditorKeydown(event) { - // Check for Ctrl+Z (Undo) - if ((event.ctrlKey || event.metaKey) && event.key === 'z' && !event.shiftKey) { - event.preventDefault(); - this.undo(); - return; - } - - // Check for Ctrl+Y or Ctrl+Shift+Z (Redo) - if ((event.ctrlKey || event.metaKey) && - (event.key === 'y' || (event.key === 'z' && event.shiftKey))) { - event.preventDefault(); - this.redo(); - return; - } - - // Save state before modifications (with debouncing) - if (!this.pendingUndoSave) { - this.pendingUndoSave = true; - setTimeout(() => { - this.saveUndoState(); - this.pendingUndoSave = false; - }, 500); - } - } - // Enhanced image manipulation methods - addResizeHandles(container) { - const handles = ['nw', 'ne', 'sw', 'se', 'n', 's', 'w', 'e']; - - handles.forEach(handle => { - const resizeHandle = document.createElement('div'); - resizeHandle.className = `resize-handle ${handle}`; - resizeHandle.style.position = 'absolute'; - resizeHandle.style.background = '#4f46e5'; - resizeHandle.style.border = '2px solid white'; - resizeHandle.style.borderRadius = '50%'; - resizeHandle.style.width = '12px'; - resizeHandle.style.height = '12px'; - resizeHandle.style.zIndex = '1001'; - - // Position handles - switch(handle) { - case 'nw': resizeHandle.style.top = '-6px'; resizeHandle.style.left = '-6px'; resizeHandle.style.cursor = 'nw-resize'; break; - case 'ne': resizeHandle.style.top = '-6px'; resizeHandle.style.right = '-6px'; resizeHandle.style.cursor = 'ne-resize'; break; - case 'sw': resizeHandle.style.bottom = '-6px'; resizeHandle.style.left = '-6px'; resizeHandle.style.cursor = 'sw-resize'; break; - case 'se': resizeHandle.style.bottom = '-6px'; resizeHandle.style.right = '-6px'; resizeHandle.style.cursor = 'se-resize'; break; - case 'n': resizeHandle.style.top = '-6px'; resizeHandle.style.left = '50%'; resizeHandle.style.transform = 'translateX(-50%)'; resizeHandle.style.cursor = 'n-resize'; break; - case 's': resizeHandle.style.bottom = '-6px'; resizeHandle.style.left = '50%'; resizeHandle.style.transform = 'translateX(-50%)'; resizeHandle.style.cursor = 's-resize'; break; - case 'w': resizeHandle.style.top = '50%'; resizeHandle.style.left = '-6px'; resizeHandle.style.transform = 'translateY(-50%)'; resizeHandle.style.cursor = 'w-resize'; break; - case 'e': resizeHandle.style.top = '50%'; resizeHandle.style.right = '-6px'; resizeHandle.style.transform = 'translateY(-50%)'; resizeHandle.style.cursor = 'e-resize'; break; - } - - // Add resize functionality - this.addResizeFunctionality(resizeHandle, container, handle); - - container.appendChild(resizeHandle); - }); - } - - // Add delete handle to image - addDeleteHandle(container) { - const deleteHandle = document.createElement('button'); - deleteHandle.className = 'delete-handle'; - deleteHandle.innerHTML = '×'; - deleteHandle.style.position = 'absolute'; - deleteHandle.style.top = '-8px'; - deleteHandle.style.right = '-8px'; - deleteHandle.style.background = '#ef4444'; - deleteHandle.style.color = 'white'; - deleteHandle.style.border = 'none'; - deleteHandle.style.borderRadius = '50%'; - deleteHandle.style.width = '20px'; - deleteHandle.style.height = '20px'; - deleteHandle.style.fontSize = '12px'; - deleteHandle.style.cursor = 'pointer'; - deleteHandle.style.zIndex = '1002'; - deleteHandle.style.display = 'flex'; - deleteHandle.style.alignItems = 'center'; - deleteHandle.style.justifyContent = 'center'; - deleteHandle.style.boxShadow = '0 2px 4px rgba(0, 0, 0, 0.2)'; - - deleteHandle.addEventListener('click', (e) => { - e.stopPropagation(); - this.saveUndoState(); - container.remove(); - }); - - deleteHandle.addEventListener('mouseenter', () => { - deleteHandle.style.background = '#dc2626'; - deleteHandle.style.transform = 'scale(1.1)'; - }); - - deleteHandle.addEventListener('mouseleave', () => { - deleteHandle.style.background = '#ef4444'; - deleteHandle.style.transform = 'scale(1)'; - }); - - container.appendChild(deleteHandle); - } - - // Add resize functionality to handle - addResizeFunctionality(handle, container, direction) { - let isResizing = false; - let startX, startY, startWidth, startHeight, startLeft, startTop; - - handle.addEventListener('mousedown', (e) => { - e.stopPropagation(); - isResizing = true; - - startX = e.clientX; - startY = e.clientY; - startWidth = parseInt(window.getComputedStyle(container).width, 10); - startHeight = parseInt(window.getComputedStyle(container).height, 10); - startLeft = parseInt(window.getComputedStyle(container).left, 10); - startTop = parseInt(window.getComputedStyle(container).top, 10); - - document.addEventListener('mousemove', handleResize); - document.addEventListener('mouseup', stopResize); - }); - - const handleResize = (e) => { - if (!isResizing) return; - - const deltaX = e.clientX - startX; - const deltaY = e.clientY - startY; - - let newWidth = startWidth; - let newHeight = startHeight; - let newLeft = startLeft; - let newTop = startTop; - - switch(direction) { - case 'se': - newWidth = Math.max(50, startWidth + deltaX); - newHeight = Math.max(50, startHeight + deltaY); - break; - case 'sw': - newWidth = Math.max(50, startWidth - deltaX); - newHeight = Math.max(50, startHeight + deltaY); - newLeft = startLeft + (startWidth - newWidth); - break; - case 'ne': - newWidth = Math.max(50, startWidth + deltaX); - newHeight = Math.max(50, startHeight - deltaY); - newTop = startTop + (startHeight - newHeight); - break; - case 'nw': - newWidth = Math.max(50, startWidth - deltaX); - newHeight = Math.max(50, startHeight - deltaY); - newLeft = startLeft + (startWidth - newWidth); - newTop = startTop + (startHeight - newHeight); - break; - case 'e': - newWidth = Math.max(50, startWidth + deltaX); - break; - case 'w': - newWidth = Math.max(50, startWidth - deltaX); - newLeft = startLeft + (startWidth - newWidth); - break; - case 's': - newHeight = Math.max(50, startHeight + deltaY); - break; - case 'n': - newHeight = Math.max(50, startHeight - deltaY); - newTop = startTop + (startHeight - newHeight); - break; - } - - container.style.width = newWidth + 'px'; - container.style.height = newHeight + 'px'; - container.style.left = newLeft + 'px'; - container.style.top = newTop + 'px'; - }; - - const stopResize = () => { - isResizing = false; - document.removeEventListener('mousemove', handleResize); - document.removeEventListener('mouseup', stopResize); - }; - } - - // Make element draggable (enhanced version) - makeDraggable(element) { - let isDragging = false; - let startX, startY, startLeft, startTop; - - element.addEventListener('mousedown', (e) => { - // Don't start drag if clicking on resize handles or delete button - if (e.target.classList.contains('resize-handle') || e.target.classList.contains('delete-handle')) { - return; - } - - isDragging = true; - startX = e.clientX; - startY = e.clientY; - startLeft = parseInt(window.getComputedStyle(element).left, 10); - startTop = parseInt(window.getComputedStyle(element).top, 10); - - element.style.cursor = 'grabbing'; - document.addEventListener('mousemove', handleDrag); - document.addEventListener('mouseup', stopDrag); - }); - - const handleDrag = (e) => { - if (!isDragging) return; - - const deltaX = e.clientX - startX; - const deltaY = e.clientY - startY; - - element.style.left = (startLeft + deltaX) + 'px'; - element.style.top = (startTop + deltaY) + 'px'; - }; - - const stopDrag = () => { - isDragging = false; - element.style.cursor = 'move'; - document.removeEventListener('mousemove', handleDrag); - document.removeEventListener('mouseup', stopDrag); - }; - } - // Select draggable element - selectDraggableElement(element) { - // Remove selection from all draggable elements - const editor = this.template.querySelector('.enhanced-editor-content'); - if (editor) { - const allDraggable = editor.querySelectorAll('.draggable-element, .draggable-image-container, .draggable-table-container'); - allDraggable.forEach(el => { - if (el !== element) { - el.classList.remove('selected'); - // Remove any resize handles - const resizeHandles = el.querySelectorAll('.resize-handle'); - resizeHandles.forEach(handle => handle.remove()); - // Remove any delete buttons - const deleteButtons = el.querySelectorAll('.delete-handle, .delete-image-btn'); - deleteButtons.forEach(btn => btn.remove()); - } - }); - } - - // Add selection to clicked element - element.classList.add('selected'); - - // Add resize handles and controls to the selected element - if (element.classList.contains('draggable-image-container')) { - const img = element.querySelector('img'); - if (img) { - this.addResizeHandles(img); - this.addDeleteButton(element); - } - } else if (element.classList.contains('draggable-table-container')) { - this.addTableResizeHandles(element); - this.addDeleteButton(element); - } else if (element.tagName && element.tagName.toLowerCase() === 'img') { - // If already wrapped, ensure handles on container - if (element.parentElement && element.parentElement.classList && element.parentElement.classList.contains('draggable-image-container')) { - const container = element.parentElement; - this.addResizeHandles(container); - this.makeDraggable(container); - this.addDeleteButton(container); - this.highlightSelectedElement(container); - return; - } - // Wrap plain image and add handles; preserve on-screen size and position - const editor = this.template.querySelector('.enhanced-editor-content'); - const rect = element.getBoundingClientRect(); - const editorRect = editor ? editor.getBoundingClientRect() : { left: 0, top: 0 }; - const currentWidth = element.offsetWidth; - const currentHeight = element.offsetHeight; - - const container = document.createElement('div'); - container.className = 'draggable-image-container'; - container.style.position = 'absolute'; - container.style.left = (rect.left - editorRect.left + (editor ? editor.scrollLeft : 0)) + 'px'; - container.style.top = (rect.top - editorRect.top + (editor ? editor.scrollTop : 0)) + 'px'; - container.style.zIndex = window.getComputedStyle(element).zIndex || 'auto'; - container.style.display = 'inline-block'; - - // Move the image into container and preserve size - // Set container and image sizing - container.style.width = currentWidth + 'px'; - container.style.height = currentHeight + 'px'; - element.style.width = '100%'; - element.style.height = '100%'; - element.style.display = 'block'; - element.style.objectFit = 'cover'; - - if (editor) { - editor.appendChild(container); - } else { - element.parentNode.insertBefore(container, element); - } - container.appendChild(element); - container.classList.add('no-frame'); - this.addResizeHandles(container); - this.makeDraggable(container); - this.addDeleteButton(container); - this.highlightSelectedElement(container); - } - } - - // Add delete button to element - addDeleteButton(element) { - // Remove existing delete button if any - const existingDelete = element.querySelector('.delete-handle, .delete-image-btn'); - if (existingDelete) { - existingDelete.remove(); - } - - const deleteBtn = document.createElement('div'); - deleteBtn.className = 'delete-handle'; - deleteBtn.innerHTML = '×'; - deleteBtn.style.cssText = ` + const deleteBtn = document.createElement("div"); + deleteBtn.className = "delete-handle"; + deleteBtn.innerHTML = "×"; + deleteBtn.style.cssText = ` position: absolute; top: -10px; right: -10px; @@ -8603,31 +9657,30 @@ export default class PropertyTemplateSelector extends LightningElement { z-index: 1000; box-shadow: 0 2px 4px rgba(0,0,0,0.2); `; - - deleteBtn.addEventListener('click', (e) => { - e.stopPropagation(); - element.remove(); - }); - - element.appendChild(deleteBtn); - } - - // Add table resize handles - addTableResizeHandles(tableContainer) { - // Remove existing resize handles if any - const existingHandles = tableContainer.querySelectorAll('.resize-handle'); - existingHandles.forEach(handle => handle.remove()); - - const table = tableContainer.querySelector('table'); - if (!table) return; - - // Add resize handles to table corners - const positions = ['nw', 'ne', 'sw', 'se']; - positions.forEach(pos => { - const handle = document.createElement('div'); - handle.className = `resize-handle resize-${pos}`; - handle.dataset.position = pos; - handle.style.cssText = ` + + deleteBtn.addEventListener("click", (e) => { + e.stopPropagation(); + element.remove(); + }); + + element.appendChild(deleteBtn); + } + // Add table resize handles + addTableResizeHandles(tableContainer) { + // Remove existing resize handles if any + const existingHandles = tableContainer.querySelectorAll(".resize-handle"); + existingHandles.forEach((handle) => handle.remove()); + + const table = tableContainer.querySelector("table"); + if (!table) return; + + // Add resize handles to table corners + const positions = ["nw", "ne", "sw", "se"]; + positions.forEach((pos) => { + const handle = document.createElement("div"); + handle.className = `resize-handle resize-${pos}`; + handle.dataset.position = pos; + handle.style.cssText = ` position: absolute; width: 8px; height: 8px; @@ -8636,117 +9689,57 @@ export default class PropertyTemplateSelector extends LightningElement { cursor: ${pos}-resize; z-index: 1000; `; - - // Position the handle - switch(pos) { - case 'nw': - handle.style.top = '-4px'; - handle.style.left = '-4px'; - break; - case 'ne': - handle.style.top = '-4px'; - handle.style.right = '-4px'; - break; - case 'sw': - handle.style.bottom = '-4px'; - handle.style.left = '-4px'; - break; - case 'se': - handle.style.bottom = '-4px'; - handle.style.right = '-4px'; - break; - } - - // Enable resizing using shared startResize - handle.addEventListener('mousedown', (e) => { - e.preventDefault(); - e.stopPropagation(); - this.startResize(e, tableContainer, pos); - }); - - tableContainer.appendChild(handle); - }); - } - // Force re-render by updating a tracked property - forceRerender() { - // Update a dummy property to force reactivity - this.renderKey = this.renderKey ? this.renderKey + 1 : 1; - } - - // Debug method to log current state - logCurrentState() { - } - - // Test method to manually set an image (for debugging) - testSetImage() { - this.selectedImageUrl = 'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'; - this.selectedImageName = 'Test Image'; - this.insertButtonDisabled = false; - this.logCurrentState(); - } + // Position the handle + switch (pos) { + case "nw": + handle.style.top = "-4px"; + handle.style.left = "-4px"; + break; + case "ne": + handle.style.top = "-4px"; + handle.style.right = "-4px"; + break; + case "sw": + handle.style.bottom = "-4px"; + handle.style.left = "-4px"; + break; + case "se": + handle.style.bottom = "-4px"; + handle.style.right = "-4px"; + break; + } - connectedCallback() { - this.loadSavedTemplates(); - } - // Helper method for generating amenities HTML - generateAmenitiesHTML(data) { - const amenities = [ - { key: 'Swimming Pool', icon: 'fa-swimming-pool', value: data.Swimming_Pool__c || 'Yes' }, - { key: 'Gym', icon: 'fa-dumbbell', value: data.Gym__c || 'Yes' }, - { key: 'Parking', icon: 'fa-car', value: data.Parking_Spaces__c || '2' }, - { key: 'Garden', icon: 'fa-tree', value: data.Garden__c || 'Yes' }, - { key: 'Security', icon: 'fa-shield-alt', value: data.Security__c || '24/7' }, - { key: 'Balcony', icon: 'fa-home', value: data.Balcony__c || 'Yes' }, - { key: 'Air Conditioning', icon: 'fa-snowflake', value: data.AC__c || 'Central' }, - { key: 'WiFi', icon: 'fa-wifi', value: data.WiFi__c || 'Included' } - ]; - - return amenities.map(amenity => ` -
    - - ${amenity.key}: ${amenity.value} -
    - `).join(''); - } - - // Helper method for generating property gallery HTML - generatePropertyGalleryHTML() { - const images = this.realPropertyImages || []; - if (images.length === 0) { - const exteriorImage = this.getExteriorImageUrl(); - const interiorImage = this.getSmartImageForSection('interior', 'https://images.unsplash.com/photo-1616486338812-3dadae4b4ace?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); - const bedroomImage = this.getSmartImageForSection('bedroom', 'https://images.unsplash.com/photo-1586023492125-27b2c045efd7?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); - const kitchenImage = this.getSmartImageForSection('kitchen', 'https://images.unsplash.com/photo-1556909114-f6e7ad7d3136?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'); - - return ` - - - - - `; - } - - // Generate gallery items with proper grid positioning - const galleryItems = images.slice(0, 4).map((image, index) => { - if (index === 0) { - // First image spans full width - return ``; - } else { - // Other images in grid columns - return ``; - } - }).join(''); - - return galleryItems; - } - - // Helper method for getting smart images for sections - getSmartImageForSection(section, fallback) { - const images = this.realPropertyImages || []; - const sectionImage = images.find(img => - img.category && img.category.toLowerCase().includes(section.toLowerCase()) - ); - return sectionImage ? sectionImage.url : fallback; - } -} \ No newline at end of file + // Enable resizing using shared startResize + handle.addEventListener("mousedown", (e) => { + e.preventDefault(); + e.stopPropagation(); + this.startResize(e, tableContainer, pos); + }); + + tableContainer.appendChild(handle); + }); + } + + // Force re-render by updating a tracked property + forceRerender() { + // Update a dummy property to force reactivity + this.renderKey = this.renderKey ? this.renderKey + 1 : 1; + } + + // Debug method to log current state + logCurrentState() {} + + // Test method to manually set an image (for debugging) + testSetImage() { + this.selectedImageUrl = + "https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200"; + this.selectedImageName = "Test Image"; + this.insertButtonDisabled = false; + this.logCurrentState(); + } + + connectedCallback() { + this.loadSavedTemplates(); + } +} \ No newline at end of file diff --git a/template samples/the_vertice.html b/template samples/the_vertice.html new file mode 100644 index 0000000..4a11026 --- /dev/null +++ b/template samples/the_vertice.html @@ -0,0 +1,604 @@ + + + + + + Modern Urban Residences Brochure - Updated + + + + + + + + +
    +
    +
    +
    An Urban Oasis
    +

    THE VERTICE

    +
    18 Skyline Avenue, Metropolis Centre, MC 90210
    +
    + +
    + +
    +
    + +
    +
    +

    Where Design Meets Desire.

    +

    The Vertice is not just a building; it's a bold statement on modern urban living. Conceived for the discerning individual, it offers a unique blend of architectural prowess, bespoke interiors, and an unparalleled lifestyle experience right in the heart of the city.

    +

    Every residence is a testament to quality, featuring panoramic city views from floor-to-ceiling windows, intelligent home systems, and finishes selected from the finest materials around the globe. This is more than a home; it's a new perspective.

    +
    +
    +
    +
    + THE VERTICE + Page 02 / 06 +
    +
    +
    + +
    +
    + +
    + + + + + +
    +
    + THE VERTICE + Page 03 / 06 +
    +
    +
    + +
    +
    + +

    An unrivaled collection of amenities offers residents a resort-style living experience. From the serene rooftop pool to the state-of-the-art wellness center, every detail is crafted for comfort, convenience, and luxury.

    +
    +
    +
    +
    +

    Lifestyle Amenities

    +
      +
    • Rooftop Infinity Pool
    • +
    • Fitness Center
    • +
    • Residents' Sky Lounge
    • +
    • Private Cinema Room
    • +
    • Wellness Spa & Sauna
    • +
    • Business Center
    • +
    • 24/7 Concierge
    • +
    • Secure Parking
    • +
    +
    +
    +

    Key Specifications

    +
    Status New Development
    +
    Property Type Condominium
    +
    Year Built 2025
    +
    Technology Integrated Smart Home
    +
    Design Sustainable & Eco-Friendly
    +
    +
    +
    +
    + THE VERTICE + Page 04 / 06 +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +

    Two-Bedroom Residence

    +
    +
    +
    1,450
    +
    SQ. FT.
    +
    +
    +
    2
    +
    BEDROOMS
    +
    +
    +
    2
    +
    BATHROOMS
    +
    +
    +
    1
    +
    BALCONY
    +
    +
    +

    A thoughtfully designed space perfect for urban professionals or small families, combining comfort with panoramic city views.

    +
    +
    +
    +
    +
    +

    Three-Bedroom Penthouse

    +
    +
    +
    3,200
    +
    SQ. FT.
    +
    +
    +
    3
    +
    BEDROOMS
    +
    +
    +
    3.5
    +
    BATHROOMS
    +
    +
    +
    1
    +
    TERRACE
    +
    +
    +

    The pinnacle of luxury living, this penthouse offers expansive spaces, premium finishes, and exclusive access to a private rooftop terrace.

    +
    +
    +
    +

    Additional Information

    +
    +
    Pets
    Allowed (w/ restrictions)
    +
    Smoking
    In designated areas
    +
    Availability
    Q4 2025
    +
    Parking
    2 Spaces per Unit
    +
    Security Deposit
    2 Months
    +
    Utilities
    Sub-metered
    +
    +
    +
    +
    + THE VERTICE + Page 05 / 06 +
    +
    +
    + +
    +
    + +
    +
    +

    Schedule a Private Viewing

    +

    Experience The Vertice firsthand. Contact our sales executive to arrange an exclusive tour of the property and available residences.

    +
    +
    Alexander Valentine
    +
    Sales Executive, Elysian Properties
    +
    + (555) 123-9876
    + alex.v@elysian.com +
    +
    +
    +
    +

    Neighborhood Highlights

    +
      +
    • Landmarks: Central Park (5 min)
    • +
    • Transportation: Metro Line A (2 min walk)
    • +
    • Schools: Metropolis Intl. (10 min)
    • +
    • Shopping: The Galleria Mall (8 min)
    • +
    • Airport: 25 min drive
    • +
    +
    +
    + +
    +
    + + + \ No newline at end of file