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
+13 -12
View File
@@ -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>
+14
View File
@@ -2,6 +2,20 @@
<div class="info">
<div class="desc">
<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="separator no-border"></div>
<div class="artists ellip" v-if="props.track.artists[0] !== ''">
+13 -9
View File
@@ -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(&quot;${queue.current.image}&quot;)`,
}"
></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;
}
}
}
+5 -2
View File
@@ -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 {
+7 -4
View File
@@ -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;
+4 -1
View File
@@ -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,
+17 -3
View File
@@ -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;
}
}
}
+17 -22
View File
@@ -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">
-63
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
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 };
-47
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,
};
+2
View File
@@ -53,6 +53,8 @@ interface Playlist {
description?: string;
image?: string;
tracks?: Track[];
count?: number;
lastUpdated?: number;
}
interface Notif {
+17 -1
View File
@@ -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
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;
});
},
},
});
+15 -26
View File
@@ -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);
},
},
});
+14 -22
View File
@@ -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>
+6 -2
View File
@@ -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>