mirror of
https://github.com/tcsenpai/swingmusic.git
synced 2025-07-28 13:41:42 +00:00
major refactors
- add album page store - show loaders in beforeEnter guards - show bitrate on now playing card - etc
This commit is contained in:
parent
0c1e792839
commit
dbb27734fe
@ -7,6 +7,7 @@ from app import api
|
||||
from app import helpers, cache
|
||||
from app import functions
|
||||
from app.lib import albumslib, trackslib
|
||||
|
||||
album_bp = Blueprint("album", __name__, url_prefix="")
|
||||
|
||||
|
||||
@ -44,12 +45,18 @@ def get_album_tracks():
|
||||
return {"songs": songs, "info": album}
|
||||
|
||||
|
||||
@album_bp.route("/album/<title>/<artist>/bio")
|
||||
@cache.cached()
|
||||
def get_album_bio(title, artist):
|
||||
@album_bp.route("/album/bio", methods=["POST"])
|
||||
def get_album_bio():
|
||||
"""Returns the album bio for the given album."""
|
||||
bio = functions.get_album_bio(title, artist)
|
||||
return {"bio": bio}, 200
|
||||
data = request.get_json()
|
||||
print(data)
|
||||
|
||||
bio = functions.get_album_bio(data["album"], data["albumartist"])
|
||||
|
||||
if bio is not None:
|
||||
return {"bio": bio}
|
||||
else:
|
||||
return {"bio": "No bio found."}, 404
|
||||
|
||||
|
||||
@album_bp.route("/album/artists", methods=["POST"])
|
||||
|
@ -21,6 +21,7 @@ def get_all_playlists():
|
||||
playlists = []
|
||||
|
||||
for pl in ppp:
|
||||
pl.count = len(pl.tracks)
|
||||
pl.tracks = []
|
||||
playlists.append(pl)
|
||||
|
||||
@ -33,7 +34,7 @@ def create_playlist():
|
||||
|
||||
playlist = {
|
||||
"name": data["name"],
|
||||
"description": [],
|
||||
"description": "",
|
||||
"tracks": [],
|
||||
"count": 0,
|
||||
"lastUpdated": 0,
|
||||
@ -74,6 +75,7 @@ def add_track_to_playlist(playlist_id: str):
|
||||
def get_single_p_info(playlist_id: str):
|
||||
for p in api.PLAYLISTS:
|
||||
if p.playlistid == playlist_id:
|
||||
p.count = len(p.tracks)
|
||||
return {"data": p}
|
||||
|
||||
|
||||
|
@ -4,7 +4,7 @@ Contains all the track routes.
|
||||
|
||||
from flask import Blueprint, send_file
|
||||
|
||||
from app import instances
|
||||
from app import instances, api
|
||||
|
||||
track_bp = Blueprint("track", __name__, url_prefix="/")
|
||||
|
||||
@ -15,7 +15,11 @@ def send_track_file(trackid):
|
||||
Returns an audio file that matches the passed id to the client.
|
||||
"""
|
||||
try:
|
||||
filepath = instances.songs_instance.get_song_by_id(trackid)["filepath"]
|
||||
filepath = [
|
||||
file["filepath"]
|
||||
for file in api.PRE_TRACKS
|
||||
if file["_id"]["$oid"] == trackid
|
||||
][0]
|
||||
return send_file(filepath, mimetype="audio/mp3")
|
||||
except FileNotFoundError:
|
||||
return "File not found", 404
|
||||
|
@ -147,7 +147,7 @@ def use_defaults() -> str:
|
||||
"""
|
||||
Returns a path to a random image in the defaults directory.
|
||||
"""
|
||||
path = str(random.randint(0, 10)) + ".webp"
|
||||
path = "defaults/" + str(random.randint(0, 20)) + ".webp"
|
||||
return path
|
||||
|
||||
|
||||
@ -266,14 +266,14 @@ def parse_album_artist_tag(audio):
|
||||
return albumartist
|
||||
|
||||
|
||||
def parse_album_tag(audio):
|
||||
def parse_album_tag(audio, full_path: str):
|
||||
"""
|
||||
Parses the album tag from an audio file.
|
||||
"""
|
||||
try:
|
||||
album = audio["album"][0]
|
||||
except (KeyError, IndexError):
|
||||
album = "Unknown"
|
||||
album = full_path.split("/")[-1]
|
||||
|
||||
return album
|
||||
|
||||
@ -339,7 +339,7 @@ def get_tags(fullpath: str) -> dict:
|
||||
"artists": parse_artist_tag(audio),
|
||||
"title": parse_title_tag(audio, fullpath),
|
||||
"albumartist": parse_album_artist_tag(audio),
|
||||
"album": parse_album_tag(audio),
|
||||
"album": parse_album_tag(audio, fullpath),
|
||||
"genre": parse_genre_tag(audio),
|
||||
"date": parse_date_tag(audio)[:4],
|
||||
"tracknumber": parse_track_number(audio),
|
||||
@ -365,16 +365,13 @@ def get_album_bio(title: str, albumartist: str):
|
||||
response = requests.get(last_fm_url)
|
||||
data = response.json()
|
||||
except:
|
||||
return "None"
|
||||
return None
|
||||
|
||||
try:
|
||||
bio = data["album"]["wiki"]["summary"].split('<a href="https://www.last.fm/')[0]
|
||||
except KeyError:
|
||||
bio = None
|
||||
|
||||
if bio is None:
|
||||
return "None"
|
||||
|
||||
return bio
|
||||
|
||||
|
||||
|
@ -54,3 +54,4 @@ def get_track_by_id(trackid: str) -> models.Track:
|
||||
for track in api.TRACKS:
|
||||
if track.trackid == trackid:
|
||||
return track
|
||||
|
||||
|
@ -60,19 +60,24 @@ def add_track(filepath: str) -> None:
|
||||
api.TRACKS.append(models.Track(tags))
|
||||
|
||||
folder = folderslib.create_folder(tags["folder"])
|
||||
print(f"💙💙 {tags['folder']}")
|
||||
print(folder)
|
||||
|
||||
if folder not in api.FOLDERS:
|
||||
api.FOLDERS.append(folder)
|
||||
print(f"added folder {folder.path}")
|
||||
|
||||
|
||||
def remove_track(filepath: str) -> None:
|
||||
"""
|
||||
Removes a track from the music dict.
|
||||
"""
|
||||
print(filepath)
|
||||
fpath = filepath.split("/")[-1]
|
||||
|
||||
try:
|
||||
trackid = instances.songs_instance.get_song_by_path(filepath)["_id"]["$oid"]
|
||||
except TypeError:
|
||||
print(f"💙 Watchdog Error: Error removing track {filepath} TypeError")
|
||||
return
|
||||
|
||||
instances.songs_instance.remove_song_by_id(trackid)
|
||||
@ -81,6 +86,10 @@ def remove_track(filepath: str) -> None:
|
||||
if track.trackid == trackid:
|
||||
api.TRACKS.remove(track)
|
||||
|
||||
for folder in api.FOLDERS:
|
||||
if folder.path == filepath.replace(fpath, ""):
|
||||
api.FOLDERS.remove(folder)
|
||||
|
||||
|
||||
class Handler(PatternMatchingEventHandler):
|
||||
files_to_process = []
|
||||
|
@ -69,6 +69,16 @@ class Album:
|
||||
self.image = settings.IMG_THUMB_URI + tags["image"]
|
||||
|
||||
|
||||
def get_p_track(ptrack):
|
||||
for track in api.TRACKS:
|
||||
if (
|
||||
track.title == ptrack["title"]
|
||||
and track.artists == ptrack["artists"]
|
||||
and ptrack["album"] == track.album
|
||||
):
|
||||
return track
|
||||
|
||||
|
||||
def create_playlist_tracks(playlist_tracks: List) -> List[Track]:
|
||||
"""
|
||||
Creates a list of model.Track objects from a list of playlist track dicts.
|
||||
@ -76,13 +86,9 @@ def create_playlist_tracks(playlist_tracks: List) -> List[Track]:
|
||||
tracks: List[Track] = []
|
||||
|
||||
for t in playlist_tracks:
|
||||
for track in api.TRACKS:
|
||||
if (
|
||||
track.title == t["title"]
|
||||
and track.artists == t["artists"]
|
||||
and track.album == t["album"]
|
||||
):
|
||||
tracks.append(track)
|
||||
track = get_p_track(t)
|
||||
if track is not None:
|
||||
tracks.append(track)
|
||||
|
||||
return tracks
|
||||
|
||||
@ -96,8 +102,8 @@ class Playlist:
|
||||
description: str
|
||||
image: str
|
||||
tracks: List[Track]
|
||||
count: int
|
||||
lastUpdated: int
|
||||
count: int = 0
|
||||
"""A list of track objects in the playlist"""
|
||||
|
||||
def __init__(self, data):
|
||||
@ -106,11 +112,9 @@ class Playlist:
|
||||
self.description = data["description"]
|
||||
self.image = ""
|
||||
self.tracks = create_playlist_tracks(data["tracks"])
|
||||
self.count = len(data["tracks"])
|
||||
self.lastUpdated = data["lastUpdated"]
|
||||
|
||||
|
||||
|
||||
@dataclass
|
||||
class Folder:
|
||||
name: str
|
||||
|
@ -1,4 +1,7 @@
|
||||
/home/cwilvx/.cache/pypoetry/virtualenvs/musicx_server-jsG71GtA-py3.8/bin/python manage.py
|
||||
|
||||
ppath=$(poetry run which python)
|
||||
|
||||
$ppath manage.py
|
||||
|
||||
|
||||
#python manage.py
|
||||
|
@ -15,7 +15,8 @@
|
||||
:song="song"
|
||||
:index="index + 1"
|
||||
@updateQueue="updateQueue"
|
||||
@loadAlbum="loadAlbum"
|
||||
:isPlaying="queue.playing"
|
||||
:isCurrent="queue.current.trackid == song.trackid"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -33,7 +34,6 @@ import { useRoute } from "vue-router";
|
||||
|
||||
import SongItem from "../shared/SongItem.vue";
|
||||
|
||||
import routeLoader from "../../composables/routeLoader.js";
|
||||
import state from "../../composables/state";
|
||||
import useQStore from "../../stores/queue";
|
||||
import { Track } from "../../interfaces";
|
||||
@ -43,28 +43,29 @@ const queue = useQStore();
|
||||
const props = defineProps<{
|
||||
tracks: Track[];
|
||||
path?: string;
|
||||
pname?: string;
|
||||
playlistid?: string;
|
||||
}>();
|
||||
|
||||
let route = useRoute().name;
|
||||
console.log(route);
|
||||
const search_query = state.search_query;
|
||||
|
||||
function updateQueue(song: Track) {
|
||||
|
||||
function updateQueue(track: Track) {
|
||||
switch (route) {
|
||||
// check which route the play request come from
|
||||
case "FolderView":
|
||||
queue.playFromFolder(props.path, props.tracks, song);
|
||||
queue.playFromFolder(props.path, props.tracks);
|
||||
queue.play(track);
|
||||
break;
|
||||
case "AlbumView":
|
||||
queue.playFromAlbum(track.album, track.albumartist, props.tracks);
|
||||
queue.play(track);
|
||||
break;
|
||||
case "PlaylistView":
|
||||
queue.playFromPlaylist(props.pname, props.playlistid, props.tracks);
|
||||
queue.play(track);
|
||||
break;
|
||||
}
|
||||
|
||||
// perks.updateQueue(song, type);
|
||||
}
|
||||
|
||||
function loadAlbum(title, albumartist) {
|
||||
routeLoader.toAlbum(title, albumartist);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -2,6 +2,20 @@
|
||||
<div class="info">
|
||||
<div class="desc">
|
||||
<div>
|
||||
<div class="art">
|
||||
<div
|
||||
class="l-image image rounded"
|
||||
:style="{
|
||||
backgroundImage: `url("${track.image}")`,
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
<div id="bitrate">
|
||||
<span v-if="track.bitrate > 330"
|
||||
>FLAC • {{ track.bitrate }}</span
|
||||
>
|
||||
<span v-else>MP3 | {{ track.bitrate }}</span>
|
||||
</div>
|
||||
<div class="title ellip">{{ props.track.title }}</div>
|
||||
<div class="separator no-border"></div>
|
||||
<div class="artists ellip" v-if="props.track.artists[0] !== ''">
|
||||
|
@ -4,15 +4,6 @@
|
||||
<div class="button menu image rounded"></div>
|
||||
<div class="separator no-border"></div>
|
||||
<div>
|
||||
<div class="art">
|
||||
<div
|
||||
class="l-image image rounded"
|
||||
:style="{
|
||||
backgroundImage: `url("${queue.current.image}")`,
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
<div class="separator no-border"></div>
|
||||
<SongCard :track="queue.current" />
|
||||
<Progress :seek="queue.seek" :pos="queue.current_time" />
|
||||
<HotKeys
|
||||
@ -81,6 +72,7 @@ const queue = useQStore();
|
||||
width: 100%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
margin-bottom: $small;
|
||||
|
||||
.l-image {
|
||||
height: 12rem;
|
||||
@ -88,6 +80,18 @@ const queue = useQStore();
|
||||
}
|
||||
}
|
||||
|
||||
#bitrate {
|
||||
position: absolute;
|
||||
font-size: 0.75rem;
|
||||
width: max-content;
|
||||
padding: 0.2rem;
|
||||
top: 13.25rem;
|
||||
left: 1.5rem;
|
||||
background-color: $black;
|
||||
border-radius: $smaller;
|
||||
box-shadow: 0rem 0rem 1rem rgba(0, 0, 0, 0.438);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: 900;
|
||||
}
|
||||
|
@ -58,12 +58,13 @@ export default {
|
||||
|
||||
<style lang="scss">
|
||||
.f-artists {
|
||||
height: 15.5em;
|
||||
height: 14.5em;
|
||||
width: calc(100%);
|
||||
padding: $small;
|
||||
padding-bottom: 0;
|
||||
border-radius: $small;
|
||||
user-select: none;
|
||||
background: linear-gradient(58deg, $gray 0%, rgba(5, 0, 7, 0.5) 100%);
|
||||
background: linear-gradient(0deg, transparent, $black);
|
||||
position: relative;
|
||||
|
||||
.header {
|
||||
@ -75,7 +76,8 @@ export default {
|
||||
.headin {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 900;
|
||||
display: flex;
|
||||
// border: solid;
|
||||
margin-left: $small;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<div class="p-header">
|
||||
<div class="carddd circular">
|
||||
<div class="carddd">
|
||||
<div class="type">❤ Playlist</div>
|
||||
<div class="title ellip">{{ props.info.name }}</div>
|
||||
<div class="desc">
|
||||
{{ props.info.desc[0] }}
|
||||
</div>
|
||||
<div class="duration rounded">4 Tracks • 3 Hours</div>
|
||||
<div class="duration">4 Tracks • 3 Hours</div>
|
||||
<div class="btns">
|
||||
<PlayBtnRect />
|
||||
</div>
|
||||
@ -77,6 +77,7 @@ const props = defineProps<{
|
||||
width: 25rem;
|
||||
padding: 1rem;
|
||||
background-color: rgba(5, 4, 4, 0.829);
|
||||
border-radius: .75rem;
|
||||
|
||||
.type {
|
||||
color: rgba(255, 255, 255, 0.692);
|
||||
@ -85,6 +86,7 @@ const props = defineProps<{
|
||||
.title {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 900;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.desc {
|
||||
@ -105,6 +107,7 @@ const props = defineProps<{
|
||||
padding: $smaller;
|
||||
border: solid 1px $gray1;
|
||||
user-select: none;
|
||||
border-radius: $smaller;
|
||||
}
|
||||
|
||||
.btns {
|
||||
|
@ -14,9 +14,11 @@
|
||||
<p class="title ellip">{{ queue.next.title }}</p>
|
||||
<hr />
|
||||
<p class="artist ellip">
|
||||
<span v-for="artist in putCommas(queue.next.artists)" :key="artist">{{
|
||||
artist
|
||||
}}</span>
|
||||
<span
|
||||
v-for="artist in putCommas(queue.next.artists)"
|
||||
:key="artist"
|
||||
>{{ artist }}</span
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -37,10 +39,11 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import perks from "../../composables/perks.js";
|
||||
import { ref } from "@vue/reactivity";
|
||||
import TrackItem from "../shared/TrackItem.vue";
|
||||
import useQStore from "../../stores/queue";
|
||||
import { Track } from "../../interfaces.js";
|
||||
import { onBeforeMount } from "vue";
|
||||
|
||||
const queue = useQStore();
|
||||
|
||||
const putCommas = perks.putCommas;
|
||||
|
@ -8,6 +8,8 @@
|
||||
v-for="track in props.tracks"
|
||||
:key="track.trackid"
|
||||
:track="track"
|
||||
:isPlaying="queue.playing"
|
||||
:isCurrent="queue.current.trackid == track.trackid"
|
||||
/>
|
||||
</tbody>
|
||||
</table>
|
||||
@ -19,9 +21,10 @@
|
||||
<script setup>
|
||||
import LoadMore from "./LoadMore.vue";
|
||||
import TrackItem from "../shared/TrackItem.vue";
|
||||
import useQStore from "../../stores/queue";
|
||||
|
||||
let counter = 0;
|
||||
|
||||
const queue = useQStore();
|
||||
const props = defineProps({
|
||||
tracks: {
|
||||
type: Object,
|
||||
|
@ -7,7 +7,7 @@
|
||||
<div class="image rounded"></div>
|
||||
<div class="bottom">
|
||||
<div class="name ellip">{{ props.playlist.name }}</div>
|
||||
<div class="count">N Tracks</div>
|
||||
<div class="count">{{ props.playlist.count }} Tracks</div>
|
||||
</div>
|
||||
</router-link>
|
||||
</template>
|
||||
@ -23,21 +23,35 @@ const props = defineProps<{
|
||||
<style lang="scss">
|
||||
.p-card {
|
||||
width: 100%;
|
||||
background-color: $gray;
|
||||
background: $gray;
|
||||
padding: $small;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: $accent;
|
||||
.bottom > .count {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
.image {
|
||||
min-width: 100%;
|
||||
height: 10rem;
|
||||
background-image: url("../../assets/images/eggs.jpg");
|
||||
background-size: auto 10rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
margin-top: $small;
|
||||
|
||||
.name {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.count {
|
||||
font-size: $medium;
|
||||
color: $red;
|
||||
color: $indigo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="songlist-item rounded"
|
||||
:class="[
|
||||
{ current: current.trackid === props.song.trackid },
|
||||
{ 'context-on': context_on },
|
||||
]"
|
||||
:class="[{ current: props.isCurrent }, { 'context-on': context_on }]"
|
||||
@dblclick="emitUpdate(props.song)"
|
||||
@contextmenu="showContextMenu"
|
||||
>
|
||||
@ -17,8 +14,8 @@
|
||||
>
|
||||
<div
|
||||
class="now-playing-track image"
|
||||
v-if="current.trackid === props.song.trackid"
|
||||
:class="{ active: is_playing, not_active: !is_playing }"
|
||||
v-if="props.isPlaying && props.isCurrent"
|
||||
:class="{ active: isPlaying, not_active: !isPlaying }"
|
||||
></div>
|
||||
</div>
|
||||
<div @click="emitUpdate(props.song)">
|
||||
@ -46,14 +43,20 @@
|
||||
<span class="artist">{{ props.song.albumartist }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="song-album">
|
||||
<div
|
||||
class="album ellip"
|
||||
@click="emitLoadAlbum(props.song.album, props.song.albumartist)"
|
||||
>
|
||||
<router-link
|
||||
class="song-album"
|
||||
:to="{
|
||||
name: 'AlbumView',
|
||||
params: {
|
||||
album: props.song.album,
|
||||
artist: props.song.albumartist,
|
||||
},
|
||||
}"
|
||||
>
|
||||
<div class="album ellip">
|
||||
{{ props.song.album }}
|
||||
</div>
|
||||
</div>
|
||||
</router-link>
|
||||
<div class="song-duration">
|
||||
{{ perks.formatSeconds(props.song.length) }}
|
||||
</div>
|
||||
@ -62,7 +65,6 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import perks from "../../composables/perks.js";
|
||||
import state from "../../composables/state";
|
||||
import useContextStore from "../../stores/context";
|
||||
import useModalStore from "../../stores/modal";
|
||||
|
||||
@ -72,7 +74,6 @@ import { Track } from "../../interfaces.js";
|
||||
|
||||
const contextStore = useContextStore();
|
||||
const modalStore = useModalStore();
|
||||
|
||||
const context_on = ref(false);
|
||||
|
||||
const showContextMenu = (e: Event) => {
|
||||
@ -92,23 +93,17 @@ const showContextMenu = (e: Event) => {
|
||||
const props = defineProps<{
|
||||
song: Track;
|
||||
index: Number;
|
||||
isPlaying: Boolean;
|
||||
isCurrent: Boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "updateQueue", song: Track): void;
|
||||
(e: "loadAlbum", album: string, artist: string): void;
|
||||
}>();
|
||||
|
||||
function emitUpdate(track: Track) {
|
||||
emit("updateQueue", track);
|
||||
}
|
||||
|
||||
function emitLoadAlbum(title: string, artist: string) {
|
||||
emit("loadAlbum", title, artist);
|
||||
}
|
||||
|
||||
const is_playing = state.is_playing;
|
||||
const current = state.current;
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -1,63 +0,0 @@
|
||||
import axios from "axios";
|
||||
import state from "./state";
|
||||
|
||||
const getAlbumTracks = async (album, artist) => {
|
||||
let data = {};
|
||||
|
||||
await axios
|
||||
.post(state.settings.uri + "/album/tracks", {
|
||||
album: album,
|
||||
artist: artist,
|
||||
})
|
||||
.then((res) => {
|
||||
data = res.data;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const getAlbumArtists = async (album, artist) => {
|
||||
let artists = [];
|
||||
|
||||
await axios
|
||||
.post(state.settings.uri + "/album/artists", {
|
||||
album: album,
|
||||
artist: artist,
|
||||
})
|
||||
.then((res) => {
|
||||
artists = res.data.artists;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
return artists;
|
||||
};
|
||||
|
||||
const getAlbumBio = async (name, artist) => {
|
||||
const res = await fetch(
|
||||
state.settings.uri +
|
||||
"/album/" +
|
||||
encodeURIComponent(name.replaceAll("/", "|")) +
|
||||
"/" +
|
||||
encodeURIComponent(artist.replaceAll("/", "|")) +
|
||||
"/bio"
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
const message = `An error has occurred: ${res.status}`;
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
return data.bio;
|
||||
};
|
||||
|
||||
export default {
|
||||
getAlbumTracks,
|
||||
getAlbumArtists,
|
||||
getAlbumBio,
|
||||
};
|
65
src/composables/album.ts
Normal file
65
src/composables/album.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import axios from "axios";
|
||||
import state from "./state";
|
||||
import { AlbumInfo, Track } from "../interfaces";
|
||||
|
||||
const getAlbumTracks = async (album: string, artist: string) => {
|
||||
let data = {
|
||||
info: <AlbumInfo>{},
|
||||
tracks: <Track[]>[],
|
||||
};
|
||||
|
||||
await axios
|
||||
.post(state.settings.uri + "/album/tracks", {
|
||||
album: album,
|
||||
artist: artist,
|
||||
})
|
||||
.then((res) => {
|
||||
data.info = res.data.info;
|
||||
data.tracks = res.data.songs;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const getAlbumArtists = async (album, artist) => {
|
||||
let artists = [];
|
||||
|
||||
await axios
|
||||
.post(state.settings.uri + "/album/artists", {
|
||||
album: album,
|
||||
artist: artist,
|
||||
})
|
||||
.then((res) => {
|
||||
artists = res.data.artists;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
return artists;
|
||||
};
|
||||
|
||||
const getAlbumBio = async (album: string, albumartist: string) => {
|
||||
let bio = null;
|
||||
|
||||
await axios
|
||||
.post(state.settings.uri + "/album/bio", {
|
||||
album: album,
|
||||
albumartist: albumartist,
|
||||
})
|
||||
.then((res) => {
|
||||
bio = res.data.bio;
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.response.status === 404) {
|
||||
bio = null;
|
||||
}
|
||||
});
|
||||
|
||||
return bio;
|
||||
};
|
||||
|
||||
export { getAlbumTracks, getAlbumArtists, getAlbumBio };
|
@ -1,47 +0,0 @@
|
||||
import Router from "@/router";
|
||||
|
||||
import album from "./album.js";
|
||||
import state from "./state";
|
||||
|
||||
async function toAlbum(title, artist) {
|
||||
console.log("routing to album");
|
||||
|
||||
state.loading.value = true;
|
||||
await album
|
||||
.getAlbumTracks(title, artist)
|
||||
.then((data) => {
|
||||
state.album.tracklist = data.songs;
|
||||
state.album.info = data.info;
|
||||
})
|
||||
.then(
|
||||
await album.getAlbumArtists(title, artist).then((data) => {
|
||||
state.album.artists = data;
|
||||
})
|
||||
)
|
||||
.then(
|
||||
album.getAlbumBio(title, artist).then((data) => {
|
||||
if (data === "None") {
|
||||
state.album.bio = null;
|
||||
} else {
|
||||
state.album.bio = data;
|
||||
}
|
||||
})
|
||||
)
|
||||
.then(() => {
|
||||
Router.push({
|
||||
name: "AlbumView",
|
||||
params: {
|
||||
album: title,
|
||||
artist: artist,
|
||||
},
|
||||
});
|
||||
state.loading.value = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
toAlbum,
|
||||
};
|
@ -53,6 +53,8 @@ interface Playlist {
|
||||
description?: string;
|
||||
image?: string;
|
||||
tracks?: Track[];
|
||||
count?: number;
|
||||
lastUpdated?: number;
|
||||
}
|
||||
|
||||
interface Notif {
|
||||
|
@ -13,6 +13,8 @@ import SettingsView from "../views/SettingsView.vue";
|
||||
import usePStore from "../stores/playlists";
|
||||
import usePTrackStore from "../stores/p.ptracks";
|
||||
import useFStore from "../stores/folder";
|
||||
import useAStore from "../stores/album";
|
||||
import state from "../composables/state";
|
||||
|
||||
const routes = [
|
||||
{
|
||||
@ -25,8 +27,9 @@ const routes = [
|
||||
name: "FolderView",
|
||||
component: FolderView,
|
||||
beforeEnter: async (to) => {
|
||||
console.log("beforeEnter")
|
||||
state.loading.value = true;
|
||||
await useFStore().fetchAll(to.params.path);
|
||||
state.loading.value = false;
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -38,7 +41,9 @@ const routes = [
|
||||
name: "Playlists",
|
||||
component: Playlists,
|
||||
beforeEnter: async () => {
|
||||
state.loading.value = true;
|
||||
await usePStore().fetchAll();
|
||||
state.loading.value = false;
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -46,7 +51,9 @@ const routes = [
|
||||
name: "PlaylistView",
|
||||
component: PlaylistView,
|
||||
beforeEnter: async (to) => {
|
||||
state.loading.value = true;
|
||||
await usePTrackStore().fetchAll(to.params.pid);
|
||||
state.loading.value = false;
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -58,6 +65,15 @@ const routes = [
|
||||
path: "/albums/:album/:artist",
|
||||
name: "AlbumView",
|
||||
component: AlbumView,
|
||||
beforeEnter: async (to) => {
|
||||
state.loading.value = true;
|
||||
await useAStore().fetchTracksAndArtists(
|
||||
to.params.album,
|
||||
to.params.artist
|
||||
);
|
||||
state.loading.value = false;
|
||||
useAStore().fetchBio(to.params.album, to.params.artist);
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/artists",
|
||||
|
31
src/stores/album.ts
Normal file
31
src/stores/album.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { Track, Artist, AlbumInfo } from "../interfaces";
|
||||
import {
|
||||
getAlbumTracks,
|
||||
getAlbumArtists,
|
||||
getAlbumBio,
|
||||
} from "../composables/album";
|
||||
|
||||
export default defineStore("album", {
|
||||
state: () => ({
|
||||
info: <AlbumInfo>{},
|
||||
tracks: <Track[]>[],
|
||||
artists: <Artist[]>[],
|
||||
bio: null,
|
||||
}),
|
||||
actions: {
|
||||
async fetchTracksAndArtists(title: string, albumartist: string) {
|
||||
const tracks = await getAlbumTracks(title, albumartist);
|
||||
const artists = await getAlbumArtists(title, albumartist);
|
||||
|
||||
this.tracks = tracks.tracks;
|
||||
this.info = tracks.info;
|
||||
this.artists = artists;
|
||||
},
|
||||
fetchBio(title: string, albumartist: string) {
|
||||
getAlbumBio(title, albumartist).then((bio) => {
|
||||
this.bio = bio;
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
@ -64,17 +64,18 @@ export default defineStore("Queue", {
|
||||
progressElem: HTMLElement,
|
||||
audio: new Audio(),
|
||||
current: <Track>{},
|
||||
playing: false,
|
||||
current_time: 0,
|
||||
next: <Track>{},
|
||||
prev: <Track>{},
|
||||
playing: false,
|
||||
current_time: 0,
|
||||
from: <fromFolder>{} || <fromAlbum>{} || <fromPlaylist>{},
|
||||
tracks: <Track[]>[],
|
||||
tracks: <Track[]>[defaultTrack],
|
||||
}),
|
||||
actions: {
|
||||
play(track: Track) {
|
||||
const uri = state.settings.uri + "/file/" + track.trackid;
|
||||
const elem = document.getElementById("progress");
|
||||
this.updateCurrent(track);
|
||||
|
||||
new Promise((resolve, reject) => {
|
||||
this.audio.src = uri;
|
||||
@ -82,8 +83,8 @@ export default defineStore("Queue", {
|
||||
this.audio.onerror = reject;
|
||||
})
|
||||
.then(() => {
|
||||
this.updateCurrent(track);
|
||||
this.audio.play().then(() => {
|
||||
|
||||
this.playing = true;
|
||||
notif(track, this.playPause, this.playNext, this.playPrev);
|
||||
|
||||
@ -172,49 +173,37 @@ export default defineStore("Queue", {
|
||||
this.prev = this.tracks[index - 1];
|
||||
}
|
||||
},
|
||||
setNewQueue(current: Track, tracklist: Track[]) {
|
||||
this.play(current);
|
||||
|
||||
setNewQueue(tracklist: Track[]) {
|
||||
if (this.tracks !== tracklist) {
|
||||
this.tracks = tracklist;
|
||||
addQToLocalStorage(this.from, this.tracks);
|
||||
}
|
||||
},
|
||||
playFromFolder(fpath: string, tracks: Track[], current: Track) {
|
||||
playFromFolder(fpath: string, tracks: Track[]) {
|
||||
this.setNewQueue(tracks);
|
||||
|
||||
this.from = <fromFolder>{
|
||||
type: FromOptions.folder,
|
||||
path: fpath,
|
||||
};
|
||||
|
||||
this.setNewQueue(current, tracks);
|
||||
},
|
||||
playFromAlbum(
|
||||
aname: string,
|
||||
albumartist: string,
|
||||
tracks: Track[],
|
||||
current: Track
|
||||
) {
|
||||
playFromAlbum(aname: string, albumartist: string, tracks: Track[]) {
|
||||
this.setNewQueue(tracks);
|
||||
|
||||
this.from = <fromAlbum>{
|
||||
type: FromOptions.album,
|
||||
name: aname,
|
||||
albumartist: albumartist,
|
||||
};
|
||||
|
||||
this.setNewQueue(current, tracks);
|
||||
},
|
||||
playFromPlaylist(
|
||||
pname: string,
|
||||
pid: string,
|
||||
tracks: Track[],
|
||||
current: Track
|
||||
) {
|
||||
playFromPlaylist(pname: string, pid: string, tracks: Track[]) {
|
||||
this.setNewQueue(tracks);
|
||||
|
||||
this.from = <fromPlaylist>{
|
||||
type: FromOptions.playlist,
|
||||
name: pname,
|
||||
playlistid: pid,
|
||||
};
|
||||
|
||||
this.setNewQueue(current, tracks);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -1,47 +1,39 @@
|
||||
<template>
|
||||
<div class="al-view rounded">
|
||||
<div>
|
||||
<Header :album_info="state.album.info" />
|
||||
<Header :album_info="album.info" />
|
||||
</div>
|
||||
<div class="separator" id="av-sep"></div>
|
||||
<div class="songs rounded">
|
||||
<SongList :songs="state.album.tracklist" />
|
||||
<SongList :tracks="album.tracks" />
|
||||
</div>
|
||||
<div class="separator" id="av-sep"></div>
|
||||
<FeaturedArtists :artists="state.album.artists" />
|
||||
<div v-if="state.album.bio">
|
||||
<FeaturedArtists :artists="album.artists" />
|
||||
<div v-if="album.bio">
|
||||
<div class="separator" id="av-sep"></div>
|
||||
<AlbumBio :bio="state.album.bio" v-if="state.album.bio" />
|
||||
<AlbumBio :bio="album.bio" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRoute } from "vue-router";
|
||||
import { onMounted } from "@vue/runtime-core";
|
||||
import { watch } from "vue";
|
||||
<script setup lang="ts">
|
||||
import Header from "../components/AlbumView/Header.vue";
|
||||
import AlbumBio from "../components/AlbumView/AlbumBio.vue";
|
||||
|
||||
import SongList from "../components/FolderView/SongList.vue";
|
||||
import FeaturedArtists from "../components/PlaylistView/FeaturedArtists.vue";
|
||||
|
||||
import state from "@/composables/state";
|
||||
import routeLoader from "@/composables/routeLoader.js";
|
||||
import useAStore from "../stores/album";
|
||||
import { onBeforeRouteUpdate } from "vue-router";
|
||||
|
||||
const route = useRoute();
|
||||
const album = useAStore();
|
||||
|
||||
onMounted(() => {
|
||||
routeLoader.toAlbum(route.params.album, route.params.artist);
|
||||
|
||||
watch(
|
||||
() => route.params,
|
||||
() => {
|
||||
if (route.name === "AlbumView") {
|
||||
routeLoader.toAlbum(route.params.album, route.params.artist);
|
||||
}
|
||||
}
|
||||
onBeforeRouteUpdate(async (to) => {
|
||||
await album.fetchTracksAndArtists(
|
||||
to.params.album.toString(),
|
||||
to.params.artist.toString()
|
||||
);
|
||||
album.fetchBio(to.params.album.toString(), to.params.artist.toString());
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -4,7 +4,11 @@
|
||||
<div class="separator no-border"></div>
|
||||
|
||||
<div class="songlist rounded">
|
||||
<SongList :songs="playlist.tracks" />
|
||||
<SongList
|
||||
:tracks="playlist.tracks"
|
||||
:pname="info.name"
|
||||
:playlistid="playlist.playlistid"
|
||||
/>
|
||||
</div>
|
||||
<div class="separator no-border"></div>
|
||||
<FeaturedArtists />
|
||||
@ -41,7 +45,7 @@ const info = {
|
||||
}
|
||||
.songlist {
|
||||
padding: $small;
|
||||
min-height: 100%;
|
||||
min-height: calc(100% - 30rem);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
Loading…
x
Reference in New Issue
Block a user