✨ feat: Estúdio de Cenas em Camadas - IA desmonta imagem em elementos editáveis individualmente no canvas
This commit is contained in:
+192
@@ -7601,6 +7601,198 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
let dragStartWidth, dragStartHeight;
|
||||
let activeAssetTab = 'postit';
|
||||
|
||||
// --- ESTÚDIO DE CENAS EM CAMADAS ---
|
||||
const btnDecomposeScene = document.getElementById('btnDecomposeScene');
|
||||
const sceneDecomposePrompt = document.getElementById('sceneDecomposePrompt');
|
||||
const sceneDecomposeLoader = document.getElementById('sceneDecomposeLoader');
|
||||
const sceneDecomposeStatus = document.getElementById('sceneDecomposeStatus');
|
||||
const sceneLayersList = document.getElementById('sceneLayersList');
|
||||
const sceneLayersItems = document.getElementById('sceneLayersItems');
|
||||
|
||||
// Cria um item de camada de imagem direto no canvas (background ou sticker)
|
||||
function addSceneLayerToCanvas(layerData) {
|
||||
if (!canvasModeActive || !cartazPrintableArea) return;
|
||||
|
||||
const item = document.createElement('div');
|
||||
item.className = 'canvas-item canvas-scene-layer';
|
||||
item.setAttribute('data-layer-name', layerData.name || '');
|
||||
item.setAttribute('data-rotation', '0');
|
||||
item.style.position = 'absolute';
|
||||
item.style.left = (layerData.left || 10) + '%';
|
||||
item.style.top = (layerData.top || 10) + '%';
|
||||
item.style.width = (layerData.width || 35) + '%';
|
||||
item.style.height = (layerData.height || 35) + '%';
|
||||
item.style.zIndex = layerData.isBackground ? '1' : String(10 + (layerData.zOffset || 0));
|
||||
item.style.background = 'transparent';
|
||||
|
||||
if (layerData.isBackground) {
|
||||
// Fundo: cobrindo 100% e não selecionável como item de controle
|
||||
item.style.left = '0';
|
||||
item.style.top = '0';
|
||||
item.style.width = '100%';
|
||||
item.style.height = '100%';
|
||||
item.style.zIndex = '0';
|
||||
item.style.pointerEvents = 'none'; // Por padrão, o fundo não captura eventos para facilitar a seleção dos elementos acima
|
||||
item.innerHTML = `<img src="${layerData.url}"
|
||||
style="width:100%; height:100%; object-fit:cover; pointer-events:none; display:block;">`;
|
||||
item.title = '🖼️ Cenário de Fundo';
|
||||
} else {
|
||||
item.innerHTML = `<img src="${layerData.url}"
|
||||
style="width:100%; height:100%; object-fit:contain; pointer-events:none;">`;
|
||||
item.title = '🎬 ' + (layerData.name || 'Elemento');
|
||||
}
|
||||
|
||||
const handle = document.createElement('div');
|
||||
handle.className = 'canvas-resize-handle';
|
||||
item.appendChild(handle);
|
||||
|
||||
cartazPrintableArea.appendChild(item);
|
||||
|
||||
if (!layerData.isBackground) {
|
||||
setupCanvasItemEvents(item);
|
||||
// Permitir que o fundo seja selecionável após clicar nele diretamente
|
||||
} else {
|
||||
// para o fundo, adicionar capacidade de clicar para habilitar arrastar
|
||||
item.style.pointerEvents = 'auto';
|
||||
setupCanvasItemEvents(item);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
// Renderiza a lista de camadas no painel lateral
|
||||
function renderSceneLayers(layers) {
|
||||
if (!sceneLayersItems) return;
|
||||
sceneLayersItems.innerHTML = '';
|
||||
|
||||
const allLayers = [{ name: '🖼️ Cenário de Fundo', isBackground: true }, ...layers.map(l => ({ ...l }))];
|
||||
allLayers.forEach((layer, index) => {
|
||||
const chip = document.createElement('div');
|
||||
chip.style.cssText = `display:flex; align-items:center; gap:8px; padding:5px 8px;
|
||||
background:rgba(255,255,255,0.05); border:1px solid rgba(139,92,246,0.2);
|
||||
border-radius:6px; font-size:0.78rem; color:var(--text-primary); cursor:pointer;`;
|
||||
chip.innerHTML = `
|
||||
<span style="font-size:1rem;">${layer.isBackground ? '🌄' : '✨'}</span>
|
||||
<span style="flex:1;">${layer.name}</span>
|
||||
<span style="font-size:0.68rem; color:var(--text-secondary);">Camada ${index}</span>
|
||||
`;
|
||||
chip.title = `${layer.isBackground ? 'Fundo da cena' : 'Elemento: ' + layer.name}`;
|
||||
sceneLayersItems.appendChild(chip);
|
||||
});
|
||||
}
|
||||
|
||||
if (btnDecomposeScene) {
|
||||
btnDecomposeScene.addEventListener('click', async () => {
|
||||
const prompt = sceneDecomposePrompt ? sceneDecomposePrompt.value.trim() : '';
|
||||
if (!prompt) {
|
||||
showToast('Descreva a cena antes de gerar!', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// Garantir que o modo canvas está ativo
|
||||
if (!canvasModeActive) {
|
||||
if (cartazEditArea && cartazEditArea.style.display !== 'none') {
|
||||
btnEditCartazToggle && btnEditCartazToggle.click();
|
||||
}
|
||||
enterCanvasMode();
|
||||
}
|
||||
|
||||
// Mostrar loading
|
||||
btnDecomposeScene.disabled = true;
|
||||
btnDecomposeScene.style.opacity = '0.6';
|
||||
if (sceneDecomposeLoader) {
|
||||
sceneDecomposeLoader.style.display = 'flex';
|
||||
}
|
||||
if (sceneLayersList) sceneLayersList.style.display = 'none';
|
||||
|
||||
const statusSteps = [
|
||||
'Planejando a cena com IA...',
|
||||
'Identificando elementos e cenário...',
|
||||
'Gerando imagem de fundo...',
|
||||
'Gerando elementos individuais...',
|
||||
'Montando as camadas no canvas...'
|
||||
];
|
||||
let stepIdx = 0;
|
||||
const statusInterval = setInterval(() => {
|
||||
if (sceneDecomposeStatus && stepIdx < statusSteps.length) {
|
||||
sceneDecomposeStatus.textContent = statusSteps[stepIdx++];
|
||||
}
|
||||
}, 3500);
|
||||
|
||||
try {
|
||||
const titulo = activeCartazData?.titulo || '';
|
||||
const response = await fetch('/api/cartazes/decompose-scene', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||
},
|
||||
body: JSON.stringify({ tema: prompt, titulo })
|
||||
});
|
||||
|
||||
clearInterval(statusInterval);
|
||||
|
||||
if (!response.ok) {
|
||||
const errData = await response.json().catch(() => ({}));
|
||||
throw new Error(errData.error || 'Erro na geração da cena.');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (!data.success) throw new Error('Resposta inválida do servidor.');
|
||||
|
||||
if (sceneDecomposeStatus) sceneDecomposeStatus.textContent = 'Montando as camadas no canvas...';
|
||||
|
||||
// Limpar canvas antes de montar (remover outros items de cena se houver)
|
||||
const oldLayers = cartazPrintableArea.querySelectorAll('.canvas-scene-layer');
|
||||
oldLayers.forEach(l => l.remove());
|
||||
|
||||
// Adicionar background
|
||||
if (data.backgroundUrl) {
|
||||
addSceneLayerToCanvas({ url: data.backgroundUrl, name: 'Cenário de Fundo', isBackground: true });
|
||||
}
|
||||
|
||||
// Adicionar cada elemento com posição escalonada
|
||||
if (Array.isArray(data.layers)) {
|
||||
data.layers.forEach((layer, idx) => {
|
||||
if (layer.url) {
|
||||
const spread = [
|
||||
{ left: 10, top: 30 },
|
||||
{ left: 38, top: 35 },
|
||||
{ left: 62, top: 28 },
|
||||
{ left: 25, top: 55 }
|
||||
];
|
||||
const pos = spread[idx % spread.length];
|
||||
addSceneLayerToCanvas({
|
||||
url: layer.url,
|
||||
name: layer.name || `Elemento ${idx + 1}`,
|
||||
isBackground: false,
|
||||
left: pos.left,
|
||||
top: pos.top,
|
||||
width: 28,
|
||||
height: 28,
|
||||
zOffset: idx + 1
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Renderizar lista de camadas no painel
|
||||
renderSceneLayers(data.layers);
|
||||
if (sceneLayersList) sceneLayersList.style.display = 'flex';
|
||||
}
|
||||
|
||||
showToast('🎬 Cena montada em camadas! Clique nos elementos para mover, girar e redimensionar.', 'success');
|
||||
|
||||
} catch (err) {
|
||||
clearInterval(statusInterval);
|
||||
console.error('[Decompose Scene]', err);
|
||||
showToast('Erro ao criar a cena em camadas: ' + err.message, 'error');
|
||||
} finally {
|
||||
btnDecomposeScene.disabled = false;
|
||||
btnDecomposeScene.style.opacity = '1';
|
||||
if (sceneDecomposeLoader) sceneDecomposeLoader.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function enterCanvasMode() {
|
||||
canvasModeActive = true;
|
||||
if (btnCanvasModeToggle) {
|
||||
|
||||
Reference in New Issue
Block a user