Commit 8ec85b48 authored by SpinShare's avatar SpinShare

songdetail 2.0

parent 95d569d1
<template> <template>
<section class="section-song-detail"> <section :class="!apiFinished ? 'section-song-detail-loading' : 'section-song-detail'">
<div class="song-detail-background" :style="'background-image: url(' + cover + '), url(' + require('@/assets/img/defaultAlbumArt.jpg') + ');'" v-if="apiFinished"> <div class="song-detail" v-if="apiFinished">
<div class="song-detail-dim"> <div class="song-meta">
<div class="song-detail"> <div class="cover" :style="'background-image: url(' + cover + '), url(' + require('@/assets/img/defaultAlbumArt.jpg') + ');'"></div>
<div class="song-cover" :style="'background-image: url(' + cover + '), url(' + require('@/assets/img/defaultAlbumArt.jpg') + ');'"></div> <div class="song-metadata">
<div class="song-meta-data">
<div class="song-title">{{ title }}</div> <div class="song-title">{{ title }}</div>
<div class="song-subtitle">{{ subtitle }}</div> <div class="song-subtitle">{{ subtitle }}</div>
<div class="song-artist">{{ artist }}</div> <div class="song-artist">{{ artist }} &bull; {{ $t('songdetail.createdBy', { charter: charter}) }}</div>
<div class="song-charter">{{ $t('songdetail.createdBy') + charter }}</div> </div>
<div class="song-tags"> </div>
<router-link class="tag" v-for="tag in tags" v-bind:key="tag" :to="{ name: 'Search', params: { searchQuery: tag } }">{{ tag }}</router-link> <div :class="'song-actions ' + (previewIsPlaying ? 'player-active' : '')">
<div v-on:click="AddToQueue()" class="action">
<div class="icon">
<i class="mdi mdi-download"></i>
</div>
</div>
<div class="action-player">
<div class="icon" v-on:click="TogglePreview()">
<i class="mdi mdi-play" v-if="!previewIsPlaying"></i>
<i class="mdi mdi-stop" v-if="previewIsPlaying"></i>
</div>
<div class="volume">
<input type="range" min="0" max="100" value="50" v-model="previewVolume" class="playerVolume" v-on:change="UpdateVolume()" />
</div>
</div>
<div v-on:click="CopyLink()" class="action">
<div class="icon">
<i class="mdi mdi-content-copy"></i>
</div>
</div>
<div v-on:click="OpenReport()" class="action">
<div class="icon">
<i class="mdi mdi-flag-outline"></i>
</div>
</div>
</div>
<div class="song-statistics">
<div class="stat">
<div class="icon">
<i class="mdi mdi-arm-flex"></i>
</div>
<div class="difficulties">
<img src="@/assets/img/difficultyEasy.svg" :class="hasEasyDifficulty ? 'active' : ''" alt="Easy Difficulty" />
<img src="@/assets/img/difficultyNormal.svg" :class="hasNormalDifficulty ? 'active' : ''" alt="Normal Difficulty" />
<img src="@/assets/img/difficultyHard.svg" :class="hasHardDifficulty ? 'active' : ''" alt="Hard Difficulty" />
<img src="@/assets/img/difficultyExtreme.svg" :class="hasExtremeDifficulty ? 'active' : ''" alt="Extreme Difficulty" />
<img src="@/assets/img/difficultyXD.svg" :class="hasXDDifficulty ? 'active' : ''" alt="xD Difficulty" />
</div>
</div>
<div class="stat">
<div class="icon">
<i class="mdi mdi-calendar-clock"></i>
</div>
<div class="content">
{{ uploadDate.date | formatDate}}
</div>
</div>
<div class="stat">
<div class="icon">
<i class="mdi mdi-eye"></i>
</div>
<div class="content">
{{ views ? views : 0 }}
</div>
</div>
<div class="stat">
<div class="icon">
<i class="mdi mdi-download"></i>
</div>
<div class="content">
{{ downloads ? downloads : 0 }}
</div>
</div> </div>
<div class="song-difficulties">
<img src="@/assets/img/difficultyEasy.svg" :class="hasEasyDifficulty ? 'active' : ''" />
<img src="@/assets/img/difficultyNormal.svg" :class="hasNormalDifficulty ? 'active' : ''" />
<img src="@/assets/img/difficultyHard.svg" :class="hasHardDifficulty ? 'active' : ''" />
<img src="@/assets/img/difficultyExtreme.svg" :class="hasExtremeDifficulty ? 'active' : ''" />
<img src="@/assets/img/difficultyXD.svg" :class="hasXDDifficulty ? 'active' : ''" />
</div> </div>
<div class="song-uploader"> <div class="song-uploader">
<div class="label">{{ $t('songdetail.uploadedBy') }}</div>
<UserItem v-bind="uploader" /> <UserItem v-bind="uploader" />
</div> </div>
<div class="song-description" v-if="description || tags.length > 0">
<div class="text" v-if="description">{{ description }}</div>
<div class="tags">
<router-link class="tag" v-for="tag in tags" v-bind:key="tag" :to="{ name: 'Search', params: { searchQuery: tag } }">{{ tag }}</router-link>
</div> </div>
</div> </div>
</div> </div>
<div class="song-social" v-if="apiFinished">
<div class="tab-header">
<router-link :to="{ name: 'SongDetailReviews', params: { id: id } }" class="tab-header-item tab-header-item-reviews"><span>{{ $t('songdetail.tabs.reviews') }}</span></router-link>
<router-link :to="{ name: 'SongDetailSpinPlays', params: { id: id } }" class="tab-header-item tab-header-item-spinplays"><span>{{ $t('songdetail.tabs.spinplays') }}</span></router-link>
</div> </div>
<div class="song-detail-actions" v-if="apiFinished">
<button class="button-download button button-primary" v-on:click="AddToQueue()">{{ $t('songdetail.actions.downloadButton') }}</button> <router-view></router-view>
<button class="button-preview button" v-on:click="PlayPreview()">{{ $t('songdetail.actions.playPreviewButton') }}</button>
<button class="button-copylink button" v-on:click="CopyLink()">{{ $t('songdetail.actions.copyLinkButton') }}</button>
<button class="button-report button" v-on:click="OpenReport()">{{ $t('songdetail.actions.reportButton') }}</button>
</div> </div>
<Loading v-if="!apiFinished" /> <Loading v-if="!apiFinished" />
...@@ -67,8 +127,15 @@ ...@@ -67,8 +127,15 @@
hasXDDifficulty: false, hasXDDifficulty: false,
tags: [], tags: [],
uploader: null, uploader: null,
uploadDate: null,
previewPath: "", previewPath: "",
downloadPath: "" downloadPath: "",
downloads: 0,
views: 0,
description: "",
previewIsPlaying: false,
currentPreviewAudio: null,
previewVolume: 50
} }
}, },
mounted: function() { mounted: function() {
...@@ -91,6 +158,10 @@ ...@@ -91,6 +158,10 @@
} }
this.$data.previewPath = data.data.paths.ogg; this.$data.previewPath = data.data.paths.ogg;
this.$data.downloadPath = data.data.paths.zip; this.$data.downloadPath = data.data.paths.zip;
this.$data.downloads = data.data.downloads;
this.$data.views = data.data.views;
this.$data.description = data.data.description;
this.$data.uploadDate = data.data.uploadDate;
ssapi.getUserDetail(data.data.uploader).then((data) => { ssapi.getUserDetail(data.data.uploader).then((data) => {
this.$data.uploader = data.data; this.$data.uploader = data.data;
...@@ -101,99 +172,175 @@ ...@@ -101,99 +172,175 @@
methods: { methods: {
AddToQueue: function() { AddToQueue: function() {
this.$root.$emit('download', {id: this.$data.id, cover: this.$data.cover, title: this.$data.title, artist: this.$data.artist, downloadPath: this.$data.downloadPath}); this.$root.$emit('download', {id: this.$data.id, cover: this.$data.cover, title: this.$data.title, artist: this.$data.artist, downloadPath: this.$data.downloadPath});
},
PlayPreview: function() {
}, },
CopyLink: function() { CopyLink: function() {
clipboard.writeText("https://spinsha.re/song/" + this.$data.id); clipboard.writeText("https://spinsha.re/song/" + this.$data.id);
}, },
OpenReport: function() { OpenReport: function() {
shell.openExternal("https://spinsha.re/report/song/" + this.$data.id); shell.openExternal("https://spinsha.re/report/song/" + this.$data.id);
},
TogglePreview: function() {
this.$data.previewIsPlaying = !this.$data.previewIsPlaying;
if(this.$data.previewIsPlaying) {
this.PlayPreview();
} else {
this.StopPreview();
}
},
PlayPreview: function() {
this.$data.currentPreviewAudio = new Audio(this.$data.previewPath);
this.$data.previewVolume = 50;
this.$data.currentPreviewAudio.volume = this.$data.previewVolume / 100;
this.$data.currentPreviewAudio.play();
this.$data.currentPreviewAudio.onended = () => {
this.StopPreview();
}
this.$data.previewIsPlaying = true;
},
StopPreview: function() {
if(this.$data.currentPreviewAudio) {
this.$data.currentPreviewAudio.pause();
this.$data.currentPreviewAudio.currentTime = 0;
} }
this.$data.currentPreviewAudio = null;
this.$data.previewIsPlaying = false;
},
UpdateVolume: function() {
this.$data.currentPreviewAudio.volume = this.$data.previewVolume / 100;
}
},
beforeDestroy: function() {
this.StopPreview();
} }
} }
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
.section-song-detail { .section-song-detail {
& .song-detail-background { display: grid;
background-size: cover; grid-template-columns: 500px 1fr;
background-position: center; grid-gap: 25px;
padding: 50px;
& .song-detail-dim {
backdrop-filter: blur(10px);
background: linear-gradient(180deg, rgba(0,0,0,0.4), #212629);
& .song-detail { & .song-detail {
padding: 50px; & .song-meta {
display: grid; display: grid;
grid-template-columns: 200px 1fr; grid-template-columns: auto 1fr;
grid-gap: 25px; background: rgba(255,255,255,0.1);
border-radius: 4px;
overflow: hidden;
& .song-cover { & .cover {
width: 200px; margin-left: 10px;
height: 200px;
align-self: center; align-self: center;
background: #eee; justify-self: center;
border-radius: 6px; height: 64px;
background-size: cover; width: 64px;
border-radius: 4px;
background-position: center; background-position: center;
background-size: cover;
} }
& .song-meta-data {
& .song-metadata {
padding: 20px;
& .song-title { & .song-title {
font-weight: bold; font-weight: bold;
font-size: 48px; font-size: 18px;
word-break: break-all; margin-bottom: 2px;
} }
& .song-subtitle { & .song-subtitle {
font-size: 20px; margin-bottom: 5px;
word-break: break-all;
} }
& .song-artist { & .song-artist {
margin-top: 5px;
font-size: 18px;
word-break: break-all;
}
& .song-charter {
margin-top: 10px;
font-size: 14px;
opacity: 0.6; opacity: 0.6;
word-break: break-all;
} }
& .song-tags { }
margin-top: 10px; }
& .song-actions {
margin-top: 25px;
width: 500px;
display: flex;
transition: all 0.2s ease-in-out;
border-radius: 4px;
overflow: hidden;
& .tag { & .action, & .action-player {
display: inline-block; background: linear-gradient(135deg, rgba(255,255,255,0.3), rgba(255,255,255,0.1));
font-size: 12px; width: 25%;
font-weight: bold; display: flex;
color: #222; justify-content: center;
background: #fff; align-items: center;
padding: 5px 20px; color: #fff;
border-radius: 50px;
margin-right: 10px;
margin-top: 5px;
text-decoration: none; text-decoration: none;
word-break: break-all; transition: all 0.2s ease-in-out;
transition: 0.2s ease-in-out all; cursor: pointer;
& .icon {
display: flex;
justify-content: center;
align-items: center;
height: 48px;
width: 48px;
font-size: 24px;
}
& .volume {
width: 0px;
overflow: hidden;
padding-left: 0px;
transition: all 0.2s ease-in-out;
}
&:hover { &:hover {
opacity: 0.6; opacity: 0.6;
cursor: pointer; }
}
&.player-active {
& .action {
width: calc((100% - 250px) / 3);
}
& .action-player {
width: 250px;
& .volume {
width: 170px;
padding-left: 20px;
}
} }
} }
} }
& .song-statistics {
display: grid;
grid-template-columns: 1fr;
grid-gap: 10px;
background: rgba(255,255,255,0.1);
padding: 20px;
border-radius: 4px;
margin-top: 25px;
& .stat {
display: grid;
grid-template-columns: 30px 1fr;
grid-gap: 15px;
& .song-difficulties { & .icon {
margin-top: 15px;
height: 20px;
display: flex; display: flex;
justify-content: center;
align-items: center;
font-size: 22px;
}
& .difficulties {
align-self: center;
& img { & img {
height: 20px; height: 25px;
margin-right: 10px; margin-right: 5px;
opacity: 0.3; opacity: 0.4;
&.active { &.active {
opacity: 1; opacity: 1;
...@@ -201,49 +348,110 @@ ...@@ -201,49 +348,110 @@
} }
} }
& .song-uploader { & .content {
margin-top: 15px; align-self: center;
display: flex; opacity: 0.6;
& .user-item {
width: auto;
padding-right: 15px;
}
} }
} }
} }
& .song-uploader {
display: grid;
grid-template-columns: auto 1fr;
grid-gap: 15px;
margin-top: 25px;
& .label {
align-self: center;
opacity: 0.6;
} }
} }
& .song-detail-actions { & .song-description {
padding: 50px; background: rgba(255,255,255,0.1);
padding-top: 0px; border-radius: 4px;
margin-top: 25px;
display: grid; display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr; grid-gap: 0px;
grid-gap: 25px;
& .button { & .text {
padding: 15px 0px; line-height: 1.5em;
font-size: 16px; padding: 20px;
transition: 0.2s ease-in-out all, 0.1s ease-in-out transform; opacity: 0.7;
}
& .tags {
padding: 20px;
&.button-primary { & .tag {
margin-right: 10px;
background: #fff; background: #fff;
color: #222; color: #000;
border-radius: 50px;
padding: 7px 14px;
font-size: 12px;
text-decoration: none;
transition: 0.2s ease-in-out opacity;
&:hover { &:hover {
background: #fff; cursor: pointer;
color: #222; opacity: 0.6;
} }
} }
}
}
}
& .song-social {
& .tab-header {
display: flex;
&:hover { & .tab-header-item {
background: rgba(255,255,255,0.2); font-size: 14px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 0.25em;
padding: 15px 40px;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
opacity: 0.4;
background: rgba(0,0,0,0.4);
transition: 0.2s ease-in-out all;
color: #fff; color: #fff;
opacity: 0.6; text-decoration: none;
transform: translateY(-4px);
&:not(.active):hover {
cursor: pointer;
opacity: 0.75;
}
&.active {
}
&.router-link-exact-active {
opacity: 1;
background: rgba(255,255,255,0.1);
}
}
}
& .tab {
background: rgba(255,255,255,0.1);
border-radius: 4px;
border-top-left-radius: 0px;
padding: 20px;
display: grid;
grid-gap: 25px;
}
}
}
.section-song-detail-loading {
display: grid;
grid-template-columns: 1fr;
padding: 50px;
}
@media screen and (min-width: 1700px) {
.section-song-detail {
& .song-social {
& .tab.tab-spinplays {
& .spinplays {
grid-template-columns: 1fr 1fr 1fr;
} }
&:active {
transform: translateY(-2px);
} }
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment