Evolução do Design Canvas 2.0: biblioteca de assets no Supabase e controle de rotação/opacidade
This commit is contained in:
+247
-3
@@ -7570,10 +7570,19 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const canvasFontSize = document.getElementById('canvasFontSize');
|
||||
const canvasTextColor = document.getElementById('canvasTextColor');
|
||||
const canvasBgColorInput = document.getElementById('canvasBgColor');
|
||||
const canvasRotation = document.getElementById('canvasRotation');
|
||||
const canvasOpacity = document.getElementById('canvasOpacity');
|
||||
const btnCanvasZIndexUp = document.getElementById('btnCanvasZIndexUp');
|
||||
const btnCanvasZIndexDown = document.getElementById('btnCanvasZIndexDown');
|
||||
const btnCanvasDelete = document.getElementById('btnCanvasDelete');
|
||||
|
||||
// Novos controles do Painel de Assets Lateral
|
||||
const canvasAssetSearch = document.getElementById('canvasAssetSearch');
|
||||
const canvasAssetsContainer = document.getElementById('canvasAssetsContainer');
|
||||
const btnTabAssetPostits = document.getElementById('btnTabAssetPostits');
|
||||
const btnTabAssetStickers = document.getElementById('btnTabAssetStickers');
|
||||
const btnTabAssetFormas = document.getElementById('btnTabAssetFormas');
|
||||
|
||||
let canvasModeActive = false;
|
||||
let selectedCanvasItem = null;
|
||||
let isDraggingCanvas = false;
|
||||
@@ -7581,6 +7590,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
let dragStartX, dragStartY;
|
||||
let dragStartLeft, dragStartTop;
|
||||
let dragStartWidth, dragStartHeight;
|
||||
let activeAssetTab = 'postit';
|
||||
|
||||
function enterCanvasMode() {
|
||||
canvasModeActive = true;
|
||||
@@ -7590,6 +7600,15 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
btnCanvasModeToggle.style.color = 'white';
|
||||
}
|
||||
if (canvasControlPanel) canvasControlPanel.style.display = 'flex';
|
||||
|
||||
// Alternar painéis laterais
|
||||
const configPanel = document.getElementById('cartazLeftConfigPanel');
|
||||
const canvasPanel = document.getElementById('cartazLeftCanvasPanel');
|
||||
if (configPanel) configPanel.style.display = 'none';
|
||||
if (canvasPanel) canvasPanel.style.display = 'flex';
|
||||
|
||||
// Iniciar na aba correspondente de assets
|
||||
selectAssetTab(activeAssetTab);
|
||||
|
||||
cartazPrintableArea.style.position = 'relative';
|
||||
const rectParent = cartazPrintableArea.getBoundingClientRect();
|
||||
@@ -7639,7 +7658,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
|
||||
cartazPreviewContent.style.display = 'none';
|
||||
showToast('Modo Design Canvas ativado! Arraste, redimensione e clique duas vezes para editar o texto.', 'info');
|
||||
showToast('Modo Design Canvas ativado! Adicione elementos e ajuste o mural livremente.', 'info');
|
||||
}
|
||||
|
||||
function exitCanvasMode() {
|
||||
@@ -7650,6 +7669,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
btnCanvasModeToggle.style.color = 'var(--text-secondary)';
|
||||
}
|
||||
if (canvasControlPanel) canvasControlPanel.style.display = 'none';
|
||||
|
||||
// Alternar painéis de volta
|
||||
const configPanel = document.getElementById('cartazLeftConfigPanel');
|
||||
const canvasPanel = document.getElementById('cartazLeftCanvasPanel');
|
||||
if (configPanel) configPanel.style.display = 'flex';
|
||||
if (canvasPanel) canvasPanel.style.display = 'none';
|
||||
|
||||
if (selectedCanvasItem) {
|
||||
selectedCanvasItem.classList.remove('active-item');
|
||||
@@ -7660,8 +7685,47 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
items.forEach(item => {
|
||||
item.removeAttribute('contenteditable');
|
||||
});
|
||||
|
||||
showToast('Layout do Canvas salvo com sucesso!', 'success');
|
||||
|
||||
// Limpar o HTML para persistência (remover seleções/handles)
|
||||
const tempContainer = document.createElement('div');
|
||||
tempContainer.innerHTML = cartazPrintableArea.innerHTML;
|
||||
tempContainer.querySelectorAll('.canvas-resize-handle').forEach(h => h.remove());
|
||||
tempContainer.querySelectorAll('.canvas-item').forEach(el => {
|
||||
el.classList.remove('active-item');
|
||||
el.removeAttribute('contenteditable');
|
||||
if (el.style.border && el.style.border.includes('dashed')) {
|
||||
el.style.border = 'none';
|
||||
}
|
||||
});
|
||||
const cleanHtml = tempContainer.innerHTML;
|
||||
|
||||
// Persistir alterações de canvas no banco
|
||||
if (activeCartazData && activeCartazData.id) {
|
||||
fetch(`/api/cartazes/${activeCartazData.id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
conteudo: cleanHtml,
|
||||
corFundo: cartazBgColor.value,
|
||||
corTexto: cartazTextColor.value
|
||||
})
|
||||
}).then(res => {
|
||||
if (res.ok) {
|
||||
showToast('Layout do Canvas salvo no servidor!', 'success');
|
||||
loadCartazesHistory();
|
||||
} else {
|
||||
showToast('Erro ao salvar layout no servidor.', 'error');
|
||||
}
|
||||
}).catch(e => {
|
||||
console.error(e);
|
||||
showToast('Erro de conexão ao salvar canvas.', 'error');
|
||||
});
|
||||
} else {
|
||||
showToast('Layout salvo temporariamente. Crie o cartaz para salvar definitivamente.', 'warning');
|
||||
}
|
||||
}
|
||||
|
||||
function setupCanvasItemEvents(item) {
|
||||
@@ -7916,6 +7980,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
const bg = rgbToHex(window.getComputedStyle(selectedCanvasItem).backgroundColor);
|
||||
if (canvasBgColorInput) canvasBgColorInput.value = bg;
|
||||
|
||||
const rot = selectedCanvasItem.getAttribute('data-rotation') || '0';
|
||||
if (canvasRotation) canvasRotation.value = rot;
|
||||
|
||||
const op = window.getComputedStyle(selectedCanvasItem).opacity || '1';
|
||||
if (canvasOpacity) canvasOpacity.value = Math.round(parseFloat(op) * 100);
|
||||
}
|
||||
|
||||
if (canvasFontFamily) {
|
||||
@@ -7938,6 +8008,23 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
if (selectedCanvasItem) selectedCanvasItem.style.backgroundColor = canvasBgColorInput.value;
|
||||
});
|
||||
}
|
||||
if (canvasRotation) {
|
||||
canvasRotation.addEventListener('input', () => {
|
||||
if (selectedCanvasItem) {
|
||||
const val = canvasRotation.value;
|
||||
selectedCanvasItem.style.transform = `rotate(${val}deg)`;
|
||||
selectedCanvasItem.setAttribute('data-rotation', val);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (canvasOpacity) {
|
||||
canvasOpacity.addEventListener('input', () => {
|
||||
if (selectedCanvasItem) {
|
||||
const val = canvasOpacity.value;
|
||||
selectedCanvasItem.style.opacity = parseFloat(val) / 100;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (btnCanvasZIndexUp) {
|
||||
btnCanvasZIndexUp.addEventListener('click', () => {
|
||||
if (selectedCanvasItem) {
|
||||
@@ -8039,4 +8126,161 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function selectAssetTab(tab) {
|
||||
activeAssetTab = tab;
|
||||
[btnTabAssetPostits, btnTabAssetStickers, btnTabAssetFormas].forEach(btn => {
|
||||
if (!btn) return;
|
||||
const tabIdName = `btnTabAsset${tab.charAt(0).toUpperCase() + tab.slice(1)}s`;
|
||||
if (btn.id === tabIdName) {
|
||||
btn.classList.add('active');
|
||||
btn.style.background = 'rgba(59, 130, 246, 0.1)';
|
||||
btn.style.color = 'var(--text-primary)';
|
||||
btn.style.fontWeight = 'bold';
|
||||
} else {
|
||||
btn.classList.remove('active');
|
||||
btn.style.background = 'transparent';
|
||||
btn.style.color = 'var(--text-secondary)';
|
||||
btn.style.fontWeight = 'normal';
|
||||
}
|
||||
});
|
||||
loadCanvasAssets(tab, canvasAssetSearch ? canvasAssetSearch.value : '');
|
||||
}
|
||||
|
||||
if (btnTabAssetPostits) btnTabAssetPostits.addEventListener('click', () => selectAssetTab('postit'));
|
||||
if (btnTabAssetStickers) btnTabAssetStickers.addEventListener('click', () => selectAssetTab('sticker'));
|
||||
if (btnTabAssetFormas) btnTabAssetFormas.addEventListener('click', () => selectAssetTab('forma'));
|
||||
|
||||
if (canvasAssetSearch) {
|
||||
canvasAssetSearch.addEventListener('input', (e) => {
|
||||
loadCanvasAssets(activeAssetTab, e.target.value);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadCanvasAssets(tipo, busca = '') {
|
||||
if (!canvasAssetsContainer) return;
|
||||
canvasAssetsContainer.innerHTML = '<div style="grid-column: 1/-1; text-align: center; color: var(--text-secondary); font-size: 0.8rem; padding: 20px;">Carregando...</div>';
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/biblioteca-assets?tipo=${tipo}&busca=${encodeURIComponent(busca)}`, {
|
||||
headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
|
||||
});
|
||||
const data = await res.json();
|
||||
canvasAssetsContainer.innerHTML = '';
|
||||
|
||||
if (!data || data.length === 0) {
|
||||
canvasAssetsContainer.innerHTML = '<div style="grid-column: 1/-1; text-align: center; color: var(--text-muted); font-size: 0.75rem; padding: 20px;">Nenhum item encontrado.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
data.forEach(asset => {
|
||||
const item = document.createElement('div');
|
||||
item.style.cursor = 'pointer';
|
||||
item.style.padding = '8px';
|
||||
item.style.borderRadius = '6px';
|
||||
item.style.background = 'var(--bg-tertiary)';
|
||||
item.style.border = '1px solid var(--border-light)';
|
||||
item.style.display = 'flex';
|
||||
item.style.flexDirection = 'column';
|
||||
item.style.alignItems = 'center';
|
||||
item.style.justifyContent = 'center';
|
||||
item.style.gap = '6px';
|
||||
item.style.transition = 'all 0.2s';
|
||||
item.title = asset.nome;
|
||||
|
||||
item.addEventListener('mouseenter', () => {
|
||||
item.style.transform = 'scale(1.05)';
|
||||
item.style.borderColor = 'rgba(59, 130, 246, 0.5)';
|
||||
});
|
||||
item.addEventListener('mouseleave', () => {
|
||||
item.style.transform = 'scale(1)';
|
||||
item.style.borderColor = 'var(--border-light)';
|
||||
});
|
||||
|
||||
let previewHtml = '';
|
||||
if (asset.tipo === 'postit') {
|
||||
previewHtml = `<div style="width: 32px; height: 32px; background: ${asset.url}; border-radius: 4px; box-shadow: 1px 2px 4px rgba(0,0,0,0.15); transform: rotate(-5deg);"></div>`;
|
||||
} else if (asset.tipo === 'sticker') {
|
||||
previewHtml = `<img src="${asset.url}" style="width: 36px; height: 36px; object-fit: contain;">`;
|
||||
} else if (asset.tipo === 'forma') {
|
||||
if (asset.url === 'rect') {
|
||||
previewHtml = `<div style="width: 36px; height: 26px; border: 2px solid var(--text-primary); border-radius: 2px;"></div>`;
|
||||
} else if (asset.url === 'circle') {
|
||||
previewHtml = `<div style="width: 32px; height: 32px; border: 2px solid var(--text-primary); border-radius: 50%;"></div>`;
|
||||
} else if (asset.url === 'ellipse') {
|
||||
previewHtml = `<div style="width: 36px; height: 22px; border: 2px solid var(--text-primary); border-radius: 50%;"></div>`;
|
||||
} else if (asset.url === 'line') {
|
||||
previewHtml = `<div style="width: 36px; height: 2px; background: var(--text-primary); margin: 15px 0;"></div>`;
|
||||
}
|
||||
}
|
||||
|
||||
item.innerHTML = `
|
||||
${previewHtml}
|
||||
<span style="font-size: 0.65rem; color: var(--text-primary); text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; width: 100%;">${asset.nome}</span>
|
||||
`;
|
||||
|
||||
item.addEventListener('click', () => addAssetToCanvas(asset));
|
||||
canvasAssetsContainer.appendChild(item);
|
||||
});
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
canvasAssetsContainer.innerHTML = '<div style="grid-column: 1/-1; text-align: center; color: #ef4444; font-size: 0.75rem; padding: 10px;">Erro ao carregar elementos.</div>';
|
||||
}
|
||||
}
|
||||
|
||||
function addAssetToCanvas(asset) {
|
||||
if (!canvasModeActive) return;
|
||||
|
||||
const item = document.createElement('div');
|
||||
item.className = 'canvas-item';
|
||||
item.style.position = 'absolute';
|
||||
item.style.left = '35%';
|
||||
item.style.top = '35%';
|
||||
item.style.width = '30%';
|
||||
item.style.height = '15%';
|
||||
item.style.zIndex = '10';
|
||||
|
||||
if (asset.tipo === 'postit') {
|
||||
item.style.background = asset.url;
|
||||
item.style.color = '#2d3748';
|
||||
item.style.boxShadow = '3px 5px 10px rgba(0,0,0,0.15)';
|
||||
item.style.borderRadius = '4px';
|
||||
item.style.transform = 'rotate(' + (Math.random() * 8 - 4) + 'deg)';
|
||||
item.style.padding = '12px';
|
||||
item.style.fontFamily = "'Caveat', cursive";
|
||||
item.style.fontSize = '22px';
|
||||
item.innerHTML = 'Anotação...';
|
||||
item.setAttribute('contenteditable', 'true');
|
||||
item.style.width = '35%';
|
||||
item.style.height = '20%';
|
||||
} else if (asset.tipo === 'sticker') {
|
||||
item.style.background = 'transparent';
|
||||
item.innerHTML = `<img src="${asset.url}" style="width: 100%; height: 100%; object-fit: contain; pointer-events: none;">`;
|
||||
item.style.width = '20%';
|
||||
item.style.height = '20%';
|
||||
} else if (asset.tipo === 'forma') {
|
||||
item.style.background = 'transparent';
|
||||
item.style.border = '3px solid ' + (cartazTextColor.value || '#2d3748');
|
||||
if (asset.url === 'rect') {
|
||||
item.style.borderRadius = '4px';
|
||||
} else if (asset.url === 'circle' || asset.url === 'ellipse') {
|
||||
item.style.borderRadius = '50%';
|
||||
item.style.width = '20%';
|
||||
item.style.height = '20%';
|
||||
} else if (asset.url === 'line') {
|
||||
item.style.border = 'none';
|
||||
item.style.borderTop = '4px solid ' + (cartazTextColor.value || '#2d3748');
|
||||
item.style.height = '10px';
|
||||
item.style.width = '40%';
|
||||
}
|
||||
}
|
||||
|
||||
const handle = document.createElement('div');
|
||||
handle.className = 'canvas-resize-handle';
|
||||
item.appendChild(handle);
|
||||
|
||||
cartazPrintableArea.appendChild(item);
|
||||
setupCanvasItemEvents(item);
|
||||
item.click();
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user