mirror of
https://github.com/tcsenpai/swingmusic.git
synced 2025-07-28 21:51:41 +00:00
show albums from artist at the bottom of album page
+ add a testing genres strip in album page + misc refactors
This commit is contained in:
parent
da852e72f3
commit
4a49d48011
51
src/components/AlbumView/ArtistAlbums.vue
Normal file
51
src/components/AlbumView/ArtistAlbums.vue
Normal file
@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div class="albums-from-artist">
|
||||
<h3>
|
||||
<span>More from {{ artist.artist }} </span>
|
||||
<span class="see-more">SEE ALL</span>
|
||||
</h3>
|
||||
<div class="cards">
|
||||
<AlbumCard v-for="a in artist.albums" :album="a" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import AlbumCard from "../shared/AlbumCard.vue";
|
||||
|
||||
import useAlbumStore from "@/stores/pages/album";
|
||||
import { content_width } from "@/stores/content-width";
|
||||
import { computed, onBeforeMount } from "vue";
|
||||
import { AlbumInfo } from "@/interfaces";
|
||||
|
||||
defineProps<{
|
||||
artist: {
|
||||
artist: string;
|
||||
albums: AlbumInfo[];
|
||||
};
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.albums-from-artist {
|
||||
overflow: hidden;
|
||||
padding-top: 1rem;
|
||||
|
||||
h3 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr max-content;
|
||||
align-items: center;
|
||||
padding: 0 $medium;
|
||||
|
||||
.see-more {
|
||||
font-size: $medium;
|
||||
}
|
||||
}
|
||||
|
||||
.cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
|
||||
gap: 2rem 0
|
||||
}
|
||||
}
|
||||
</style>
|
50
src/components/AlbumView/GenreBanner.vue
Normal file
50
src/components/AlbumView/GenreBanner.vue
Normal file
@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<div class="genres-banner">
|
||||
<div v-for="genre in genres" class="rounded pad-sm">{{ genre }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from "vue";
|
||||
import useAlbumStore from "@/stores/pages/album";
|
||||
|
||||
const genres = ["Genres", "RNB", "Alternative", "Genres", "RNB", "Alternative"];
|
||||
|
||||
onMounted(() => {
|
||||
// onMounted, fetch data to be used in the component below this one.
|
||||
const album = useAlbumStore();
|
||||
album.fetchArtistAlbums();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.genres-banner {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-top: 1rem;
|
||||
font-size: 0.9rem;
|
||||
padding-left: $medium;
|
||||
text-transform: capitalize;
|
||||
user-select: none;
|
||||
|
||||
div {
|
||||
background-color: $gray5;
|
||||
min-width: 4rem;
|
||||
text-align: center;
|
||||
outline: solid 1px $gray3;
|
||||
padding: $small 1rem;
|
||||
|
||||
&:first-child {
|
||||
background-color: white;
|
||||
color: black;
|
||||
outline-color: white;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $darkestblue;
|
||||
outline-color: $darkestblue;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -49,6 +49,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@ -59,7 +60,7 @@ import { albumHeaderSmall } from "@/stores/content-width";
|
||||
import useNavStore from "@/stores/nav";
|
||||
import useAlbumStore from "@/stores/pages/album";
|
||||
import { formatSeconds, useVisibility } from "@/utils";
|
||||
import { getButtonColor, isLight } from "../../composables/colors/album";
|
||||
import { isLight } from "../../composables/colors/album";
|
||||
import { playSources } from "../../composables/enums";
|
||||
import { AlbumInfo } from "../../interfaces";
|
||||
|
||||
@ -73,6 +74,7 @@ const albumheaderthing = ref<any>(null);
|
||||
const imguri = paths.images;
|
||||
const nav = useNavStore();
|
||||
|
||||
|
||||
/**
|
||||
* Calls the `toggleShowPlay` method which toggles the play button in the nav.
|
||||
* Emits the `resetBottomPadding` event to reset the album page content bottom padding.
|
||||
@ -87,6 +89,8 @@ useVisibility(albumheaderthing, handleVisibilityState);
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
|
||||
.a-header {
|
||||
display: grid;
|
||||
grid-template-columns: max-content 1fr;
|
||||
|
@ -40,7 +40,7 @@ function getSource() {
|
||||
location: {
|
||||
name: Routes.album,
|
||||
params: {
|
||||
hash: source.hash,
|
||||
hash: source.albumhash,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -2,7 +2,7 @@
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'AlbumView',
|
||||
params: { hash: album.hash },
|
||||
params: { hash: album.albumhash },
|
||||
}"
|
||||
class="result-item"
|
||||
>
|
||||
|
@ -149,6 +149,8 @@ function showMenu(e: MouseEvent) {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
height: 3rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
.text {
|
||||
opacity: 1;
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { paths } from "@/config";
|
||||
import state from "../state";
|
||||
import { NotifType, useNotifStore } from "@/stores/notification";
|
||||
import { AlbumInfo, Track } from "../../interfaces";
|
||||
import useAxios from "./useAxios";
|
||||
import { NotifType, useNotifStore } from "@/stores/notification";
|
||||
|
||||
const {
|
||||
album: albumUrl,
|
||||
albumartists: albumArtistsUrl,
|
||||
albumbio: albumBioUrl
|
||||
} = paths.api
|
||||
albumbio: albumBioUrl,
|
||||
albumsByArtistUrl,
|
||||
} = paths.api;
|
||||
|
||||
const getAlbumData = async (hash: string, ToastStore: typeof useNotifStore) => {
|
||||
interface AlbumData {
|
||||
@ -66,4 +66,30 @@ const getAlbumBio = async (hash: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
export { getAlbumData as getAlbumTracks, getAlbumArtists, getAlbumBio };
|
||||
const getAlbumsFromArtist = async (
|
||||
albumartist: string,
|
||||
limit: number = 2,
|
||||
exclude: string
|
||||
) => {
|
||||
const { data } = await useAxios({
|
||||
url: albumsByArtistUrl,
|
||||
props: {
|
||||
albumartist: albumartist,
|
||||
limit: limit,
|
||||
exclude: exclude,
|
||||
},
|
||||
});
|
||||
|
||||
if (data) {
|
||||
return data.data;
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
export {
|
||||
getAlbumData as getAlbumTracks,
|
||||
getAlbumArtists,
|
||||
getAlbumBio,
|
||||
getAlbumsFromArtist,
|
||||
};
|
||||
|
@ -34,7 +34,7 @@ export default function play(
|
||||
useQueue.playFromAlbum(
|
||||
a_store.info.title,
|
||||
a_store.info.albumartist,
|
||||
a_store.info.hash,
|
||||
a_store.info.albumhash,
|
||||
a_store.allTracks
|
||||
);
|
||||
useQueue.play();
|
||||
|
@ -44,6 +44,9 @@ const paths = {
|
||||
get albumbio() {
|
||||
return this.album + "/bio";
|
||||
},
|
||||
get albumsByArtistUrl(){
|
||||
return this.album + "/from-artist"
|
||||
},
|
||||
folder: baseApiUrl + "/folder",
|
||||
playlist: {
|
||||
base: baseApiUrl + "/playlist",
|
||||
@ -85,4 +88,4 @@ const paths = {
|
||||
},
|
||||
};
|
||||
|
||||
export { paths, toggleMode };
|
||||
export { paths, toggleMode };
|
||||
|
@ -43,7 +43,7 @@ export interface AlbumInfo {
|
||||
date: string;
|
||||
image: string;
|
||||
artistimg: string;
|
||||
hash: string;
|
||||
albumhash: string;
|
||||
colors: string[];
|
||||
copyright?: string;
|
||||
|
||||
@ -91,7 +91,7 @@ export interface fromFolder {
|
||||
export interface fromAlbum {
|
||||
type: FromOptions.album;
|
||||
name: string;
|
||||
hash: string;
|
||||
albumhash: string;
|
||||
albumartist: string;
|
||||
}
|
||||
export interface fromPlaylist {
|
||||
|
@ -4,8 +4,12 @@ import { ComputedRef } from "vue";
|
||||
import { AlbumDisc } from "./../../interfaces";
|
||||
|
||||
import { FuseTrackOptions } from "@/composables/enums";
|
||||
import { content_width } from "@/stores/content-width";
|
||||
|
||||
import { getAlbumTracks } from "../../composables/fetch/album";
|
||||
import {
|
||||
getAlbumsFromArtist,
|
||||
getAlbumTracks,
|
||||
} from "../../composables/fetch/album";
|
||||
import { AlbumInfo, Artist, FuseResult, Track } from "../../interfaces";
|
||||
import { useNotifStore } from "../notification";
|
||||
|
||||
@ -49,6 +53,7 @@ export default defineStore("album", {
|
||||
info: <AlbumInfo>{},
|
||||
rawTracks: <Track[]>[],
|
||||
artists: <Artist[]>[],
|
||||
albumArtists: <{ artist: string; albums: AlbumInfo[] }[]>[],
|
||||
bio: null,
|
||||
}),
|
||||
actions: {
|
||||
@ -62,9 +67,23 @@ export default defineStore("album", {
|
||||
this.rawTracks = album.tracks;
|
||||
this.info = album.info;
|
||||
},
|
||||
async fetchArtistAlbums() {
|
||||
const albumartist = this.info.albumartist;
|
||||
const cardWidth = 8 * 16;
|
||||
const visible_cards = Math.floor(content_width.value / cardWidth);
|
||||
|
||||
this.albumArtists = await getAlbumsFromArtist(
|
||||
albumartist,
|
||||
visible_cards,
|
||||
this.info.albumhash
|
||||
);
|
||||
},
|
||||
resetQuery() {
|
||||
this.query = "";
|
||||
},
|
||||
resetAlbumArtists() {
|
||||
this.albumArtists = [];
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
discs(): Disc {
|
||||
|
@ -161,7 +161,7 @@ export default defineStore("Queue", {
|
||||
this.from = <fromAlbum>{
|
||||
type: FromOptions.album,
|
||||
name: aname,
|
||||
hash: albumhash,
|
||||
albumhash: albumhash,
|
||||
albumartist: albumartist,
|
||||
};
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
<component
|
||||
:is="item.component"
|
||||
v-bind="item.props"
|
||||
:style="{ maxHeight: `${item.size}px` }"
|
||||
@playThis="
|
||||
playFromAlbum(item.props.track.index - item.props.track.disc)
|
||||
"
|
||||
@ -33,8 +34,11 @@ import useAlbumStore from "@/stores/pages/album";
|
||||
import useQueueStore from "@/stores/queue";
|
||||
|
||||
import AlbumDiscBar from "@/components/AlbumView/AlbumDiscBar.vue";
|
||||
import ArtistAlbums from "@/components/AlbumView/ArtistAlbums.vue";
|
||||
import GenreBanner from "@/components/AlbumView/GenreBanner.vue";
|
||||
import Header from "@/components/AlbumView/Header.vue";
|
||||
import SongItem from "@/components/shared/SongItem.vue";
|
||||
|
||||
import { isSmall } from "@/stores/content-width";
|
||||
|
||||
const album = useAlbumStore();
|
||||
@ -42,8 +46,12 @@ const queue = useQueueStore();
|
||||
|
||||
interface ScrollerItem {
|
||||
id: string;
|
||||
component: typeof Header | typeof SongItem;
|
||||
props: any;
|
||||
component:
|
||||
| typeof Header
|
||||
| typeof SongItem
|
||||
| typeof GenreBanner
|
||||
| typeof ArtistAlbums;
|
||||
props?: any;
|
||||
size: number;
|
||||
}
|
||||
|
||||
@ -62,12 +70,29 @@ class songItem {
|
||||
}
|
||||
}
|
||||
|
||||
const genreBanner: ScrollerItem = {
|
||||
id: "genre-banner",
|
||||
component: GenreBanner,
|
||||
size: 64,
|
||||
};
|
||||
|
||||
function getSongItems() {
|
||||
return album.tracks.map((track) => {
|
||||
return new songItem(track);
|
||||
});
|
||||
}
|
||||
|
||||
function getArtistAlbumComponents(): ScrollerItem[] {
|
||||
return album.albumArtists.map((artist) => {
|
||||
return {
|
||||
id: Math.random().toString(),
|
||||
component: ArtistAlbums,
|
||||
props: { artist },
|
||||
size: 20 * 16,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const scrollerItems = computed(() => {
|
||||
const header: ScrollerItem = {
|
||||
id: "album-header",
|
||||
@ -75,22 +100,29 @@ const scrollerItems = computed(() => {
|
||||
props: {
|
||||
album: album.info,
|
||||
},
|
||||
size: 18 * 16,
|
||||
size: 19 * 16,
|
||||
};
|
||||
|
||||
return [header, ...getSongItems()];
|
||||
return [
|
||||
header,
|
||||
...getSongItems(),
|
||||
genreBanner,
|
||||
...getArtistAlbumComponents(),
|
||||
];
|
||||
});
|
||||
|
||||
function playFromAlbum(index: number) {
|
||||
const { title, artist, hash } = album.info;
|
||||
queue.playFromAlbum(title, artist, hash, album.allTracks);
|
||||
const { title, albumartist, albumhash } = album.info;
|
||||
queue.playFromAlbum(title, albumartist, albumhash, album.allTracks);
|
||||
queue.play(index);
|
||||
}
|
||||
|
||||
onBeforeRouteUpdate(async (to: RouteLocationNormalized) => {
|
||||
await album
|
||||
.fetchTracksAndArtists(to.params.hash.toString())
|
||||
.then(() => album.resetQuery());
|
||||
await album.fetchTracksAndArtists(to.params.hash.toString()).then(() => {
|
||||
album.resetQuery();
|
||||
album.resetAlbumArtists();
|
||||
album.fetchArtistAlbums();
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeRouteLeave(() => {
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="search-albums-view grid-page" v-auto-animate>
|
||||
<AlbumCard
|
||||
v-for="album in search.albums.value"
|
||||
:key="album.hash"
|
||||
:key="album.albumhash"
|
||||
:album="album"
|
||||
/>
|
||||
</div>
|
||||
|
Loading…
x
Reference in New Issue
Block a user