@mindfiredigital/page-builder
Version:
328 lines (322 loc) • 11.5 kB
JavaScript
import { Canvas } from '../canvas/Canvas.js';
import { ImageComponent } from './ImageComponent.js';
export class ContainerComponent {
constructor() {
this.MINIMUM_SIZE = 20;
this.originalWidth = 0;
this.originalHeight = 0;
this.originalX = 0;
this.originalY = 0;
this.originalMouseX = 0;
this.originalMouseY = 0;
this.currentResizer = null;
this.resize = e => {
if (!this.currentResizer) return;
const deltaX = e.pageX - this.originalMouseX;
const deltaY = e.pageY - this.originalMouseY;
if (this.currentResizer.classList.contains('bottom-right')) {
const width = this.originalWidth + deltaX;
const height = this.originalHeight + deltaY;
if (width > this.MINIMUM_SIZE) {
this.element.style.width = `${width}px`;
}
if (height > this.MINIMUM_SIZE) {
this.element.style.height = `${height}px`;
}
} else if (this.currentResizer.classList.contains('bottom-left')) {
const height = this.originalHeight + deltaY;
const width = this.originalWidth - deltaX;
if (height > this.MINIMUM_SIZE) {
this.element.style.height = `${height}px`;
}
if (width > this.MINIMUM_SIZE) {
this.element.style.width = `${width}px`;
this.element.style.left = `${this.originalX + deltaX}px`;
}
} else if (this.currentResizer.classList.contains('top-right')) {
const width = this.originalWidth + deltaX;
const height = this.originalHeight - deltaY;
if (width > this.MINIMUM_SIZE) {
this.element.style.width = `${width}px`;
}
if (height > this.MINIMUM_SIZE) {
this.element.style.height = `${height}px`;
this.element.style.top = `${this.originalY + deltaY}px`;
}
} else if (this.currentResizer.classList.contains('top-left')) {
const width = this.originalWidth - deltaX;
const height = this.originalHeight - deltaY;
if (width > this.MINIMUM_SIZE) {
this.element.style.width = `${width}px`;
this.element.style.left = `${this.originalX + deltaX}px`;
}
if (height > this.MINIMUM_SIZE) {
this.element.style.height = `${height}px`;
this.element.style.top = `${this.originalY + deltaY}px`;
}
}
};
/**
* On mouse up event the resizing stops and captures the state
* Which will help keep tracking of state for undo/redo purpose
*/
this.stopResize = () => {
window.removeEventListener('mousemove', this.resize);
window.removeEventListener('mouseup', this.stopResize);
this.currentResizer = null;
//capture each resized state
Canvas.historyManager.captureState();
};
this.element = document.createElement('div');
this.element.classList.add('container-component');
this.element.setAttribute('draggable', 'true');
// Initialize resizers container
this.resizers = document.createElement('div');
this.resizers.classList.add('resizers');
this.element.appendChild(this.resizers);
// Add resizer handles
this.addResizeHandles();
// Add styles
this.addStyles();
// Add event listeners
this.initializeEventListeners();
}
addResizeHandles() {
const positions = [
{ class: 'top-left', cursor: 'nwse-resize' },
{ class: 'top-right', cursor: 'nesw-resize' },
{ class: 'bottom-left', cursor: 'nesw-resize' },
{ class: 'bottom-right', cursor: 'nwse-resize' },
];
positions.forEach(position => {
const resizer = document.createElement('div');
resizer.classList.add('resizer', position.class);
resizer.addEventListener('mousedown', e => this.initResize(e, resizer));
this.resizers.appendChild(resizer);
});
}
initResize(e, resizer) {
e.preventDefault();
this.currentResizer = resizer;
// Store original dimensions and positions
this.originalWidth = parseFloat(
getComputedStyle(this.element).getPropertyValue('width')
);
this.originalHeight = parseFloat(
getComputedStyle(this.element).getPropertyValue('height')
);
this.originalX = this.element.getBoundingClientRect().left;
this.originalY = this.element.getBoundingClientRect().top;
this.originalMouseX = e.pageX;
this.originalMouseY = e.pageY;
// Add resize events
window.addEventListener('mousemove', this.resize);
window.addEventListener('mouseup', this.stopResize);
}
initializeEventListeners() {
this.element.addEventListener('dragstart', this.onDragStart.bind(this));
this.element.addEventListener('drop', this.onDrop.bind(this));
this.element.addEventListener('dragover', event => event.preventDefault());
this.element.addEventListener('mouseover', this.onMouseOver.bind(this));
this.element.addEventListener('mouseleave', this.onMouseLeave.bind(this));
}
onDragStart(event) {
event.stopPropagation();
}
makeDraggable(element) {
let isDragging = false;
let startX = 0;
let startY = 0;
let offsetX = 0;
let offsetY = 0;
const onMouseDown = event => {
event.preventDefault();
event.stopPropagation();
isDragging = true;
// Calculate initial positions
startX = event.clientX;
startY = event.clientY;
const rect = element.getBoundingClientRect();
offsetX = rect.left;
offsetY = rect.top;
// Add event listeners for dragging
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('mouseup', onMouseUp);
};
const onMouseMove = event => {
if (!isDragging) return;
// Calculate the new position
const deltaX = event.clientX - startX;
const deltaY = event.clientY - startY;
// Update the element's position using CSS transform
element.style.transform = `translate(${offsetX + deltaX}px, ${offsetY + deltaY}px)`;
};
const onMouseUp = () => {
isDragging = false;
// Remove event listeners to stop dragging
window.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('mouseup', onMouseUp);
};
// Attach the mousedown event to the element
element.addEventListener('mousedown', onMouseDown);
}
onDrop(event) {
var _a;
event.preventDefault();
event.stopPropagation();
const componentType =
(_a = event.dataTransfer) === null || _a === void 0
? void 0
: _a.getData('component-type');
if (!componentType) return;
const component = Canvas.createComponent(componentType);
if (!component) return;
const containerClass = this.element.classList[2];
const uniqueClass = Canvas.generateUniqueClass(
componentType,
true,
containerClass
);
component.classList.add(uniqueClass);
const label = document.createElement('span');
label.className = 'component-label';
label.textContent = uniqueClass;
component.id = uniqueClass;
label.style.display = 'none';
component.appendChild(label);
component.addEventListener('mouseenter', e => this.showLabel(e, component));
component.addEventListener('mouseleave', e => this.hideLabel(e, component));
this.element.appendChild(component);
// Apply draggable functionality to the new component
this.makeDraggable(component);
// Capture state for undo/redo
Canvas.historyManager.captureState();
}
showLabel(event, component) {
event.stopPropagation();
const label = component.querySelector('.component-label');
if (label) {
label.style.display = 'block';
}
}
hideLabel(event, component) {
event.stopPropagation();
const label = component.querySelector('.component-label');
if (label) {
label.style.display = 'none';
}
}
onMouseOver(event) {
event.stopPropagation();
const elements = document.querySelectorAll('.container-highlight');
// Loop through each element and remove the class
elements.forEach(element => {
element.classList.remove('container-highlight');
});
if (event.target === this.element) {
this.element.classList.add('container-highlight');
}
}
onMouseLeave(event) {
if (event.target === this.element) {
this.element.classList.remove('container-highlight');
}
}
addStyles() {
const style = document.createElement('style');
style.textContent = `
.container-component {
position: relative !important;
display: flex;
min-width: 100px;
min-height: 100px;
cursor: grab;
border: 1px solid #ddd;
}
.resizer {
width: 10px;
height: 10px;
border-radius: 50%;
background: white;
border: 2px solid #4286f4;
position: absolute;
}
.resizer.top-left {
left: -5px;
top: -5px;
cursor: nwse-resize;
}
.resizer.top-right {
right: -5px;
top: -5px;
cursor: nesw-resize;
}
.resizer.bottom-left {
left: -5px;
bottom: -5px;
cursor: nesw-resize;
}
.resizer.bottom-right {
right: -5px;
bottom: -5px;
cursor: nwse-resize;
}
`;
document.head.appendChild(style);
}
create() {
return this.element;
}
static restoreResizer(element) {
// Remove any existing resizers
const oldResizers = element.querySelector('.resizers');
if (oldResizers) {
oldResizers.remove();
}
// Create new resizers container
const resizersDiv = document.createElement('div');
resizersDiv.classList.add('resizers');
// Create temporary container instance to bind event listeners
const container = new ContainerComponent();
container.element = element;
container.resizers = resizersDiv;
// Add resize handles
container.addResizeHandles();
// Add new resizers to the element
element.appendChild(resizersDiv);
}
static restoreContainer(container) {
// Restore resizer functionality
ContainerComponent.restoreResizer(container);
// Create a temporary instance of ContainerComponent to reuse its methods
const containerInstance = new ContainerComponent();
containerInstance.element = container;
// Reapply controls to child components inside the container
const containerChildren = container.querySelectorAll('.editable-component');
containerChildren.forEach(child => {
var _a;
// Add control buttons and draggable listeners
Canvas.controlsManager.addControlButtons(child);
Canvas.addDraggableListeners(child);
// Bind the showLabel and hideLabel methods
child.addEventListener('mouseenter', event =>
containerInstance.showLabel(event, child)
);
child.addEventListener('mouseleave', event =>
containerInstance.hideLabel(event, child)
);
// If the child is an image component, restore the image upload feature
if (child.classList.contains('image-component')) {
const imageSrc =
((_a = child.querySelector('img')) === null || _a === void 0
? void 0
: _a.getAttribute('src')) || ''; // Get the saved image source
ImageComponent.restoreImageUpload(child, imageSrc);
}
// If the child is itself a container, restore it recursively
if (child.classList.contains('container-component')) {
this.restoreContainer(child);
}
});
}
}