major refactors

- add album page store
- show loaders in beforeEnter guards
- show bitrate on now playing card
- etc
This commit is contained in:
geoffrey45 2022-04-03 01:03:32 +03:00
parent 0c1e792839
commit dbb27734fe
26 changed files with 300 additions and 245 deletions

View File

@ -7,6 +7,7 @@ from app import api
from app import helpers, cache from app import helpers, cache
from app import functions from app import functions
from app.lib import albumslib, trackslib from app.lib import albumslib, trackslib
album_bp = Blueprint("album", __name__, url_prefix="") album_bp = Blueprint("album", __name__, url_prefix="")
@ -44,12 +45,18 @@ def get_album_tracks():
return {"songs": songs, "info": album} return {"songs": songs, "info": album}
@album_bp.route("/album/<title>/<artist>/bio") @album_bp.route("/album/bio", methods=["POST"])
@cache.cached() def get_album_bio():
def get_album_bio(title, artist):
"""Returns the album bio for the given album.""" """Returns the album bio for the given album."""
bio = functions.get_album_bio(title, artist) data = request.get_json()
return {"bio": bio}, 200 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"]) @album_bp.route("/album/artists", methods=["POST"])

View File

@ -21,6 +21,7 @@ def get_all_playlists():
playlists = [] playlists = []
for pl in ppp: for pl in ppp:
pl.count = len(pl.tracks)
pl.tracks = [] pl.tracks = []
playlists.append(pl) playlists.append(pl)
@ -33,7 +34,7 @@ def create_playlist():
playlist = { playlist = {
"name": data["name"], "name": data["name"],
"description": [], "description": "",
"tracks": [], "tracks": [],
"count": 0, "count": 0,
"lastUpdated": 0, "lastUpdated": 0,
@ -74,6 +75,7 @@ def add_track_to_playlist(playlist_id: str):
def get_single_p_info(playlist_id: str): def get_single_p_info(playlist_id: str):
for p in api.PLAYLISTS: for p in api.PLAYLISTS:
if p.playlistid == playlist_id: if p.playlistid == playlist_id:
p.count = len(p.tracks)
return {"data": p} return {"data": p}

View File

@ -4,7 +4,7 @@ Contains all the track routes.
from flask import Blueprint, send_file from flask import Blueprint, send_file
from app import instances from app import instances, api
track_bp = Blueprint("track", __name__, url_prefix="/") 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. Returns an audio file that matches the passed id to the client.
""" """
try: 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") return send_file(filepath, mimetype="audio/mp3")
except FileNotFoundError: except FileNotFoundError:
return "File not found", 404 return "File not found", 404

View File

@ -147,7 +147,7 @@ def use_defaults() -> str:
""" """
Returns a path to a random image in the defaults directory. 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 return path
@ -266,14 +266,14 @@ def parse_album_artist_tag(audio):
return albumartist return albumartist
def parse_album_tag(audio): def parse_album_tag(audio, full_path: str):
""" """
Parses the album tag from an audio file. Parses the album tag from an audio file.
""" """
try: try:
album = audio["album"][0] album = audio["album"][0]
except (KeyError, IndexError): except (KeyError, IndexError):
album = "Unknown" album = full_path.split("/")[-1]
return album return album
@ -339,7 +339,7 @@ def get_tags(fullpath: str) -> dict:
"artists": parse_artist_tag(audio), "artists": parse_artist_tag(audio),
"title": parse_title_tag(audio, fullpath), "title": parse_title_tag(audio, fullpath),
"albumartist": parse_album_artist_tag(audio), "albumartist": parse_album_artist_tag(audio),
"album": parse_album_tag(audio), "album": parse_album_tag(audio, fullpath),
"genre": parse_genre_tag(audio), "genre": parse_genre_tag(audio),
"date": parse_date_tag(audio)[:4], "date": parse_date_tag(audio)[:4],
"tracknumber": parse_track_number(audio), "tracknumber": parse_track_number(audio),
@ -365,16 +365,13 @@ def get_album_bio(title: str, albumartist: str):
response = requests.get(last_fm_url) response = requests.get(last_fm_url)
data = response.json() data = response.json()
except: except:
return "None" return None
try: try:
bio = data["album"]["wiki"]["summary"].split('<a href="https://www.last.fm/')[0] bio = data["album"]["wiki"]["summary"].split('<a href="https://www.last.fm/')[0]
except KeyError: except KeyError:
bio = None bio = None
if bio is None:
return "None"
return bio return bio

View File

@ -54,3 +54,4 @@ def get_track_by_id(trackid: str) -> models.Track:
for track in api.TRACKS: for track in api.TRACKS:
if track.trackid == trackid: if track.trackid == trackid:
return track return track

View File

@ -60,19 +60,24 @@ def add_track(filepath: str) -> None:
api.TRACKS.append(models.Track(tags)) api.TRACKS.append(models.Track(tags))
folder = folderslib.create_folder(tags["folder"]) folder = folderslib.create_folder(tags["folder"])
print(f"💙💙 {tags['folder']}")
print(folder)
if folder not in api.FOLDERS: if folder not in api.FOLDERS:
api.FOLDERS.append(folder) api.FOLDERS.append(folder)
print(f"added folder {folder.path}")
def remove_track(filepath: str) -> None: def remove_track(filepath: str) -> None:
""" """
Removes a track from the music dict. Removes a track from the music dict.
""" """
print(filepath) fpath = filepath.split("/")[-1]
try: try:
trackid = instances.songs_instance.get_song_by_path(filepath)["_id"]["$oid"] trackid = instances.songs_instance.get_song_by_path(filepath)["_id"]["$oid"]
except TypeError: except TypeError:
print(f"💙 Watchdog Error: Error removing track {filepath} TypeError")
return return
instances.songs_instance.remove_song_by_id(trackid) instances.songs_instance.remove_song_by_id(trackid)
@ -81,6 +86,10 @@ def remove_track(filepath: str) -> None:
if track.trackid == trackid: if track.trackid == trackid:
api.TRACKS.remove(track) api.TRACKS.remove(track)
for folder in api.FOLDERS:
if folder.path == filepath.replace(fpath, ""):
api.FOLDERS.remove(folder)
class Handler(PatternMatchingEventHandler): class Handler(PatternMatchingEventHandler):
files_to_process = [] files_to_process = []

View File

@ -69,6 +69,16 @@ class Album:
self.image = settings.IMG_THUMB_URI + tags["image"] 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]: def create_playlist_tracks(playlist_tracks: List) -> List[Track]:
""" """
Creates a list of model.Track objects from a list of playlist track dicts. Creates a list of model.Track objects from a list of playlist track dicts.
@ -76,12 +86,8 @@ def create_playlist_tracks(playlist_tracks: List) -> List[Track]:
tracks: List[Track] = [] tracks: List[Track] = []
for t in playlist_tracks: for t in playlist_tracks:
for track in api.TRACKS: track = get_p_track(t)
if ( if track is not None:
track.title == t["title"]
and track.artists == t["artists"]
and track.album == t["album"]
):
tracks.append(track) tracks.append(track)
return tracks return tracks
@ -96,8 +102,8 @@ class Playlist:
description: str description: str
image: str image: str
tracks: List[Track] tracks: List[Track]
count: int
lastUpdated: int lastUpdated: int
count: int = 0
"""A list of track objects in the playlist""" """A list of track objects in the playlist"""
def __init__(self, data): def __init__(self, data):
@ -106,11 +112,9 @@ class Playlist:
self.description = data["description"] self.description = data["description"]
self.image = "" self.image = ""
self.tracks = create_playlist_tracks(data["tracks"]) self.tracks = create_playlist_tracks(data["tracks"])
self.count = len(data["tracks"])
self.lastUpdated = data["lastUpdated"] self.lastUpdated = data["lastUpdated"]
@dataclass @dataclass
class Folder: class Folder:
name: str name: str

View File

@ -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 #python manage.py

View File

@ -15,7 +15,8 @@
:song="song" :song="song"
:index="index + 1" :index="index + 1"
@updateQueue="updateQueue" @updateQueue="updateQueue"
@loadAlbum="loadAlbum" :isPlaying="queue.playing"
:isCurrent="queue.current.trackid == song.trackid"
/> />
</div> </div>
</div> </div>
@ -33,7 +34,6 @@ import { useRoute } from "vue-router";
import SongItem from "../shared/SongItem.vue"; import SongItem from "../shared/SongItem.vue";
import routeLoader from "../../composables/routeLoader.js";
import state from "../../composables/state"; import state from "../../composables/state";
import useQStore from "../../stores/queue"; import useQStore from "../../stores/queue";
import { Track } from "../../interfaces"; import { Track } from "../../interfaces";
@ -43,28 +43,29 @@ const queue = useQStore();
const props = defineProps<{ const props = defineProps<{
tracks: Track[]; tracks: Track[];
path?: string; path?: string;
pname?: string;
playlistid?: string;
}>(); }>();
let route = useRoute().name; let route = useRoute().name;
console.log(route);
const search_query = state.search_query; const search_query = state.search_query;
function updateQueue(song: Track) { function updateQueue(track: Track) {
switch (route) { switch (route) {
// check which route the play request come from // check which route the play request come from
case "FolderView": case "FolderView":
queue.playFromFolder(props.path, props.tracks, song); queue.playFromFolder(props.path, props.tracks);
queue.play(track);
break; break;
case "AlbumView": 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; break;
} }
// perks.updateQueue(song, type);
}
function loadAlbum(title, albumartist) {
routeLoader.toAlbum(title, albumartist);
} }
</script> </script>

View File

@ -2,6 +2,20 @@
<div class="info"> <div class="info">
<div class="desc"> <div class="desc">
<div> <div>
<div class="art">
<div
class="l-image image rounded"
:style="{
backgroundImage: `url(&quot;${track.image}&quot;)`,
}"
></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="title ellip">{{ props.track.title }}</div>
<div class="separator no-border"></div> <div class="separator no-border"></div>
<div class="artists ellip" v-if="props.track.artists[0] !== ''"> <div class="artists ellip" v-if="props.track.artists[0] !== ''">

