Compare commits

...

3 Commits

Author SHA1 Message Date
Effie
fd57f5813d add docker to the mix 2023-07-12 19:41:50 +10:00
Effie
16642918fa adds buttons to search bars 2023-07-12 19:10:39 +10:00
Effie
297a20f3e0 gets everything settled up to the suggest page 2023-07-04 15:56:57 +10:00
24 changed files with 230 additions and 101 deletions

1
.dockerignore Normal file
View File

@ -0,0 +1 @@
__pycache__/

3
.gitignore vendored
View File

@ -1,3 +1,4 @@
/songbook.sqlite
/static/clip/
/__pycache__/
/__pycache__/
/suggestions.csv

14
Dockerfile Normal file
View File

@ -0,0 +1,14 @@
FROM python:3.11-slim
RUN pip3 install poetry
RUN poetry config virtualenvs.create false
RUN mkdir /opt/app
WORKDIR /opt/app
COPY pyproject.toml poetry.lock ./
RUN poetry install --no-dev
COPY ./ ./
CMD ["gunicorn", "--bind", "0.0.0.0"]

10
app.py
View File

@ -329,7 +329,7 @@ def songpage(id):
song_info = db.execute('''
select
name, name_jp
name, name_jp, location
from
song
where
@ -419,11 +419,17 @@ def suggestpage():
@app.route('/sent', methods=['POST'])
def sentpage():
print(flask.request.headers)
with open ('suggestions.csv', 'a', newline='') as suggestlog:
logwriter = csv.writer(suggestlog)
logwriter.writerow([
datetime.datetime.now().isoformat(),
flask.request.form['suggest-description'],
flask.request.form['connection-0-desc'][:4],
flask.request.form['connection-0-id'][:4],
flask.request.form['connection-1-desc'][:4],
flask.request.form['connection-1-id'][:4],
flask.request.form['suggest-description'][:10_000],
])
return flask.render_template('sent.html',
)

View File

@ -37,7 +37,7 @@ for song, motif, start, duration, album, track in db.execute('''
# get clip filename
destination = f'static/clip/{song:04}-{motif:03}.flac'
destination = f'static/clip/{song:04}-{motif:03}.mp3'
print(destination)

View File

@ -15,13 +15,13 @@ clipdata = pyexcel_odsr.get_data('reference_clips.ods', skip_empty_rows=True)
# import song
cur.executemany(
'insert or ignore into song(id,name,name_jp) values(?, ?, ?)',
((row[0],row[3],row[4]) for row in data['Song'][1:])
'insert or ignore into song(id,name,name_jp,location) values(?, ?, ?, ?)',
((row[0],row[3],row[4],'' if len(row)<11 else row[10]) for row in data['Song'][1:])
)
# import album
cur.executemany(
'insert into album(id,name,date,code) values (?, ?, ?, ?)',
'insert into album(id,name,date,code,description,updates) values (?, ?, ?, ?, ?, ?)',
data['Album'][1:]
)

5
db.sql
View File

@ -4,13 +4,16 @@ create table song(
id int primary key,
name text not null,
name_jp text not null,
location text not null,
unique(name,name_jp)
);
create table album(
id int primary key,
name text not null,
date date not null,
date date not null,
description text not null,
updates text not null,
code char(3) not null
);

6
docker-compose.yaml Normal file
View File

@ -0,0 +1,6 @@
services:
app:
build: .
init: true
ports:
- "127.0.0.1:5123:8000"

1
gunicorn.conf.py Normal file
View File

@ -0,0 +1 @@
wsgi_app = 'app:app'

17
poetry.lock generated
View File

@ -44,6 +44,20 @@ Werkzeug = ">=2.3.3"
async = ["asgiref (>=3.2)"]
dotenv = ["python-dotenv"]
[[package]]
name = "gunicorn"
version = "20.1.0"
description = "WSGI HTTP Server for UNIX"
category = "main"
optional = false
python-versions = ">=3.5"
[package.extras]
eventlet = ["eventlet (>=0.24.1)"]
gevent = ["gevent (>=1.4.0)"]
setproctitle = ["setproctitle"]
tornado = ["tornado (>=0.2)"]
[[package]]
name = "itsdangerous"
version = "2.1.2"
@ -164,13 +178,14 @@ watchdog = ["watchdog (>=2.3)"]
[metadata]
lock-version = "1.1"
python-versions = "^3.10"
content-hash = "eb105df2967c9247e5d32c49b9996442ba31976fb4da0c99bffa003aa8242182"
content-hash = "1a5905d329305a82f0568b7fdea227b713ee393a14f79e5247af77551ffda804"
[metadata.files]
blinker = []
click = []
colorama = []
flask = []
gunicorn = []
itsdangerous = []
jinja2 = []
lml = []

View File

@ -10,6 +10,7 @@ sox = "^1.4.1"
Jinja2 = "^3.1.2"
pyexcel-odsr = "^0.6.0"
Flask = "^2.3.2"
gunicorn = "^20.1.0"
[tool.poetry.dev-dependencies]

Binary file not shown.

Binary file not shown.

View File

@ -13,12 +13,14 @@ window.addEventListener('load', (event) => {
function albumselect(code){
const albumheader = document.getElementById(code);
const searchbar = document.getElementById('search-input');
for (const other of document.querySelectorAll('.sort-header.album.open')){
other.classList.remove('open');
}
albumheader.classList.add('open');
document.getElementById('search-input').value = '';
document.getElementById('search-results').replaceChildren();
searchbar.value = '';
searchbar.dispatchEvent(new Event('input'));
document.getElementById('search-results-header').replaceChildren();
}
// Toggle header when clicked
@ -39,11 +41,16 @@ function albumHeaderClick(clickevent){
}
// Fill out form with suggest search result
function suggestform1(id, desc){
document.getElementById('connection-1').valueAsNumber = id;
document.getElementById('connection-1-desc').valueAsNumber = desc;
document.getElementById('search-input-suggest-1').value = '';
document.getElementById('search-results-1').replaceChildren();
function suggestform(id, desc, clickevent){
const searchresults = clickevent.target.parentElement.parentElement;
const form = searchresults.dataset.form;
const searchbar = document.getElementById(`search-input-suggest-${form}`)
document.getElementById(`connection-${form}`).innerHTML = clickevent.target.innerHTML;
document.getElementById(`connection-${form}-id`).value = id;
document.getElementById(`connection-${form}-desc`).value = desc;
searchbar.value = '';
searchbar.dispatchEvent(new Event('input'));
searchresults.replaceChildren();
}
// Mutually exclusive audio playback
@ -61,4 +68,28 @@ function onlyPlayOneIn(container) {
document.addEventListener("DOMContentLoaded", function() {
onlyPlayOneIn(document.body);
});
});
// Search clear button
function searchIconChange(event, buttonid, barid) {
const searchbutton = document.getElementById(buttonid);
const searchicon = document.getElementById(barid);
if (event.target.value.length !== 0) {
searchbutton.classList.remove('hidden');
searchicon.classList.remove('search-empty');
}
else {
searchbutton.classList.add('hidden');
searchicon.classList.add('search-empty');
}
}
function clearSearch(searchid, resultid, clickevent) {
const searchbar = document.getElementById(searchid);
const searchresults = document.getElementById(resultid);
searchbar.value = '';
searchresults.replaceChildren();
searchbar.dispatchEvent(new Event('input'));
clickevent.preventDefault();
}

View File

@ -52,6 +52,12 @@ span{
color: var(--text);
}
p{
margin-left: 1.2rem;
margin-right: 1.2rem;
text-align: center;
}
/* HEADER */
header{
@ -199,39 +205,52 @@ header{
/* SEARCH BAR */
.search-bar{
display: flex; flex-shrink: 0; flex-direction: column; justify-content: center; align-items: center;
display: flex; justify-content: center; align-items: center; flex-wrap: wrap;
max-width: 64rem;
width: 100%;
height: 3.6rem;
margin-left: auto; margin-right: auto;
background-color: var(--block);
border-style: solid; border-top: 0; border-left: 0; border-right: 0; border-color: var(--thin-border); border-width: 1px;
padding-top: .6rem; padding-bottom: .6rem;
/* border-style: solid; border-top: 0; border-left: 0; border-right: 0; border-color: var(--thin-border); border-width: 1px; */
}
.search-items{
position: relative;
height: 2.4rem;
width: 80%;
width: calc(100% - 3.6rem);
margin-right: 1.2rem;
margin-left: .6rem;
}
.search-items input{
/* all:unset; */
.search-bar.search-empty::before, .suggest input::before{
content: "🔎︎";
width: 1.2rem;
text-align: center;
margin-left: .6rem;
}
.search-items input, .suggest input{
all: unset;
font: unset;
color: unset;
background-color: var(--search-back);
width: 100%;
height: 2.4rem;
border-style: solid; border-color: var(--thin-border); border-width: 1px;
padding: 0;
}
.search-icon{
height: 100%;
content: "🔎︎";
}
.search-clear.hidden{
display: none;
}
/* SEARCH RESULTS */
.search-results{
display: flex; flex-direction: column;
width: 100%;
cursor: pointer;
}
.search-results h2{
display: none;
}
@ -243,9 +262,13 @@ header{
list-style: none;
z-index: 1;
border-style: solid; border-color: var(--thin-border); border-width: 1px; border-top: 0; border-bottom: 0;
position: absolute;
width: 100%;
cursor: pointer;
overflow: hidden;
}
.search-results li{
.search-results li, .suggest-search-results{
display: flex; align-items: center;
min-height: 2.4rem;
border-style: solid; border-width: 1px; border-color: var(--thin-border); border-left: 0; border-top: 0; border-right: 0;
@ -255,7 +278,7 @@ header{
text-decoration: none;
}
.search-icon{
.search-results .search-icon{
min-width: 2.6rem;
}
@ -372,6 +395,7 @@ footer{
.sort-header{
border-style: solid; border-top: 0; border-left: 0; border-right: 0; border-width: 1px; border-color: var(--thin-border);
font-size: calc(1.1rem + .1vw);
}
.sort-header.album{
@ -586,12 +610,14 @@ footer{
display: flex; flex-direction: column;
width: 100%;
justify-content: center;
margin-left: 1.2rem; margin-right: 1.2rem;
}
.song-location h2{
text-align: center;
margin-bottom: 1.2rem;
}
.entrypage-connections{
display: flex; flex-direction: column;
width: 100%;
@ -633,13 +659,16 @@ footer{
/* SUGGEST PAGE */
.suggestbox{
display: flex; flex-wrap: wrap; justify-content: center; gap: 4.8rem;
display: flex; flex-wrap: wrap; align-items: center; gap: 2.4rem; flex-direction: column;
margin-top: 2.4rem;
overflow: hidden;
height: 36rem;
}
.suggestbox h2{
width: 100%;
text-align: center;
text-decoration: underline;
}
.search-items.suggest{
@ -651,17 +680,31 @@ footer{
}
.suggest-desc textarea{
width: 100%;
width: calc(100% - 8px);
resize: none;
}
.suggest-button{
width: 100%;
margin-bottom: 2.4rem;
}
#connection-1{
display: none;
.suggest-selection{
display: flex;
justify-content: center;
border-style: none;
}
#connection-1-desc{
.suggest{
width: clamp(24rem, 24rem, 100%);
}
.suggest-search{
display: flex; justify-content: center; flex-wrap: wrap; gap: 2.4rem; align-items: center;
margin-left: 1.2rem;
margin-right: 1.2rem;
width: 80%;
}
.search-number{
display: none;
}

View File

@ -1,4 +0,0 @@
2023-06-27T15:35:08.245616,Testing the form
2023-06-27T15:35:34.195039,testing again
2023-06-27T21:54:35.835288,"Hello from Moss and Nat's
"
1 2023-06-27T15:35:08.245616 Testing the form
2 2023-06-27T15:35:34.195039 testing again
3 2023-06-27T21:54:35.835288 Hello from Moss and Nat's

View File

@ -1,7 +1,7 @@
<li class="entrypage-clip">
<audio
controls
src = "{{ url_for('static', filename='clip/') }}{{ '%04d' % song_id }}-{{ '%03d' % motif_id }}.flac" type="audio/flac">
src = "{{ url_for('static', filename='clip/') }}{{ '%04d' % song_id }}-{{ '%03d' % motif_id }}.mp3" type="audio/flac">
</audio>
<span class="clip-pointer">

View File

@ -9,7 +9,7 @@
<hgroup class="home-title">
<h1>The Eorzea Songbook</h1>
<h3>An unofficial library of motifs in the Final Fantasy XIV soundtrack.</h3>
<p>Last updated: 26/06/2023</p>
<!-- <p>Last updated: 26/06/2023</p> -->
</hgroup>
<div class="home-nav">
@ -32,7 +32,7 @@
<div class="entrypage-clip">
<audio
controls
src = "{{ url_for('static', filename='clip/') }}0288-024.flac" type="audio/flac">
src = "{{ url_for('static', filename='clip/') }}0288-024.mp3" type="audio/flac">
</audio>
<span class="clip-pointer">
<img src="{{ url_for('static', filename='songicon.png') }}" alt="Song Icon" class="connection-icon"/>
@ -43,7 +43,7 @@
<div class="entrypage-clip">
<audio
controls
src = "{{ url_for('static', filename='clip/') }}0317-024.flac" type="audio/flac">
src = "{{ url_for('static', filename='clip/') }}0317-024.mp3" type="audio/flac">
</audio>
<span class="clip-pointer">
<img src="{{ url_for('static', filename='songicon.png') }}" alt="Song Icon" class="connection-icon"/>
@ -54,7 +54,7 @@
<div class="entrypage-clip">
<audio
controls
src = "{{ url_for('static', filename='clip/') }}0294-024.flac" type="audio/flac">
src = "{{ url_for('static', filename='clip/') }}0294-024.mp3" type="audio/flac">
</audio>
<span class="clip-pointer">
<img src="{{ url_for('static', filename='songicon.png') }}" alt="Song Icon" class="connection-icon"/>
@ -65,7 +65,7 @@
<div class="entrypage-clip">
<audio
controls
src = "{{ url_for('static', filename='clip/') }}0337-024.flac" type="audio/flac">
src = "{{ url_for('static', filename='clip/') }}0337-024.mp3" type="audio/flac">
</audio>
<span class="clip-pointer">
<img src="{{ url_for('static', filename='songicon.png') }}" alt="Song Icon" class="connection-icon"/>

View File

@ -1,7 +1,7 @@
<li class="entrypage-clip">
<audio
controls
src = "{{ url_for('static', filename='clip/') }}{{ '%04d' % song_id }}-{{ '%03d' % motif_id }}.flac" type="audio/flac">
src = "{{ url_for('static', filename='clip/') }}{{ '%04d' % song_id }}-{{ '%03d' % motif_id }}.mp3" type="audio/flac">
</audio>
<span class="clip-pointer">
<img src="{{ url_for('static', filename='songicon.png') }}" alt="Song Icon" class="connection-icon"/>

View File

@ -22,17 +22,20 @@
</nav>
</header>
<div class="search-bar">
<div id="top-search" class="search-bar search-empty">
<!-- <span class="search-icon"></span> -->
<button id="search-button" class="search-clear hidden" onclick="clearSearch('search-input', 'search-results-header', event)">X</button>
<div class="search-items">
<input
id="search-input"
type="search"
type="text"
name="q"
autocomplete="off"
placeholder="Search..."
placeholder=" Search..."
hx-get="/search"
hx-trigger="keyup changed delay:500ms, search"
hx-trigger="keyup changed delay:250ms, search"
hx-target="#search-results-header"
oninput="searchIconChange(event, 'search-button', 'top-search')"
/>
<div id="search-results-header" class="search-results header"></div>
</div>

Binary file not shown.

View File

@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}
{% for name, _ in song_info %}{{ name }}{% endfor %}
{% for name, _, _ in song_info %}{{ name }}{% endfor %}
{% endblock %}
{% set clipmode = "motif" %}
@ -11,7 +11,7 @@
<hgroup class="title-box">
<div class="eng-title">
<img src="{{ url_for('static', filename='songicon.png') }}" alt="Song Icon" />
{% for name, name_jp in song_info %}
{% for name, name_jp, _ in song_info %}
<h1>{{ name }}</h1>
{% endfor %}
<h3 class="title-album">
@ -20,7 +20,7 @@
{% endfor %}
</h3>
</div>
{% for name, name_jp in song_info %}
{% for name, name_jp, _ in song_info %}
{% if name_jp %}
<h2 class="title-jp">{{name_jp}}</h2>
{% endif %}
@ -28,9 +28,9 @@
</hgroup>
<!-- Artist info -->
<div class="track-info" >
<!-- Artist info -->
<div class="songpage-detail">
<h2 >Artist: </h2>
<ul>
@ -60,13 +60,14 @@
<!-- Location -->
{% if description %}
<div class="song-location">
<h2>Used In:</h2>
</div>
{% endif %}
{% for _, _, location in song_info %}
{% if location %}
<div class="song-location">
<h2>Appearances:</h2>
<p class="songpage-location">{{ location }}</p>
</div>
{% endif %}
{% endfor %}
<!-- Motifs -->

View File

@ -4,38 +4,48 @@
{% block content %}
<p>If you know about a shared motif that you can't find on the website, you can tell me about it here. Just pick the two songs that share a motif - or the motif itself, if it already has a page - and tell me what to listen out for.</p>
<p>If you know about songs in the Final Fantasy XIV soundtrack that share a connection, and you can't find it on the website, tell me about it here. Just pick the two songs that share a motif - or the motif itself, if it already has a page - and tell me what to listen out for.</p>
<form action="/sent" method="post" >
<div class="suggestbox">
<div class="search-items suggest">
<h2>Song / Motif 1</h2>
<input
id="search-input-suggest-1"
type="search"
name="q"
autocomplete="off"
placeholder="Search..."
hx-get="/suggestsearch"
hx-trigger="keyup changed delay:500ms, search"
hx-target="#search-results-1"
/>
<div id="search-results-1" class="search-results"></div>
<div class="suggest-search">
{% for i in range(2) %}
<div class="suggest">
<h2>Song / Motif {{ i + 1 }}</h2>
<div id="connection-{{ i }}" class="suggest-search-results suggest-selection">
<div>Nothing selected yet.</div>
</div>
<div id="suggest-bar-{{ i }}" class="search-bar search-empty">
<div class="search-items">
<button id="suggest-button-{{ i }}" class="search-clear hidden" onclick="clearSearch('search-input-suggest-{{ i }}', 'search-results-{{ i }}', event)">X</button>
<input
id="search-input-suggest-{{ i }}"
type="text"
name="q"
autocomplete="off"
placeholder=" Search..."
hx-get="/suggestsearch"
hx-trigger="keyup changed delay:250ms, search"
hx-target="#search-results-{{ i }}"
oninput="searchIconChange(event, 'suggest-button-{{ i }}', 'suggest-bar-{{ i }}')"
/>
<div id="search-results-{{ i }}" class="search-results" data-form="{{ i }}"></div>
</div>
</div>
</div>
<input required type="text" name="connection-{{ i }}-desc" id="connection-{{ i }}-desc" class="search-number"/>
<input required type="number" name="connection-{{ i }}-id" id="connection-{{ i }}-id" class="search-number"/>
{% endfor %}
</div>
<input type="number" name="connection-1-desc" id="connection-1-desc"/>
<input type="number" name="connection-1" id="connection-1" min="1" max="733"/>
<div class="suggest-desc">
<textarea name="suggest-description">
Suggest a different connection!
</textarea>
<textarea maxlength="10000" rows="6" required name="suggest-description" placeholder="Describe the connection here."></textarea>
</div>
<div class="suggest-button">
<input type="submit" />
<input type="submit" value="Submit"/>
</div>
</div>
</form>
{% endblock %}

View File

@ -1,22 +1,19 @@
<h2>Search Results:</h2>
<ul>
{% for desc, id, name, _ in searchresult %}
{% if desc == 'song' %}
<li onclick="suggestform('{{ id }}', 1)">
<span class="search-icon">
<img src="{{ url_for('static', filename='songicon.png') }}" alt="Song Icon"/>
</span>
<span class="search-type">Song:</span>
<li onclick="suggestform('{{ id }}', '{{ desc }}', event)">
{% if desc == 'song' %}
<span class="search-icon suggest-icon">
<img src="{{ url_for('static', filename='songicon.png') }}" alt="Song Icon"/>
</span>
<span class="search-type">Song:</span>
{% elif desc == 'motif' %}
<span class="search-icon">
<img src="{{ url_for('static', filename='motificon.png') }}" alt="Motif Icon"/>
</span>
<span class="search-type">Motif:</span>
{% endif %}
{{ name }}
</li>
{% elif desc == 'motif' %}
<li onclick="suggestform('{{ id }}', 2)">
<span class="search-icon">
<img src="{{ url_for('static', filename='motificon.png') }}" alt="Motif Icon"/>
</span>
<span class="search-type">Motif:</span>
{{ name }}
</li></a>
{% endif %}
{% endfor %}
</ul>