Implementa o editor interativo de Canvas para montagem livre de cartazes

This commit is contained in:
2026-06-07 16:40:35 +00:00
parent 264c38a7ce
commit a31764811b
3 changed files with 613 additions and 7 deletions
+521 -6
View File
@@ -7185,10 +7185,35 @@ document.addEventListener('DOMContentLoaded', () => {
const tamanho = document.getElementById('cartazTamanhoSelect') ? document.getElementById('cartazTamanhoSelect').value : 'A4';
const orientacao = document.getElementById('cartazOrientacaoSelect') ? document.getElementById('cartazOrientacaoSelect').value : 'retrato';
const imgContainerHtml = cartazPreviewImageContainer.style.display === 'block'
const imgContainerHtml = cartazPreviewImageContainer.style.display === 'block' && !canvasModeActive
? `<div style="width: 100%; margin-bottom: 20px; border-radius: 8px; overflow: hidden; border: 2px solid rgba(0,0,0,0.1); text-align: center; background: rgba(0,0,0,0.02);"><img src="${cartazPreviewImage.src}" style="max-width: 100%; max-height: 350px; object-fit: contain; display: inline-block;"></div>`
: '';
let printContent = '';
if (canvasModeActive) {
// Clone printable area and cleanup handles / active state borders for print output
const tempContainer = document.createElement('div');
tempContainer.innerHTML = cartazPrintableArea.innerHTML;
tempContainer.querySelectorAll('.canvas-resize-handle').forEach(h => h.remove());
tempContainer.querySelectorAll('.canvas-item').forEach(item => {
item.classList.remove('active-item');
item.removeAttribute('contenteditable');
// We can remove border style only if it was a dashed editing border
if (item.style.border.includes('dashed')) {
item.style.border = 'none';
}
});
printContent = tempContainer.innerHTML;
} else {
printContent = `
<h1>${title}</h1>
${imgContainerHtml}
<div class="content-area">
${cartazPreviewContent.innerHTML}
</div>
`;
}
const printWindow = window.open('', '_blank');
printWindow.document.write(`
<html>
@@ -7225,6 +7250,9 @@ document.addEventListener('DOMContentLoaded', () => {
border: ${border === 'none' ? 'none' : `${border} ${text}`};
display: flex;
flex-direction: column;
position: relative;
overflow: hidden;
container-type: inline-size;
}
h1 {
text-align: center;
@@ -7260,6 +7288,18 @@ document.addEventListener('DOMContentLoaded', () => {
li {
margin-bottom: 12px;
}
/* Estilos específicos do Canvas no PDF/Print */
.canvas-item {
position: absolute !important;
box-sizing: border-box;
background: rgba(255, 255, 255, 0.3);
border-radius: 8px;
padding: 10px;
border: none !important;
box-shadow: none !important;
}
@media print {
body { background: transparent; padding: 0; display: block; }
.cartaz-frame {
@@ -7277,11 +7317,7 @@ document.addEventListener('DOMContentLoaded', () => {
</head>
<body>
<div class="cartaz-frame">
<h1>${title}</h1>
${imgContainerHtml}
<div class="content-area">
${cartazPreviewContent.innerHTML}
</div>
${printContent}
</div>
<script>
window.onload = function() {
@@ -7409,4 +7445,483 @@ document.addEventListener('DOMContentLoaded', () => {
cartazesHistoryContainer.innerHTML = '<div style="color: #ef4444; padding: 20px;">Erro ao carregar histórico de cartazes.</div>';
}
}
// --- MODO DESIGN CANVAS (EDITOR INTERATIVO DE CARTAZES) ---
const btnCanvasModeToggle = document.getElementById('btnCanvasModeToggle');
const canvasControlPanel = document.getElementById('canvasControlPanel');
const btnCanvasAddText = document.getElementById('btnCanvasAddText');
const btnCanvasAddCard = document.getElementById('btnCanvasAddCard');
const canvasFontFamily = document.getElementById('canvasFontFamily');
const canvasFontSize = document.getElementById('canvasFontSize');
const canvasTextColor = document.getElementById('canvasTextColor');
const canvasBgColorInput = document.getElementById('canvasBgColor');
const btnCanvasZIndexUp = document.getElementById('btnCanvasZIndexUp');
const btnCanvasZIndexDown = document.getElementById('btnCanvasZIndexDown');
const btnCanvasDelete = document.getElementById('btnCanvasDelete');
let canvasModeActive = false;
let selectedCanvasItem = null;
let isDraggingCanvas = false;
let isResizingCanvas = false;
let dragStartX, dragStartY;
let dragStartLeft, dragStartTop;
let dragStartWidth, dragStartHeight;
function enterCanvasMode() {
canvasModeActive = true;
if (btnCanvasModeToggle) {
btnCanvasModeToggle.textContent = '💾 Salvar Canvas';
btnCanvasModeToggle.style.background = '#10a37f';
btnCanvasModeToggle.style.color = 'white';
}
if (canvasControlPanel) canvasControlPanel.style.display = 'flex';
cartazPrintableArea.style.position = 'relative';
const rectParent = cartazPrintableArea.getBoundingClientRect();
const items = [];
if (cartazPreviewTitle) items.push(cartazPreviewTitle);
if (cartazPreviewImageContainer && cartazPreviewImageContainer.style.display !== 'none') {
items.push(cartazPreviewImageContainer);
}
const cards = Array.from(cartazPreviewContent.children);
cards.forEach(card => {
items.push(card);
});
const clientRects = items.map(item => item.getBoundingClientRect());
items.forEach((item, index) => {
const rect = clientRects[index];
const leftPercent = ((rect.left - rectParent.left) / rectParent.width) * 100;
const topPercent = ((rect.top - rectParent.top) / rectParent.height) * 100;
const widthPercent = (rect.width / rectParent.width) * 100;
const heightPercent = (rect.height / rectParent.height) * 100;
item.classList.add('canvas-item');
item.style.position = 'absolute';
item.style.left = `${leftPercent}%`;
item.style.top = `${topPercent}%`;
item.style.width = `${widthPercent}%`;
item.style.height = `${heightPercent}%`;
item.style.margin = '0';
item.setAttribute('contenteditable', 'true');
if (!item.querySelector('.canvas-resize-handle')) {
const handle = document.createElement('div');
handle.className = 'canvas-resize-handle';
item.appendChild(handle);
}
if (item.parentNode === cartazPreviewContent) {
cartazPrintableArea.appendChild(item);
}
setupCanvasItemEvents(item);
});
cartazPreviewContent.style.display = 'none';
showToast('Modo Design Canvas ativado! Arraste, redimensione e clique duas vezes para editar o texto.', 'info');
}
function exitCanvasMode() {
canvasModeActive = false;
if (btnCanvasModeToggle) {
btnCanvasModeToggle.textContent = '🎨 Design Canvas';
btnCanvasModeToggle.style.background = 'transparent';
btnCanvasModeToggle.style.color = 'var(--text-secondary)';
}
if (canvasControlPanel) canvasControlPanel.style.display = 'none';
if (selectedCanvasItem) {
selectedCanvasItem.classList.remove('active-item');
selectedCanvasItem = null;
}
const items = cartazPrintableArea.querySelectorAll('.canvas-item');
items.forEach(item => {
item.removeAttribute('contenteditable');
});
showToast('Layout do Canvas salvo com sucesso!', 'success');
}
function setupCanvasItemEvents(item) {
item.addEventListener('click', (e) => {
if (!canvasModeActive) return;
e.stopPropagation();
if (selectedCanvasItem) {
selectedCanvasItem.classList.remove('active-item');
}
selectedCanvasItem = item;
item.classList.add('active-item');
updateToolbarForSelected();
});
item.addEventListener('mousedown', (e) => {
if (!canvasModeActive) return;
if (e.target.classList.contains('canvas-resize-handle')) return;
if (document.activeElement === item && e.target !== item && !e.target.classList.contains('canvas-item')) {
return;
}
e.preventDefault();
selectedCanvasItem = item;
document.querySelectorAll('.canvas-item').forEach(el => el.classList.remove('active-item'));
item.classList.add('active-item');
updateToolbarForSelected();
isDraggingCanvas = true;
dragStartX = e.clientX;
dragStartY = e.clientY;
const parentRect = cartazPrintableArea.getBoundingClientRect();
const itemRect = item.getBoundingClientRect();
dragStartLeft = ((itemRect.left - parentRect.left) / parentRect.width) * 100;
dragStartTop = ((itemRect.top - parentRect.top) / parentRect.height) * 100;
document.addEventListener('mousemove', handleCanvasDrag);
document.addEventListener('mouseup', stopCanvasDrag);
});
item.addEventListener('touchstart', (e) => {
if (!canvasModeActive) return;
if (e.target.classList.contains('canvas-resize-handle')) return;
if (document.activeElement === item && e.target !== item) return;
selectedCanvasItem = item;
document.querySelectorAll('.canvas-item').forEach(el => el.classList.remove('active-item'));
item.classList.add('active-item');
updateToolbarForSelected();
isDraggingCanvas = true;
const touch = e.touches[0];
dragStartX = touch.clientX;
dragStartY = touch.clientY;
const parentRect = cartazPrintableArea.getBoundingClientRect();
const itemRect = item.getBoundingClientRect();
dragStartLeft = ((itemRect.left - parentRect.left) / parentRect.width) * 100;
dragStartTop = ((itemRect.top - parentRect.top) / parentRect.height) * 100;
document.addEventListener('touchmove', handleCanvasTouchDrag, { passive: false });
document.addEventListener('touchend', stopCanvasDrag);
});
const handle = item.querySelector('.canvas-resize-handle');
if (handle) {
handle.addEventListener('mousedown', (e) => {
if (!canvasModeActive) return;
e.stopPropagation();
e.preventDefault();
isResizingCanvas = true;
dragStartX = e.clientX;
dragStartY = e.clientY;
const parentRect = cartazPrintableArea.getBoundingClientRect();
const itemRect = item.getBoundingClientRect();
dragStartWidth = (itemRect.width / parentRect.width) * 100;
dragStartHeight = (itemRect.height / parentRect.height) * 100;
document.addEventListener('mousemove', handleCanvasResize);
document.addEventListener('mouseup', stopCanvasResize);
});
handle.addEventListener('touchstart', (e) => {
if (!canvasModeActive) return;
e.stopPropagation();
e.preventDefault();
isResizingCanvas = true;
const touch = e.touches[0];
dragStartX = touch.clientX;
dragStartY = touch.clientY;
const parentRect = cartazPrintableArea.getBoundingClientRect();
const itemRect = item.getBoundingClientRect();
dragStartWidth = (itemRect.width / parentRect.width) * 100;
dragStartHeight = (itemRect.height / parentRect.height) * 100;
document.addEventListener('touchmove', handleCanvasTouchResize, { passive: false });
document.addEventListener('touchend', stopCanvasResize);
});
}
}
function handleCanvasDrag(e) {
if (!isDraggingCanvas || !selectedCanvasItem) return;
const dx = e.clientX - dragStartX;
const dy = e.clientY - dragStartY;
const parentRect = cartazPrintableArea.getBoundingClientRect();
const dxPercent = (dx / parentRect.width) * 100;
const dyPercent = (dy / parentRect.height) * 100;
let newLeft = dragStartLeft + dxPercent;
let newTop = dragStartTop + dyPercent;
const itemRect = selectedCanvasItem.getBoundingClientRect();
const itemWPercent = (itemRect.width / parentRect.width) * 100;
const itemHPercent = (itemRect.height / parentRect.height) * 100;
if (newLeft < 0) newLeft = 0;
if (newTop < 0) newTop = 0;
if (newLeft + itemWPercent > 100) newLeft = 100 - itemWPercent;
if (newTop + itemHPercent > 100) newTop = 100 - itemHPercent;
selectedCanvasItem.style.left = `${newLeft}%`;
selectedCanvasItem.style.top = `${newTop}%`;
}
function handleCanvasTouchDrag(e) {
if (!isDraggingCanvas || !selectedCanvasItem) return;
e.preventDefault();
const touch = e.touches[0];
const dx = touch.clientX - dragStartX;
const dy = touch.clientY - dragStartY;
const parentRect = cartazPrintableArea.getBoundingClientRect();
const dxPercent = (dx / parentRect.width) * 100;
const dyPercent = (dy / parentRect.height) * 100;
let newLeft = dragStartLeft + dxPercent;
let newTop = dragStartTop + dyPercent;
const itemRect = selectedCanvasItem.getBoundingClientRect();
const itemWPercent = (itemRect.width / parentRect.width) * 100;
const itemHPercent = (itemRect.height / parentRect.height) * 100;
if (newLeft < 0) newLeft = 0;
if (newTop < 0) newTop = 0;
if (newLeft + itemWPercent > 100) newLeft = 100 - itemWPercent;
if (newTop + itemHPercent > 100) newTop = 100 - itemHPercent;
selectedCanvasItem.style.left = `${newLeft}%`;
selectedCanvasItem.style.top = `${newTop}%`;
}
function stopCanvasDrag() {
isDraggingCanvas = false;
document.removeEventListener('mousemove', handleCanvasDrag);
document.removeEventListener('mouseup', stopCanvasDrag);
document.removeEventListener('touchmove', handleCanvasTouchDrag);
document.removeEventListener('touchend', stopCanvasDrag);
}
function handleCanvasResize(e) {
if (!isResizingCanvas || !selectedCanvasItem) return;
const dx = e.clientX - dragStartX;
const dy = e.clientY - dragStartY;
const parentRect = cartazPrintableArea.getBoundingClientRect();
const dxPercent = (dx / parentRect.width) * 100;
const dyPercent = (dy / parentRect.height) * 100;
let newWidth = dragStartWidth + dxPercent;
let newHeight = dragStartHeight + dyPercent;
if (newWidth < 10) newWidth = 10;
if (newHeight < 5) newHeight = 5;
const itemLeft = parseFloat(selectedCanvasItem.style.left) || 0;
const itemTop = parseFloat(selectedCanvasItem.style.top) || 0;
if (itemLeft + newWidth > 100) newWidth = 100 - itemLeft;
if (itemTop + newHeight > 100) newHeight = 100 - itemTop;
selectedCanvasItem.style.width = `${newWidth}%`;
selectedCanvasItem.style.height = `${newHeight}%`;
}
function handleCanvasTouchResize(e) {
if (!isResizingCanvas || !selectedCanvasItem) return;
e.preventDefault();
const touch = e.touches[0];
const dx = touch.clientX - dragStartX;
const dy = touch.clientY - dragStartY;
const parentRect = cartazPrintableArea.getBoundingClientRect();
const dxPercent = (dx / parentRect.width) * 100;
const dyPercent = (dy / parentRect.height) * 100;
let newWidth = dragStartWidth + dxPercent;
let newHeight = dragStartHeight + dyPercent;
if (newWidth < 10) newWidth = 10;
if (newHeight < 5) newHeight = 5;
const itemLeft = parseFloat(selectedCanvasItem.style.left) || 0;
const itemTop = parseFloat(selectedCanvasItem.style.top) || 0;
if (itemLeft + newWidth > 100) newWidth = 100 - itemLeft;
if (itemTop + newHeight > 100) newHeight = 100 - itemTop;
selectedCanvasItem.style.width = `${newWidth}%`;
selectedCanvasItem.style.height = `${newHeight}%`;
}
function stopCanvasResize() {
isResizingCanvas = false;
document.removeEventListener('mousemove', handleCanvasResize);
document.removeEventListener('mouseup', stopCanvasResize);
document.removeEventListener('touchmove', handleCanvasTouchResize);
document.removeEventListener('touchend', stopCanvasResize);
}
function rgbToHex(rgb) {
if (!rgb || rgb === 'transparent') return '#ffffff';
if (rgb.startsWith('#')) return rgb;
const match = rgb.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/);
if (!match) return '#ffffff';
const r = parseInt(match[1]);
const g = parseInt(match[2]);
const b = parseInt(match[3]);
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
function updateToolbarForSelected() {
if (!selectedCanvasItem) return;
const ff = window.getComputedStyle(selectedCanvasItem).fontFamily.replace(/['"]/g, '').split(',')[0].trim();
if (canvasFontFamily) canvasFontFamily.value = ff;
const fs = parseFloat(window.getComputedStyle(selectedCanvasItem).fontSize);
if (canvasFontSize) canvasFontSize.value = Math.round(fs);
const col = rgbToHex(window.getComputedStyle(selectedCanvasItem).color);
if (canvasTextColor) canvasTextColor.value = col;
const bg = rgbToHex(window.getComputedStyle(selectedCanvasItem).backgroundColor);
if (canvasBgColorInput) canvasBgColorInput.value = bg;
}
if (canvasFontFamily) {
canvasFontFamily.addEventListener('change', () => {
if (selectedCanvasItem) selectedCanvasItem.style.fontFamily = canvasFontFamily.value;
});
}
if (canvasFontSize) {
canvasFontSize.addEventListener('input', () => {
if (selectedCanvasItem) selectedCanvasItem.style.fontSize = `${canvasFontSize.value}px`;
});
}
if (canvasTextColor) {
canvasTextColor.addEventListener('input', () => {
if (selectedCanvasItem) selectedCanvasItem.style.color = canvasTextColor.value;
});
}
if (canvasBgColorInput) {
canvasBgColorInput.addEventListener('input', () => {
if (selectedCanvasItem) selectedCanvasItem.style.backgroundColor = canvasBgColorInput.value;
});
}
if (btnCanvasZIndexUp) {
btnCanvasZIndexUp.addEventListener('click', () => {
if (selectedCanvasItem) {
const currentZ = parseInt(selectedCanvasItem.style.zIndex) || 1;
selectedCanvasItem.style.zIndex = currentZ + 1;
}
});
}
if (btnCanvasZIndexDown) {
btnCanvasZIndexDown.addEventListener('click', () => {
if (selectedCanvasItem) {
const currentZ = parseInt(selectedCanvasItem.style.zIndex) || 1;
selectedCanvasItem.style.zIndex = Math.max(1, currentZ - 1);
}
});
}
if (btnCanvasDelete) {
btnCanvasDelete.addEventListener('click', () => {
if (selectedCanvasItem) {
selectedCanvasItem.remove();
selectedCanvasItem = null;
}
});
}
if (btnCanvasAddText) {
btnCanvasAddText.addEventListener('click', () => {
if (!canvasModeActive) return;
const textItem = document.createElement('div');
textItem.className = 'canvas-item';
textItem.innerHTML = 'Novo Texto (Clique duplo para editar)';
textItem.style.left = '10%';
textItem.style.top = '15%';
textItem.style.width = '50%';
textItem.style.height = '8%';
textItem.style.fontSize = '20px';
textItem.style.color = cartazTextColor.value || '#2d3748';
textItem.style.fontFamily = cartazFontSelect.value || 'Fredoka';
textItem.setAttribute('contenteditable', 'true');
const handle = document.createElement('div');
handle.className = 'canvas-resize-handle';
textItem.appendChild(handle);
cartazPrintableArea.appendChild(textItem);
setupCanvasItemEvents(textItem);
textItem.click();
});
}
if (btnCanvasAddCard) {
btnCanvasAddCard.addEventListener('click', () => {
if (!canvasModeActive) return;
const cardItem = document.createElement('div');
cardItem.className = 'canvas-item';
cardItem.style.background = 'rgba(255, 255, 255, 0.4)';
cardItem.style.border = '2px dashed ' + (cartazTextColor.value || '#2d3748');
cardItem.style.borderRadius = '12px';
cardItem.style.left = '20%';
cardItem.style.top = '25%';
cardItem.style.width = '45%';
cardItem.style.height = '20%';
cardItem.style.padding = '15px';
cardItem.innerHTML = '<h3>Novo Card</h3><p>Edite este texto...</p>';
cardItem.style.fontFamily = cartazFontSelect.value || 'Fredoka';
cardItem.setAttribute('contenteditable', 'true');
const handle = document.createElement('div');
handle.className = 'canvas-resize-handle';
cardItem.appendChild(handle);
cartazPrintableArea.appendChild(cardItem);
setupCanvasItemEvents(cardItem);
cardItem.click();
});
}
if (btnCanvasModeToggle) {
btnCanvasModeToggle.addEventListener('click', () => {
if (canvasModeActive) {
exitCanvasMode();
} else {
if (cartazEditArea && cartazEditArea.style.display !== 'none') {
btnEditCartazToggle.click();
}
enterCanvasMode();
}
});
}
if (cartazPrintableArea) {
cartazPrintableArea.addEventListener('click', (e) => {
if (!canvasModeActive) return;
if (e.target === cartazPrintableArea) {
if (selectedCanvasItem) {
selectedCanvasItem.classList.remove('active-item');
selectedCanvasItem = null;
}
}
});
}
});