feat: adiciona recurso de salvar/carregar/excluir projetos na Fábrica de Quadrinhos

This commit is contained in:
2026-06-03 14:54:09 +00:00
parent 351e9b0040
commit 9aac360fe5
3 changed files with 460 additions and 4 deletions
+313 -4
View File
@@ -3483,6 +3483,13 @@ const initApp = () => {
const comicsVideoPlayer = document.getElementById('comicsVideoPlayer');
const comicsVideoDownloadBtn = document.getElementById('comicsVideoDownloadBtn');
const comicsProjectSelect = document.getElementById('comicsProjectSelect');
const btnNewComicsProject = document.getElementById('btnNewComicsProject');
const btnDeleteComicsProject = document.getElementById('btnDeleteComicsProject');
const comicsProjectTitleInput = document.getElementById('comicsProjectTitleInput');
const btnSaveComicsProject = document.getElementById('btnSaveComicsProject');
let currentComicsProjectId = null;
let selectedComicsQty = 5;
let selectedComicsBubbles = true;
let selectedComicsRatio = '16:9';
@@ -3582,23 +3589,325 @@ const initApp = () => {
});
}
// Abrir e fechar modal Fábrica de Quadrinhos
const openComicsModal = () => {
fabricaQuadrinhosModal.style.display = 'flex';
// --- GERENCIADOR DE PROJETOS DE HQ ---
const startNewComicsProject = () => {
currentComicsProjectId = null;
if (comicsProjectSelect) comicsProjectSelect.value = '';
if (comicsProjectTitleInput) comicsProjectTitleInput.value = '';
if (btnDeleteComicsProject) btnDeleteComicsProject.style.display = 'none';
comicsTemaInput.value = '';
comicsResultArea.style.display = 'none';
comicsGenerateLoader.style.display = 'none';
comicsVideoResult.style.display = 'none';
comicsVideoLoader.style.display = 'none';
btnGenerateComics.disabled = false;
comicsLoaderProgress.style.width = '0%';
if (comicsLoaderProgress) comicsLoaderProgress.style.width = '0%';
// Set active state for default quick options
selectedComicsQty = 5;
document.querySelectorAll('.btn-comics-qty').forEach(b => {
if (b.dataset.qty === '5') b.classList.add('active');
else b.classList.remove('active');
});
selectedComicsBubbles = true;
document.querySelectorAll('.btn-comics-bubbles').forEach(b => {
if (b.dataset.bubbles === 'true') b.classList.add('active');
else b.classList.remove('active');
});
selectedComicsRatio = '16:9';
document.querySelectorAll('.btn-comics-ratio').forEach(b => {
if (b.dataset.ratio === '16:9') b.classList.add('active');
else b.classList.remove('active');
});
if (comicsCenarioSelect) {
comicsCenarioSelect.value = 'no parque de diversões colorido';
if (comicsCenarioCustomInput) {
comicsCenarioCustomInput.style.display = 'none';
comicsCenarioCustomInput.value = '';
}
}
// Inicializar lista de personagens com padrão se estiver vazia
comicsCharListContainer.innerHTML = '';
addCharacterRow("Menino", "Lucas", false);
addCharacterRow("Menina", "Mariana", false);
generatedPanelsData = [];
comicsGridContainer.innerHTML = '';
};
const loadComicsProjectsList = async () => {
if (!comicsProjectSelect) return;
try {
const resp = await fetch('/api/comics/projects');
if (!resp.ok) throw new Error('Falha ao listar projetos');
const projects = await resp.json();
comicsProjectSelect.innerHTML = '<option value="">-- Carregar projeto salvo... --</option>';
projects.forEach(proj => {
const opt = document.createElement('option');
opt.value = proj.id;
opt.textContent = `${proj.titulo} (${new Date(proj.updated_at).toLocaleDateString('pt-BR')})`;
comicsProjectSelect.appendChild(opt);
});
if (currentComicsProjectId) {
comicsProjectSelect.value = currentComicsProjectId;
}
} catch (err) {
console.error('Erro ao listar projetos:', err);
}
};
const loadComicsProject = async (id) => {
if (!id) {
startNewComicsProject();
return;
}
try {
const resp = await fetch(`/api/comics/projects/${id}`);
if (!resp.ok) throw new Error('Falha ao carregar projeto');
const { project, panels } = await resp.json();
currentComicsProjectId = project.id;
if (comicsProjectTitleInput) comicsProjectTitleInput.value = project.titulo || '';
comicsTemaInput.value = project.tema || '';
if (btnDeleteComicsProject) btnDeleteComicsProject.style.display = 'block';
// Carregar proporção
selectedComicsRatio = project.proporcao || '16:9';
document.querySelectorAll('.btn-comics-ratio').forEach(b => {
if (b.dataset.ratio === selectedComicsRatio) b.classList.add('active');
else b.classList.remove('active');
});
// Carregar cenário
if (comicsCenarioSelect) {
const standardOptions = ['no parque de diversões colorido', 'na sala de aula da escola infantil', 'em uma floresta encantada cheia de flores', 'no zoológico interagindo com animais', 'num jardim ensolarado de uma casa'];
if (standardOptions.includes(project.cenario)) {
comicsCenarioSelect.value = project.cenario;
if (comicsCenarioCustomInput) comicsCenarioCustomInput.style.display = 'none';
} else {
comicsCenarioSelect.value = 'custom';
if (comicsCenarioCustomInput) {
comicsCenarioCustomInput.style.display = 'block';
comicsCenarioCustomInput.value = project.cenario || '';
}
}
}
// Carregar quantidade e balões baseados nos painéis retornados
selectedComicsQty = panels.length || 5;
document.querySelectorAll('.btn-comics-qty').forEach(b => {
if (parseInt(b.dataset.qty) === selectedComicsQty) b.classList.add('active');
else b.classList.remove('active');
});
const hasDialogue = panels.some(p => p.dialogue);
selectedComicsBubbles = hasDialogue;
document.querySelectorAll('.btn-comics-bubbles').forEach(b => {
if ((b.dataset.bubbles === 'true') === selectedComicsBubbles) b.classList.add('active');
else b.classList.remove('active');
});
// Carregar personagens
comicsCharListContainer.innerHTML = '';
if (project.character_description_global) {
try {
const chars = JSON.parse(project.character_description_global);
if (Array.isArray(chars)) {
chars.forEach(c => {
addCharacterRow(c.type || '', c.name || '', c.isAnimal || false);
});
}
} catch (e) {
addCharacterRow("Menino", "Lucas", false);
addCharacterRow("Menina", "Mariana", false);
}
} else {
addCharacterRow("Menino", "Lucas", false);
addCharacterRow("Menina", "Mariana", false);
}
// Carregar os painéis
generatedPanelsData = panels.map(p => ({
panel_number: p.panel_number,
imageUrl: p.image_url,
dialogue: p.dialogue || '',
image_prompt: p.image_prompt || ''
}));
if (generatedPanelsData.length > 0) {
comicsResultTitle.textContent = project.titulo || 'História em Quadrinhos';
comicsGridContainer.innerHTML = '';
generatedPanelsData.forEach(panel => {
const panelCard = document.createElement('div');
panelCard.className = 'comic-panel-card';
const imgContainer = document.createElement('div');
imgContainer.className = `comic-panel-image-container ratio-${selectedComicsRatio.replace(':', '-')}`;
const img = document.createElement('img');
img.className = 'comic-panel-image';
img.src = panel.imageUrl;
img.alt = `Quadro ${panel.panel_number}`;
imgContainer.appendChild(img);
const badge = document.createElement('div');
badge.className = 'comic-panel-number-badge';
badge.textContent = panel.panel_number;
imgContainer.appendChild(badge);
if (selectedComicsBubbles && panel.dialogue) {
const bubble = document.createElement('div');
bubble.className = 'comic-speech-bubble';
bubble.textContent = panel.dialogue;
imgContainer.appendChild(bubble);
}
panelCard.appendChild(imgContainer);
if (panel.dialogue) {
const caption = document.createElement('div');
caption.className = 'comic-caption-text';
caption.textContent = panel.dialogue;
panelCard.appendChild(caption);
}
comicsGridContainer.appendChild(panelCard);
});
comicsResultArea.style.display = 'flex';
} else {
comicsResultArea.style.display = 'none';
}
comicsVideoResult.style.display = 'none';
comicsVideoLoader.style.display = 'none';
btnGenerateComics.disabled = false;
} catch (err) {
console.error(err);
alert('Erro ao carregar projeto: ' + err.message);
}
};
const saveComicsProject = async () => {
const titulo = comicsProjectTitleInput ? comicsProjectTitleInput.value.trim() : '';
if (!titulo) {
alert('Por favor, digite um título para salvar seu projeto.');
return;
}
const characters = [];
document.querySelectorAll('.comic-char-row').forEach(row => {
const type = row.querySelector('.char-type-input').value.trim();
const name = row.querySelector('.char-name-input').value.trim();
const isAnimal = row.querySelector('span').textContent === '🐾';
if (type || name) {
characters.push({ type, name, isAnimal });
}
});
let cenario = comicsCenarioSelect.value;
if (cenario === 'custom') {
cenario = comicsCenarioCustomInput.value.trim();
}
const body = {
id: currentComicsProjectId,
titulo,
tema: comicsTemaInput.value.trim(),
cenario,
proporcao: selectedComicsRatio,
character_description_global: JSON.stringify(characters),
panels: generatedPanelsData.map(p => ({
panel_number: p.panel_number,
image_url: p.imageUrl,
image_prompt: p.image_prompt || '',
dialogue: p.dialogue || ''
}))
};
try {
const resp = await fetch('/api/comics/projects', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
if (!resp.ok) {
const err = await resp.json();
throw new Error(err.error || 'Erro ao salvar projeto');
}
const data = await resp.json();
currentComicsProjectId = data.id;
alert('Projeto salvo com sucesso!');
if (btnDeleteComicsProject) btnDeleteComicsProject.style.display = 'block';
await loadComicsProjectsList();
} catch (err) {
console.error(err);
alert('Erro ao salvar projeto: ' + err.message);
}
};
const deleteComicsProject = async () => {
if (!currentComicsProjectId) return;
if (!confirm('Tem certeza de que deseja excluir este projeto permanentemente?')) return;
try {
const resp = await fetch(`/api/comics/projects/${currentComicsProjectId}`, {
method: 'DELETE'
});
if (!resp.ok) throw new Error('Falha ao excluir projeto');
alert('Projeto excluído com sucesso!');
startNewComicsProject();
await loadComicsProjectsList();
} catch (err) {
console.error(err);
alert('Erro ao excluir projeto: ' + err.message);
}
};
// Abrir e fechar modal Fábrica de Quadrinhos
const openComicsModal = () => {
fabricaQuadrinhosModal.style.display = 'flex';
startNewComicsProject();
loadComicsProjectsList();
};
// Listeners do Gerenciador de Projetos
if (comicsProjectSelect) {
comicsProjectSelect.addEventListener('change', (e) => {
loadComicsProject(e.target.value);
});
}
if (btnNewComicsProject) {
btnNewComicsProject.addEventListener('click', () => {
if (confirm('Deseja iniciar um novo projeto? Alterações não salvas serão perdidas.')) {
startNewComicsProject();
}
});
}
if (btnSaveComicsProject) {
btnSaveComicsProject.addEventListener('click', saveComicsProject);
}
if (btnDeleteComicsProject) {
btnDeleteComicsProject.addEventListener('click', deleteComicsProject);
}
if (barBtnComics) {
barBtnComics.addEventListener('click', openComicsModal);
}
+17
View File
@@ -746,6 +746,23 @@
</div>
<div class="settings-modal-body" style="padding: 20px; overflow-y: auto; display: flex; flex-direction: column; gap: 18px; flex: 1;">
<!-- GERENCIADOR DE PROJETOS -->
<div style="background: rgba(255,255,255,0.03); border: 1px solid var(--border-light); padding: 14px; border-radius: 12px; display: flex; flex-direction: column; gap: 10px;">
<label style="color: var(--text-secondary); font-size: 0.82rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">📁 Meus Projetos de Quadrinhos</label>
<div style="display: flex; gap: 10px; align-items: center;">
<select id="comicsProjectSelect" class="obs-select" style="flex: 1; padding: 8px 12px; font-size: 0.9rem;">
<option value="">-- Carregar projeto salvo... --</option>
</select>
<button type="button" id="btnNewComicsProject" class="btn-music-option" style="padding: 8px 14px; font-size: 0.82rem; border-color: var(--text-secondary); color: var(--text-primary); background: transparent; margin: 0; white-space: nowrap;">✨ Novo</button>
<button type="button" id="btnDeleteComicsProject" class="btn-music-option" style="padding: 8px 14px; font-size: 0.82rem; border-color: #ef4444; color: #ef4444; background: rgba(239, 68, 68, 0.05); margin: 0; white-space: nowrap; display: none;">🗑️ Excluir</button>
</div>
<div style="display: flex; align-items: center; gap: 10px;">
<input type="text" id="comicsProjectTitleInput" class="obs-input" placeholder="Título do Projeto (Ex: Lucas e Mariana no Parque)" style="flex: 1; margin: 0; padding: 8px 12px; font-size: 0.9rem;">
<button type="button" id="btnSaveComicsProject" class="btn-music-option" style="padding: 8px 14px; font-size: 0.82rem; border-color: #10a37f; color: white; background: #10a37f; margin: 0; white-space: nowrap; font-weight: 600;">💾 Salvar</button>
</div>
</div>
<!-- TEMA -->
<div class="settings-group">
<label style="color: var(--text-secondary); font-size: 0.82rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">1. Tema da História</label>