|
class InkBoard { |
|
constructor() { |
|
this.currentCreationId = null; |
|
this.currentSection = 'create'; |
|
this.init(); |
|
} |
|
|
|
init() { |
|
this.setupEventListeners(); |
|
this.loadGallery(); |
|
this.showSection('create'); |
|
} |
|
|
|
setupEventListeners() { |
|
document.getElementById('scene-form').addEventListener('submit', (e) => { |
|
e.preventDefault(); |
|
this.generateContent(); |
|
}); |
|
|
|
document.getElementById('save-journal').addEventListener('click', () => { |
|
this.saveJournal(); |
|
}); |
|
|
|
document.getElementById('journalModal').addEventListener('hidden.bs.modal', () => { |
|
this.currentCreationId = null; |
|
document.getElementById('journal-text').value = ''; |
|
}); |
|
} |
|
|
|
async generateContent() { |
|
const sceneIdea = document.getElementById('scene-idea').value.trim(); |
|
|
|
if (!sceneIdea) { |
|
this.showAlert('Please enter a scene idea', 'danger'); |
|
return; |
|
} |
|
|
|
try { |
|
this.showLoading(true); |
|
this.setButtonLoading(true); |
|
|
|
const response = await fetch('/generate', { |
|
method: 'POST', |
|
headers: { 'Content-Type': 'application/json' }, |
|
body: JSON.stringify({ scene_idea: sceneIdea }) |
|
}); |
|
|
|
const data = await response.json(); |
|
|
|
if (data.success) { |
|
this.displayResult(data); |
|
this.loadGallery(); |
|
document.getElementById('scene-idea').value = ''; |
|
this.showAlert('Story and image generated successfully!', 'success'); |
|
} else { |
|
this.showAlert(data.error || 'Failed to generate content', 'danger'); |
|
} |
|
|
|
} catch (error) { |
|
console.error('Error generating content:', error); |
|
this.showAlert('Network error. Please try again.', 'danger'); |
|
} finally { |
|
this.showLoading(false); |
|
this.setButtonLoading(false); |
|
} |
|
} |
|
|
|
displayResult(data) { |
|
const resultsSection = document.getElementById('results-section'); |
|
|
|
const imageSection = data.image_url ? ` |
|
<div class="col-lg-6 mb-4"> |
|
<div class="image-container"> |
|
<img src="${data.image_url}" alt="Generated Scene" class="img-fluid rounded shadow"> |
|
</div> |
|
</div>` : ''; |
|
|
|
const storyColClass = data.image_url ? 'col-lg-6' : 'col-12'; |
|
|
|
const downloadButton = data.image_url ? ` |
|
<button class="btn btn-outline-success btn-sm" onclick="inkBoard.downloadImage('${data.image_url}')"> |
|
<i class="fas fa-download me-1"></i> Download Image |
|
</button>` : ''; |
|
|
|
const resultHTML = ` |
|
<div class="col-12 mb-5"> |
|
<div class="card shadow-lg border-0"> |
|
<div class="card-body p-4"> |
|
<h4 class="card-title text-center mb-4"><i class="fas fa-sparkles me-2"></i>Your Creation</h4> |
|
<div class="row"> |
|
${imageSection} |
|
<div class="${storyColClass}"> |
|
<div class="story-container"> |
|
<h5 class="mb-3"><i class="fas fa-book-open me-2"></i>Your Story</h5> |
|
<p class="story-text">${data.story}</p> |
|
<div class="mt-3"> |
|
<button class="btn btn-outline-primary btn-sm me-2" onclick="inkBoard.openJournal('${data.creation_id}')"> |
|
<i class="fas fa-journal-whills me-1"></i>Add Journal Entry |
|
</button> |
|
${downloadButton} |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div>`; |
|
|
|
resultsSection.innerHTML = resultHTML; |
|
resultsSection.scrollIntoView({ behavior: 'smooth' }); |
|
} |
|
|
|
async loadGallery() { |
|
try { |
|
const response = await fetch('/get_creations'); |
|
const data = await response.json(); |
|
|
|
const galleryGrid = document.getElementById('gallery-grid'); |
|
|
|
if (data.creations && data.creations.length > 0) { |
|
const galleryHTML = data.creations.map(creation => this.createGalleryItem(creation)).join(''); |
|
galleryGrid.innerHTML = galleryHTML; |
|
} else { |
|
galleryGrid.innerHTML = ` |
|
<div class="col-12 text-center py-5"> |
|
<i class="fas fa-palette fa-3x text-muted mb-3"></i> |
|
<h5 class="text-muted">No creations yet</h5> |
|
<p class="text-muted">Start by describing a scene above!</p> |
|
</div>`; |
|
} |
|
} catch (error) { |
|
console.error('Error loading gallery:', error); |
|
} |
|
} |
|
|
|
createGalleryItem(creation) { |
|
const journalEntry = creation.journal_entry ? ` |
|
<div class="journal-entry"> |
|
<small class="text-muted"> |
|
<i class="fas fa-journal-whills me-1"></i> Journal Entry: |
|
</small> |
|
<p class="mb-0 mt-1">${creation.journal_entry}</p> |
|
</div>` : ''; |
|
|
|
const imageSection = creation.image_url ? ` |
|
<img src="${creation.image_url}" alt="Scene: ${creation.scene_idea}" loading="lazy">` : ''; |
|
|
|
const downloadButton = creation.image_url ? ` |
|
<button class="btn btn-outline-success btn-sm" onclick="inkBoard.downloadImage('${creation.image_url}')"> |
|
<i class="fas fa-download me-1"></i> Download |
|
</button>` : ''; |
|
|
|
return ` |
|
<div class="gallery-item"> |
|
${imageSection} |
|
<div class="gallery-item-content"> |
|
<div class="gallery-item-scene"> |
|
<i class="fas fa-quote-left me-1"></i> ${creation.scene_idea} |
|
</div> |
|
<div class="gallery-item-story">${creation.story}</div> |
|
${journalEntry} |
|
<div class="gallery-item-actions"> |
|
<button class="btn btn-outline-primary btn-sm" onclick="inkBoard.openJournal('${creation.id}', '${creation.journal_entry || ''}')"> |
|
<i class="fas fa-journal-whills me-1"></i> ${creation.journal_entry ? 'Edit' : 'Add'} Journal |
|
</button> |
|
${downloadButton} |
|
</div> |
|
</div> |
|
</div>`; |
|
} |
|
|
|
openJournal(creationId, existingText = '') { |
|
this.currentCreationId = creationId; |
|
document.getElementById('journal-text').value = existingText; |
|
|
|
const modal = new bootstrap.Modal(document.getElementById('journalModal')); |
|
modal.show(); |
|
} |
|
|
|
async saveJournal() { |
|
if (!this.currentCreationId) { |
|
this.showAlert('No creation selected', 'danger'); |
|
return; |
|
} |
|
|
|
const journalText = document.getElementById('journal-text').value.trim(); |
|
|
|
try { |
|
const response = await fetch('/save_journal', { |
|
method: 'POST', |
|
headers: { 'Content-Type': 'application/json' }, |
|
body: JSON.stringify({ |
|
creation_id: this.currentCreationId, |
|
journal_entry: journalText |
|
}) |
|
}); |
|
|
|
const data = await response.json(); |
|
|
|
if (data.success) { |
|
this.showAlert('Journal entry saved!', 'success'); |
|
this.loadGallery(); |
|
|
|
const modal = bootstrap.Modal.getInstance(document.getElementById('journalModal')); |
|
modal.hide(); |
|
} else { |
|
this.showAlert(data.error || 'Failed to save journal', 'danger'); |
|
} |
|
} catch (error) { |
|
console.error('Error saving journal:', error); |
|
this.showAlert('Network error. Please try again.', 'danger'); |
|
} |
|
} |
|
|
|
downloadImage(imageUrl) { |
|
const link = document.createElement('a'); |
|
link.href = imageUrl; |
|
link.download = `inkboard-creation-${Date.now()}.png`; |
|
link.target = '_blank'; |
|
document.body.appendChild(link); |
|
link.click(); |
|
document.body.removeChild(link); |
|
this.showAlert('Image download started!', 'success'); |
|
} |
|
|
|
showLoading(show) { |
|
const loadingSection = document.getElementById('loading-section'); |
|
const resultsSection = document.getElementById('results-section'); |
|
|
|
if (show) { |
|
loadingSection.classList.remove('d-none'); |
|
resultsSection.innerHTML = ''; |
|
} else { |
|
loadingSection.classList.add('d-none'); |
|
} |
|
} |
|
|
|
setButtonLoading(loading) { |
|
const btn = document.getElementById('generate-btn'); |
|
const btnText = btn.querySelector('.btn-text'); |
|
const spinner = btn.querySelector('.spinner-border'); |
|
|
|
if (loading) { |
|
btn.classList.add('loading'); |
|
btn.disabled = true; |
|
btnText.classList.add('d-none'); |
|
spinner.classList.remove('d-none'); |
|
} else { |
|
btn.classList.remove('loading'); |
|
btn.disabled = false; |
|
btnText.classList.remove('d-none'); |
|
spinner.classList.add('d-none'); |
|
} |
|
} |
|
|
|
showAlert(message, type) { |
|
const existingAlert = document.querySelector('.alert'); |
|
if (existingAlert) existingAlert.remove(); |
|
|
|
const alert = document.createElement('div'); |
|
alert.className = `alert alert-${type} alert-dismissible fade show`; |
|
alert.innerHTML = `${message} |
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>`; |
|
|
|
const main = document.querySelector('main'); |
|
main.insertBefore(alert, main.firstChild); |
|
|
|
setTimeout(() => { |
|
if (alert.parentNode) alert.remove(); |
|
}, 5000); |
|
} |
|
|
|
showSection(sectionName) { |
|
document.querySelectorAll('.dashboard-section').forEach(section => { |
|
section.classList.add('d-none'); |
|
}); |
|
|
|
document.querySelectorAll('.btn-nav').forEach(btn => { |
|
btn.classList.remove('active'); |
|
}); |
|
|
|
const targetSection = document.getElementById(`${sectionName}-section`); |
|
if (targetSection) targetSection.classList.remove('d-none'); |
|
|
|
const buttonSelectors = { |
|
'create': 'Create Story', |
|
'gallery': 'Gallery', |
|
'journal': 'Journal' |
|
}; |
|
|
|
document.querySelectorAll('.btn-nav').forEach(btn => { |
|
if (btn.textContent.trim().includes(buttonSelectors[sectionName])) { |
|
btn.classList.add('active'); |
|
} |
|
}); |
|
|
|
this.currentSection = sectionName; |
|
|
|
if (sectionName === 'gallery') { |
|
this.loadGallery(); |
|
} else if (sectionName === 'journal') { |
|
this.loadJournalEntries(); |
|
} |
|
} |
|
|
|
async loadJournalEntries() { |
|
try { |
|
const response = await fetch('/get_creations'); |
|
const data = await response.json(); |
|
|
|
const journalContainer = document.getElementById('journal-entries'); |
|
const entriesWithJournal = data.creations?.filter(creation => creation.journal_entry) || []; |
|
|
|
if (entriesWithJournal.length > 0) { |
|
const journalHTML = entriesWithJournal.map(creation => ` |
|
<div class="journal-entry-card mb-3"> |
|
<h5>${creation.scene_idea}</h5> |
|
<p>${creation.journal_entry}</p> |
|
</div>`).join(''); |
|
journalContainer.innerHTML = journalHTML; |
|
} else { |
|
journalContainer.innerHTML = ` |
|
<div class="col-12 text-center py-5"> |
|
<i class="fas fa-journal-whills fa-3x text-muted mb-3"></i> |
|
<h5 class="text-muted">No journal entries yet</h5> |
|
</div>`; |
|
} |
|
} catch (error) { |
|
console.error('Error loading journal entries:', error); |
|
} |
|
} |
|
} |
|
|
|
|
|
const inkBoard = new InkBoard(); |