📜
layout.js
Back
📝 Javascript ⚡ Executable Ctrl+S: Save • Ctrl+R: Run • Ctrl+F: Find
let divCounter = 0; let isDragging = false; let currentDiv = null; let offset = { x: 0, y: 0 }; let sortable = null; const settings = window.LAYOUT_SETTINGS; const colors = settings.colors; function initSortable() { const flowContainer = document.getElementById('flowContainer'); 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.selectedDiv) { window.selectedDiv.classList.remove('selected'); window.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.selectedDiv) return; const divId = window.selectedDiv.id; const props = window.divProperties.get(divId); if (type === 'locked') { window.selectedDiv.classList.remove('absolute'); window.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.selectedDiv, insertBefore); } else { flowContainer.appendChild(window.selectedDiv); } window.selectedDiv.style.left = ''; window.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.selectedDiv.querySelector('.lock-indicator'); if (indicator) indicator.textContent = '🔒 Flow'; props.positioning = 'locked'; } else if (type === 'absolute') { window.selectedDiv.classList.remove('locked'); window.selectedDiv.classList.add('absolute'); document.getElementById('canvas').appendChild(window.selectedDiv); window.selectedDiv.style.left = '100px'; window.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.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.selectedDiv) { window.selectedDiv.classList.remove('selected'); } window.selectedDiv = div; div.classList.add('selected'); const panel = document.getElementById('propertiesPanel'); panel.classList.add('visible'); const divId = div.id; const props = window.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; const width = (widthPercent / 100) * window.innerWidth; const height = (heightPercent / 100) * window.innerHeight; const padding = (paddingPercent / 100) * Math.min(window.innerWidth, window.innerHeight); const colorIndex = Math.floor(Math.random() * 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 = colors[colorIndex]; window.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.selectedDiv === div) { window.selectedDiv = null; document.getElementById('propertiesPanel').classList.remove('visible'); } window.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.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.selectedDiv === currentDiv) { window.updatePositionSliders(); } e.preventDefault(); } function stopDrag() { if (currentDiv) { currentDiv.style.zIndex = ''; currentDiv = null; } isDragging = false; } function handleResize() { window.divProperties.forEach((props, divId) => { const div = document.getElementById(divId); if (div && props.positioning === 'absolute') { const newWidth = (props.width.percent / 100) * window.innerWidth; const newHeight = (props.height.percent / 100) * window.innerHeight; const newX = (props.x.percent / 100) * window.innerWidth; const newY = (props.y.percent / 100) * window.innerHeight; 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.width = newWidth + 'px'; div.style.height = newHeight + 'px'; div.style.left = constrainedX + 'px'; div.style.top = constrainedY + 'px'; props.width.px = newWidth; props.height.px = newHeight; props.x.px = constrainedX; props.y.px = constrainedY; props.x.percent = (constrainedX / window.innerWidth) * 100; props.y.percent = (constrainedY / window.innerHeight) * 100; } }); if (window.selectedDiv) { window.updateSliderRanges(); window.updateSliderValues(); } } // Initialize document.addEventListener('click', (e) => { if (!e.target.closest('.layout-div') && !e.target.closest('.properties-panel')) { if (window.selectedDiv) { window.selectedDiv.classList.remove('selected'); window.selectedDiv = null; document.getElementById('propertiesPanel').classList.remove('visible'); } } }); window.addEventListener('resize', handleResize); window.initColorGrid(); initSortable(); addDiv(); // Expose necessary functions window.addDiv = addDiv; window.deleteDiv = deleteDiv; window.setPositioning = setPositioning;