let divCounter = 0;
let isDragging = false;
let currentDiv = null;
let offset = { x: 0, y: 0 };
let sortable = null;
// Wait for settings to be loaded
document.addEventListener('DOMContentLoaded', function() {
// Ensure settings are available
if (!window.LAYOUT_SETTINGS) {
console.error('Settings not loaded!');
return;
}
const settings = window.LAYOUT_SETTINGS;
function initSortable() {
const flowContainer = document.getElementById('flowContainer');
if (!flowContainer) {
console.error('Flow container not found!');
return;
}
sortable = Sortable.create(flowContainer, {
animation: settings.sortable.animation,
delay: settings.sortable.delay,
delayOnTouchStart: settings.sortable.delayOnTouchStart,
ghostClass: 'sortable-ghost',
dragClass: 'sortable-drag',
filter: function(evt) {
const target = evt.target;
return target.classList.contains('delete-btn') ||
target.closest('.delete-btn') ||
!target.closest('.layout-div').classList.contains('locked');
},
onStart: function(evt) {
if (window.AppState.selectedDiv) {
window.AppState.selectedDiv.classList.remove('selected');
window.AppState.selectedDiv = null;
document.getElementById('propertiesPanel').classList.remove('visible');
}
},
onEnd: function(evt) {
console.log('Div reordered from index', evt.oldIndex, 'to', evt.newIndex);
},
onClick: function(evt) {
const clickedDiv = evt.item;
if (clickedDiv && !evt.target.classList.contains('delete-btn') && !evt.target.closest('.delete-btn')) {
selectDiv(clickedDiv);
}
}
});
}
function setPositioning(type) {
if (!window.AppState.selectedDiv) return;
const divId = window.AppState.selectedDiv.id;
const props = window.AppState.divProperties.get(divId);
if (type === 'locked') {
window.AppState.selectedDiv.classList.remove('absolute');
window.AppState.selectedDiv.classList.add('locked');
const flowContainer = document.getElementById('flowContainer');
const currentIndex = parseInt(divId.split('-')[1]);
let insertBefore = null;
const flowDivs = Array.from(flowContainer.children);
for (let flowDiv of flowDivs) {
const flowIndex = parseInt(flowDiv.id.split('-')[1]);
if (flowIndex > currentIndex) {
insertBefore = flowDiv;
break;
}
}
if (insertBefore) {
flowContainer.insertBefore(window.AppState.selectedDiv, insertBefore);
} else {
flowContainer.appendChild(window.AppState.selectedDiv);
}
window.AppState.selectedDiv.style.left = '';
window.AppState.selectedDiv.style.top = '';
document.getElementById('xSlider').disabled = true;
document.getElementById('ySlider').disabled = true;
document.getElementById('xDisplay').textContent = 'Flow Layout';
document.getElementById('yDisplay').textContent = 'Flow Layout';
document.getElementById('lockedBtn').classList.add('active');
document.getElementById('absoluteBtn').classList.remove('absolute-active');
const indicator = window.AppState.selectedDiv.querySelector('.lock-indicator');
if (indicator) indicator.textContent = '🔒 Flow';
props.positioning = 'locked';
} else if (type === 'absolute') {
window.AppState.selectedDiv.classList.remove('locked');
window.AppState.selectedDiv.classList.add('absolute');
document.getElementById('canvas').appendChild(window.AppState.selectedDiv);
window.AppState.selectedDiv.style.left = '100px';
window.AppState.selectedDiv.style.top = '100px';
document.getElementById('xSlider').disabled = false;
document.getElementById('ySlider').disabled = false;
document.getElementById('lockedBtn').classList.remove('active');
document.getElementById('absoluteBtn').classList.add('absolute-active');
const indicator = window.AppState.selectedDiv.querySelector('.lock-indicator');
if (indicator) indicator.textContent = '📍 Abs';
props.positioning = 'absolute';
props.x = { px: 100, percent: (100 / window.innerWidth) * 100 };
props.y = { px: 100, percent: (100 / window.innerHeight) * 100 };
window.updateSliderRanges();
window.updatePositionSliders();
}
}
function selectDiv(div) {
setTimeout(() => {
if (window.AppState.selectedDiv) {
window.AppState.selectedDiv.classList.remove('selected');
}
window.AppState.selectedDiv = div;
div.classList.add('selected');
const panel = document.getElementById('propertiesPanel');
panel.classList.add('visible');
const divId = div.id;
const props = window.AppState.divProperties.get(divId);
if (props.positioning === 'locked') {
document.getElementById('lockedBtn').classList.add('active');
document.getElementById('absoluteBtn').classList.remove('absolute-active');
document.getElementById('xSlider').disabled = true;
document.getElementById('ySlider').disabled = true;
document.getElementById('xDisplay').textContent = 'Flow Layout';
document.getElementById('yDisplay').textContent = 'Flow Layout';
} else {
document.getElementById('lockedBtn').classList.remove('active');
document.getElementById('absoluteBtn').classList.add('absolute-active');
document.getElementById('xSlider').disabled = false;
document.getElementById('ySlider').disabled = false;
}
window.updateSliderRanges();
window.updateSliderValues();
window.updateColorSelection(div);
}, settings.animations.selectionDelay);
}
function addDiv() {
divCounter++;
const flowContainer = document.getElementById('flowContainer');
const newDiv = document.createElement('div');
newDiv.className = 'layout-div locked';
newDiv.id = `div-${divCounter}`;
const widthPercent = settings.sliders.width.percent.default;
const heightPercent = settings.sliders.height.percent.default + (Math.random() * 5);
const paddingPercent = settings.sliders.padding.percent.default;
// Use available width accounting for canvas padding
const availableWidth = window.innerWidth - 40;
const width = (widthPercent / 100) * availableWidth;
const height = (heightPercent / 100) * window.innerHeight;
const padding = (paddingPercent / 100) * Math.min(window.innerWidth, window.innerHeight);
const colorIndex = Math.floor(Math.random() * settings.colors.length);
newDiv.innerHTML = `
<div class="lock-indicator">🔒 Flow</div>
<span>Div ${divCounter}</span>
<button class="delete-btn" onclick="deleteDiv(event, 'div-${divCounter}')" title="Delete">×</button>
`;
newDiv.style.width = width + 'px';
newDiv.style.height = height + 'px';
newDiv.style.padding = padding + 'px';
newDiv.style.background = settings.colors[colorIndex];
window.AppState.divProperties.set(`div-${divCounter}`, {
positioning: 'locked',
name: `Div ${divCounter}`,
width: {
px: width,
percent: widthPercent
},
height: {
px: height,
percent: heightPercent
},
padding: {
px: padding,
percent: paddingPercent
},
x: { px: 0, percent: 0 },
y: { px: 0, percent: 0 }
});
newDiv.addEventListener('mousedown', startDrag);
newDiv.addEventListener('touchstart', startDrag, { passive: false });
newDiv.addEventListener('click', function(e) {
if (!e.target.classList.contains('delete-btn') && !e.target.closest('.delete-btn')) {
selectDiv(newDiv);
}
});
flowContainer.appendChild(newDiv);
}
function deleteDiv(event, divId) {
event.stopPropagation();
const div = document.getElementById(divId);
if (div) {
if (window.AppState.selectedDiv === div) {
window.AppState.selectedDiv = null;
document.getElementById('propertiesPanel').classList.remove('visible');
}
window.AppState.divProperties.delete(divId);
div.style.animation = 'slideIn 0.3s ease reverse';
setTimeout(() => {
div.remove();
}, settings.animations.deleteAnimation);
}
}
function startDrag(e) {
if (e.currentTarget.classList.contains('locked')) {
return;
}
if (e.target.classList.contains('delete-btn')) return;
isDragging = false;
let startTime = Date.now();
let startX = e.type === 'touchstart' ? e.touches[0].clientX : e.clientX;
let startY = e.type === 'touchstart' ? e.touches[0].clientY : e.clientY;
currentDiv = e.currentTarget;
currentDiv.style.zIndex = settings.drag.zIndexActive;
const rect = currentDiv.getBoundingClientRect();
offset.x = startX - rect.left;
offset.y = startY - rect.top;
const onMove = (e) => {
const currentX = e.type === 'touchmove' ? e.touches[0].clientX : e.clientX;
const currentY = e.type === 'touchmove' ? e.touches[0].clientY : e.clientY;
const distance = Math.sqrt(Math.pow(currentX - startX, 2) + Math.pow(currentY - startY, 2));
if (distance > settings.drag.threshold || Date.now() - startTime > settings.drag.timeThreshold) {
isDragging = true;
}
if (isDragging) {
drag(e);
}
};
const onEnd = (e) => {
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onEnd);
document.removeEventListener('touchmove', onMove);
document.removeEventListener('touchend', onEnd);
if (!isDragging && Date.now() - startTime < settings.drag.clickTimeLimit) {
selectDiv(currentDiv);
}
stopDrag();
};
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onEnd);
document.addEventListener('touchmove', onMove, { passive: false });
document.addEventListener('touchend', onEnd);
e.preventDefault();
e.stopPropagation();
}
function drag(e) {
if (!isDragging || !currentDiv || currentDiv.classList.contains('locked')) return;
const clientX = e.type === 'touchmove' ? e.touches[0].clientX : e.clientX;
const clientY = e.type === 'touchmove' ? e.touches[0].clientY : e.clientY;
const newX = clientX - offset.x;
const newY = clientY - offset.y;
const maxX = window.innerWidth - currentDiv.offsetWidth;
const maxY = window.innerHeight - currentDiv.offsetHeight;
const constrainedX = Math.max(0, Math.min(newX, maxX));
const constrainedY = Math.max(0, Math.min(newY, maxY));
currentDiv.style.left = constrainedX + 'px';
currentDiv.style.top = constrainedY + 'px';
const divId = currentDiv.id;
const props = window.AppState.divProperties.get(divId);
if (props) {
props.x.px = constrainedX;
props.x.percent = (constrainedX / window.innerWidth) * 100;
props.y.px = constrainedY;
props.y.percent = (constrainedY / window.innerHeight) * 100;
}
if (window.AppState.selectedDiv === currentDiv) {
window.updatePositionSliders();
}
e.preventDefault();
}
function stopDrag() {
if (currentDiv) {
currentDiv.style.zIndex = '';
currentDiv = null;
}
isDragging = false;
}
function handleResize() {
window.AppState.divProperties.forEach((props, divId) => {
const div = document.getElementById(divId);
if (div) {
// Always recalculate from percentage values for consistency
const availableWidth = window.innerWidth - 40;
const newWidth = (props.width.percent / 100) * availableWidth;
const newHeight = (props.height.percent / 100) * window.innerHeight;
const newPadding = (props.padding.percent / 100) * Math.min(window.innerWidth, window.innerHeight);
// Update DOM
div.style.width = newWidth + 'px';
div.style.height = newHeight + 'px';
div.style.padding = newPadding + 'px';
// Update stored pixel values
props.width.px = newWidth;
props.height.px = newHeight;
props.padding.px = newPadding;
// Handle absolute positioned divs
if (props.positioning === 'absolute') {
const newX = (props.x.percent / 100) * window.innerWidth;
const newY = (props.y.percent / 100) * window.innerHeight;
// Ensure div stays within new viewport
const maxX = window.innerWidth - newWidth;
const maxY = window.innerHeight - newHeight;
const constrainedX = Math.max(0, Math.min(newX, maxX));
const constrainedY = Math.max(0, Math.min(newY, maxY));
div.style.left = constrainedX + 'px';
div.style.top = constrainedY + 'px';
props.x.px = constrainedX;
props.y.px = constrainedY;
// Update percentages if position was constrained
props.x.percent = (constrainedX / window.innerWidth) * 100;
props.y.percent = (constrainedY / window.innerHeight) * 100;
}
}
});
if (window.AppState.selectedDiv) {
window.updateSliderRanges();
window.updateSliderValues();
}
}
// Initialize event listeners
document.addEventListener('click', (e) => {
if (!e.target.closest('.layout-div') && !e.target.closest('.properties-panel')) {
if (window.AppState.selectedDiv) {
window.AppState.selectedDiv.classList.remove('selected');
window.AppState.selectedDiv = null;
document.getElementById('propertiesPanel').classList.remove('visible');
}
}
});
window.addEventListener('resize', handleResize);
// Set initial unit to percentage
window.AppState.currentUnit = '%';
document.getElementById('pxBtn').classList.remove('active');
document.getElementById('percentBtn').classList.add('active');
// Update HTML defaults to percentage mode
document.getElementById('widthSlider').min = '5';
document.getElementById('widthSlider').max = '100';
document.getElementById('widthSlider').value = '100';
document.getElementById('widthDisplay').textContent = '100%';
document.getElementById('heightSlider').min = '5';
document.getElementById('heightSlider').max = '100';
document.getElementById('heightSlider').value = '15';
document.getElementById('heightDisplay').textContent = '15%';
document.getElementById('paddingSlider').min = '0';
document.getElementById('paddingSlider').max = '10';
document.getElementById('paddingSlider').value = '2';
document.getElementById('paddingDisplay').textContent = '2%';
// Initialize
window.initColorGrid();
initSortable();
addDiv();
// Export functions to global scope
window.addDiv = addDiv;
window.deleteDiv = deleteDiv;
window.setPositioning = setPositioning;
});
// Fallback initialization if DOMContentLoaded already fired
if (document.readyState === 'loading') {
// Already handled above
} else {
// DOM is already loaded, trigger the initialization
const event = new Event('DOMContentLoaded');
document.dispatchEvent(event);
}