commit d17fffd9a96f0bf745d9211be6e5085ca0fdcdd0
parent 540b3af694cecfa7a5c39718692a386fc0988667
Author: Asher Morgan <59518073+ashermorgan@users.noreply.github.com>
Date: Thu, 28 Mar 2024 15:04:33 -0700
Move create steps to separate routes
This improves the behavior of the browser back button
Diffstat:
7 files changed, 225 insertions(+), 225 deletions(-)
diff --git a/songs2slides/routes.py b/songs2slides/routes.py
@@ -1,4 +1,5 @@
-from flask import abort, Blueprint, render_template, request, send_file
+from flask import abort, Blueprint, redirect, render_template, request, \
+ send_file, url_for
import tempfile
from songs2slides import core
@@ -40,11 +41,20 @@ def home():
return render_template('home.html')
@bp.get('/create/')
-def create():
- return render_template('create.html', step=1, songs=[], missing=0)
+def create_root():
+ return redirect(url_for('.create_step_1'), 301)
-@bp.post('/create/')
-def get_lyrics():
+@bp.get('/create/step-1/')
+def create_step_1():
+ return render_template('create-step-1.html')
+
+@bp.get('/create/step-2/')
+def create_step_2_get():
+ # GET requests not allowed, redirect to step 1
+ return redirect(url_for('.create_step_1'), 302)
+
+@bp.post('/create/step-2/')
+def create_step_2():
# Parse form data
songs = parse_form(request.form)
@@ -61,10 +71,15 @@ def get_lyrics():
missing = sum([1 for x in songs if x.lyrics == None])
# Return song data
- return render_template('create.html', step=2, songs=songs, missing=missing)
+ return render_template('create-step-2.html', songs=songs, missing=missing)
+
+@bp.get('/slides/')
+def slides_get():
+ # GET requests not allowed, redirect to home page
+ return redirect(url_for('.home'), 302)
@bp.post('/slides/')
-def create_slides():
+def slides():
# Parse form data
songs = parse_form(request.form)
title_slides = 'title-slides' in request.form
diff --git a/songs2slides/static/create.js b/songs2slides/static/create.js
@@ -1,13 +1,19 @@
addEventListener('submit', () => {
- if (document.getElementById('step-1').hidden == false) {
+ if (document.getElementById('step-1')) {
// Show step 1 spinner
- document.getElementById('post-step-1').hidden = false
+ document.getElementById('post-submit').hidden = false
} else if (document.querySelector('input[value=pptx]').checked) {
- // Show step 2 downloading message
+ // Show step 2 downloading message and hide step 2 form
+ document.getElementById('post-submit').hidden = false
document.getElementById('step-2').hidden = true
- document.getElementById('post-step-2').hidden = false
}
-});
+})
+
+addEventListener('pageshow', () => {
+ // Correct page state after returning via browser back button
+ document.getElementById('post-submit').hidden = true
+ document.getElementsByTagName('form')[0].hidden = false
+})
/* step 1 functions */
function add_song() {
@@ -31,11 +37,3 @@ function renumber_songs() {
songs[i].children[3].children[0].onclick = () => remove_song(i)
}
}
-
-/* step 2 functions */
-function back() {
- document.getElementById('step-1').hidden = false
- document.getElementById('post-step-1').hidden = true
- document.getElementById('step-2').hidden = true
- document.getElementById('post-step-2').hidden = true
-}
diff --git a/songs2slides/templates/create-step-1.html b/songs2slides/templates/create-step-1.html
@@ -0,0 +1,81 @@
+{% 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="step-1" method="POST" action="{{ url_for('.create_step_2') }}">
+ <h1>Step 1: Select Songs</h1>
+
+ <p>
+ Select the songs to include in the slide show by their title and artist.
+ </p>
+
+ <template id="row-template">
+ <tr>
+ <td></td>
+ <td>
+ <input name="title-" placeholder="Song title"
+ aria-label="Song title" required/>
+ </td>
+ <td>
+ <input name="artist-" placeholder="Song artist"
+ aria-label="Song artist"/>
+ </td>
+ <td>
+ <button class="icon" type="button" title="Remove">
+ <img src="{{ url_for('static', filename='trash.svg') }}"
+ alt="Remove icon"/>
+ </button>
+ </td>
+ </tr>
+ </template>
+
+ <table>
+ <thead>
+ <tr>
+ <th></th>
+ <th>Title</th>
+ <th>Artist</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody id="songs">
+ <tr>
+ <td>1.</td>
+ <td>
+ <input type="text" name="title-1" placeholder="Song title"
+ aria-label="Song title" required/>
+ </td>
+ <td>
+ <input type="text" name="artist-1"
+ placeholder="Song artist" aria-label="Song title"/>
+ </td>
+ <td>
+ <button class="icon" type="button" onclick="remove_song(1)"
+ title="Remove">
+ <img src="{{ url_for('static', filename='trash.svg') }}"
+ alt="Remove icon"/>
+ </button>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+ <div id="actions">
+ <input type="button" value="Add song" onclick="add_song()"/>
+ <button type="submit">
+ Next
+ </button>
+ </div>
+</form>
+
+<div id="post-submit" class="loading-modal" hidden>
+ <div>
+ <p>Loading your song lyrics...</p>
+ <div class="spinner"></div>
+ </div>
+</div>
+{% endblock main %}
diff --git a/songs2slides/templates/create-step-2.html b/songs2slides/templates/create-step-2.html
@@ -0,0 +1,105 @@
+{% 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 %}
+
+{% set format_hint =
+'One blank line represents the start of a new slide and three blank ' +
+'lines represent an empty slide.'
+%}
+
+{% block main %}
+<form id="step-2" method="POST" action="{{ url_for('.slides') }}">
+ <h1>Step 2: Preview Slides</h1>
+ <p>
+ Review the parsed song lyrics below and make any necessary corrections.
+ {{ format_hint }}
+ </p>
+
+ {% if missing > 0 %}
+ <p>
+ Lyrics must be entered manually for
+ {{ missing }} {% if missing == 1 %} song. {% else %} songs. {% endif %}
+ </p>
+ {% endif %}
+
+ <div>
+ {% for song in songs %}
+ <details
+ {% if missing == 0 and loop.index == 1 %} open {% endif %}
+ {% if not song.lyrics %} open class="missing" {% endif %}>
+
+ <input hidden name="title-{{ loop.index }}"
+ value="{{ song.title }}"/>
+ <input hidden name="artist-{{ loop.index }}"
+ value="{{ song.artist }}"/>
+
+ <summary>
+ <i>{{ song.title }}</i>
+
+ {% if song.artist %}
+ ({{ song.artist }})
+ {% endif %}
+
+ {% if not song.lyrics %}
+ <span>lyrics not found</span>
+ {% endif %}
+ </summary>
+
+ {% if song.lyrics %}
+ <textarea name="lyrics-{{ loop.index }}" placeholder="{{format_hint}}"
+ aria-label="{{ song.title }} Lyrics">{{ song.lyrics }}</textarea>
+ {% else %}
+ <textarea name="lyrics-{{ loop.index }}" placeholder="{{
+ 'Lyrics not found, please enter them here manually.\n\n' +
+ format_hint }}" aria-label="{{ song.title }} Lyrics"></textarea>
+ {% endif %}
+ </details>
+ {% 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
+ </button>
+ </div>
+</form>
+
+<div id="post-submit" hidden>
+ <p>
+ Your slide show is being downloaded,
+ thank you for using Songs2Slides.
+ </p>
+ <p>
+ <a href="{{ url_for('.create_step_1') }}">Create another slide show</a>
+ </p>
+</div>
+{% endblock main %}
diff --git a/songs2slides/templates/create.html b/songs2slides/templates/create.html
@@ -1,199 +0,0 @@
-{% 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="step-1" method="POST" {% if step == 2 %} hidden {% endif %}>
- <h1>Step 1: Select Songs</h1>
-
- <p>
- Select the songs to include in the slide show by their title and artist.
- </p>
-
- <template id="row-template">
- <tr>
- <td></td>
- <td>
- <input name="title-" placeholder="Song title" required/>
- </td>
- <td>
- <input name="artist-" placeholder="Song artist"/>
- </td>
- <td>
- <button class="icon" type="button" title="Remove">
- <img src="{{ url_for('static', filename='trash.svg') }}"
- alt="Remove icon"/>
- </button>
- </td>
- </tr>
- </template>
-
- <table>
- <thead>
- <tr>
- <th></th>
- <th>Title</th>
- <th>Artist</th>
- <th></th>
- </tr>
- </thead>
- <tbody id="songs">
- {% if step == 1 %}
- <tr>
- <td>1.</td>
- <td>
- <input type="text" name="title-1" placeholder="Song title" required/>
- </td>
- <td>
- <input type="text" name="artist-1" placeholder="Song artist"/>
- </td>
- <td>
- <button class="icon" type="button" onclick="remove_song(1)"
- title="Remove">
- <img src="{{ url_for('static', filename='trash.svg') }}"
- alt="Remove icon"/>
- </button>
- </td>
- </tr>
- {% else %}
- {% for song in songs %}
- <tr>
- <td>{{ loop.index }}.</td>
- <td>
- <input type="text" name="title-{{ loop.index }}" required
- placeholder="Song title" value="{{ song.title }}"/>
- </td>
- <td>
- <input type="text" name="artist-{{ loop.index }}"
- placeholder="Song artist" value="{{ song.artist }}"/>
- </td>
- <td>
- <button class="icon" type="button" title="Remove"
- onclick="remove_song({{ loop.index }})">
- <img src="{{ url_for('static', filename='trash.svg') }}"
- alt="Remove icon"/>
- </button>
- </td>
- </tr>
- {% endfor %}
- {% endif %}
- </tbody>
- </table>
-
- <div id="actions">
- <input type="button" value="Add song" onclick="add_song()"/>
- <button type="submit">
- Next
- </button>
- </div>
-</form>
-
-<div id="post-step-1" class="loading-modal" hidden>
- <div>
- <p>Loading your song lyrics...</p>
- <div class="spinner"></div>
- </div>
-</div>
-
-<form id="step-2" method="POST" {% if step == 1 %} hidden {% endif %}
- action="{{ url_for('.create_slides') }}">
-
- {% set format_hint =
- 'One blank line represents the start of a new slide and three blank ' +
- 'lines represent an empty slide.'
- %}
-
- <h1>Step 2: Preview Slides</h1>
- <p>
- Review the parsed song lyrics below and make any necessary corrections.
- {{ format_hint }}
- </p>
-
- {% if missing > 0 %}
- <p>
- Lyrics must be entered manually for
- {{ missing }} {% if missing == 1 %} song. {% else %} songs. {% endif %}
- </p>
- {% endif %}
-
- <div>
- {% for song in songs %}
- <details
- {% if missing == 0 and loop.index == 1 %} open {% endif %}
- {% if not song.lyrics %} open class="missing" {% endif %}>
-
- <input hidden name="title-{{ loop.index }}"
- value="{{ song.title }}"/>
- <input hidden name="artist-{{ loop.index }}"
- value="{{ song.artist }}"/>
-
- <summary>
- <i>{{ song.title }}</i>
-
- {% if song.artist %}
- ({{ song.artist }})
- {% endif %}
-
- {% if not song.lyrics %}
- <span>lyrics not found</span>
- {% endif %}
- </summary>
-
- {% if song.lyrics %}
- <textarea name="lyrics-{{ loop.index }}"
- placeholder="{{format_hint}}">{{ song.lyrics }}</textarea>
- {% else %}
- <textarea name="lyrics-{{ loop.index }}" placeholder="{{
- 'Lyrics not found, please enter them here manually.\n\n' +
- format_hint }}"></textarea>
- {% endif %}
- </details>
- {% 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="back()" type="button" value="Back"/>
- <button type="submit">
- Create Slide Show
- </button>
- </div>
-</form>
-
-<div id="post-step-2" hidden>
- <p>
- Your slide show is being downloaded,
- thank you for using Songs2Slides.
- </p>
- <p>
- <a href="{{ url_for('.create') }}">Create another slide show</a>
- </p>
-</div>
-{% endblock main %}
diff --git a/songs2slides/templates/home.html b/songs2slides/templates/home.html
@@ -6,5 +6,5 @@
{% block main %}
<p>Create lyric slide shows easily and quickly</p>
-<a href="{{ url_for('.create') }} ">Get Started</a>
+<a href="{{ url_for('.create_step_1') }} ">Get Started</a>
{% endblock main %}
diff --git a/tests/test_routes.py b/tests/test_routes.py
@@ -22,7 +22,7 @@ class TestRoutes(unittest.TestCase):
mocked_parse.side_effect = ['L1', 'L2']
# Send request
- self.client.post('/create/', data={
+ self.client.post('/create/step-2/', data={
'title-1': 'T1',
'artist-1': 'A1',
'title-2': 'T2',
@@ -32,7 +32,7 @@ class TestRoutes(unittest.TestCase):
# Assert mocks called correctly
mocked_get.assert_has_calls([call('T1', 'A1'), call('T2', 'A2')])
mocked_parse.assert_has_calls([call('L1', 4), call('L2', 4)])
- mocked_render.assert_called_with('create.html', step=2, songs=songs, missing=0)
+ mocked_render.assert_called_with('create-step-2.html', songs=songs, missing=0)
def test_get_lyrics_one_error(self):
with patch('songs2slides.core.get_song_data') as mocked_get, \
@@ -48,7 +48,7 @@ class TestRoutes(unittest.TestCase):
mocked_parse.side_effect = ['L1', 'L2']
# Send request
- self.client.post('/create/', data={
+ self.client.post('/create/step-2/', data={
'title-1': 'T1',
'artist-1': 'A1',
'title-2': 'T2',
@@ -58,13 +58,13 @@ class TestRoutes(unittest.TestCase):
# Assert mocks called correctly
mocked_get.assert_has_calls([call('T1', 'A1'), call('T2', 'A2')])
mocked_parse.assert_has_calls([call('L2', 4)])
- mocked_render.assert_called_with('create.html', step=2, songs=songs, missing=1)
+ mocked_render.assert_called_with('create-step-2.html', songs=songs, missing=1)
def test_get_lyrics_missing_artist(self):
with patch('songs2slides.core.get_song_data') as mocked_get:
# Send request
- res = self.client.post('/create/', data={
+ res = self.client.post('/create/step-2/', data={
'title-1': 'T1',
'title-2': 'T2',
'artist-2': 'A2',