implement track favoriting

update img route
This commit is contained in:
geoffrey45 2022-12-17 22:42:11 +03:00 committed by Mungai Njoroge
parent 35a8446f8b
commit c7cc687286
12 changed files with 141 additions and 31 deletions

View File

@ -18,6 +18,7 @@ import ErrorSvg from "../assets/icons/toast/error.svg";
import InfoSvg from "../assets/icons/toast/info.svg"; import InfoSvg from "../assets/icons/toast/info.svg";
import SuccessSvg from "../assets/icons/toast/ok.svg"; import SuccessSvg from "../assets/icons/toast/ok.svg";
import WorkingSvg from "../assets/icons/toast/working.svg"; import WorkingSvg from "../assets/icons/toast/working.svg";
import HeartSvg from "../assets/icons/heart.svg";
const notifStore = useNotifStore(); const notifStore = useNotifStore();
@ -31,6 +32,8 @@ function getSvg(notif: NotifType) {
return SuccessSvg; return SuccessSvg;
case NotifType.Working: case NotifType.Working:
return WorkingSvg; return WorkingSvg;
case NotifType.Favorite:
return HeartSvg;
} }
} }
</script> </script>
@ -70,7 +73,7 @@ function getSvg(notif: NotifType) {
background-image: linear-gradient(275deg, $bg, $bg1 74%); background-image: linear-gradient(275deg, $bg, $bg1 74%);
} }
.new-notif.info { .new-notif.info, .new-notif.favorite {
$bg: rgb(28, 102, 238); $bg: rgb(28, 102, 238);
$bg1: rgba(31, 144, 236, 0.15); $bg1: rgba(31, 144, 236, 0.15);
background-image: linear-gradient(275deg, $bg, $bg1 74%); background-image: linear-gradient(275deg, $bg, $bg1 74%);
@ -78,7 +81,7 @@ function getSvg(notif: NotifType) {
.new-notif.success { .new-notif.success {
$bg: rgb(5, 167, 53); $bg: rgb(5, 167, 53);
$bg1: rgba(5, 167, 54, .15); $bg1: rgba(5, 167, 54, 0.15);
background-image: linear-gradient(275deg, $bg, $bg1 74%); background-image: linear-gradient(275deg, $bg, $bg1 74%);
} }

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="artist-discography-nav"> <div class="artist-discography-nav">
<h1 class="ellip">Creedence Clearwater Revival</h1> <h1 class="ellip">{{ store.artistname }}</h1>
<div class="buttons"> <div class="buttons">
<div class="select rounded-sm" v-auto-animate="{ duration: 10 }"> <div class="select rounded-sm" v-auto-animate="{ duration: 10 }">
<button class="selected" @click.prevent="showDropDown = !showDropDown"> <button class="selected" @click.prevent="showDropDown = !showDropDown">
@ -21,7 +21,6 @@
</div> </div>
</div> </div>
</div> </div>
<button class="rounded-sm"><GridSvg /></button>
</div> </div>
</div> </div>
</template> </template>
@ -69,7 +68,6 @@ onClickOutside(dropOptionsRef, (e) => {
.buttons { .buttons {
display: flex; display: flex;
gap: $small;
} }
.selected { .selected {
@ -86,7 +84,7 @@ onClickOutside(dropOptionsRef, (e) => {
.select { .select {
position: relative; position: relative;
width: 7rem; width: 8rem;
display: flex; display: flex;
align-items: center; align-items: center;
font-size: calc($medium + 2px); font-size: calc($medium + 2px);
@ -103,7 +101,7 @@ onClickOutside(dropOptionsRef, (e) => {
.option { .option {
padding: $small; padding: $small;
border-bottom: 1px solid $gray4; border-bottom: 1px solid $gray4;
width: 6.5rem; width: 7.5rem;
&:hover { &:hover {
border-radius: $smaller; border-radius: $smaller;

View File

@ -5,11 +5,18 @@
@dblclick.prevent="emitUpdate" @dblclick.prevent="emitUpdate"
@contextmenu.prevent="showMenu" @contextmenu.prevent="showMenu"
> >
<div class="index t-center ellip" @dblclick.prevent.stop="() => {}"> <div
<span class="text"> class="index t-center ellip"
@click.prevent="addToFav(track.trackhash)"
@dblclick.prevent.stop="() => {}"
>
<div class="text">
{{ index }} {{ index }}
</span> </div>
<HeartSvg /> <div class="heart-icon">
<HeartFillSvg v-if="fav" />
<HeartSvg v-else />
</div>
</div> </div>
<div class="flex"> <div class="flex">
<div @click.prevent="emitUpdate" class="thumbnail"> <div @click.prevent="emitUpdate" class="thumbnail">
@ -71,10 +78,13 @@ import { Track } from "@/interfaces";
import { formatSeconds } from "@/utils"; import { formatSeconds } from "@/utils";
import HeartSvg from "@/assets/icons/heart.svg"; import HeartSvg from "@/assets/icons/heart.svg";
import HeartFillSvg from "@/assets/icons/heart.fill.svg";
import OptionSvg from "@/assets/icons/more.svg"; import OptionSvg from "@/assets/icons/more.svg";
import ArtistName from "./ArtistName.vue"; import ArtistName from "./ArtistName.vue";
import useQueueStore from "@/stores/queue"; import useQueueStore from "@/stores/queue";
import { addFavorite, removeFavorite } from "@/composables/fetch/favorite";
import { favType } from "@/composables/enums";
const imguri = paths.images.thumb.small; const imguri = paths.images.thumb.small;
const context_menu_showing = ref(false); const context_menu_showing = ref(false);
@ -107,6 +117,26 @@ function isCurrent() {
function isCurrentPlaying() { function isCurrentPlaying() {
return isCurrent() && queue.playing; return isCurrent() && queue.playing;
} }
const fav = ref(props.track.is_favorite);
async function addToFav(trackhash: string) {
if (fav.value) {
const removed = await removeFavorite(favType.track, trackhash);
if (removed) {
fav.value = false;
}
return;
}
const added = await addFavorite(favType.track, trackhash);
if (added) {
fav.value = true;
}
}
</script> </script>
<style lang="scss"> <style lang="scss">
@ -137,7 +167,6 @@ function isCurrentPlaying() {
} }
cursor: pointer; cursor: pointer;
// outline: solid 1px;
.title { .title {
margin-bottom: 2px; margin-bottom: 2px;
@ -149,11 +178,15 @@ function isCurrentPlaying() {
.index { .index {
.text { .text {
transition-delay: 500ms;
transform: translateX($smaller); transform: translateX($smaller);
opacity: 0; opacity: 0;
} }
svg { .heart-icon {
transition-delay: 500ms;
transform: translateX(0); transform: translateX(0);
opacity: 1; opacity: 1;
} }
@ -189,17 +222,17 @@ function isCurrentPlaying() {
display: block; display: block;
margin: auto 0; margin: auto 0;
transition: all 0.25s; transition: all 0.25s;
transform: translateX(0); width: 100%;
} }
svg { .heart-icon {
position: absolute; position: absolute;
left: 0; left: -2px;
display: grid;
height: 100%;
align-content: center;
transition: all 0.2s; transition: all 0.2s;
top: $medium; transform: translateX(-1.5rem);
opacity: 0;
transform: translateX(-1rem);
cursor: pointer;
} }
} }

View File

@ -12,6 +12,7 @@ export enum NotifType {
Info = "info", Info = "info",
Error = "error", Error = "error",
Working = "working", Working = "working",
Favorite = "favorite",
} }
export enum FromOptions { export enum FromOptions {
@ -51,3 +52,9 @@ export enum discographyAlbumTypes {
eps = "EPs", eps = "EPs",
appearances = "Appearances", appearances = "Appearances",
} }
export enum favType {
artist = "artist",
album = "album",
track = "track",
}

View File

@ -26,6 +26,7 @@ const getArtistAlbums = async (hash: string, all = false, limit = 6) => {
eps: Album[]; eps: Album[];
singles: Album[]; singles: Album[];
appearances: Album[]; appearances: Album[];
artistname: string;
} }
const { data, error } = await useAxios({ const { data, error } = await useAxios({

View File

@ -0,0 +1,47 @@
import useAxios from "./useAxios";
import { paths } from "@/config";
import { favType, NotifType } from "@/composables/enums";
import { useNotifStore as notif } from "@/stores/notification";
export async function addFavorite(favtype: favType, itemhash: string) {
const { data, error } = await useAxios({
url: paths.api.favorite,
props: {
type: favtype,
hash: itemhash,
},
});
if (error) {
notif().showNotification("Something funny happened!", NotifType.Error);
return false;
}
if (data) {
notif().showNotification("Added to favorites!", NotifType.Success);
}
return true;
}
export async function removeFavorite(favtype: favType, itemhash: string) {
const { data, error } = await useAxios({
url: paths.api.removeFavorite,
props: {
type: favtype,
hash: itemhash,
},
});
if (error) {
notif().showNotification("Something funny happened!", NotifType.Error);
return false;
}
if (data) {
notif().showNotification("Removed from favorites!", NotifType.Error);
}
return true;
}

View File

@ -7,7 +7,7 @@ const {
new: newPlaylistUrl, new: newPlaylistUrl,
all: allPlaylistsUrl, all: allPlaylistsUrl,
base: basePlaylistUrl, base: basePlaylistUrl,
artists: playlistArtistsUrl artists: playlistArtistsUrl,
} = paths.api.playlist; } = paths.api.playlist;
/** /**
@ -109,7 +109,7 @@ async function getPlaylist(pid: string) {
async function updatePlaylist(pid: string, playlist: FormData, pStore: any) { async function updatePlaylist(pid: string, playlist: FormData, pStore: any) {
const uri = `${basePlaylistUrl}/${pid}/update`; const uri = `${basePlaylistUrl}/${pid}/update`;
const { data, error } = await useAxios({ const { data, status } = await useAxios({
url: uri, url: uri,
put: true, put: true,
props: playlist, props: playlist,
@ -118,8 +118,9 @@ async function updatePlaylist(pid: string, playlist: FormData, pStore: any) {
}, },
}); });
if (error) { if (status == 400) {
new Notification("Something funny happened!", NotifType.Error); new Notification("Failed: Unsupported image", NotifType.Error);
return;
} }
if (data) { if (data) {

View File

@ -21,7 +21,7 @@ export default function (queue: typeof useQStore) {
let ctrlKey = e.ctrlKey; let ctrlKey = e.ctrlKey;
function FocusedOnInput(target: HTMLElement) { function FocusedOnInput(target: HTMLElement) {
return target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "BUTTON"; return target.tagName === "INPUT" || target.tagName === "TEXTAREA";
} }
if (FocusedOnInput(target)) return; if (FocusedOnInput(target)) return;

View File

@ -12,7 +12,6 @@ const domains: D = {
const ports = { const ports = {
api: 1970, api: 1970,
images: 1971,
}; };
const imageRoutes = { const imageRoutes = {
@ -34,7 +33,7 @@ function toggleMode() {
const domain = () => domains[mode]; const domain = () => domains[mode];
const baseImgUrl = domain() + ports.images; const baseImgUrl = domain() + ports.api + "/img";
const baseApiUrl = domain() + ports.api; const baseApiUrl = domain() + ports.api;
@ -42,6 +41,10 @@ const paths = {
api: { api: {
album: baseApiUrl + "/album", album: baseApiUrl + "/album",
artist: baseApiUrl + "/artist", artist: baseApiUrl + "/artist",
favorite: baseApiUrl + "/favorite",
get removeFavorite() {
return this.favorite + "/remove";
},
get albumartists() { get albumartists() {
return this.album + "/artists"; return this.album + "/artists";
}, },
@ -95,6 +98,4 @@ const paths = {
}, },
}; };
export { paths, toggleMode }; export { paths, toggleMode };

View File

@ -24,6 +24,7 @@ export interface Track extends AlbumDisc {
trackhash: string; trackhash: string;
copyright?: string; copyright?: string;
filetype: string; filetype: string;
is_favorite: boolean;
} }
export interface Folder { export interface Folder {

View File

@ -5,7 +5,7 @@ import { getArtistAlbums } from "@/composables/fetch/artists";
export default defineStore("artistDiscography", { export default defineStore("artistDiscography", {
state: () => ({ state: () => ({
artist: <string>"", artistname: <string>"",
page: discographyAlbumTypes.all, page: discographyAlbumTypes.all,
toShow: <Album[]>[], toShow: <Album[]>[],
@ -17,6 +17,7 @@ export default defineStore("artistDiscography", {
}), }),
actions: { actions: {
setAlbums(page: discographyAlbumTypes) { setAlbums(page: discographyAlbumTypes) {
this.setPage(page);
switch (page) { switch (page) {
case discographyAlbumTypes.albums: case discographyAlbumTypes.albums:
this.toShow = this.albums; this.toShow = this.albums;
@ -48,6 +49,7 @@ export default defineStore("artistDiscography", {
this.eps = data.eps; this.eps = data.eps;
this.singles = data.singles; this.singles = data.singles;
this.appearances = data.appearances; this.appearances = data.appearances;
this.artistname = data.artistname;
}) })
.then(() => this.setAlbums(this.page)); .then(() => this.setAlbums(this.page));
}, },
@ -58,6 +60,7 @@ export default defineStore("artistDiscography", {
this.appearances = []; this.appearances = [];
this.toShow = []; this.toShow = [];
this.artistname = "";
}, },
}, },
}); });

View File

@ -1,12 +1,15 @@
<template> <template>
<div class="album-grid-view v-scroll-page"> <div class="album-grid-view v-scroll-page">
<div class="scrollable" v-auto-animate="{ duration: 100 }"> <div class="scrollable">
<AlbumCard <AlbumCard
v-for="album in artist.toShow" v-for="album in artist.toShow"
:album="album" :album="album"
:key="album.albumhash" :key="album.albumhash"
/> />
</div> </div>
<!-- <div class="no-albums rounded" v-if="artist.toShow.length == 0">
<b>No {{ artist.page }}</b>
</div> -->
</div> </div>
</template> </template>
@ -42,5 +45,17 @@ onBeforeRouteLeave(() => {
overflow: auto; overflow: auto;
max-height: 100%; max-height: 100%;
} }
.no-albums {
border: solid $red 1px;
width: 30rem;
display: block;
margin: 0 auto;
padding: 5rem;
text-align: center;
font-size: 1.25rem;
color: $red;
opacity: .5;
}
} }
</style> </style>