@mindfiredigital/page-builder
Version:
320 lines (315 loc) • 11.4 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');
// Initialize resizers container
this.resizers = document.createElement('div');
this.resizers.classList.add('resizers');
this.element.appendChild(this.resizers);
// Add resizer handles
if (Canvas.layoutMode === 'absolute') {
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) {
if (event.target === this.element) {
event.stopPropagation();
}
}
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;
label.setAttribute('contenteditable', 'false');
component.id = uniqueClass;
label.style.display = 'none';
component.appendChild(label);
if (Canvas.layoutMode === 'absolute') {
component.style.position = 'absolute';
component.style.left = `${event.offsetX}px`;
component.style.top = `${event.offsetY}px`;
Canvas.addDraggableListeners(component);
} else if (Canvas.layoutMode === 'grid') {
this.element.classList.add('container-grid-active');
}
this.element.appendChild(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 = `
.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, editable) {
const isGridMode = Canvas.layoutMode === 'grid';
// Restore resizer functionality
if (editable !== false && !isGridMode) {
ContainerComponent.restoreResizer(container);
} else {
const resizers = container.querySelectorAll('.resizers');
resizers.forEach(resizer => resizer.remove());
}
// 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
if (editable !== false) {
Canvas.controlsManager.addControlButtons(child);
if (isGridMode) {
child.classList.remove('component-resizer');
child.removeAttribute('draggable');
child.style.cursor = 'default';
child.style.position = '';
child.style.left = '';
child.style.top = '';
} else {
Canvas.addDraggableListeners(child);
child.style.position = 'absolute';
child.classList.add('component-resizer');
}
// Bind the showLabel and hideLabel methods
child.addEventListener('mouseenter', event =>
containerInstance.showLabel(event, child)
);
child.addEventListener('mouseleave', event =>
containerInstance.hideLabel(event, child)
);
} else {
const targetElements = child.querySelectorAll(`[contenteditable]`);
if (targetElements.length > 0) {
targetElements.forEach(element => {
element.removeAttribute('contenteditable');
});
}
child.classList.remove('editable-component');
child.classList.remove('component-resizer');
child.removeAttribute('draggable');
child.removeAttribute('contenteditable');
}
// 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, editable);
}
// If the child is itself a container, restore it recursively
if (child.classList.contains('container-component')) {
this.restoreContainer(child, editable);
}
});
}
}