mirror of
https://github.com/tcsenpai/swingmusic.git
synced 2025-06-09 12:37:22 +00:00
implement track favoriting
update img route
This commit is contained in:
parent
35a8446f8b
commit
c7cc687286
@ -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%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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",
|
||||||
|
}
|
||||||
|
@ -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({
|
||||||
|
47
src/composables/fetch/favorite.ts
Normal file
47
src/composables/fetch/favorite.ts
Normal 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;
|
||||||
|
}
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
|
@ -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 };
|
||||||
|
@ -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 {
|
||||||
|
@ -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 = "";
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user