View File

@ -4,15 +4,6 @@
<div class="button menu image rounded"></div> <div class="button menu image rounded"></div>
<div class="separator no-border"></div> <div class="separator no-border"></div>
<div> <div>
<div class="art">
<div
class="l-image image rounded"
:style="{
backgroundImage: `url(&quot;${queue.current.image}&quot;)`,
}"
></div>
</div>
<div class="separator no-border"></div>
<SongCard :track="queue.current" /> <SongCard :track="queue.current" />
<Progress :seek="queue.seek" :pos="queue.current_time" /> <Progress :seek="queue.seek" :pos="queue.current_time" />
<HotKeys <HotKeys
@ -81,6 +72,7 @@ const queue = useQStore();
width: 100%; width: 100%;
display: grid; display: grid;
place-items: center; place-items: center;
margin-bottom: $small;
.l-image { .l-image {
height: 12rem; 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 { .title {
font-weight: 900; font-weight: 900;
} }

View File

@ -58,12 +58,13 @@ export default {
<style lang="scss"> <style lang="scss">
.f-artists { .f-artists {
height: 15.5em; height: 14.5em;
width: calc(100%); width: calc(100%);
padding: $small; padding: $small;
padding-bottom: 0;
border-radius: $small; border-radius: $small;
user-select: none; user-select: none;
background: linear-gradient(58deg, $gray 0%, rgba(5, 0, 7, 0.5) 100%); background: linear-gradient(0deg, transparent, $black);
position: relative; position: relative;
.header { .header {
@ -75,7 +76,8 @@ export default {
.headin { .headin {
font-size: 1.5rem; font-size: 1.5rem;
font-weight: 900; font-weight: 900;
display: flex; // border: solid;
margin-left: $small;
} }
} }
} }

View File

@ -1,12 +1,12 @@
<template> <template>
<div class="p-header"> <div class="p-header">
<div class="carddd circular"> <div class="carddd">
<div class="type"> Playlist</div> <div class="type"> Playlist</div>
<div class="title ellip">{{ props.info.name }}</div> <div class="title ellip">{{ props.info.name }}</div>
<div class="desc"> <div class="desc">
{{ props.info.desc[0] }} {{ props.info.desc[0] }}
</div> </div>
<div class="duration rounded">4 Tracks 3 Hours</div> <div class="duration">4 Tracks 3 Hours</div>
<div class="btns"> <div class="btns">
<PlayBtnRect /> <PlayBtnRect />
</div> </div>
@ -77,6 +77,7 @@ const props = defineProps<{
width: 25rem; width: 25rem;
padding: 1rem; padding: 1rem;
background-color: rgba(5, 4, 4, 0.829); background-color: rgba(5, 4, 4, 0.829);
border-radius: .75rem;
.type { .type {
color: rgba(255, 255, 255, 0.692); color: rgba(255, 255, 255, 0.692);
@ -85,6 +86,7 @@ const props = defineProps<{
.title { .title {
font-size: 2.5rem; font-size: 2.5rem;
font-weight: 900; font-weight: 900;
text-transform: capitalize;
} }
.desc { .desc {
@ -105,6 +107,7 @@ const props = defineProps<{
padding: $smaller; padding: $smaller;
border: solid 1px $gray1; border: solid 1px $gray1;
user-select: none; user-select: none;
border-radius: $smaller;
} }
.btns { .btns {

View File

@ -14,9 +14,11 @@
<p class="title ellip">{{ queue.next.title }}</p> <p class="title ellip">{{ queue.next.title }}</p>
<hr /> <hr />
<p class="artist ellip"> <p class="artist ellip">
<span v-for="artist in putCommas(queue.next.artists)" :key="artist">{{ <span
artist v-for="artist in putCommas(queue.next.artists)"
}}</span> :key="artist"
>{{ artist }}</span
>
</p> </p>
</div> </div>
</div> </div>
@ -37,10 +39,11 @@
<script setup lang="ts"> <script setup lang="ts">
import perks from "../../composables/perks.js"; import perks from "../../composables/perks.js";
import { ref } from "@vue/reactivity";
import TrackItem from "../shared/TrackItem.vue"; import TrackItem from "../shared/TrackItem.vue";
import useQStore from "../../stores/queue"; import useQStore from "../../stores/queue";
import { Track } from "../../interfaces.js"; import { Track } from "../../interfaces.js";
import { onBeforeMount } from "vue";
const queue = useQStore(); const queue = useQStore();
const putCommas = perks.putCommas; const putCommas = perks.putCommas;

View File

@ -8,6 +8,8 @@
v-for="track in props.tracks" v-for="track in props.tracks"
:key="track.trackid" :key="track.trackid"
:track="track" :track="track"
:isPlaying="queue.playing"
:isCurrent="queue.current.trackid == track.trackid"
/> />
</tbody> </tbody>
</table> </table>
@ -19,9 +21,10 @@
<script setup> <script setup>
import LoadMore from "./LoadMore.vue"; import LoadMore from "./LoadMore.vue";
import TrackItem from "../shared/TrackItem.vue"; import TrackItem from "../shared/TrackItem.vue";
import useQStore from "../../stores/queue";
let counter = 0; let counter = 0;
const queue = useQStore();
const props = defineProps({ const props = defineProps({
tracks: { tracks: {
type: Object, type: Object,

View File

@ -7,7 +7,7 @@
<div class="image rounded"></div> <div class="image rounded"></div>
<div class="bottom"> <div class="bottom">
<div class="name ellip">{{ props.playlist.name }}</div> <div class="name ellip">{{ props.playlist.name }}</div>
<div class="count">N Tracks</div> <div class="count">{{ props.playlist.count }} Tracks</div>
</div> </div>
</router-link> </router-link>
</template> </template>
@ -23,21 +23,35 @@ const props = defineProps<{
<style lang="scss"> <style lang="scss">
.p-card { .p-card {
width: 100%; width: 100%;
background-color: $gray; background: $gray;
padding: $small; padding: $small;
transition: all 0.2s ease;
&:hover {
background-color: $accent;
.bottom > .count {
color: $white;
}
}
.image { .image {
min-width: 100%; min-width: 100%;
height: 10rem; height: 10rem;
background-image: url("../../assets/images/eggs.jpg"); background-image: url("../../assets/images/eggs.jpg");
background-size: auto 10rem;
transition: all 0.2s ease;
} }
.bottom { .bottom {
margin-top: $small; margin-top: $small;
.name {
text-transform: capitalize;
}
.count { .count {
font-size: $medium; font-size: $medium;
color: $red; color: $indigo;
} }
} }
} }

View File

@ -1,10 +1,7 @@
<template> <template>
<div <div
class="songlist-item rounded" class="songlist-item rounded"
:class="[ :class="[{ current: props.isCurrent }, { 'context-on': context_on }]"
{ current: current.trackid === props.song.trackid },
{ 'context-on': context_on },
]"
@dblclick="emitUpdate(props.song)" @dblclick="emitUpdate(props.song)"
@contextmenu="showContextMenu" @contextmenu="showContextMenu"
> >
@ -17,8 +14,8 @@
> >
<div <div
class="now-playing-track image" class="now-playing-track image"
v-if="current.trackid === props.song.trackid" v-if="props.isPlaying && props.isCurrent"
:class="{ active: is_playing, not_active: !is_playing }" :class="{ active: isPlaying, not_active: !isPlaying }"
></div> ></div>
</div> </div>
<div @click="emitUpdate(props.song)"> <div @click="emitUpdate(props.song)">
@ -46,14 +43,20 @@
<span class="artist">{{ props.song.albumartist }}</span> <span class="artist">{{ props.song.albumartist }}</span>
</div> </div>
</div> </div>
<div class="song-album"> <router-link
<div class="song-album"
class="album ellip" :to="{
@click="emitLoadAlbum(props.song.album, props.song.albumartist)" name: 'AlbumView',
params: {
album: props.song.album,
artist: props.song.albumartist,
},
}"
> >
<div class="album ellip">
{{ props.song.album }} {{ props.song.album }}
</div> </div>
</div> </router-link>
<div class="song-duration"> <div class="song-duration">
{{ perks.formatSeconds(props.song.length) }} {{ perks.formatSeconds(props.song.length) }}
</div> </div>
@ -62,7 +65,6 @@
<script setup lang="ts"> <script setup lang="ts">
import perks from "../../composables/perks.js"; import perks from "../../composables/perks.js";
import state from "../../composables/state";
import useContextStore from "../../stores/context"; import useContextStore from "../../stores/context";
import useModalStore from "../../stores/modal"; import useModalStore from "../../stores/modal";
@ -72,7 +74,6 @@ import { Track } from "../../interfaces.js";
const contextStore = useContextStore(); const contextStore = useContextStore();
const modalStore = useModalStore(); const modalStore = useModalStore();
const context_on = ref(false); const context_on = ref(false);
const showContextMenu = (e: Event) => { const showContextMenu = (e: Event) => {
@ -92,23 +93,17 @@ const showContextMenu = (e: Event) => {
const props = defineProps<{ const props = defineProps<{
song: Track; song: Track;
index: Number; index: Number;
isPlaying: Boolean;
isCurrent: Boolean;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
(e: "updateQueue", song: Track): void; (e: "updateQueue", song: Track): void;
(e: "loadAlbum", album: string, artist: string): void;
}>(); }>();
function emitUpdate(track: Track) { function emitUpdate(track: Track) {
emit("updateQueue", 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> </script>
<style lang="scss"> <style lang="scss">

View File

@ -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
View 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 };

View File

@ -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,
};

View File

@ -53,6 +53,8 @@ interface Playlist {
description?: string; description?: string;
image?: string; image?: string;
tracks?: Track[]; tracks?: Track[];
count?: number;
lastUpdated?: number;
} }
interface Notif { interface Notif {

View File

@ -13,6 +13,8 @@ import SettingsView from "../views/SettingsView.vue";
import usePStore from "../stores/playlists"; import usePStore from "../stores/playlists";
import usePTrackStore from "../stores/p.ptracks"; import usePTrackStore from "../stores/p.ptracks";
import useFStore from "../stores/folder"; import useFStore from "../stores/folder";
import useAStore from "../stores/album";
import state from "../composables/state";
const routes = [ const routes = [
{ {
@ -25,8 +27,9 @@ const routes = [
name: "FolderView", name: "FolderView",
component: FolderView, component: FolderView,
beforeEnter: async (to) => { beforeEnter: async (to) => {
console.log("beforeEnter") state.loading.value = true;
await useFStore().fetchAll(to.params.path); await useFStore().fetchAll(to.params.path);
state.loading.value = false;
}, },
}, },
{ {
@ -38,7 +41,9 @@ const routes = [
name: "Playlists", name: "Playlists",
component: Playlists, component: Playlists,
beforeEnter: async () => { beforeEnter: async () => {
state.loading.value = true;
await usePStore().fetchAll(); await usePStore().fetchAll();
state.loading.value = false;
}, },
}, },
{ {
@ -46,7 +51,9 @@ const routes = [
name: "PlaylistView", name: "PlaylistView",
component: PlaylistView, component: PlaylistView,
beforeEnter: async (to) => { beforeEnter: async (to) => {
state.loading.value = true;
await usePTrackStore().fetchAll(to.params.pid); await usePTrackStore().fetchAll(to.params.pid);
state.loading.value = false;
}, },
}, },
{ {
@ -58,6 +65,15 @@ const routes = [
path: "/albums/:album/:artist", path: "/albums/:album/:artist",
name: "AlbumView", name: "AlbumView",
component: 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", path: "/artists",

31
src/stores/album.ts Normal file
View 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;
});
},
},
});

View File

@ -64,17 +64,18 @@ export default defineStore("Queue", {
progressElem: HTMLElement, progressElem: HTMLElement,
audio: new Audio(), audio: new Audio(),
current: <Track>{}, current: <Track>{},
playing: false,
current_time: 0,
next: <Track>{}, next: <Track>{},
prev: <Track>{}, prev: <Track>{},
playing: false,
current_time: 0,
from: <fromFolder>{} || <fromAlbum>{} || <fromPlaylist>{}, from: <fromFolder>{} || <fromAlbum>{} || <fromPlaylist>{},
tracks: <Track[]>[], tracks: <Track[]>[defaultTrack],
}), }),
actions: { actions: {
play(track: Track) { play(track: Track) {
const uri = state.settings.uri + "/file/" + track.trackid; const uri = state.settings.uri + "/file/" + track.trackid;
const elem = document.getElementById("progress"); const elem = document.getElementById("progress");
this.updateCurrent(track);
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
this.audio.src = uri; this.audio.src = uri;
@ -82,8 +83,8 @@ export default defineStore("Queue", {
this.audio.onerror = reject; this.audio.onerror = reject;
}) })
.then(() => { .then(() => {
this.updateCurrent(track);
this.audio.play().then(() => { this.audio.play().then(() => {
this.playing = true; this.playing = true;
notif(track, this.playPause, this.playNext, this.playPrev); notif(track, this.playPause, this.playNext, this.playPrev);
@ -172,49 +173,37 @@ export default defineStore("Queue", {
this.prev = this.tracks[index - 1]; this.prev = this.tracks[index - 1];
} }
}, },
setNewQueue(current: Track, tracklist: Track[]) { setNewQueue(tracklist: Track[]) {
this.play(current);
if (this.tracks !== tracklist) { if (this.tracks !== tracklist) {
this.tracks = tracklist; this.tracks = tracklist;
addQToLocalStorage(this.from, this.tracks); addQToLocalStorage(this.from, this.tracks);
} }
}, },
playFromFolder(fpath: string, tracks: Track[], current: Track) { playFromFolder(fpath: string, tracks: Track[]) {
this.setNewQueue(tracks);
this.from = <fromFolder>{ this.from = <fromFolder>{
type: FromOptions.folder, type: FromOptions.folder,
path: fpath, path: fpath,
}; };
this.setNewQueue(current, tracks);
}, },
playFromAlbum( playFromAlbum(aname: string, albumartist: string, tracks: Track[]) {
aname: string, this.setNewQueue(tracks);
albumartist: string,
tracks: Track[],
current: Track
) {
this.from = <fromAlbum>{ this.from = <fromAlbum>{
type: FromOptions.album, type: FromOptions.album,
name: aname, name: aname,
albumartist: albumartist, albumartist: albumartist,
}; };
this.setNewQueue(current, tracks);
}, },
playFromPlaylist( playFromPlaylist(pname: string, pid: string, tracks: Track[]) {
pname: string, this.setNewQueue(tracks);
pid: string,
tracks: Track[],
current: Track
) {
this.from = <fromPlaylist>{ this.from = <fromPlaylist>{
type: FromOptions.playlist, type: FromOptions.playlist,
name: pname, name: pname,
playlistid: pid, playlistid: pid,
}; };
this.setNewQueue(current, tracks);
}, },
}, },
}); });

View File

@ -1,47 +1,39 @@
<template> <template>
<div class="al-view rounded"> <div class="al-view rounded">
<div> <div>
<Header :album_info="state.album.info" /> <Header :album_info="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="state.album.tracklist" /> <SongList :tracks="album.tracks" />
</div> </div>
<div class="separator" id="av-sep"></div> <div class="separator" id="av-sep"></div>
<FeaturedArtists :artists="state.album.artists" /> <FeaturedArtists :artists="album.artists" />
<div v-if="state.album.bio"> <div v-if="album.bio">
<div class="separator" id="av-sep"></div> <div class="separator" id="av-sep"></div>
<AlbumBio :bio="state.album.bio" v-if="state.album.bio" /> <AlbumBio :bio="album.bio" />
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup lang="ts">
import { useRoute } from "vue-router";
import { onMounted } from "@vue/runtime-core";
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";
import SongList from "../components/FolderView/SongList.vue"; import SongList from "../components/FolderView/SongList.vue";
import FeaturedArtists from "../components/PlaylistView/FeaturedArtists.vue"; import FeaturedArtists from "../components/PlaylistView/FeaturedArtists.vue";
import state from "@/composables/state"; import useAStore from "../stores/album";
import routeLoader from "@/composables/routeLoader.js"; import { onBeforeRouteUpdate } from "vue-router";
const route = useRoute(); const album = useAStore();
onMounted(() => { onBeforeRouteUpdate(async (to) => {
routeLoader.toAlbum(route.params.album, route.params.artist); await album.fetchTracksAndArtists(
to.params.album.toString(),
watch( to.params.artist.toString()
() => route.params,
() => {
if (route.name === "AlbumView") {
routeLoader.toAlbum(route.params.album, route.params.artist);
}
}
); );
album.fetchBio(to.params.album.toString(), to.params.artist.toString());
}); });
</script> </script>

View File

@ -4,7 +4,11 @@
<div class="separator no-border"></div> <div class="separator no-border"></div>
<div class="songlist rounded"> <div class="songlist rounded">
<SongList :songs="playlist.tracks" /> <SongList
:tracks="playlist.tracks"
:pname="info.name"
:playlistid="playlist.playlistid"
/>
</div> </div>
<div class="separator no-border"></div> <div class="separator no-border"></div>
<FeaturedArtists /> <FeaturedArtists />
@ -41,7 +45,7 @@ const info = {
} }
.songlist { .songlist {
padding: $small; padding: $small;
min-height: 100%; min-height: calc(100% - 30rem);
} }
} }
</style> </style>