v1.0.2-beta

This commit is contained in:
rohit 2025-09-01 01:53:15 +05:30
parent de4347330f
commit f8b6bb3e05
3 changed files with 1009 additions and 1 deletions

View File

@ -8423,4 +8423,292 @@ ol li:before {
left: -20px;
}
/* Image Insertion Modal Styles */
.image-modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 10000;
backdrop-filter: blur(5px);
}
.image-modal {
background: white;
border-radius: 16px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
width: 90%;
max-width: 800px;
max-height: 80vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
.image-modal-header {
padding: 20px 24px;
border-bottom: 1px solid #e5e7eb;
display: flex;
justify-content: space-between;
align-items: center;
background: #f9fafb;
}
.image-modal-header h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #111827;
}
.image-modal-content {
flex: 1;
padding: 24px;
overflow-y: auto;
}
.image-source-tabs {
display: flex;
gap: 8px;
margin-bottom: 24px;
border-bottom: 1px solid #e5e7eb;
}
.tab-btn {
padding: 12px 20px;
border: none;
background: transparent;
color: #6b7280;
font-weight: 500;
cursor: pointer;
border-bottom: 2px solid transparent;
transition: all 0.2s ease;
}
.tab-btn.active {
color: #4f46e5;
border-bottom-color: #4f46e5;
background: #f0f4ff;
}
.tab-btn:hover {
color: #4f46e5;
background: #f9fafb;
}
.property-images-section {
display: flex;
flex-direction: column;
gap: 20px;
}
.image-categories {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.category-btn {
padding: 8px 16px;
border: 1px solid #d1d5db;
background: white;
color: #374151;
border-radius: 8px;
font-size: 14px;
cursor: pointer;
transition: all 0.2s ease;
}
.category-btn.active {
background: #4f46e5;
color: white;
border-color: #4f46e5;
}
.category-btn:hover {
background: #f3f4f6;
border-color: #9ca3af;
}
.property-images-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 16px;
max-height: 300px;
overflow-y: auto;
}
.property-image-item {
position: relative;
aspect-ratio: 1;
border-radius: 8px;
overflow: hidden;
cursor: pointer;
border: 2px solid transparent;
transition: all 0.2s ease;
}
.property-image-item:hover {
border-color: #4f46e5;
transform: scale(1.02);
}
.property-image-item.selected {
border-color: #4f46e5;
box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.2);
}
.property-image-item img {
width: 100%;
height: 100%;
object-fit: cover;
}
.image-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
color: white;
padding: 8px;
font-size: 12px;
opacity: 0;
transition: opacity 0.2s ease;
}
.property-image-item:hover .image-overlay {
opacity: 1;
}
.local-upload-section {
display: flex;
justify-content: center;
align-items: center;
min-height: 200px;
}
.upload-area {
border: 2px dashed #d1d5db;
border-radius: 12px;
padding: 40px;
text-align: center;
cursor: pointer;
transition: all 0.2s ease;
background: #f9fafb;
width: 100%;
max-width: 400px;
}
.upload-area:hover {
border-color: #4f46e5;
background: #f0f4ff;
}
.upload-area.dragover {
border-color: #4f46e5;
background: #e0e7ff;
}
.file-input {
display: none;
}
.upload-content {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
}
.upload-content p {
margin: 0;
font-weight: 500;
color: #374151;
}
.upload-content small {
color: #6b7280;
}
.image-modal-actions {
padding: 20px 24px;
border-top: 1px solid #e5e7eb;
display: flex;
justify-content: flex-end;
gap: 12px;
background: #f9fafb;
}
/* Enhanced Draggable Image Styles */
.draggable-image-container {
position: absolute;
border: 2px solid transparent;
cursor: move;
z-index: 1000;
min-width: 50px;
min-height: 50px;
user-select: none;
}
.draggable-image-container.selected {
border-color: #4f46e5;
box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.2);
}
.draggable-image-container img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.resize-handle {
position: absolute;
background: #4f46e5;
border: 2px solid white;
border-radius: 50%;
width: 12px;
height: 12px;
z-index: 1001;
}
.resize-handle.nw { top: -6px; left: -6px; cursor: nw-resize; }
.resize-handle.ne { top: -6px; right: -6px; cursor: ne-resize; }
.resize-handle.sw { bottom: -6px; left: -6px; cursor: sw-resize; }
.resize-handle.se { bottom: -6px; right: -6px; cursor: se-resize; }
.resize-handle.n { top: -6px; left: 50%; transform: translateX(-50%); cursor: n-resize; }
.resize-handle.s { bottom: -6px; left: 50%; transform: translateX(-50%); cursor: s-resize; }
.resize-handle.w { top: 50%; left: -6px; transform: translateY(-50%); cursor: w-resize; }
.resize-handle.e { top: 50%; right: -6px; transform: translateY(-50%); cursor: e-resize; }
.delete-handle {
position: absolute;
top: -8px;
right: -8px;
background: #ef4444;
color: white;
border: none;
border-radius: 50%;
width: 20px;
height: 20px;
font-size: 12px;
cursor: pointer;
z-index: 1002;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.delete-handle:hover {
background: #dc2626;
transform: scale(1.1);
}

View File

@ -969,7 +969,7 @@
<lightning-icon icon-name="utility:text" size="x-small"></lightning-icon>
Text
</button>
<button class="toolbar-button" onclick={insertDraggableImage}>
<button class="toolbar-button" onclick={showImageInsertModal}>
<lightning-icon icon-name="utility:image" size="x-small"></lightning-icon>
Image
</button>
@ -1230,4 +1230,62 @@
</div>
</div>
</div>
<!-- Image Insertion Modal -->
<div if:true={showImageModal} class="image-modal-overlay">
<div class="image-modal">
<div class="image-modal-header">
<h3>Insert Image</h3>
<button class="close-btn" onclick={closeImageModal}></button>
</div>
<div class="image-modal-content">
<div class="image-source-tabs">
<button class={propertyTabClass} onclick={setImageSource} data-source="property">
Property Images
</button>
<button class={localTabClass} onclick={setImageSource} data-source="local">
Upload Image
</button>
</div>
<div if:true={showPropertyImagesSection} class="property-images-section">
<div class="image-categories">
<template for:each={imageCategories} for:item="category">
<button key={category.value} class="category-btn"
onclick={selectImageCategory} data-category={category.value}>
{category.label}
</button>
</template>
</div>
<div class="property-images-grid">
<template for:each={filteredPropertyImages} for:item="image">
<div key={image.url} class="property-image-item" onclick={selectPropertyImage} data-image-url={image.url}>
<img src={image.url} alt={image.name} />
<div class="image-overlay">
<span class="image-name">{image.name}</span>
</div>
</div>
</template>
</div>
</div>
<div if:true={showLocalUploadSection} class="local-upload-section">
<div class="upload-area" onclick={triggerFileUpload}>
<input type="file" class="file-input" accept="image/*" onchange={handleFileUpload} />
<div class="upload-content">
<lightning-icon icon-name="utility:upload" size="large"></lightning-icon>
<p>Click to upload image or drag and drop</p>
<small>Supports: JPG, PNG, GIF, WebP</small>
</div>
</div>
</div>
</div>
<div class="image-modal-actions">
<button class="btn btn-secondary" onclick={closeImageModal}>Cancel</button>
<button class="btn btn-primary" onclick={handleInsertButtonClick} disabled={insertButtonDisabled}>
Insert Image
</button>
</div>
</div>
</div>
</template>

View File

@ -45,6 +45,7 @@ export default class PropertyTemplateSelector extends LightningElement {
// 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' },
@ -115,6 +116,16 @@ export default class PropertyTemplateSelector extends LightningElement {
@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;
@ -144,6 +155,76 @@ export default class PropertyTemplateSelector extends LightningElement {
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() {
console.log('filteredPropertyImages called');
console.log('propertyImages:', this.propertyImages);
console.log('selectedImageCategory:', this.selectedImageCategory);
if (!this.propertyImages || this.propertyImages.length === 0) {
console.log('No property images available');
return [];
}
if (this.selectedImageCategory === 'all') {
console.log('Returning all images:', this.propertyImages);
return this.propertyImages;
}
const filtered = this.propertyImages.filter(image => {
const category = image.category ? image.category.toLowerCase() : 'none';
return category === this.selectedImageCategory;
});
console.log('Filtered images:', filtered);
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 === '';
console.log('isInsertButtonDisabled check:', disabled, 'selectedImageUrl:', this.selectedImageUrl, 'renderKey:', this.renderKey);
return disabled;
}
// Computed properties for template selection
get isBlankTemplateSelected() {
return this.selectedTemplateId === 'blank-template';
@ -3670,6 +3751,353 @@ export default class PropertyTemplateSelector extends LightningElement {
}
}
// 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()
});
});
}
console.log('Property images populated:', this.propertyImages);
}
// 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';
console.log('Property image selected:', imageUrl, imageName);
if (!imageUrl) {
console.error('No image URL found in selected item');
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;
console.log('After selection - selectedImageUrl:', this.selectedImageUrl);
console.log('Button disabled state:', this.insertButtonDisabled);
// 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
triggerFileUpload() {
const fileInput = this.template.querySelector('.file-input');
if (fileInput) {
fileInput.click();
}
}
// Handle file upload
handleFileUpload(event) {
const file = event.target.files[0];
if (file) {
console.log('File selected:', file.name);
const reader = new FileReader();
reader.onload = (e) => {
this.uploadedImageData = e.target.result;
this.selectedImageUrl = e.target.result;
this.selectedImageName = file.name;
this.insertButtonDisabled = false;
console.log('Image loaded, selectedImageUrl:', this.selectedImageUrl);
console.log('Button disabled state:', this.insertButtonDisabled);
// 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) {
console.log('updateUploadAreaWithSelectedImage called with:', imageUrl, fileName);
const uploadArea = this.template.querySelector('.upload-area');
console.log('Upload area found:', uploadArea);
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;
margin: 0 auto;
border-radius: 8px;
overflow: hidden;
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 = `
width: 100%;
height: auto;
display: block;
max-height: 150px;
object-fit: cover;
`;
// Create file name overlay
const fileNameOverlay = document.createElement('div');
fileNameOverlay.style.cssText = `
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
color: white;
padding: 8px;
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();
};
}
}
// Handle insert button click with debugging
handleInsertButtonClick() {
console.log('=== INSERT BUTTON CLICKED ===');
this.logCurrentState();
console.log('=============================');
this.insertSelectedImage();
}
// Insert selected image
insertSelectedImage() {
console.log('insertSelectedImage called');
console.log('selectedImageUrl:', this.selectedImageUrl);
console.log('insertButtonDisabled:', this.insertButtonDisabled);
// Check if we have a valid image URL
const imageUrl = this.selectedImageUrl || this.uploadedImageData;
const imageName = this.selectedImageName || 'Uploaded Image';
if (this.insertButtonDisabled || !imageUrl) {
console.error('Please select an image first');
console.error('selectedImageUrl:', this.selectedImageUrl);
console.error('uploadedImageData:', this.uploadedImageData);
console.error('insertButtonDisabled:', this.insertButtonDisabled);
alert('Please select an image first');
return;
}
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');
@ -6460,6 +6888,240 @@ export default class PropertyTemplateSelector extends LightningElement {
}
}
// 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 elements
document.querySelectorAll('.draggable-image-container').forEach(el => {
el.classList.remove('selected');
});
// Add selection to clicked element
element.classList.add('selected');
}
// 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() {
console.log('=== CURRENT STATE ===');
console.log('selectedImageUrl:', this.selectedImageUrl);
console.log('selectedImageName:', this.selectedImageName);
console.log('uploadedImageData:', this.uploadedImageData);
console.log('insertButtonDisabled:', this.insertButtonDisabled);
console.log('imageSource:', this.imageSource);
console.log('propertyImages length:', this.propertyImages ? this.propertyImages.length : 0);
console.log('====================');
}
// Test method to manually set an image (for debugging)
testSetImage() {
console.log('Testing manual image set');
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();
}