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__/

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
/songbook.sqlite
/static/clip/
/__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:]
)

3
db.sql
View File

@ -4,6 +4,7 @@ create table song(
id int primary key,
name text not null,
name_jp text not null,
location text not null,
unique(name,name_jp)
);
@ -11,6 +12,8 @@ create table album(
id int primary key,
name text 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
@ -62,3 +69,27 @@ 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..."
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 %}
{% for _, _, location in song_info %}
{% if location %}
<div class="song-location">
<h2>Used In:</h2>
<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>
<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-1"
type="search"
id="search-input-suggest-{{ i }}"
type="text"
name="q"
autocomplete="off"
placeholder=" Search..."
hx-get="/suggestsearch"
hx-trigger="keyup changed delay:500ms, search"
hx-target="#search-results-1"
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-1" class="search-results"></div>
<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 %}
<li onclick="suggestform('{{ id }}', '{{ desc }}', event)">
{% if desc == 'song' %}
<li onclick="suggestform('{{ id }}', 1)">
<span class="search-icon">
<span class="search-icon suggest-icon">
<img src="{{ url_for('static', filename='songicon.png') }}" alt="Song Icon"/>
</span>
<span class="search-type">Song:</span>
{{ 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 %}
{{ name }}
</li>
{% endfor %}
</ul>