Update static/js/script.js
Browse files- static/js/script.js +217 -87
static/js/script.js
CHANGED
@@ -1,5 +1,3 @@
|
|
1 |
-
// --- InkBoard JavaScript (Fixed) ---
|
2 |
-
|
3 |
class InkBoard {
|
4 |
constructor() {
|
5 |
this.currentCreationId = null;
|
@@ -10,29 +8,28 @@ class InkBoard {
|
|
10 |
init() {
|
11 |
this.setupEventListeners();
|
12 |
this.loadGallery();
|
13 |
-
this.showSection('create');
|
14 |
}
|
15 |
|
16 |
setupEventListeners() {
|
17 |
-
document.getElementById('scene-form')
|
18 |
e.preventDefault();
|
19 |
this.generateContent();
|
20 |
});
|
21 |
|
22 |
-
document.getElementById('save-journal')
|
23 |
this.saveJournal();
|
24 |
});
|
25 |
|
26 |
-
document.getElementById('journalModal')
|
27 |
this.currentCreationId = null;
|
28 |
document.getElementById('journal-text').value = '';
|
29 |
});
|
30 |
}
|
31 |
|
32 |
async generateContent() {
|
33 |
-
const
|
34 |
-
|
35 |
-
|
36 |
if (!sceneIdea) {
|
37 |
this.showAlert('Please enter a scene idea', 'danger');
|
38 |
return;
|
@@ -48,25 +45,20 @@ class InkBoard {
|
|
48 |
body: JSON.stringify({ scene_idea: sceneIdea })
|
49 |
});
|
50 |
|
51 |
-
const contentType = response.headers.get('content-type') || '';
|
52 |
-
if (!contentType.includes('application/json')) {
|
53 |
-
throw new Error("Server didn't return JSON – are you logged in?");
|
54 |
-
}
|
55 |
-
|
56 |
const data = await response.json();
|
57 |
|
58 |
if (data.success) {
|
59 |
this.displayResult(data);
|
60 |
this.loadGallery();
|
61 |
-
|
62 |
-
this.showAlert('
|
63 |
} else {
|
64 |
this.showAlert(data.error || 'Failed to generate content', 'danger');
|
65 |
}
|
66 |
|
67 |
} catch (error) {
|
68 |
console.error('Error generating content:', error);
|
69 |
-
this.showAlert('Network
|
70 |
} finally {
|
71 |
this.showLoading(false);
|
72 |
this.setButtonLoading(false);
|
@@ -78,53 +70,62 @@ class InkBoard {
|
|
78 |
|
79 |
const imageSection = data.image_url ? `
|
80 |
<div class="col-lg-6 mb-4">
|
81 |
-
<
|
|
|
|
|
82 |
</div>` : '';
|
83 |
|
84 |
const storyColClass = data.image_url ? 'col-lg-6' : 'col-12';
|
|
|
85 |
const downloadButton = data.image_url ? `
|
86 |
<button class="btn btn-outline-success btn-sm" onclick="inkBoard.downloadImage('${data.image_url}')">
|
87 |
<i class="fas fa-download me-1"></i> Download Image
|
88 |
</button>` : '';
|
89 |
|
90 |
-
|
91 |
-
<div class="
|
92 |
-
<
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
101 |
</div>
|
102 |
</div>
|
103 |
-
</div
|
104 |
-
|
|
|
105 |
resultsSection.scrollIntoView({ behavior: 'smooth' });
|
106 |
}
|
107 |
|
108 |
async loadGallery() {
|
109 |
try {
|
110 |
const response = await fetch('/get_creations');
|
111 |
-
|
112 |
-
const contentType = response.headers.get('content-type') || '';
|
113 |
-
if (!contentType.includes('application/json')) {
|
114 |
-
console.warn('Gallery load skipped — likely not logged in.');
|
115 |
-
return;
|
116 |
-
}
|
117 |
-
|
118 |
const data = await response.json();
|
|
|
119 |
const galleryGrid = document.getElementById('gallery-grid');
|
120 |
|
121 |
-
if (data.creations.length > 0) {
|
122 |
-
|
|
|
123 |
} else {
|
124 |
galleryGrid.innerHTML = `
|
125 |
-
<div class="text-center py-5
|
126 |
-
<
|
127 |
-
<
|
|
|
128 |
</div>`;
|
129 |
}
|
130 |
} catch (error) {
|
@@ -134,72 +135,201 @@ class InkBoard {
|
|
134 |
|
135 |
createGalleryItem(creation) {
|
136 |
const journalEntry = creation.journal_entry ? `
|
137 |
-
<div class="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
138 |
|
139 |
return `
|
140 |
-
<div class="gallery-item
|
141 |
-
|
142 |
-
<
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
148 |
</div>`;
|
149 |
}
|
150 |
|
151 |
-
openJournal(
|
152 |
-
this.currentCreationId =
|
153 |
-
document.getElementById('journal-text').value =
|
|
|
154 |
const modal = new bootstrap.Modal(document.getElementById('journalModal'));
|
155 |
modal.show();
|
156 |
}
|
157 |
|
158 |
async saveJournal() {
|
159 |
-
if (!this.currentCreationId)
|
|
|
|
|
|
|
160 |
|
161 |
-
const
|
162 |
-
await fetch('/save_journal', {
|
163 |
-
method: 'POST',
|
164 |
-
headers: { 'Content-Type': 'application/json' },
|
165 |
-
body: JSON.stringify({ creation_id: this.currentCreationId, journal_entry: text })
|
166 |
-
});
|
167 |
-
this.loadGallery();
|
168 |
-
const modal = bootstrap.Modal.getInstance(document.getElementById('journalModal'));
|
169 |
-
modal.hide();
|
170 |
-
}
|
171 |
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
|
|
|
|
|
|
|
|
|
|
176 |
|
177 |
-
|
178 |
-
const btn = document.querySelector('#scene-form button[type="submit"]');
|
179 |
-
if (btn) btn.disabled = state;
|
180 |
-
}
|
181 |
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
alertBox.className = `alert alert-${type}`;
|
186 |
-
alertBox.innerText = msg;
|
187 |
-
alertBox.style.display = 'block';
|
188 |
-
setTimeout(() => alertBox.style.display = 'none', 5000);
|
189 |
-
}
|
190 |
|
191 |
-
|
192 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
193 |
}
|
194 |
|
195 |
-
downloadImage(
|
196 |
const link = document.createElement('a');
|
197 |
-
link.href =
|
198 |
-
link.download =
|
|
|
199 |
document.body.appendChild(link);
|
200 |
link.click();
|
201 |
document.body.removeChild(link);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
202 |
}
|
203 |
}
|
204 |
|
|
|
205 |
const inkBoard = new InkBoard();
|
|
|
|
|
|
|
1 |
class InkBoard {
|
2 |
constructor() {
|
3 |
this.currentCreationId = null;
|
|
|
8 |
init() {
|
9 |
this.setupEventListeners();
|
10 |
this.loadGallery();
|
11 |
+
this.showSection('create');
|
12 |
}
|
13 |
|
14 |
setupEventListeners() {
|
15 |
+
document.getElementById('scene-form').addEventListener('submit', (e) => {
|
16 |
e.preventDefault();
|
17 |
this.generateContent();
|
18 |
});
|
19 |
|
20 |
+
document.getElementById('save-journal').addEventListener('click', () => {
|
21 |
this.saveJournal();
|
22 |
});
|
23 |
|
24 |
+
document.getElementById('journalModal').addEventListener('hidden.bs.modal', () => {
|
25 |
this.currentCreationId = null;
|
26 |
document.getElementById('journal-text').value = '';
|
27 |
});
|
28 |
}
|
29 |
|
30 |
async generateContent() {
|
31 |
+
const sceneIdea = document.getElementById('scene-idea').value.trim();
|
32 |
+
|
|
|
33 |
if (!sceneIdea) {
|
34 |
this.showAlert('Please enter a scene idea', 'danger');
|
35 |
return;
|
|
|
45 |
body: JSON.stringify({ scene_idea: sceneIdea })
|
46 |
});
|
47 |
|
|
|
|
|
|
|
|
|
|
|
48 |
const data = await response.json();
|
49 |
|
50 |
if (data.success) {
|
51 |
this.displayResult(data);
|
52 |
this.loadGallery();
|
53 |
+
document.getElementById('scene-idea').value = '';
|
54 |
+
this.showAlert('Story and image generated successfully!', 'success');
|
55 |
} else {
|
56 |
this.showAlert(data.error || 'Failed to generate content', 'danger');
|
57 |
}
|
58 |
|
59 |
} catch (error) {
|
60 |
console.error('Error generating content:', error);
|
61 |
+
this.showAlert('Network error. Please try again.', 'danger');
|
62 |
} finally {
|
63 |
this.showLoading(false);
|
64 |
this.setButtonLoading(false);
|
|
|
70 |
|
71 |
const imageSection = data.image_url ? `
|
72 |
<div class="col-lg-6 mb-4">
|
73 |
+
<div class="image-container">
|
74 |
+
<img src="${data.image_url}" alt="Generated Scene" class="img-fluid rounded shadow">
|
75 |
+
</div>
|
76 |
</div>` : '';
|
77 |
|
78 |
const storyColClass = data.image_url ? 'col-lg-6' : 'col-12';
|
79 |
+
|
80 |
const downloadButton = data.image_url ? `
|
81 |
<button class="btn btn-outline-success btn-sm" onclick="inkBoard.downloadImage('${data.image_url}')">
|
82 |
<i class="fas fa-download me-1"></i> Download Image
|
83 |
</button>` : '';
|
84 |
|
85 |
+
const resultHTML = `
|
86 |
+
<div class="col-12 mb-5">
|
87 |
+
<div class="card shadow-lg border-0">
|
88 |
+
<div class="card-body p-4">
|
89 |
+
<h4 class="card-title text-center mb-4"><i class="fas fa-sparkles me-2"></i>Your Creation</h4>
|
90 |
+
<div class="row">
|
91 |
+
${imageSection}
|
92 |
+
<div class="${storyColClass}">
|
93 |
+
<div class="story-container">
|
94 |
+
<h5 class="mb-3"><i class="fas fa-book-open me-2"></i>Your Story</h5>
|
95 |
+
<p class="story-text">${data.story}</p>
|
96 |
+
<div class="mt-3">
|
97 |
+
<button class="btn btn-outline-primary btn-sm me-2" onclick="inkBoard.openJournal('${data.creation_id}')">
|
98 |
+
<i class="fas fa-journal-whills me-1"></i>Add Journal Entry
|
99 |
+
</button>
|
100 |
+
${downloadButton}
|
101 |
+
</div>
|
102 |
+
</div>
|
103 |
+
</div>
|
104 |
+
</div>
|
105 |
</div>
|
106 |
</div>
|
107 |
+
</div>`;
|
108 |
+
|
109 |
+
resultsSection.innerHTML = resultHTML;
|
110 |
resultsSection.scrollIntoView({ behavior: 'smooth' });
|
111 |
}
|
112 |
|
113 |
async loadGallery() {
|
114 |
try {
|
115 |
const response = await fetch('/get_creations');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
116 |
const data = await response.json();
|
117 |
+
|
118 |
const galleryGrid = document.getElementById('gallery-grid');
|
119 |
|
120 |
+
if (data.creations && data.creations.length > 0) {
|
121 |
+
const galleryHTML = data.creations.map(creation => this.createGalleryItem(creation)).join('');
|
122 |
+
galleryGrid.innerHTML = galleryHTML;
|
123 |
} else {
|
124 |
galleryGrid.innerHTML = `
|
125 |
+
<div class="col-12 text-center py-5">
|
126 |
+
<i class="fas fa-palette fa-3x text-muted mb-3"></i>
|
127 |
+
<h5 class="text-muted">No creations yet</h5>
|
128 |
+
<p class="text-muted">Start by describing a scene above!</p>
|
129 |
</div>`;
|
130 |
}
|
131 |
} catch (error) {
|
|
|
135 |
|
136 |
createGalleryItem(creation) {
|
137 |
const journalEntry = creation.journal_entry ? `
|
138 |
+
<div class="journal-entry">
|
139 |
+
<small class="text-muted">
|
140 |
+
<i class="fas fa-journal-whills me-1"></i> Journal Entry:
|
141 |
+
</small>
|
142 |
+
<p class="mb-0 mt-1">${creation.journal_entry}</p>
|
143 |
+
</div>` : '';
|
144 |
+
|
145 |
+
const imageSection = creation.image_url ? `
|
146 |
+
<img src="${creation.image_url}" alt="Scene: ${creation.scene_idea}" loading="lazy">` : '';
|
147 |
+
|
148 |
+
const downloadButton = creation.image_url ? `
|
149 |
+
<button class="btn btn-outline-success btn-sm" onclick="inkBoard.downloadImage('${creation.image_url}')">
|
150 |
+
<i class="fas fa-download me-1"></i> Download
|
151 |
+
</button>` : '';
|
152 |
|
153 |
return `
|
154 |
+
<div class="gallery-item">
|
155 |
+
${imageSection}
|
156 |
+
<div class="gallery-item-content">
|
157 |
+
<div class="gallery-item-scene">
|
158 |
+
<i class="fas fa-quote-left me-1"></i> ${creation.scene_idea}
|
159 |
+
</div>
|
160 |
+
<div class="gallery-item-story">${creation.story}</div>
|
161 |
+
${journalEntry}
|
162 |
+
<div class="gallery-item-actions">
|
163 |
+
<button class="btn btn-outline-primary btn-sm" onclick="inkBoard.openJournal('${creation.id}', '${creation.journal_entry || ''}')">
|
164 |
+
<i class="fas fa-journal-whills me-1"></i> ${creation.journal_entry ? 'Edit' : 'Add'} Journal
|
165 |
+
</button>
|
166 |
+
${downloadButton}
|
167 |
+
</div>
|
168 |
+
</div>
|
169 |
</div>`;
|
170 |
}
|
171 |
|
172 |
+
openJournal(creationId, existingText = '') {
|
173 |
+
this.currentCreationId = creationId;
|
174 |
+
document.getElementById('journal-text').value = existingText;
|
175 |
+
|
176 |
const modal = new bootstrap.Modal(document.getElementById('journalModal'));
|
177 |
modal.show();
|
178 |
}
|
179 |
|
180 |
async saveJournal() {
|
181 |
+
if (!this.currentCreationId) {
|
182 |
+
this.showAlert('No creation selected', 'danger');
|
183 |
+
return;
|
184 |
+
}
|
185 |
|
186 |
+
const journalText = document.getElementById('journal-text').value.trim();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
187 |
|
188 |
+
try {
|
189 |
+
const response = await fetch('/save_journal', {
|
190 |
+
method: 'POST',
|
191 |
+
headers: { 'Content-Type': 'application/json' },
|
192 |
+
body: JSON.stringify({
|
193 |
+
creation_id: this.currentCreationId,
|
194 |
+
journal_entry: journalText
|
195 |
+
})
|
196 |
+
});
|
197 |
|
198 |
+
const data = await response.json();
|
|
|
|
|
|
|
199 |
|
200 |
+
if (data.success) {
|
201 |
+
this.showAlert('Journal entry saved!', 'success');
|
202 |
+
this.loadGallery();
|
|
|
|
|
|
|
|
|
|
|
203 |
|
204 |
+
const modal = bootstrap.Modal.getInstance(document.getElementById('journalModal'));
|
205 |
+
modal.hide();
|
206 |
+
} else {
|
207 |
+
this.showAlert(data.error || 'Failed to save journal', 'danger');
|
208 |
+
}
|
209 |
+
} catch (error) {
|
210 |
+
console.error('Error saving journal:', error);
|
211 |
+
this.showAlert('Network error. Please try again.', 'danger');
|
212 |
+
}
|
213 |
}
|
214 |
|
215 |
+
downloadImage(imageUrl) {
|
216 |
const link = document.createElement('a');
|
217 |
+
link.href = imageUrl;
|
218 |
+
link.download = `inkboard-creation-${Date.now()}.png`;
|
219 |
+
link.target = '_blank';
|
220 |
document.body.appendChild(link);
|
221 |
link.click();
|
222 |
document.body.removeChild(link);
|
223 |
+
this.showAlert('Image download started!', 'success');
|
224 |
+
}
|
225 |
+
|
226 |
+
showLoading(show) {
|
227 |
+
const loadingSection = document.getElementById('loading-section');
|
228 |
+
const resultsSection = document.getElementById('results-section');
|
229 |
+
|
230 |
+
if (show) {
|
231 |
+
loadingSection.classList.remove('d-none');
|
232 |
+
resultsSection.innerHTML = '';
|
233 |
+
} else {
|
234 |
+
loadingSection.classList.add('d-none');
|
235 |
+
}
|
236 |
+
}
|
237 |
+
|
238 |
+
setButtonLoading(loading) {
|
239 |
+
const btn = document.getElementById('generate-btn');
|
240 |
+
const btnText = btn.querySelector('.btn-text');
|
241 |
+
const spinner = btn.querySelector('.spinner-border');
|
242 |
+
|
243 |
+
if (loading) {
|
244 |
+
btn.classList.add('loading');
|
245 |
+
btn.disabled = true;
|
246 |
+
btnText.classList.add('d-none');
|
247 |
+
spinner.classList.remove('d-none');
|
248 |
+
} else {
|
249 |
+
btn.classList.remove('loading');
|
250 |
+
btn.disabled = false;
|
251 |
+
btnText.classList.remove('d-none');
|
252 |
+
spinner.classList.add('d-none');
|
253 |
+
}
|
254 |
+
}
|
255 |
+
|
256 |
+
showAlert(message, type) {
|
257 |
+
const existingAlert = document.querySelector('.alert');
|
258 |
+
if (existingAlert) existingAlert.remove();
|
259 |
+
|
260 |
+
const alert = document.createElement('div');
|
261 |
+
alert.className = `alert alert-${type} alert-dismissible fade show`;
|
262 |
+
alert.innerHTML = `${message}
|
263 |
+
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>`;
|
264 |
+
|
265 |
+
const main = document.querySelector('main');
|
266 |
+
main.insertBefore(alert, main.firstChild);
|
267 |
+
|
268 |
+
setTimeout(() => {
|
269 |
+
if (alert.parentNode) alert.remove();
|
270 |
+
}, 5000);
|
271 |
+
}
|
272 |
+
|
273 |
+
showSection(sectionName) {
|
274 |
+
document.querySelectorAll('.dashboard-section').forEach(section => {
|
275 |
+
section.classList.add('d-none');
|
276 |
+
});
|
277 |
+
|
278 |
+
document.querySelectorAll('.btn-nav').forEach(btn => {
|
279 |
+
btn.classList.remove('active');
|
280 |
+
});
|
281 |
+
|
282 |
+
const targetSection = document.getElementById(`${sectionName}-section`);
|
283 |
+
if (targetSection) targetSection.classList.remove('d-none');
|
284 |
+
|
285 |
+
const buttonSelectors = {
|
286 |
+
'create': 'Create Story',
|
287 |
+
'gallery': 'Gallery',
|
288 |
+
'journal': 'Journal'
|
289 |
+
};
|
290 |
+
|
291 |
+
document.querySelectorAll('.btn-nav').forEach(btn => {
|
292 |
+
if (btn.textContent.trim().includes(buttonSelectors[sectionName])) {
|
293 |
+
btn.classList.add('active');
|
294 |
+
}
|
295 |
+
});
|
296 |
+
|
297 |
+
this.currentSection = sectionName;
|
298 |
+
|
299 |
+
if (sectionName === 'gallery') {
|
300 |
+
this.loadGallery();
|
301 |
+
} else if (sectionName === 'journal') {
|
302 |
+
this.loadJournalEntries();
|
303 |
+
}
|
304 |
+
}
|
305 |
+
|
306 |
+
async loadJournalEntries() {
|
307 |
+
try {
|
308 |
+
const response = await fetch('/get_creations');
|
309 |
+
const data = await response.json();
|
310 |
+
|
311 |
+
const journalContainer = document.getElementById('journal-entries');
|
312 |
+
const entriesWithJournal = data.creations?.filter(creation => creation.journal_entry) || [];
|
313 |
+
|
314 |
+
if (entriesWithJournal.length > 0) {
|
315 |
+
const journalHTML = entriesWithJournal.map(creation => `
|
316 |
+
<div class="journal-entry-card mb-3">
|
317 |
+
<h5>${creation.scene_idea}</h5>
|
318 |
+
<p>${creation.journal_entry}</p>
|
319 |
+
</div>`).join('');
|
320 |
+
journalContainer.innerHTML = journalHTML;
|
321 |
+
} else {
|
322 |
+
journalContainer.innerHTML = `
|
323 |
+
<div class="col-12 text-center py-5">
|
324 |
+
<i class="fas fa-journal-whills fa-3x text-muted mb-3"></i>
|
325 |
+
<h5 class="text-muted">No journal entries yet</h5>
|
326 |
+
</div>`;
|
327 |
+
}
|
328 |
+
} catch (error) {
|
329 |
+
console.error('Error loading journal entries:', error);
|
330 |
+
}
|
331 |
}
|
332 |
}
|
333 |
|
334 |
+
// Initialize
|
335 |
const inkBoard = new InkBoard();
|