Evolução do Design Canvas 2.0: biblioteca de assets no Supabase e controle de rotação/opacidade
This commit is contained in:
+246
-2
@@ -7570,10 +7570,19 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const canvasFontSize = document.getElementById('canvasFontSize');
|
const canvasFontSize = document.getElementById('canvasFontSize');
|
||||||
const canvasTextColor = document.getElementById('canvasTextColor');
|
const canvasTextColor = document.getElementById('canvasTextColor');
|
||||||
const canvasBgColorInput = document.getElementById('canvasBgColor');
|
const canvasBgColorInput = document.getElementById('canvasBgColor');
|
||||||
|
const canvasRotation = document.getElementById('canvasRotation');
|
||||||
|
const canvasOpacity = document.getElementById('canvasOpacity');
|
||||||
const btnCanvasZIndexUp = document.getElementById('btnCanvasZIndexUp');
|
const btnCanvasZIndexUp = document.getElementById('btnCanvasZIndexUp');
|
||||||
const btnCanvasZIndexDown = document.getElementById('btnCanvasZIndexDown');
|
const btnCanvasZIndexDown = document.getElementById('btnCanvasZIndexDown');
|
||||||
const btnCanvasDelete = document.getElementById('btnCanvasDelete');
|
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 canvasModeActive = false;
|
||||||
let selectedCanvasItem = null;
|
let selectedCanvasItem = null;
|
||||||
let isDraggingCanvas = false;
|
let isDraggingCanvas = false;
|
||||||
@@ -7581,6 +7590,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
let dragStartX, dragStartY;
|
let dragStartX, dragStartY;
|
||||||
let dragStartLeft, dragStartTop;
|
let dragStartLeft, dragStartTop;
|
||||||
let dragStartWidth, dragStartHeight;
|
let dragStartWidth, dragStartHeight;
|
||||||
|
let activeAssetTab = 'postit';
|
||||||
|
|
||||||
function enterCanvasMode() {
|
function enterCanvasMode() {
|
||||||
canvasModeActive = true;
|
canvasModeActive = true;
|
||||||
@@ -7591,6 +7601,15 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
if (canvasControlPanel) canvasControlPanel.style.display = 'flex';
|
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';
|
cartazPrintableArea.style.position = 'relative';
|
||||||
const rectParent = cartazPrintableArea.getBoundingClientRect();
|
const rectParent = cartazPrintableArea.getBoundingClientRect();
|
||||||
|
|
||||||
@@ -7639,7 +7658,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
cartazPreviewContent.style.display = 'none';
|
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() {
|
function exitCanvasMode() {
|
||||||
@@ -7651,6 +7670,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
if (canvasControlPanel) canvasControlPanel.style.display = 'none';
|
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) {
|
if (selectedCanvasItem) {
|
||||||
selectedCanvasItem.classList.remove('active-item');
|
selectedCanvasItem.classList.remove('active-item');
|
||||||
selectedCanvasItem = null;
|
selectedCanvasItem = null;
|
||||||
@@ -7661,7 +7686,46 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
item.removeAttribute('contenteditable');
|
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) {
|
function setupCanvasItemEvents(item) {
|
||||||
@@ -7916,6 +7980,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
const bg = rgbToHex(window.getComputedStyle(selectedCanvasItem).backgroundColor);
|
const bg = rgbToHex(window.getComputedStyle(selectedCanvasItem).backgroundColor);
|
||||||
if (canvasBgColorInput) canvasBgColorInput.value = bg;
|
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) {
|
if (canvasFontFamily) {
|
||||||
@@ -7938,6 +8008,23 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (selectedCanvasItem) selectedCanvasItem.style.backgroundColor = canvasBgColorInput.value;
|
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) {
|
if (btnCanvasZIndexUp) {
|
||||||
btnCanvasZIndexUp.addEventListener('click', () => {
|
btnCanvasZIndexUp.addEventListener('click', () => {
|
||||||
if (selectedCanvasItem) {
|
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();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+32
-1
@@ -1778,7 +1778,7 @@
|
|||||||
<div id="panelCartazesCriar" style="display: flex; flex-direction: row; gap: 24px; height: 100%; flex-wrap: wrap;">
|
<div id="panelCartazesCriar" style="display: flex; flex-direction: row; gap: 24px; height: 100%; flex-wrap: wrap;">
|
||||||
|
|
||||||
<!-- Lado Esquerdo: Configurações -->
|
<!-- Lado Esquerdo: Configurações -->
|
||||||
<div style="flex: 1; min-width: 320px; display: flex; flex-direction: column; gap: 16px;">
|
<div id="cartazLeftConfigPanel" style="flex: 1; min-width: 320px; display: flex; flex-direction: column; gap: 16px;">
|
||||||
<h4 style="margin: 0; color: var(--text-primary); border-bottom: 1px dashed var(--border-light); padding-bottom: 8px;">1. Definições do Cartaz</h4>
|
<h4 style="margin: 0; color: var(--text-primary); border-bottom: 1px dashed var(--border-light); padding-bottom: 8px;">1. Definições do Cartaz</h4>
|
||||||
|
|
||||||
<div class="settings-group">
|
<div class="settings-group">
|
||||||
@@ -1879,6 +1879,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Lado Esquerdo: Painel de Assets para Canvas -->
|
||||||
|
<div id="cartazLeftCanvasPanel" style="display: none; flex: 1; min-width: 320px; flex-direction: column; gap: 16px; padding-right: 12px;">
|
||||||
|
<h4 style="margin: 0; color: var(--text-primary); border-bottom: 1px dashed var(--border-light); padding-bottom: 8px;">🎨 Elementos do Canvas</h4>
|
||||||
|
|
||||||
|
<div class="settings-group">
|
||||||
|
<input type="text" id="canvasAssetSearch" placeholder="🔍 Buscar elementos..." class="obs-input" style="width: 100%; padding: 10px;">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Abas do Painel de Assets -->
|
||||||
|
<div style="display: flex; gap: 8px; border-bottom: 1px solid var(--border-light); padding-bottom: 8px;">
|
||||||
|
<button id="btnTabAssetPostits" class="tab-btn active" style="font-size: 0.8rem; padding: 6px 12px; background: rgba(59,130,246,0.1); border: none; cursor: pointer; color: var(--text-primary); border-radius: 4px; font-weight: bold;">📌 Post-its</button>
|
||||||
|
<button id="btnTabAssetStickers" class="tab-btn" style="font-size: 0.8rem; padding: 6px 12px; background: transparent; border: none; cursor: pointer; color: var(--text-secondary); border-radius: 4px;">🎈 Stickers</button>
|
||||||
|
<button id="btnTabAssetFormas" class="tab-btn" style="font-size: 0.8rem; padding: 6px 12px; background: transparent; border: none; cursor: pointer; color: var(--text-secondary); border-radius: 4px;">📐 Formas</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Container dos Assets -->
|
||||||
|
<div id="canvasAssetsContainer" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(75px, 1fr)); gap: 10px; overflow-y: auto; flex: 1; max-height: 480px; padding: 5px; background: rgba(0,0,0,0.05); border-radius: 8px; min-height: 250px;">
|
||||||
|
<!-- Preenchido via JS -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Lado Direito: Preview & Editor de Código -->
|
<!-- Lado Direito: Preview & Editor de Código -->
|
||||||
<div style="flex: 1.3; min-width: 360px; display: flex; flex-direction: column; gap: 14px;">
|
<div style="flex: 1.3; min-width: 360px; display: flex; flex-direction: column; gap: 14px;">
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border-light); padding-bottom: 8px;">
|
<div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border-light); padding-bottom: 8px;">
|
||||||
@@ -1919,6 +1940,16 @@
|
|||||||
<input type="color" id="canvasBgColor" style="width: 24px; height: 24px; border: 1px solid var(--border-light); border-radius: 4px; padding: 0; cursor: pointer; background: transparent; margin: 0;">
|
<input type="color" id="canvasBgColor" style="width: 24px; height: 24px; border: 1px solid var(--border-light); border-radius: 4px; padding: 0; cursor: pointer; background: transparent; margin: 0;">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div style="display: flex; align-items: center; gap: 4px;">
|
||||||
|
<label style="font-size: 0.72rem; color: var(--text-secondary);" title="Rotação">Giro:</label>
|
||||||
|
<input type="range" id="canvasRotation" min="-180" max="180" value="0" style="width: 60px; height: 4px; cursor: pointer; margin: 0;">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display: flex; align-items: center; gap: 4px;">
|
||||||
|
<label style="font-size: 0.72rem; color: var(--text-secondary);" title="Opacidade">Opac.:</label>
|
||||||
|
<input type="range" id="canvasOpacity" min="10" max="100" value="100" style="width: 60px; height: 4px; cursor: pointer; margin: 0;">
|
||||||
|
</div>
|
||||||
|
|
||||||
<div style="display: flex; gap: 4px; align-items: center;">
|
<div style="display: flex; gap: 4px; align-items: center;">
|
||||||
<button id="btnCanvasZIndexUp" class="btn-save-settings" style="background: transparent; border: 1px solid var(--border-light); color: var(--text-secondary); padding: 2px 6px; font-size: 0.7rem; cursor: pointer;" title="Trazer para Frente">🔺</button>
|
<button id="btnCanvasZIndexUp" class="btn-save-settings" style="background: transparent; border: 1px solid var(--border-light); color: var(--text-secondary); padding: 2px 6px; font-size: 0.7rem; cursor: pointer;" title="Trazer para Frente">🔺</button>
|
||||||
<button id="btnCanvasZIndexDown" class="btn-save-settings" style="background: transparent; border: 1px solid var(--border-light); color: var(--text-secondary); padding: 2px 6px; font-size: 0.7rem; cursor: pointer;" title="Enviar para Trás">🔻</button>
|
<button id="btnCanvasZIndexDown" class="btn-save-settings" style="background: transparent; border: 1px solid var(--border-light); color: var(--text-secondary); padding: 2px 6px; font-size: 0.7rem; cursor: pointer;" title="Enviar para Trás">🔻</button>
|
||||||
|
|||||||
@@ -1607,6 +1607,62 @@ app.delete('/api/cartazes/:id', requireAuth, async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.put('/api/cartazes/:id', requireAuth, async (req, res) => {
|
||||||
|
const { id } = req.params;
|
||||||
|
const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
|
||||||
|
const { titulo, tema, corFundo, corTexto, layout, conteudo, imagemUrl } = req.body;
|
||||||
|
try {
|
||||||
|
const dbRes = await dbPool.query(
|
||||||
|
`UPDATE escola.cartazes
|
||||||
|
SET titulo = COALESCE($1, titulo),
|
||||||
|
tema = COALESCE($2, tema),
|
||||||
|
cor_fundo = COALESCE($3, cor_fundo),
|
||||||
|
cor_texto = COALESCE($4, cor_texto),
|
||||||
|
layout = COALESCE($5, layout),
|
||||||
|
conteudo = COALESCE($6, conteudo),
|
||||||
|
imagem_url = COALESCE($7, imagem_url)
|
||||||
|
WHERE id = $8 AND usuario_id = $9 RETURNING *`,
|
||||||
|
[titulo, tema, corFundo, corTexto, layout, conteudo, imagemUrl, id, usuarioId]
|
||||||
|
);
|
||||||
|
if (dbRes.rows.length === 0) {
|
||||||
|
return res.status(404).json({ error: 'Cartaz não encontrado ou não pertence ao usuário.' });
|
||||||
|
}
|
||||||
|
res.json(dbRes.rows[0]);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Erro ao atualizar cartaz:', err);
|
||||||
|
res.status(500).json({ error: 'Erro ao atualizar cartaz.' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/api/biblioteca-assets', requireAuth, async (req, res) => {
|
||||||
|
const { busca, tipo } = req.query;
|
||||||
|
try {
|
||||||
|
let query = `SELECT id, tipo, nome, url, tags FROM escola.biblioteca_assets WHERE 1=1`;
|
||||||
|
const params = [];
|
||||||
|
let paramIdx = 1;
|
||||||
|
|
||||||
|
if (tipo) {
|
||||||
|
query += ` AND tipo = $${paramIdx}`;
|
||||||
|
params.push(tipo);
|
||||||
|
paramIdx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (busca) {
|
||||||
|
query += ` AND (nome ILIKE $${paramIdx} OR $${paramIdx} = ANY(tags))`;
|
||||||
|
params.push(`%${busca}%`);
|
||||||
|
paramIdx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
query += ` ORDER BY tipo, nome ASC`;
|
||||||
|
|
||||||
|
const dbRes = await dbPool.query(query, params);
|
||||||
|
res.json(dbRes.rows);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Erro ao buscar assets da biblioteca:', err);
|
||||||
|
res.status(500).json({ error: 'Erro ao carregar assets da biblioteca.' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// ROTAS DO MUSICANDO IDEIAS
|
// ROTAS DO MUSICANDO IDEIAS
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -2173,8 +2229,52 @@ async function initDatabase() {
|
|||||||
dados BYTEA NOT NULL,
|
dados BYTEA NOT NULL,
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- Biblioteca de Assets (Stickers, Ícones, Post-its, etc.)
|
||||||
|
CREATE TABLE IF NOT EXISTS escola.biblioteca_assets (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
tipo VARCHAR(50) NOT NULL, -- 'sticker', 'postit', 'icone', 'forma'
|
||||||
|
nome VARCHAR(100) NOT NULL,
|
||||||
|
url TEXT NOT NULL,
|
||||||
|
tags TEXT[], -- array de tags para busca
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||||
|
);
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
const checkAssets = await dbPool.query("SELECT COUNT(*) FROM escola.biblioteca_assets;");
|
||||||
|
if (parseInt(checkAssets.rows[0].count) === 0) {
|
||||||
|
console.log('[Database Init] Inserindo assets padrão na biblioteca (Design Canvas 2.0)...');
|
||||||
|
await dbPool.query(`
|
||||||
|
INSERT INTO escola.biblioteca_assets (tipo, nome, url, tags) VALUES
|
||||||
|
('postit', 'Post-it Amarelo', '#feff9c', ARRAY['post-it', 'amarelo', 'anotacao']),
|
||||||
|
('postit', 'Post-it Azul', '#7afcff', ARRAY['post-it', 'azul', 'anotacao']),
|
||||||
|
('postit', 'Post-it Rosa', '#ff7eb9', ARRAY['post-it', 'rosa', 'anotacao']),
|
||||||
|
('postit', 'Post-it Verde', '#98ff98', ARRAY['post-it', 'verde', 'anotacao']),
|
||||||
|
('postit', 'Post-it Laranja', '#ffb7b2', ARRAY['post-it', 'laranja', 'anotacao']),
|
||||||
|
|
||||||
|
('sticker', 'Escola', 'https://openmoji.org/data/color/svg/1F3EB.svg', ARRAY['escola', 'colegio', 'aula']),
|
||||||
|
('sticker', 'Lápis', 'https://openmoji.org/data/color/svg/270F.svg', ARRAY['lapis', 'escrever', 'desenhar']),
|
||||||
|
('sticker', 'Caderno', 'https://openmoji.org/data/color/svg/1F4DD.svg', ARRAY['caderno', 'anotacao', 'estudo']),
|
||||||
|
('sticker', 'Paleta de Arte', 'https://openmoji.org/data/color/svg/1F3A8.svg', ARRAY['arte', 'paleta', 'pintura', 'desenho']),
|
||||||
|
('sticker', 'Quebra-cabeça', 'https://openmoji.org/data/color/svg/1F9E9.svg', ARRAY['jogo', 'quebra-cabeca', 'brinquedo']),
|
||||||
|
('sticker', 'Ursinho', 'https://openmoji.org/data/color/svg/1F9F8.svg', ARRAY['brinquedo', 'ursinho', 'fofo']),
|
||||||
|
('sticker', 'Foguete', 'https://openmoji.org/data/color/svg/1F680.svg', ARRAY['foguete', 'espaco', 'voar']),
|
||||||
|
('sticker', 'Notas Musicais', 'https://openmoji.org/data/color/svg/1F3B6.svg', ARRAY['musica', 'som', 'notas']),
|
||||||
|
('sticker', 'Gatinho', 'https://openmoji.org/data/color/svg/1F431.svg', ARRAY['animal', 'gato', 'gatinho', 'fofo']),
|
||||||
|
('sticker', 'Cachorrinho', 'https://openmoji.org/data/color/svg/1F436.svg', ARRAY['animal', 'cao', 'cachorro', 'fofo']),
|
||||||
|
('sticker', 'Maçã', 'https://openmoji.org/data/color/svg/1F34E.svg', ARRAY['fruta', 'maca', 'comida']),
|
||||||
|
('sticker', 'Balão', 'https://openmoji.org/data/color/svg/1F388.svg', ARRAY['festa', 'balao', 'bexiga']),
|
||||||
|
('sticker', 'Estrela Brilhante', 'https://openmoji.org/data/color/svg/1F31F.svg', ARRAY['estrela', 'brilho', 'premio']),
|
||||||
|
('sticker', 'Coração', 'https://openmoji.org/data/color/svg/1F496.svg', ARRAY['amor', 'coracao', 'carinho']),
|
||||||
|
('sticker', 'Balão de Fala', 'https://openmoji.org/data/color/svg/1F4AC.svg', ARRAY['fala', 'dialogo', 'comunicacao']),
|
||||||
|
|
||||||
|
('forma', 'Retângulo', 'rect', ARRAY['forma', 'retangulo', 'quadrado', 'fundo']),
|
||||||
|
('forma', 'Círculo', 'circle', ARRAY['forma', 'circulo', 'bola', 'redondo']),
|
||||||
|
('forma', 'Elipse', 'ellipse', ARRAY['forma', 'elipse', 'oval']),
|
||||||
|
('forma', 'Linha Horizontal', 'line', ARRAY['forma', 'linha', 'divisoria']);
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
// Garantir que a coluna character_description_english exista na tabela projetos_comics
|
// Garantir que a coluna character_description_english exista na tabela projetos_comics
|
||||||
await dbPool.query(`ALTER TABLE escola.projetos_comics ADD COLUMN IF NOT EXISTS character_description_english TEXT;`);
|
await dbPool.query(`ALTER TABLE escola.projetos_comics ADD COLUMN IF NOT EXISTS character_description_english TEXT;`);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user