mirror of
https://github.com/tcsenpai/swingmusic.git
synced 2025-06-10 13:07:35 +00:00
some bug fixes
- watch route params instead of route object in folderview - move to script setup on album view - use album as a reactive object instead of refs - use axios instead of fetch to get album data - improve clickable areas on songItem - move album requests to POST
This commit is contained in:
parent
8459310258
commit
6efbb47166
@ -1,9 +1,11 @@
|
|||||||
|
from crypt import methods
|
||||||
import os
|
import os
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
import urllib
|
import urllib
|
||||||
from typing import List
|
from typing import List
|
||||||
from flask import Blueprint, request, send_file
|
from flask import Blueprint, request, send_file
|
||||||
from app import functions, instances, helpers, cache
|
|
||||||
|
from app import functions, instances, helpers, cache, models
|
||||||
|
|
||||||
bp = Blueprint("api", __name__, url_prefix="")
|
bp = Blueprint("api", __name__, url_prefix="")
|
||||||
|
|
||||||
@ -30,21 +32,21 @@ def say_hi():
|
|||||||
return "^ _ ^"
|
return "^ _ ^"
|
||||||
|
|
||||||
|
|
||||||
def get_tracks(query: str) -> List:
|
def get_tracks(query: str) -> List[models.Track]:
|
||||||
"""
|
"""
|
||||||
Gets all songs with a given title.
|
Gets all songs with a given title.
|
||||||
"""
|
"""
|
||||||
return [track for track in all_the_f_music if query.lower() in track.title.lower()]
|
return [track for track in all_the_f_music if query.lower() in track.title.lower()]
|
||||||
|
|
||||||
|
|
||||||
def get_search_albums(query: str) -> List:
|
def get_search_albums(query: str) -> List[models.Track]:
|
||||||
"""
|
"""
|
||||||
Gets all songs with a given album.
|
Gets all songs with a given album.
|
||||||
"""
|
"""
|
||||||
return [track for track in all_the_f_music if query.lower() in track.album.lower()]
|
return [track for track in all_the_f_music if query.lower() in track.album.lower()]
|
||||||
|
|
||||||
|
|
||||||
def get_artists(artist: str) -> List:
|
def get_artists(artist: str) -> List[models.Track]:
|
||||||
"""
|
"""
|
||||||
Gets all songs with a given artist.
|
Gets all songs with a given artist.
|
||||||
"""
|
"""
|
||||||
@ -151,12 +153,13 @@ def find_tracks():
|
|||||||
return "🎸"
|
return "🎸"
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/album/<album>/<artist>/artists")
|
@bp.route("/album/artists", methods=["POST"])
|
||||||
@cache.cached()
|
def get_albumartists():
|
||||||
def get_albumartists(album, artist):
|
|
||||||
"""Returns a list of artists featured in a given album."""
|
"""Returns a list of artists featured in a given album."""
|
||||||
album = album.replace("|", "/")
|
data = request.get_json()
|
||||||
artist = artist.replace("|", "/")
|
|
||||||
|
album = data["album"]
|
||||||
|
artist = data["artist"]
|
||||||
|
|
||||||
tracks = []
|
tracks = []
|
||||||
|
|
||||||
@ -292,20 +295,24 @@ def get_albums():
|
|||||||
return {"albums": albums}
|
return {"albums": albums}
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/album/<title>/<artist>/tracks")
|
@bp.route("/album/tracks", methods=["POST"])
|
||||||
@cache.cached()
|
def get_album_tracks():
|
||||||
def get_album_tracks(title: str, artist: str):
|
|
||||||
"""Returns all the tracks in the given album."""
|
"""Returns all the tracks in the given album."""
|
||||||
|
data = request.get_json()
|
||||||
|
|
||||||
|
album = data["album"]
|
||||||
|
artist = data["artist"]
|
||||||
|
|
||||||
songs = []
|
songs = []
|
||||||
|
|
||||||
for track in all_the_f_music:
|
for track in all_the_f_music:
|
||||||
if track.albumartist == artist and track.album == title:
|
if track.albumartist == artist and track.album == album:
|
||||||
songs.append(track)
|
songs.append(track)
|
||||||
|
|
||||||
songs = helpers.remove_duplicates(songs)
|
songs = helpers.remove_duplicates(songs)
|
||||||
|
|
||||||
album_obj = {
|
album_obj = {
|
||||||
"name": title,
|
"name": album,
|
||||||
"count": len(songs),
|
"count": len(songs),
|
||||||
"duration": "56 Minutes",
|
"duration": "56 Minutes",
|
||||||
"image": songs[0].image,
|
"image": songs[0].image,
|
||||||
|
@ -7,6 +7,7 @@ import threading
|
|||||||
import time
|
import time
|
||||||
from typing import List
|
from typing import List
|
||||||
import requests
|
import requests
|
||||||
|
import colorgram
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
@ -15,9 +16,10 @@ from PIL import Image
|
|||||||
from app import instances
|
from app import instances
|
||||||
from app import functions
|
from app import functions
|
||||||
from app import watchdoge
|
from app import watchdoge
|
||||||
|
from app import models
|
||||||
|
|
||||||
home_dir = os.path.expanduser('~') + '/'
|
home_dir = os.path.expanduser("~") + "/"
|
||||||
app_dir = os.path.join(home_dir, '.musicx')
|
app_dir = os.path.join(home_dir, ".musicx")
|
||||||
LAST_FM_API_KEY = "762db7a44a9e6fb5585661f5f2bdf23a"
|
LAST_FM_API_KEY = "762db7a44a9e6fb5585661f5f2bdf23a"
|
||||||
|
|
||||||
|
|
||||||
@ -63,7 +65,7 @@ def run_fast_scandir(_dir: str, ext: list):
|
|||||||
files = []
|
files = []
|
||||||
|
|
||||||
for f in os.scandir(_dir):
|
for f in os.scandir(_dir):
|
||||||
if f.is_dir() and not f.name.startswith('.'):
|
if f.is_dir() and not f.name.startswith("."):
|
||||||
subfolders.append(f.path)
|
subfolders.append(f.path)
|
||||||
if f.is_file():
|
if f.is_file():
|
||||||
if os.path.splitext(f.name)[1].lower() in ext:
|
if os.path.splitext(f.name)[1].lower() in ext:
|
||||||
@ -77,24 +79,26 @@ def run_fast_scandir(_dir: str, ext: list):
|
|||||||
return subfolders, files
|
return subfolders, files
|
||||||
|
|
||||||
|
|
||||||
def remove_duplicates(array: list) -> list:
|
def remove_duplicates(tracklist: List[models.Track]) -> List[models.Track]:
|
||||||
"""
|
"""
|
||||||
Removes duplicates from a list. Returns a list without duplicates.
|
Removes duplicates from a list. Returns a list without duplicates.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
song_num = 0
|
song_num = 0
|
||||||
|
|
||||||
while song_num < len(array) - 1:
|
while song_num < len(tracklist) - 1:
|
||||||
for index, song in enumerate(array):
|
for index, song in enumerate(tracklist):
|
||||||
if array[song_num].title == song.title and \
|
if (
|
||||||
array[song_num].album == song.album and \
|
tracklist[song_num].title == song.title
|
||||||
array[song_num].artists == song.artists and \
|
and tracklist[song_num].album == song.album
|
||||||
index != song_num:
|
and tracklist[song_num].artists == song.artists
|
||||||
array.remove(song)
|
and index != song_num
|
||||||
|
):
|
||||||
|
tracklist.remove(song)
|
||||||
|
|
||||||
song_num += 1
|
song_num += 1
|
||||||
|
|
||||||
return array
|
return tracklist
|
||||||
|
|
||||||
|
|
||||||
def save_image(url: str, path: str) -> None:
|
def save_image(url: str, path: str) -> None:
|
||||||
@ -104,7 +108,7 @@ def save_image(url: str, path: str) -> None:
|
|||||||
|
|
||||||
response = requests.get(url)
|
response = requests.get(url)
|
||||||
img = Image.open(BytesIO(response.content))
|
img = Image.open(BytesIO(response.content))
|
||||||
img.save(path, 'JPEG')
|
img.save(path, "JPEG")
|
||||||
|
|
||||||
|
|
||||||
def is_valid_file(filename: str) -> bool:
|
def is_valid_file(filename: str) -> bool:
|
||||||
@ -112,7 +116,7 @@ def is_valid_file(filename: str) -> bool:
|
|||||||
Checks if a file is valid. Returns True if it is, False if it isn't.
|
Checks if a file is valid. Returns True if it is, False if it isn't.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if filename.endswith('.flac') or filename.endswith('.mp3'):
|
if filename.endswith(".flac") or filename.endswith(".mp3"):
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
@ -123,11 +127,10 @@ def create_config_dir() -> None:
|
|||||||
Creates the config directory if it doesn't exist.
|
Creates the config directory if it doesn't exist.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_home_dir = os.path.expanduser('~')
|
_home_dir = os.path.expanduser("~")
|
||||||
config_folder = os.path.join(_home_dir, app_dir)
|
config_folder = os.path.join(_home_dir, app_dir)
|
||||||
|
|
||||||
dirs = ["", "images", "images/defaults",
|
dirs = ["", "images", "images/defaults", "images/artists", "images/thumbnails"]
|
||||||
"images/artists", "images/thumbnails"]
|
|
||||||
|
|
||||||
for _dir in dirs:
|
for _dir in dirs:
|
||||||
path = os.path.join(config_folder, _dir)
|
path = os.path.join(config_folder, _dir)
|
||||||
@ -140,19 +143,32 @@ def create_config_dir() -> None:
|
|||||||
os.chmod(path, 0o755)
|
os.chmod(path, 0o755)
|
||||||
|
|
||||||
|
|
||||||
def get_all_songs() -> List:
|
def get_all_songs() -> List[models.Track]:
|
||||||
"""
|
"""
|
||||||
Gets all songs under the ~/ directory.
|
Gets all songs under the ~/ directory.
|
||||||
"""
|
"""
|
||||||
print("Getting all songs...")
|
print("Getting all songs...")
|
||||||
tracks = []
|
|
||||||
|
tracks: list[models.Track] = []
|
||||||
|
|
||||||
for track in instances.songs_instance.get_all_songs():
|
for track in instances.songs_instance.get_all_songs():
|
||||||
try:
|
try:
|
||||||
os.chmod(os.path.join(track["filepath"]), 0o755)
|
os.chmod(os.path.join(track["filepath"]), 0o755)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
instances.songs_instance.remove_song_by_filepath(track['filepath'])
|
instances.songs_instance.remove_song_by_filepath(track["filepath"])
|
||||||
|
|
||||||
tracks.append(functions.create_track_class(track))
|
tracks.append(functions.create_track_class(track))
|
||||||
|
|
||||||
return tracks
|
return tracks
|
||||||
|
|
||||||
|
|
||||||
|
def extract_colors(image) -> list:
|
||||||
|
colors = sorted(colorgram.extract(image, 2), key=lambda c: c.hsl.h)
|
||||||
|
|
||||||
|
formatted_colors = []
|
||||||
|
|
||||||
|
for color in colors:
|
||||||
|
color = f"rgb({color.rgb.r}, {color.rgb.g}, {color.rgb.b})"
|
||||||
|
formatted_colors.append(color)
|
||||||
|
|
||||||
|
return formatted_colors
|
||||||
|
17
server/poetry.lock
generated
17
server/poetry.lock
generated
@ -36,6 +36,17 @@ category = "main"
|
|||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorgram.py"
|
||||||
|
version = "1.2.0"
|
||||||
|
description = "A Python module for extracting colors from images. Get a palette of any picture!"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
pillow = ">=3.3.1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flask"
|
name = "flask"
|
||||||
version = "2.0.2"
|
version = "2.0.2"
|
||||||
@ -234,7 +245,7 @@ watchdog = ["watchdog"]
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "1.1"
|
lock-version = "1.1"
|
||||||
python-versions = "^3.8"
|
python-versions = "^3.8"
|
||||||
content-hash = "43a8f1b3d32df323e4836559445b061c5ef7540471f75ffb3365b683e953f760"
|
content-hash = "c5fb66888aa3ddc0828c3c7794409039ada460ab96b3f824fd76caa85e27fbfb"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
certifi = [
|
certifi = [
|
||||||
@ -253,6 +264,10 @@ colorama = [
|
|||||||
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
|
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
|
||||||
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
|
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
|
||||||
]
|
]
|
||||||
|
"colorgram.py" = [
|
||||||
|
{file = "colorgram.py-1.2.0-py2.py3-none-any.whl", hash = "sha256:e990769fa6df7261a450c7d5bef3a1a062f09ba1214bff67b4d6f02970a1a27b"},
|
||||||
|
{file = "colorgram.py-1.2.0.tar.gz", hash = "sha256:e77766a5f9de7207bdef8f1c22a702cbf09630eae3bc46a450b9d9f12a7bfdbf"},
|
||||||
|
]
|
||||||
flask = [
|
flask = [
|
||||||
{file = "Flask-2.0.2-py3-none-any.whl", hash = "sha256:cb90f62f1d8e4dc4621f52106613488b5ba826b2e1e10a33eac92f723093ab6a"},
|
{file = "Flask-2.0.2-py3-none-any.whl", hash = "sha256:cb90f62f1d8e4dc4621f52106613488b5ba826b2e1e10a33eac92f723093ab6a"},
|
||||||
{file = "Flask-2.0.2.tar.gz", hash = "sha256:7b2fb8e934ddd50731893bdcdb00fc8c0315916f9fcd50d22c7cc1a95ab634e2"},
|
{file = "Flask-2.0.2.tar.gz", hash = "sha256:7b2fb8e934ddd50731893bdcdb00fc8c0315916f9fcd50d22c7cc1a95ab634e2"},
|
||||||
|
@ -16,6 +16,7 @@ progress = "^1.6"
|
|||||||
gunicorn = "^20.1.0"
|
gunicorn = "^20.1.0"
|
||||||
Pillow = "^9.0.1"
|
Pillow = "^9.0.1"
|
||||||
Flask-Caching = "^1.10.1"
|
Flask-Caching = "^1.10.1"
|
||||||
|
"colorgram.py" = "^1.2.0"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
.b-bar {
|
.b-bar {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-top: solid 1px $gray;
|
background-color: $gray;
|
||||||
|
|
||||||
.grid {
|
.grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
@ -36,7 +36,7 @@
|
|||||||
|
|
||||||
.artists {
|
.artists {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
color: $red;
|
color: $white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,7 +48,6 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
margin: $small;
|
margin: $small;
|
||||||
padding: $small;
|
padding: $small;
|
||||||
background-color: $gray5;
|
|
||||||
|
|
||||||
.progress-bottom {
|
.progress-bottom {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -1,18 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="album-h">
|
<div class="album-h">
|
||||||
<div class="a-header rounded">
|
<div class="a-header rounded">
|
||||||
|
<div
|
||||||
|
class="image art shadow-lg"
|
||||||
|
:style="{ backgroundImage: `url("${encodeURI(props.album_info.image)}")` }"
|
||||||
|
></div>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<div class="top">
|
<div class="top">
|
||||||
<div class="h">Album</div>
|
<div class="h">Album</div>
|
||||||
<div class="separator no-border"></div>
|
<div class="separator no-border"></div>
|
||||||
<div class="title">{{ album_info.name }}</div>
|
<div class="title">{{ props.album_info.name }}</div>
|
||||||
<div class="artist">{{ album_info.artist }}</div>
|
<div class="artist">{{ props.album_info.artist }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="separator no-border"></div>
|
<div class="separator no-border"></div>
|
||||||
<div class="bottom">
|
<div class="bottom">
|
||||||
<div class="stats shadow-sm">
|
<div class="stats shadow-sm">
|
||||||
{{ album_info.count }} Tracks • {{ album_info.duration }} •
|
{{ props.album_info.count }} Tracks • {{ props.album_info.duration }} •
|
||||||
{{ album_info.date }}
|
{{ props.album_info.date }}
|
||||||
</div>
|
</div>
|
||||||
<div class="play rounded" @click="playAlbum">
|
<div class="play rounded" @click="playAlbum">
|
||||||
<div class="icon"></div>
|
<div class="icon"></div>
|
||||||
@ -24,22 +28,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import state from "@/composables/state.js";
|
import state from "@/composables/state.js";
|
||||||
import perks from "@/composables/perks.js";
|
import perks from "@/composables/perks.js";
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
props: ["album_info"],
|
album_info: {
|
||||||
setup() {
|
type: Object,
|
||||||
function playAlbum() {
|
default: () => ({}),
|
||||||
perks.updateQueue(state.album_song_list.value[0], "album");
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
playAlbum,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
|
function playAlbum() {
|
||||||
|
perks.updateQueue(state.album.tracklist[0], "album");
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@ -59,7 +61,7 @@ export default {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: $small;
|
padding: 1rem;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-image: linear-gradient(
|
background-image: linear-gradient(
|
||||||
56deg,
|
56deg,
|
||||||
@ -73,12 +75,20 @@ export default {
|
|||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
|
|
||||||
|
.art {
|
||||||
|
position: absolute;
|
||||||
|
width: 12rem;
|
||||||
|
height: 12rem;
|
||||||
|
left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: calc(100%);
|
height: calc(100%);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
margin-left: 13rem;
|
||||||
|
|
||||||
.top {
|
.top {
|
||||||
.h {
|
.h {
|
||||||
@ -88,6 +98,7 @@ export default {
|
|||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
font-weight: 1000;
|
font-weight: 1000;
|
||||||
color: white;
|
color: white;
|
||||||
|
text-transform: capitalize;
|
||||||
}
|
}
|
||||||
|
|
||||||
.artist {
|
.artist {
|
||||||
|
@ -13,6 +13,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="search">
|
<div class="search">
|
||||||
|
<div class="loaderr">
|
||||||
|
<Loader />
|
||||||
|
</div>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
class="search-input border"
|
class="search-input border"
|
||||||
@ -26,11 +29,13 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import perks from "@/composables/perks.js";
|
import perks from "@/composables/perks.js";
|
||||||
import { watch } from '@vue/runtime-core';
|
import { watch } from "@vue/runtime-core";
|
||||||
import useDebouncedRef from '@/composables/useDebouncedRef.js';
|
import useDebouncedRef from "@/composables/useDebouncedRef.js";
|
||||||
|
import Loader from "../shared/Loader.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ["path", "first_song"],
|
props: ["path", "first_song"],
|
||||||
|
components: { Loader },
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const query = useDebouncedRef("", 400);
|
const query = useDebouncedRef("", 400);
|
||||||
|
|
||||||
@ -63,8 +68,13 @@ export default {
|
|||||||
.folder-top .search {
|
.folder-top .search {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
display: grid;
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
place-items: end;
|
place-items: end;
|
||||||
|
|
||||||
|
.loaderr {
|
||||||
|
width: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
.search-input {
|
.search-input {
|
||||||
max-width: 20rem;
|
max-width: 20rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -69,7 +69,7 @@ import useDebouncedRef from "@/composables/useDebouncedRef";
|
|||||||
import AlbumGrid from "@/components/Search/AlbumGrid.vue";
|
import AlbumGrid from "@/components/Search/AlbumGrid.vue";
|
||||||
import ArtistGrid from "@/components/Search/ArtistGrid.vue";
|
import ArtistGrid from "@/components/Search/ArtistGrid.vue";
|
||||||
import TracksGrid from "@/components/Search/TracksGrid.vue";
|
import TracksGrid from "@/components/Search/TracksGrid.vue";
|
||||||
import Loader from "@/components/Search/Loader.vue";
|
import Loader from "@/components/shared/Loader.vue";
|
||||||
import Options from "@/components/Search/Options.vue";
|
import Options from "@/components/Search/Options.vue";
|
||||||
import Filters from "@/components/Search/Filters.vue";
|
import Filters from "@/components/Search/Filters.vue";
|
||||||
import "@/assets/css/Search/Search.scss";
|
import "@/assets/css/Search/Search.scss";
|
||||||
@ -138,7 +138,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function loadMoreTracks(start) {
|
function loadMoreTracks(start) {
|
||||||
// scrollSearchThing();
|
scrollSearchThing();
|
||||||
loadMore.loadMoreTracks(start).then((response) => {
|
loadMore.loadMoreTracks(start).then((response) => {
|
||||||
tracks.tracks = [...tracks.tracks, ...response.tracks];
|
tracks.tracks = [...tracks.tracks, ...response.tracks];
|
||||||
tracks.more = response.more;
|
tracks.more = response.more;
|
||||||
|
@ -4,15 +4,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import state from "@/composables/state.js";
|
import state from "@/composables/state.js";
|
||||||
export default {
|
|
||||||
setup() {
|
const loading = state.loading
|
||||||
return {
|
|
||||||
loading: state.loading,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
@ -1,10 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<tr class="songlist-item" :class="{ current: current.trackid === song.trackid }" @dblclick="emitUpdate(song)">
|
<tr
|
||||||
|
class="songlist-item"
|
||||||
|
:class="{ current: current.trackid === song.trackid }"
|
||||||
|
@dblclick="emitUpdate(song)"
|
||||||
|
>
|
||||||
<td class="index">{{ index }}</td>
|
<td class="index">{{ index }}</td>
|
||||||
<td class="flex" @click="emitUpdate(song)">
|
<td class="flex">
|
||||||
<div
|
<div
|
||||||
class="album-art image"
|
class="album-art image"
|
||||||
:style="{ backgroundImage: `url("${song.image}"` }"
|
:style="{ backgroundImage: `url("${song.image}"` }"
|
||||||
|
@click="emitUpdate(song)"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="now-playing-track image"
|
class="now-playing-track image"
|
||||||
@ -12,8 +17,8 @@
|
|||||||
:class="{ active: is_playing, not_active: !is_playing }"
|
:class="{ active: is_playing, not_active: !is_playing }"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div @click="emitUpdate(song)">
|
||||||
<span class="ellip">{{ song.title }}</span>
|
<span class="ellip title">{{ song.title }}</span>
|
||||||
<div class="artist ellip">
|
<div class="artist ellip">
|
||||||
<span v-for="artist in putCommas(song.artists)" :key="artist">
|
<span v-for="artist in putCommas(song.artists)" :key="artist">
|
||||||
{{ artist }}
|
{{ artist }}
|
||||||
@ -35,7 +40,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="song-album">
|
<td class="song-album">
|
||||||
<div class="ellip" @click="emitLoadAlbum(song.album, song.albumartist)">
|
<div
|
||||||
|
class="album ellip"
|
||||||
|
@click="emitLoadAlbum(song.album, song.albumartist)"
|
||||||
|
>
|
||||||
{{ song.album }}
|
{{ song.album }}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@ -94,12 +102,10 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.song-duration {
|
.song-duration {
|
||||||
font-size: .8rem;
|
font-size: 0.8rem;
|
||||||
width: 5rem !important;
|
width: 5rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
.flex {
|
.flex {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-left: 4rem;
|
padding-left: 4rem;
|
||||||
@ -113,13 +119,19 @@ export default {
|
|||||||
margin-right: 1rem;
|
margin-right: 1rem;
|
||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
border-radius: .5rem;
|
border-radius: 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.artist {
|
.artist {
|
||||||
display: none;
|
display: none;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
color: rgba(255, 255, 255, 0.719);
|
color: rgba(255, 255, 255, 0.719);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
@include phone-only {
|
@include phone-only {
|
||||||
display: unset;
|
display: unset;
|
||||||
@ -136,7 +148,7 @@ export default {
|
|||||||
border-radius: $small 0 0 $small;
|
border-radius: $small 0 0 $small;
|
||||||
}
|
}
|
||||||
|
|
||||||
td:nth-child(2){
|
td:nth-child(2) {
|
||||||
border-radius: 0 $small $small 0;
|
border-radius: 0 $small $small 0;
|
||||||
|
|
||||||
@include phone-only {
|
@include phone-only {
|
||||||
@ -194,12 +206,21 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.song-album {
|
.song-album {
|
||||||
|
.album {
|
||||||
|
cursor: pointer;
|
||||||
|
width: max-content;
|
||||||
|
}
|
||||||
|
|
||||||
@include tablet-portrait {
|
@include tablet-portrait {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.song-artists {
|
.song-artists {
|
||||||
|
.artist {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
@include phone-only {
|
@include phone-only {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
@ -1,44 +1,45 @@
|
|||||||
let base_uri = "http://0.0.0.0:9876";
|
import axios from "axios";
|
||||||
|
import state from "./state";
|
||||||
|
|
||||||
const getAlbumTracks = async (name, artist) => {
|
const getAlbumTracks = async (album, artist) => {
|
||||||
const res = await fetch(
|
let data = {};
|
||||||
base_uri +
|
|
||||||
"/album/" +
|
|
||||||
encodeURIComponent(name) + "/" +
|
|
||||||
encodeURIComponent(artist) +
|
|
||||||
"/tracks"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!res.ok) {
|
await axios
|
||||||
const message = `An error has occurred: ${res.status}`;
|
.post(state.settings.uri + "/album/tracks", {
|
||||||
throw new Error(message);
|
album: album,
|
||||||
}
|
artist: artist,
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
data = res.data;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
|
||||||
return await res.json();
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAlbumArtists = async (name, artist) => {
|
const getAlbumArtists = async (album, artist) => {
|
||||||
const res = await fetch(
|
let artists = [];
|
||||||
base_uri +
|
|
||||||
"/album/" +
|
|
||||||
encodeURIComponent(name.replaceAll("/", "|")) +
|
|
||||||
"/" +
|
|
||||||
encodeURIComponent(artist.replaceAll("/", "|")) +
|
|
||||||
"/artists"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!res.ok) {
|
await axios
|
||||||
const message = `An error has occurred: ${res.status}`;
|
.post(state.settings.uri + "/album/artists", {
|
||||||
throw new Error(message);
|
album: album,
|
||||||
}
|
artist: artist,
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
artists = res.data.artists;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
|
||||||
const data = await res.json();
|
return artists;
|
||||||
return data.artists;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAlbumBio = async (name, artist) => {
|
const getAlbumBio = async (name, artist) => {
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
base_uri +
|
state.settings.uri +
|
||||||
"/album/" +
|
"/album/" +
|
||||||
encodeURIComponent(name.replaceAll("/", "|")) +
|
encodeURIComponent(name.replaceAll("/", "|")) +
|
||||||
"/" +
|
"/" +
|
||||||
|
@ -89,7 +89,7 @@ const updateQueue = async (song, type) => {
|
|||||||
list = state.folder_song_list.value;
|
list = state.folder_song_list.value;
|
||||||
break;
|
break;
|
||||||
case "album":
|
case "album":
|
||||||
list = state.album_song_list.value;
|
list = state.album.tracklist;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,24 +5,25 @@ import state from "./state.js";
|
|||||||
|
|
||||||
async function toAlbum(title, artist) {
|
async function toAlbum(title, artist) {
|
||||||
console.log("routing to album");
|
console.log("routing to album");
|
||||||
|
|
||||||
state.loading.value = true;
|
state.loading.value = true;
|
||||||
await album
|
await album
|
||||||
.getAlbumTracks(title, artist)
|
.getAlbumTracks(title, artist)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
state.album_song_list.value = data.songs;
|
state.album.tracklist = data.songs;
|
||||||
state.album_info.value = data.info;
|
state.album.info = data.info;
|
||||||
})
|
})
|
||||||
.then(
|
.then(
|
||||||
await album.getAlbumArtists(title, artist).then((data) => {
|
await album.getAlbumArtists(title, artist).then((data) => {
|
||||||
state.album_artists.value = data;
|
state.album.artists = data;
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.then(
|
.then(
|
||||||
album.getAlbumBio(title, artist).then((data) => {
|
album.getAlbumBio(title, artist).then((data) => {
|
||||||
if (data === "None") {
|
if (data === "None") {
|
||||||
state.album_bio.value = null;
|
state.album.bio = null;
|
||||||
} else {
|
} else {
|
||||||
state.album_bio.value = data;
|
state.album.bio = data;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { ref } from "@vue/reactivity";
|
import { ref } from "@vue/reactivity";
|
||||||
|
import { reactive } from "vue";
|
||||||
|
|
||||||
const search_query = ref("");
|
const search_query = ref("");
|
||||||
|
|
||||||
@ -33,10 +34,12 @@ const prev = ref({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const album_song_list = ref([]);
|
const album = reactive({
|
||||||
const album_info = ref([]);
|
tracklist: [],
|
||||||
const album_artists = ref([]);
|
info: {},
|
||||||
const album_bio = ref("");
|
artists: [],
|
||||||
|
bio: "",
|
||||||
|
});
|
||||||
|
|
||||||
const filters = ref([]);
|
const filters = ref([]);
|
||||||
|
|
||||||
@ -45,9 +48,9 @@ const loading = ref(false);
|
|||||||
|
|
||||||
const is_playing = ref(false);
|
const is_playing = ref(false);
|
||||||
|
|
||||||
const search_tracks = ref([]);
|
const settings = reactive({
|
||||||
const search_albums = ref([]);
|
uri: "http://0.0.0.0:9876",
|
||||||
const search_artists = ref([]);
|
})
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
search_query,
|
search_query,
|
||||||
@ -60,11 +63,6 @@ export default {
|
|||||||
magic_flag,
|
magic_flag,
|
||||||
loading,
|
loading,
|
||||||
is_playing,
|
is_playing,
|
||||||
search_tracks,
|
album,
|
||||||
search_albums,
|
settings,
|
||||||
search_artists,
|
|
||||||
album_song_list,
|
|
||||||
album_info,
|
|
||||||
album_artists,
|
|
||||||
album_bio,
|
|
||||||
};
|
};
|
||||||
|
@ -5,11 +5,6 @@ import router from "./router";
|
|||||||
|
|
||||||
import "../src/assets/css/global.scss";
|
import "../src/assets/css/global.scss";
|
||||||
|
|
||||||
import mitt from "mitt";
|
|
||||||
|
|
||||||
const emitter = mitt();
|
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
app.use(router);
|
app.use(router);
|
||||||
app.provide('emitter', emitter);
|
|
||||||
app.mount('#app');
|
app.mount('#app');
|
@ -1,26 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="al-view rounded">
|
<div class="al-view rounded">
|
||||||
<div>
|
<div>
|
||||||
<Header :album_info="album_info" />
|
<Header :album_info="state.album.info" />
|
||||||
</div>
|
</div>
|
||||||
<div class="separator" id="av-sep"></div>
|
<div class="separator" id="av-sep"></div>
|
||||||
<div class="songs rounded">
|
<div class="songs rounded">
|
||||||
<SongList :songs="album_songs" />
|
<SongList :songs="state.album.tracklist" />
|
||||||
</div>
|
</div>
|
||||||
<div class="separator" id="av-sep"></div>
|
<div class="separator" id="av-sep"></div>
|
||||||
<FeaturedArtists :artists="artists" />
|
<FeaturedArtists :artists="state.album.artists" />
|
||||||
<div v-if="bio">
|
<div v-if="state.album.bio">
|
||||||
<div class="separator" id="av-sep"></div>
|
<div class="separator" id="av-sep"></div>
|
||||||
<AlbumBio :bio="bio" v-if="bio" />
|
<AlbumBio :bio="state.album.bio" v-if="state.album.bio" />
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="separator" id="av-sep"></div> -->
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
import { onMounted } from "@vue/runtime-core";
|
import { onMounted } from "@vue/runtime-core";
|
||||||
import { onUnmounted } from "@vue/runtime-core";
|
|
||||||
import { watch } from "vue";
|
import { watch } from "vue";
|
||||||
import Header from "../components/AlbumView/Header.vue";
|
import Header from "../components/AlbumView/Header.vue";
|
||||||
import AlbumBio from "../components/AlbumView/AlbumBio.vue";
|
import AlbumBio from "../components/AlbumView/AlbumBio.vue";
|
||||||
@ -31,15 +29,10 @@ import FeaturedArtists from "../components/PlaylistView/FeaturedArtists.vue";
|
|||||||
import state from "@/composables/state.js";
|
import state from "@/composables/state.js";
|
||||||
import routeLoader from "@/composables/routeLoader.js";
|
import routeLoader from "@/composables/routeLoader.js";
|
||||||
|
|
||||||
export default {
|
const route = useRoute();
|
||||||
components: {
|
|
||||||
Header,
|
onMounted(() => {
|
||||||
AlbumBio,
|
routeLoader.toAlbum(route.params.album, route.params.artist);
|
||||||
SongList,
|
|
||||||
FeaturedArtists,
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const route = useRoute();
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => route.params,
|
() => route.params,
|
||||||
@ -49,27 +42,7 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
});
|
||||||
onMounted(() => {
|
|
||||||
console.log("mounted");
|
|
||||||
routeLoader.toAlbum(route.params.album, route.params.artist);
|
|
||||||
});
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
state.album_song_list.value = [];
|
|
||||||
state.album_info.value = {};
|
|
||||||
state.album_artists.value = [];
|
|
||||||
state.album_bio.value = "";
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
album_songs: state.album_song_list,
|
|
||||||
album_info: state.album_info,
|
|
||||||
artists: state.album_artists,
|
|
||||||
bio: state.album_bio,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@ -101,13 +101,16 @@ export default {
|
|||||||
|
|
||||||
getDirData(path.value);
|
getDirData(path.value);
|
||||||
|
|
||||||
watch(route, (new_route) => {
|
watch(
|
||||||
path.value = new_route.params.path;
|
() => route.params,
|
||||||
|
() => {
|
||||||
|
path.value = route.params.path;
|
||||||
|
|
||||||
if (!path.value) return;
|
if (!path.value) return;
|
||||||
|
|
||||||
getDirData(path.value);
|
getDirData(path.value);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
function updateQueryString(value) {
|
function updateQueryString(value) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user