commit f45fd2a4d792ca7980b153de765588e1f0902bd8
parent 9886598e21b902463cc87a841004b854611c6a37
Author: Asher Morgan <59518073+ashermorgan@users.noreply.github.com>
Date: Tue, 9 Apr 2024 17:18:31 -0700
Create step 3 page for slideshow options
Diffstat:
8 files changed, 195 insertions(+), 109 deletions(-)
diff --git a/songs2slides/routes.py b/songs2slides/routes.py
@@ -73,6 +73,19 @@ def create_step_2():
# Return song data
return render_template('create-step-2.html', songs=songs, missing=missing)
+@bp.get('/create/step-3/')
+def create_step_3_get():
+ # GET requests not allowed, redirect to step 1
+ return redirect(url_for('.create_step_1'), 302)
+
+@bp.post('/create/step-3/')
+def create_step_3():
+ # Parse form data
+ songs = parse_form(request.form)
+
+ # Return song data
+ return render_template('create-step-3.html', songs=songs)
+
@bp.get('/post-download/')
def post_download():
return render_template('post-download.html')
diff --git a/songs2slides/static/create-step-1.js b/songs2slides/static/create-step-1.js
@@ -1,32 +0,0 @@
-addEventListener('submit', () => {
- // Show loading spinner
- document.getElementById('post-submit').hidden = false
-})
-
-addEventListener('pageshow', () => {
- // Correct page state after returning via browser back button
- document.getElementById('post-submit').hidden = true
- document.getElementById('step-1').hidden = false
-})
-
-function add_song() {
- let row = document.getElementById('row-template').content.children[0].cloneNode(true)
- document.getElementById('songs').appendChild(row)
- renumber_songs()
-}
-
-function remove_song(n) {
- document.getElementsByTagName('tr')[n].remove()
- renumber_songs()
- if (document.getElementsByTagName('tr').length === 1) add_song()
-}
-
-function renumber_songs() {
- const songs = document.getElementsByTagName('tr')
- for (let i = 1; i < songs.length - 1; i++) {
- songs[i].children[0].textContent = `${i}.`
- songs[i].children[1].children[0].name = `title-${i}`
- songs[i].children[2].children[0].name = `artist-${i}`
- songs[i].children[3].children[0].onclick = () => remove_song(i)
- }
-}
diff --git a/songs2slides/static/create-step-2.js b/songs2slides/static/create-step-2.js
@@ -1,41 +0,0 @@
-addEventListener('submit', () => {
- // Save settings
- const form = document.getElementById('step-2')
- storage_set('title-slides', form['title-slides'].checked)
- storage_set('blank-slides', form['blank-slides'].checked)
- storage_set('output-type', form['output-type'].value)
-
- // Redirect to post download message
- if (form['output-type'].value === 'pptx') {
- setTimeout(() => {
- // On Chrome, redirecting after a form submission doesn't work
- // unless setTimeout is used
- // (REDIRECT_URL set in create-step-2.html template)
- window.location.href = REDIRECT_URL
- }, 100)
- }
-})
-
-addEventListener('pageshow', () => {
- // Load settings
- const form = document.getElementById('step-2')
- form['title-slides'].checked = storage_get('title-slides', true)
- form['blank-slides'].checked = storage_get('blank-slides', true)
- form['output-type'].value = storage_get('output-type', 'html')
-})
-
-// Global Songs2Slides localStorage prefix
-const PREFIX = 's2s'
-
-function storage_get(key, default_value) {
- try {
- value = JSON.parse(localStorage.getItem(`${PREFIX}.${key}`))
- } catch {
- return clonedDefault
- }
- return value === null ? default_value : value
-}
-
-function storage_set(key, value) {
- localStorage.setItem(`${PREFIX}.${key}`, JSON.stringify(value))
-}
diff --git a/songs2slides/static/create.js b/songs2slides/static/create.js
@@ -0,0 +1,77 @@
+// Global Songs2Slides localStorage prefix
+const PREFIX = 's2s'
+
+// Page load/reload handler
+addEventListener('pageshow', () => {
+ if (STEP === 1 || STEP == 2) {
+ // Correct page state after returning via browser back button
+ document.getElementById('post-submit').hidden = true
+ } else if (STEP === 3) {
+ // Load settings
+ const form = document.getElementById('create-form')
+ form['title-slides'].checked = storage_get('title-slides', true)
+ form['blank-slides'].checked = storage_get('blank-slides', true)
+ form['output-type'].value = storage_get('output-type', 'html')
+ }
+})
+
+// Form submit handler
+addEventListener('submit', () => {
+ if (STEP === 1 || STEP === 2) {
+ // Show loading spinner
+ document.getElementById('post-submit').hidden = false
+ } else if (STEP === 3) {
+ // Save settings
+ const form = document.getElementById('create-form')
+ storage_set('title-slides', form['title-slides'].checked)
+ storage_set('blank-slides', form['blank-slides'].checked)
+ storage_set('output-type', form['output-type'].value)
+
+ // Redirect to post download message
+ if (form['output-type'].value === 'pptx') {
+ setTimeout(() => {
+ // On Chrome, redirecting after a form submission doesn't work
+ // unless setTimeout is used
+ // (REDIRECT_URL set in create-step-3.html template)
+ window.location.href = REDIRECT_URL
+ }, 100)
+ }
+ }
+})
+
+// Step 1 functions
+function add_song() {
+ let row = document.getElementById('row-template').content.children[0].cloneNode(true)
+ document.getElementById('songs').appendChild(row)
+ renumber_songs()
+}
+
+function remove_song(n) {
+ document.getElementsByTagName('tr')[n].remove()
+ renumber_songs()
+ if (document.getElementsByTagName('tr').length === 1) add_song()
+}
+
+function renumber_songs() {
+ const songs = document.getElementsByTagName('tr')
+ for (let i = 1; i < songs.length - 1; i++) {
+ songs[i].children[0].textContent = `${i}.`
+ songs[i].children[1].children[0].name = `title-${i}`
+ songs[i].children[2].children[0].name = `artist-${i}`
+ songs[i].children[3].children[0].onclick = () => remove_song(i)
+ }
+}
+
+// Step 3 helper functions
+function storage_get(key, default_value) {
+ try {
+ value = JSON.parse(localStorage.getItem(`${PREFIX}.${key}`))
+ } catch {
+ return clonedDefault
+ }
+ return value === null ? default_value : value
+}
+
+function storage_set(key, value) {
+ localStorage.setItem(`${PREFIX}.${key}`, JSON.stringify(value))
+}
diff --git a/songs2slides/templates/create-step-1.html b/songs2slides/templates/create-step-1.html
@@ -2,11 +2,11 @@
{% block head %}
<link rel="stylesheet" href="{{ url_for('static', filename='create.css') }}"/>
-<script src="{{ url_for('static', filename='create-step-1.js') }}"></script>
+<script src="{{ url_for('static', filename='create.js') }}"></script>
{% endblock head %}
{% block main %}
-<form id="step-1" method="POST" action="{{ url_for('.create_step_2') }}">
+<form method="POST" action="{{ url_for('.create_step_2') }}">
<h1>Step 1: Select Songs</h1>
<p>
@@ -80,8 +80,12 @@
<div id="post-submit" class="loading-modal" hidden>
<div>
- <p>Loading your song lyrics...</p>
+ <p>Searching for your song lyrics...</p>
<div class="spinner"></div>
</div>
</div>
+
+<script>
+ const STEP = 1
+</script>
{% endblock main %}
diff --git a/songs2slides/templates/create-step-2.html b/songs2slides/templates/create-step-2.html
@@ -2,26 +2,29 @@
{% block head %}
<link rel="stylesheet" href="{{ url_for('static', filename='create.css') }}"/>
-<script src="{{ url_for('static', filename='create-step-2.js') }}"></script>
+<script src="{{ url_for('static', filename='create.js') }}"></script>
{% endblock head %}
{% set format_hint =
-'A blank line represents the start of a new slide and three blank ' +
-'lines represent an empty slide.'
+'Each stanza will appear on its own slide. ' +
+'Stanzas must be separated by one blank line. ' +
+'Three blank lines represent an empty slide.'
%}
{% block main %}
-<form id="step-2" method="POST" action="{{ url_for('.slides') }}">
+<form method="POST" action="{{ url_for('.create_step_3') }}">
<h1>Step 2: Review Lyrics</h1>
<p>
Review the parsed song lyrics below and make any necessary corrections.
+ </p>
+ <p>
{{ format_hint }}
</p>
{% if missing > 0 %}
<p>
- Lyrics must be entered manually for
- {{ missing }} {% if missing == 1 %} song. {% else %} songs. {% endif %}
+ Lyrics must be entered manually for <strong>{{ missing }}
+ song{% if missing != 1 %}s{% endif %}</strong>.
</p>
{% endif %}
@@ -60,40 +63,22 @@
{% endfor %}
</div>
- <div>
- <fieldset>
- <legend>Extra slides:</legend>
- <label>
- <input type="checkbox" name="title-slides" checked/>
- Include a title slide before each song
- </label>
- <label>
- <input type="checkbox" name="blank-slides" checked/>
- Include a blank slide between each song
- </label>
- </fieldset>
- <fieldset>
- <legend>Output type:</legend>
- <label>
- <input type="radio" name="output-type" value="html" checked/>
- Web View
- </label>
- <label>
- <input type="radio" name="output-type" value="pptx"/>
- PowerPoint download
- </label>
- </fieldset>
- </div>
-
<div id="actions">
<input onclick="history.back()" type="button" value="Back"/>
<button type="submit">
- Create Slide Show
+ Next
</button>
</div>
</form>
+<div id="post-submit" class="loading-modal" hidden>
+ <div>
+ <p>Updating your song lyrics...</p>
+ <div class="spinner"></div>
+ </div>
+</div>
+
<script>
- const REDIRECT_URL = "{{ url_for('.post_download') }}"
+ const STEP = 2
</script>
{% endblock main %}
diff --git a/songs2slides/templates/create-step-3.html b/songs2slides/templates/create-step-3.html
@@ -0,0 +1,59 @@
+{% extends "layout.html" %}
+
+{% block head %}
+<link rel="stylesheet" href="{{ url_for('static', filename='create.css') }}"/>
+<script src="{{ url_for('static', filename='create.js') }}"></script>
+{% endblock head %}
+
+{% block main %}
+<form id="create-form" method="POST" action="{{ url_for('.slides') }}">
+ <h1>Step 3: Create Slideshow</h1>
+ <p>
+ Customize your slideshow with the options below.
+ </p>
+
+ {% for song in songs %}
+ <input hidden name="title-{{ loop.index }}"
+ value="{{ song.title }}"/>
+ <input hidden name="artist-{{ loop.index }}"
+ value="{{ song.artist }}"/>
+ <textarea hidden name="lyrics-{{ loop.index }}"
+ >{{ song.lyrics }}</textarea>
+ {% endfor %}
+
+ <fieldset>
+ <legend>Extra slides:</legend>
+ <label>
+ <input type="checkbox" name="title-slides" checked/>
+ Include a title slide before each song
+ </label>
+ <label>
+ <input type="checkbox" name="blank-slides" checked/>
+ Include a blank slide between each song
+ </label>
+ </fieldset>
+ <fieldset>
+ <legend>Output type:</legend>
+ <label>
+ <input type="radio" name="output-type" value="html" checked/>
+ Web View
+ </label>
+ <label>
+ <input type="radio" name="output-type" value="pptx"/>
+ PowerPoint download
+ </label>
+ </fieldset>
+
+ <div id="actions">
+ <input onclick="history.back()" type="button" value="Back"/>
+ <button type="submit">
+ Create
+ </button>
+ </div>
+</form>
+
+<script>
+ const STEP = 3
+ const REDIRECT_URL = "{{ url_for('.post_download') }}"
+</script>
+{% endblock main %}
diff --git a/tests/test_routes.py b/tests/test_routes.py
@@ -76,6 +76,27 @@ class TestRoutes(unittest.TestCase):
# Assert response has 400 status code
self.assertEqual(res.status_code, 400)
+ def test_update_lyrics(self):
+ with patch('songs2slides.routes.render_template') as mocked_render:
+
+ # Send request
+ self.client.post('/create/step-3/', data={
+ 'title-1': 'T1',
+ 'artist-1': 'A1',
+ 'lyrics-1': 'L1',
+ 'title-2': 'T2',
+ 'artist-2': 'A2',
+ 'lyrics-2': 'L2',
+ 'output-type': 'html',
+ 'title-slides': 'on',
+ })
+
+ # Assert render_template called correctly
+ mocked_render.assert_called_with('create-step-3.html', songs=[
+ core.SongData('T1', 'A1', 'L1'),
+ core.SongData('T2', 'A2', 'L2'),
+ ])
+
def test_create_slides_basic(self):
with patch('songs2slides.core.assemble_slides') as mocked_assemble, \
patch('songs2slides.core.create_pptx') as mocked_create, \