diff --git a/app/models.py b/app/models.py index 3c2851b..195ac47 100644 --- a/app/models.py +++ b/app/models.py @@ -15,6 +15,7 @@ class Song(BaseModel): aca_artist: Optional[str] title: str yt_url: Optional[str] + thumbnail: Optional[str] is_aca: bool arng_url: Optional[str] categories: dict[str, bool] diff --git a/app/routers/admin.py b/app/routers/admin.py index 62c6f2a..40fd220 100644 --- a/app/routers/admin.py +++ b/app/routers/admin.py @@ -4,6 +4,8 @@ from app.dependencies import session from app.routers.user import get_current_user import pandas as pd import numpy as np +import re +import requests router = APIRouter( prefix="/admin", @@ -20,6 +22,27 @@ def get_main_category(categories) -> int: else: return np.argmax(categories != None, axis=0) +def youtube_url_validation(url): + youtube_regex = ( + r'(https?://)?(www\.)?' + '(youtube|youtu|youtube-nocookie)\.(com|be)/' + '(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})') + + youtube_regex_match = re.match(youtube_regex, url) + if youtube_regex_match: + return youtube_regex_match + + return False + +def get_thumbnail(url): + m = youtube_url_validation(url) + if m: + thumbnail_url = "https://img.youtube.com/vi/" + m.group(6) + "/mqdefault.jpg" + return thumbnail_url + elif "spotify" in url: + return re.findall(r'(https?://i.scdn.co/image[^"]+)', requests.get(url).text)[0] + else: + return "/static/cover.jpg" @router.post("/process_file") async def create_upload_file(link_share: str): @@ -36,6 +59,7 @@ async def create_upload_file(link_share: str): aca_artist=row[1], title=row[2], yt_url=row[3], + thumbnail=get_thumbnail(row[3]), is_aca=row[4] == "ja", arng_url=row[5], categories={n: v for n, v in zip( diff --git a/app/sql_models.py b/app/sql_models.py index a92a8fc..9c46f0e 100644 --- a/app/sql_models.py +++ b/app/sql_models.py @@ -10,6 +10,7 @@ class SqlSong(Base): aca_artist = Column(String) title = Column(String) yt_url = Column(String) + thumbnail = Column(String) is_aca = Column(Boolean) arng_url = Column(String) diff --git a/requirements.txt b/requirements.txt index b25acae..9c7501d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,5 @@ python-jose[cryptography] passlib[bcrypt] python-multipart jinja2 -openpyxl \ No newline at end of file +openpyxl +requests \ No newline at end of file diff --git a/static/colors.css b/static/colors.css index 9859662..4c7065e 100644 --- a/static/colors.css +++ b/static/colors.css @@ -1,90 +1,90 @@ .cat-1 { - background-color: #1f77b4; + --main-color: #1f77b4; } .cat-2 { - background-color: #ff7f0e; + --main-color: #ff7f0e; } .cat-3 { - background-color: #2ca02c; + --main-color: #2ca02c; } .cat-4 { - background-color: #d62728; + --main-color: #d62728; } .cat-5 { - background-color: #9467bd; + --main-color: #9467bd; } .cat-6 { - background-color: #8c564b; + --main-color: #8c564b; } .cat-7 { - background-color: #e377c2; + --main-color: #e377c2; } .cat-8 { - background-color: #7f7f7f; + --main-color: #7f7f7f; } .cat-9 { - background-color: #bcbd22; + --main-color: #bcbd22; } .cat-10 { - background-color: #17becf; + --main-color: #17becf; } .cat-11 { - background-color: #a1c9f4; + --main-color: #a1c9f4; } .cat-12 { - background-color: #ffb482; + --main-color: #ffb482; } .cat-13 { - background-color: #8de5a1; + --main-color: #8de5a1; } .cat-14 { - background-color: #ff9f9b; + --main-color: #ff9f9b; } .cat-15 { - background-color: #d0bbff; + --main-color: #d0bbff; } .cat-16 { - background-color: #debb9b; + --main-color: #debb9b; } .cat-17 { - background-color: #fab0e4; + --main-color: #fab0e4; } .cat-18 { - background-color: #cfcfcf; + --main-color: #cfcfcf; } .cat-19 { - background-color: #fffea3; + --main-color: #fffea3; } .cat-20 { - background-color: #b9f2f0; + --main-color: #b9f2f0; } .cat-21 { - background-color: #001c7f; + --main-color: #001c7f; } .cat-22 { - background-color: #b1400d; + --main-color: #b1400d; } .cat-23 { - background-color: #12711c; + --main-color: #12711c; } .cat-24 { - background-color: #8c0800; + --main-color: #8c0800; } .cat-25 { - background-color: #591e71; + --main-color: #591e71; } .cat-26 { - background-color: #592f0d; + --main-color: #592f0d; } .cat-27 { - background-color: #a23582; + --main-color: #a23582; } .cat-28 { - background-color: #3c3c3c; + --main-color: #3c3c3c; } .cat-29 { - background-color: #b8850a; + --main-color: #b8850a; } .cat-30 { - background-color: #006374; + --main-color: #006374; } diff --git a/static/cover.jpg b/static/cover.jpg index 1f3d723..67e3605 100644 Binary files a/static/cover.jpg and b/static/cover.jpg differ diff --git a/static/index.html b/static/index.html deleted file mode 100644 index 7db222f..0000000 --- a/static/index.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - Hello jQuery - - - - - -
-

The ID is

-

The content is

-
- - diff --git a/static/landing_script.js b/static/landing_script.js index d822fbc..c9e0de0 100644 --- a/static/landing_script.js +++ b/static/landing_script.js @@ -5,4 +5,5 @@ $(document).ready(function () { localStorage.setItem('session_id', s_id) } $('.vote-from-existing').attr('href', '?session_id=' + s_id); + window.location.href = "/?session_id=" + s_id; }); diff --git a/static/site.css b/static/site.css index 899a81d..a997bb1 100644 --- a/static/site.css +++ b/static/site.css @@ -2,10 +2,9 @@ box-sizing: border-box; margin: 0; padding: 0; -} - -body { - padding: 0.5em; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; } @media only screen and (min-resolution: 200dpi) { @@ -24,7 +23,8 @@ body { border-radius: 0.5em; width: 30em; font-family: sans-serif; - margin-bottom: 1em; + margin-bottom: 1rem; + margin-left: 0.5em; } @@ -34,12 +34,21 @@ body { height: 6em; float: left; margin-right: 1em; + text-align: center; + background-color: white; } .cover { display: block; - width: 100%; - height: auto; + max-width: 100%; + max-height: 100%; + position: absolute; + top: 50%; + left: 50%; + -webkit-transform: translate(-50%, -50%); + -ms-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + transition: .3s ease; } @@ -64,6 +73,10 @@ body { filter: drop-shadow(0px 0px 1em black); } +.cover-container:hover .cover { + filter: brightness(0.6); +} + .overlay img { height: 50%; position: absolute; @@ -86,7 +99,7 @@ body { } .button { - height: 2em; + height: 1.5em; width: 3em; display: inline-block; text-align: center; @@ -160,7 +173,7 @@ body { white-space: nowrap; font-size: 0.7em; line-height: 1.2em; - margin-top: 0.2em; + margin-top: 0.3em; } .categories span { @@ -171,14 +184,66 @@ body { color: white; max-width: 10em; overflow: clip; + background-color: var(--main-color); } .vote-buttons { margin-top: 0.5em; } -.song-data { +.song-title, +.song-artist { width: 100%; overflow: clip; white-space: nowrap; +} + +.song-artist { + font-size: 0.7em; +} + +h1 { + font-family: sans-serif; + padding: 0.1em; + padding-left: 0.2em; + margin-bottom: 1rem; + font-size: 1.5em; + background-color: color-mix(in srgb, var(--main-color) 60%, transparent); + border-bottom: 0.3rem solid var(--main-color); +} + +#yt-player, +#spotify-player { + position: fixed; + bottom: 0.5em; + right: 0.5em; + padding: 0; + display: none; + justify-content: center; + border-radius: 20px; +} + +#spotify-player { + bottom: 0.2em; + background-color: blue; +} + +#close-player { + position: absolute; + bottom: 320px; + right: 0.5em; + background-color: rgba(255,255,255,0.9); + width: 3em; + height: 3em; + border-radius: 1.5em; + display: none; +} +#close-player img { + position: relative; + left: 50%; + top: 50%; + -webkit-transform: translate(-50%, -50%); + -ms-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + width: 1.5em; } \ No newline at end of file diff --git a/static/voting_script.js b/static/voting_script.js index 0b76135..d472767 100644 --- a/static/voting_script.js +++ b/static/voting_script.js @@ -78,25 +78,36 @@ function vote(song_id, vote) { const getQueryParameter = (param) => new URLSearchParams(document.location.search.substring(1)).get(param); +var all_songs = {} + $(document).ready(function () { var session_id = getQueryParameter("session_id"); var songTemplate = $('script[data-template="song"]').text().split(/\$\{(.+?)\}/g); function render(props) { - return function(tok, i) { return (i % 2) ? props[tok] : tok; }; + return function (tok, i) { return (i % 2) ? props[tok] : tok; }; } - song_list = {} + $.ajax({ - url: "/songs", + url: "/songs/", data: { user_id: session_id } }).then(function (songs) { + song_list = {} + + cat_to_id = {} + $.each(songs, function (key, song) { + all_songs[song.id] = song; + var mc = song.main_category; + if (!song.is_aca) { + mc = "Wildcard" + } if (!(mc in song_list)) { song_list[mc] = "" } @@ -107,25 +118,102 @@ $(document).ready(function () { if (is_cat) { cats = cats + '' + cat_name + ''; } + cat_to_id[cat_name] = cat_id cat_id += 1 }); + cat_to_id["Wildcard"] = cat_id + + + artist = ""; + if (song.og_artist) { + artist += song.og_artist; + if (song.aca_artist && (song.aca_artist !== song.og_artist)) { + artist += " / "; + artist += song.aca_artist; + } + } else { + artist = song.aca_artist; + } - var s = songTemplate.map(render({ - "id" : song.id, - "title" : song.og_artist + ": " + song.title, - "cover_image" : "cover.jpg", - "no_selected" : (song.vote == -1) ? "selected" : "", - "neutral_selected" : (song.vote == 0) ? "selected" : "", - "yes_selected" : (song.vote == 1) ? "selected" : "", - "categories" : cats + "id": song.id, + "title": song.title, + "artist": artist, //song.og_artist + ": " + song.aca_artist + "cover_image": song.thumbnail, + "no_selected": (song.vote == -1) ? "selected" : "", + "neutral_selected": (song.vote == 0) ? "selected" : "", + "yes_selected": (song.vote == 1) ? "selected" : "", + "categories": cats })).join('') song_list[mc] += s }); - $.each(song_list, function(mc, s) { - $('body').append("

" + mc + "

"); - $('body').append(s); + $.each(cat_to_id, function (cat_name, cat_id) { + if (cat_name in song_list) { + $('#songs').append("

" + cat_name + "

"); + $('#songs').append(song_list[cat_name]); + } }); + + makeScroll(); }); }); + +var is_playing = -1; + +var spotify_embed_controller; + +window.onSpotifyIframeApiReady = (IFrameAPI) => { + const element = document.getElementById('spotify-embed'); + const options = { + width: '640', + height: '360' + }; + const callback = (EmbedController) => { + spotify_embed_controller = EmbedController; + }; + IFrameAPI.createController(element, options, callback); + }; + + +function play(id) { + $("#yt-player").css("display", "none"); + $("#spotify-player").css("display", "none"); + $("#close-player").css("display", "none"); + $("#yt-player").html(""); + spotify_embed_controller.pause(); + + if (is_playing == id) { + is_playing = -1; + } else { + is_playing = id; + + song = all_songs[id]; + yt_id = song.yt_url.split('v=')[1] + spotify_id = song.yt_url.split('/track/')[1] + + if (yt_id) { + $("#yt-player").css("display", "flex"); + $("#close-player").css("display", "block"); + iframe_code = ''; + $("#yt-player").html(iframe_code); + } + else if (spotify_id) { + $("#spotify-player").css("display", "flex"); + $("#close-player").css("display", "block"); + spotify_embed_controller.loadUri("spotify:track:" + spotify_id); + spotify_embed_controller.play(); + } + else { + $("#yt-player").css("display", "none"); + $("#spotify-player").css("display", "none"); + $("#yt-player").html(""); + spotify_embed_controller.pause(); + window.open(song.yt_url, '_blank').focus(); + } + } +} + +// + +// https://open.spotify.com/intl-de/track/2DS7lDZNFM7safSGNm8vd4 \ No newline at end of file diff --git a/templates/item.html b/templates/item.html deleted file mode 100644 index 9ba6afa..0000000 --- a/templates/item.html +++ /dev/null @@ -1,9 +0,0 @@ - - - Item Details - - - -

Item ID: {{ id }}

- - \ No newline at end of file diff --git a/templates/voting.html b/templates/voting.html index ca7298a..706cfb6 100644 --- a/templates/voting.html +++ b/templates/voting.html @@ -4,20 +4,22 @@ Liederwahl +