feat: adiciona recurso de salvar/carregar/excluir projetos na Fábrica de Quadrinhos
This commit is contained in:
+313
-4
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user