Commit e8f82ad7 authored by Andreas Heimann's avatar Andreas Heimann

fixed login, added playlists

parent ef025358
<template>
<div class="song-item" v-on:auxclick="shortDownload($event)" v-on:contextmenu="showContextMenu($event)">
<router-link :to="{ name: 'SongDetail', params: { id: id } }">
<div class="song-cover" :style="'background-image: url(' + cover + '), url(' + require('@/assets/img/defaultAlbumArt.jpg') + ');'">
<div class="song-charter-info">
<div class="song-charter"><i class="mdi mdi-account-circle"></i><span>{{ charter }}</span></div>
</div>
</div>
<div class="song-metadata">
<div class="song-title">{{ title }}</div>
<div class="song-artist">{{ artist }}</div>
<div class="song-difficulties">
<div :class="hasEasyDifficulty ? 'difficulty active' : 'difficulty'"><span>E</span></div>
<div :class="hasNormalDifficulty ? 'difficulty active' : 'difficulty'"><span>N</span></div>
<div :class="hasHardDifficulty ? 'difficulty active' : 'difficulty'"><span>H</span></div>
<div :class="hasExtremeDifficulty ? 'difficulty active' : 'difficulty'"><span>EX</span></div>
<div :class="hasXDDifficulty ? 'difficulty active' : 'difficulty'"><span>XD</span></div>
</div>
</div>
</router-link>
</div>
</template>
<script>
import { remote } from 'electron';
const { clipboard, shell } = remote;
export default {
name: 'SongItem',
props: [
'id',
'cover',
'title',
'subtitle',
'artist',
'charter',
'hasEasyDifficulty',
'hasNormalDifficulty',
'hasHardDifficulty',
'hasExtremeDifficulty',
'hasXDDifficulty',
'zip'
],
data: function() {
return {
isContextMenuActive: false
}
},
mounted: function() {
},
methods: {
showContextMenu: function(e) {
if(e != undefined) {
e.preventDefault();
}
this.$root.$emit('showContextMenu', {
x: e.pageX,
y: e.pageY,
items: [
{ icon: "eye", title: this.$t('contextmenu.open'), method: () => { this.$router.push({ name: 'SongDetail', params: { id: this.$props.id } }); } },
{ icon: "earth", title: this.$t('contextmenu.openOnSpinShare'), method: () => { shell.openExternal("https://spinsha.re/song/" + this.$props.id); } },
{ icon: "link", title: this.$t('contextmenu.copyLink'), method: () => { clipboard.writeText('https://spinsha.re/playlist/' + this.$props.id); } },
{ icon: "download", title: this.$t('contextmenu.download'), method: () => { this.download(); } }
]});
}
}
}
</script>
<style scoped lang="less">
</style>
...@@ -59,7 +59,7 @@ ...@@ -59,7 +59,7 @@
y: e.pageY, y: e.pageY,
items: [ items: [
{ icon: "eye", title: this.$t('contextmenu.open'), method: () => { this.$router.push({ name: 'SongDetail', params: { id: this.$props.id } }); } }, { icon: "eye", title: this.$t('contextmenu.open'), method: () => { this.$router.push({ name: 'SongDetail', params: { id: this.$props.id } }); } },
{ icon: "earth", title: this.$t('contextmenu.openOnSpinShare'), method: () => { shell.openExternal("https://spinsha.re/report/song/" + this.$props.id); } }, { icon: "earth", title: this.$t('contextmenu.openOnSpinShare'), method: () => { shell.openExternal("https://spinsha.re/song/" + this.$props.id); } },
{ icon: "link", title: this.$t('contextmenu.copyLink'), method: () => { clipboard.writeText('https://spinsha.re/song/' + this.$props.id); } }, { icon: "link", title: this.$t('contextmenu.copyLink'), method: () => { clipboard.writeText('https://spinsha.re/song/' + this.$props.id); } },
{ icon: "download", title: this.$t('contextmenu.download'), method: () => { this.download(); } } { icon: "download", title: this.$t('contextmenu.download'), method: () => { this.download(); } }
]}); ]});
......
<template> <template>
<div class="song-row"> <div class="song-row">
<div class="song-list"> <div class="song-list" v-if="!playlist">
<slot name="song-list"></slot>
</div>
<div class="song-list-playlist" v-if="playlist">
<slot name="song-list"></slot> <slot name="song-list"></slot>
</div> </div>
<div class="noresults"> <div class="noresults">
...@@ -16,7 +19,8 @@ ...@@ -16,7 +19,8 @@
name: 'SongRow', name: 'SongRow',
props: [ props: [
'title', 'title',
'noactions' 'noactions',
'playlist'
] ]
} }
</script> </script>
...@@ -32,6 +36,11 @@ ...@@ -32,6 +36,11 @@
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr; grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
grid-gap: 15px; grid-gap: 15px;
} }
& .song-list-playlist {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
grid-gap: 15px;
}
& .song-list-noresults { & .song-list-noresults {
display: none; display: none;
background: rgba(255,255,255,0.1); background: rgba(255,255,255,0.1);
...@@ -54,4 +63,26 @@ ...@@ -54,4 +63,26 @@
} }
} }
} }
@media screen and (max-width: 1600px) {
.song-row {
& .song-list {
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
}
& .song-list-playlist {
grid-template-columns: 1fr 1fr 1fr 1fr;
}
}
}
@media screen and (max-width: 1300px) {
.song-row {
& .song-list {
grid-template-columns: 1fr 1fr 1fr 1fr;
}
& .song-list-playlist {
grid-template-columns: 1fr 1fr 1fr;
}
}
}
</style> </style>
This diff is collapsed.
...@@ -16,6 +16,7 @@ import ViewUserDetail from '../views/UserDetail.vue'; ...@@ -16,6 +16,7 @@ import ViewUserDetail from '../views/UserDetail.vue';
import ViewUserDetailCharts from '../views/UserDetailCharts.vue'; import ViewUserDetailCharts from '../views/UserDetailCharts.vue';
import ViewUserDetailReviews from '../views/UserDetailReviews.vue'; import ViewUserDetailReviews from '../views/UserDetailReviews.vue';
import ViewUserDetailSpinPlays from '../views/UserDetailSpinPlays.vue'; import ViewUserDetailSpinPlays from '../views/UserDetailSpinPlays.vue';
import ViewPlaylistDetail from '../views/PlaylistDetail.vue';
import ViewSettings from '../views/Settings.vue'; import ViewSettings from '../views/Settings.vue';
import ViewTournament from '../views/Tournament.vue'; import ViewTournament from '../views/Tournament.vue';
import ViewError from '../views/Error.vue'; import ViewError from '../views/Error.vue';
...@@ -106,6 +107,10 @@ const routes = [{ ...@@ -106,6 +107,10 @@ const routes = [{
component: ViewUserDetailSpinPlays component: ViewUserDetailSpinPlays
} }
] ]
}, {
path: '/playlist/:id',
name: 'PlaylistDetail',
component: ViewPlaylistDetail,
}, { }, {
path: '/settings', path: '/settings',
name: 'Settings', name: 'Settings',
......
...@@ -75,24 +75,32 @@ ...@@ -75,24 +75,32 @@
console.log("Trying connection with code '" + this.$data.connectCode + "'"); console.log("Trying connection with code '" + this.$data.connectCode + "'");
ssapi.getConnectToken(this.$data.connectCode).then((data) => { try {
switch(data.status) { ssapi.getConnectToken(this.$data.connectCode).then((data) => {
case 200: console.log(data);
// Successfull
userSettings.set("connectToken", data.data); switch (data.status) {
this.loadIntoProfile(); case 200:
break; // Successfull
case 403: userSettings.set("connectToken", data.data);
case 404: this.loadIntoProfile();
// Token invalid break;
this.showLoginBox(); default:
this.$data.apiLoginCodeError = true; // Token invalid
break; this.showLoginBox();
} this.$data.apiLoginCodeError = true;
}).catch((error) => { break;
}
}).catch((error) => {
console.error(error);
this.showLoginBox();
this.$data.apiLoginServerError = true;
});
} catch(error) {
console.error(error);
this.showLoginBox(); this.showLoginBox();
this.$data.apiLoginServerError = true; this.$data.apiLoginServerError = true;
}); }
}, },
showLoginBox: function() { showLoginBox: function() {
this.$data.apiLoginLoading = false; this.$data.apiLoginLoading = false;
......
<template>
<section :class="!apiFinished ? 'section-playlist-detail-loading' : 'section-playlist-detail'">
<section class="section-playlist-detail" v-if="apiFinished">
<div class="cover" :style="'background-image: url(' + cover + ');'">
<div class="shade" v-if="!isOfficial">
<div class="content">
<div class="title">{{ title }}<span class="official-badge" v-if="isOfficial">OFFICIAL</span></div>
<div class="quickinfo">{{ songs.length }} Charts</div>
</div>
</div>
</div>
<div class="playlist-content">
<div class="playlist-detail">
<div class="playlist-description">
{{ description }}
</div>
<div class="playlist-actions">
<div class="action-row">
<div v-on:click="CopyLink()" class="action">
<div class="icon">
<i class="mdi mdi-content-copy"></i>
</div>
</div>
<div v-on:click="AddToQueue()" class="action">
<div class="icon">
<i class="mdi mdi-download"></i>
</div>
</div>
</div>
</div>
<div class="playlist-uploader" v-if="!isOfficial">
<div class="label">Created by</div>
<UserItem v-bind="user" />
</div>
<div class="playlist-charters" v-if="songs.length > 0">
<div class="label">With Charts by</div>
<div class="charters">
charters
</div>
</div>
</div>
<SongRow v-if="apiFinished && songs.length > 0" v-bind:playlist="true">
<template v-slot:song-list>
<SongItem
v-for="song in songs"
v-bind:key="song.id"
v-bind="song"
v-bind:cover="song.cover" />
</template>
</SongRow>
<div class="list-noresults" v-if="songs.length === 0">
<div class="noresults-text">This playlist is empty.</div>
</div>
</div>
</section>
<Loading v-if="!apiFinished" />
</section>
</template>
<script>
import { remote } from 'electron';
const { clipboard, shell } = remote;
import SSAPI from '@/modules/module.api.js';
import UserSettings from '@/modules/module.usersettings.js';
import UserItem from '@/components/User/UserItem.vue';
import SongRow from '@/components/Song/SongRow.vue';
import SongItem from '@/components/Song/SongItem.vue';
import CollapsableText from '@/components/CollapsableText.vue';
import Loading from '@/components/Loading.vue';
export default {
name: 'SongDetail',
components: {
UserItem,
SongItem,
SongRow,
CollapsableText,
Loading
},
data: function() {
return {
apiFinished: false,
id: 0,
cover: "",
title: "",
description: "",
isOfficial: false,
user: null,
songs: [],
}
},
mounted: function() {
let ssapi = new SSAPI();
let userSettings = new UserSettings();
let routeID = this.$route.params.id;
ssapi.getPlaylistDetail(routeID).then((data) => {
if(data.status == 200) {
this.$data.id = data.data.id;
this.$data.cover = data.data.paths.cover;
this.$data.title = data.data.title;
this.$data.description = data.data.description;
this.$data.isOfficial = data.data.isOfficial;
this.$data.user = data.data.user;
this.$data.songs = data.data.songs;
this.$data.apiFinished = true;
} else {
this.$router.push({ name: 'Error', params: { errorCode: data.status } });
}
}).catch((error) => {
console.error(error);
this.$router.push({ name: 'Error', params: { errorCode: 500 } });
});
},
methods: {
AddToQueue: function() {
this.$data.songs.forEach((songItem) => {
this.$root.$emit('download', {id: songItem.id, cover: songItem.cover, title: songItem.title, artist: songItem.artist, downloadPath: songItem.zip});
});
},
CopyLink: function() {
clipboard.writeText("https://spinsha.re/playlist/" + this.$data.id);
},
},
beforeDestroy: function() {
}
}
</script>
<style scoped lang="less">
.section-playlist-detail {
& .cover {
width: 100%;
height: 300px;
background: rgba(255, 255, 255, 0.1) center;
background-size: cover;
& .shade {
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.4);
transition: 0.2s ease all;
display: flex;
justify-content: center;
align-items: center;
& .content {
text-align: center;
text-shadow: 0 4px 18px rgba(0, 0, 0, 0.6);
& .title {
font-size: 48px;
margin-bottom: 5px;
font-family: 'Oswald', sans-serif;
& .official-badge {
display: inline-block;
font-family: 'Open Sans', sans-serif;
font-weight: bold;
font-size: 12px;
padding: 2px 10px;
border-radius: 4px;
background: #fff;
color: #000;
text-shadow: 0 0 0 transparent;
margin-left: 10px;
transform: translateY(-11px);
}
}
& .quickinfo {
font-size: 18px;
opacity: 0.6;
}
}
}
}
& .playlist-content {
padding: 50px;
display: grid;
grid-template-columns: 400px 1fr;
grid-gap: 25px;
& .playlist-detail {
& .playlist-description {
background: #383C3F;
border-radius: 4px;
line-height: 1.5em;
display: grid;
grid-gap: 20px;
padding: 20px;
& .text {
line-height: 1.5em;
opacity: 0.7;
}
}
& .playlist-actions {
margin-top: 25px;
transition: all 0.2s ease-in-out;
border-radius: 4px;
overflow: hidden;
background: #fff;
& .action-row {
display: flex;
& .action {
background: linear-gradient(135deg, rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.3));
flex-grow: 1;
display: flex;
justify-content: center;
align-items: center;
color: #222;
text-decoration: none;
transition: all 0.2s ease-in-out;
cursor: pointer;
& .icon {
display: flex;
justify-content: center;
align-items: center;
height: 48px;
width: 48px;
font-size: 24px;
}
&:hover {
opacity: 0.6;
}
}
}
}
& .playlist-uploader {
display: grid;
grid-template-columns: auto 1fr;
grid-gap: 15px;
margin-top: 25px;
& .label {
align-self: center;
opacity: 0.6;
}
}
& .playlist-charters {
display: grid;
grid-template-rows: auto 1fr;
grid-gap: 8px;
margin-top: 25px;
& .label {
align-self: center;
opacity: 0.6;
}
& .charters {
background: #383C3F;
padding: 15px;
line-height: 1.5em;
border-radius: 4px;
}
& .username {
opacity: 1;
color: #fff;
text-decoration: none;
transition: 0.2s ease-in-out opacity;
&:hover {
opacity: 0.6;
}
}
}
}
}
}
</style>
\ No newline at end of file
...@@ -99,11 +99,16 @@ ...@@ -99,11 +99,16 @@
if(this.$data.searchQuery !== "") { if(this.$data.searchQuery !== "") {
ssapi.search(this.$data.searchQuery).then((data) => { ssapi.search(this.$data.searchQuery).then((data) => {
console.log(data);
if(data.status === 200) { if(data.status === 200) {
this.$data.searchResultsUsers = data.data.users; this.$data.searchResultsUsers = data.data.users;
this.$data.searchResultsSongs = data.data.songs; this.$data.searchResultsSongs = data.data.songs;
this.$data.apiFinished = true; this.$data.apiFinished = true;
} else if(data.status === 404) {
this.$data.searchResultsUsers = [];
this.$data.searchResultsSongs = [];
} else { } else {
this.$router.push({ name: 'Error', params: { errorCode: data.status } }); this.$router.push({ name: 'Error', params: { errorCode: data.status } });
} }
......
...@@ -59,7 +59,7 @@ ...@@ -59,7 +59,7 @@
</div> </div>
</div> </div>
<!-- Botch --> <!-- Botch -->
<!-- <div class="settings-box"> <div class="settings-box">
<div class="settings-title">Tournament Hub</div> <div class="settings-title">Tournament Hub</div>
<div class="settings-item"> <div class="settings-item">
<div class="settings-label">Open</div> <div class="settings-label">Open</div>
...@@ -67,7 +67,7 @@ ...@@ -67,7 +67,7 @@
<router-link to="/tournament" class="button">Open WarpZone</router-link> <router-link to="/tournament" class="button">Open WarpZone</router-link>
</div> </div>
</div> </div>
</div> --> </div>
</div> </div>
</section> </section>
</template> </template>
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<header> <header>
<div class="title">Tournament WarpZone</div> <div class="title">Tournament WarpZone</div>
<div class="description"> <div class="description">
This page looks very ugly, sorry about that &dash; Andreas It's gonna be better next SSSO! I promise!! &dash; Laura
</div> </div>
</header> </header>
<div class="tournament-content" v-if="apiFinished"> <div class="tournament-content" v-if="apiFinished">
...@@ -15,21 +15,21 @@ ...@@ -15,21 +15,21 @@
<div class="check-result" v-if="missingCharts.length > 0"> <div class="check-result" v-if="missingCharts.length > 0">
<div class="list-title title-missing">Missing</div> <div class="list-title title-missing">Missing</div>
<div class="item" v-for="missingChart in missingCharts" v-bind:key="missingChart.id"> <div class="item" v-for="missingChart in missingCharts" v-bind:key="missingChart.id">
<div class="cover" :style="'background-image: url(' + missingChart.paths.cover + '), url(' + require('@/assets/img/defaultAlbumArt.jpg') + ');'"></div> <div class="cover" :style="'background-image: url(' + missingChart.cover + '), url(' + require('@/assets/img/defaultAlbumArt.jpg') + ');'"></div>
<div class="title">{{ missingChart.title }}</div> <div class="title">{{ missingChart.title }}</div>
</div> </div>
</div> </div>
<div class="check-result" v-if="outdatedCharts.length > 0"> <div class="check-result" v-if="outdatedCharts.length > 0">
<div class="list-title title-outdated">Outdated</div> <div class="list-title title-outdated">Outdated</div>
<div class="item" v-for="outdatedChart in outdatedCharts" v-bind:key="outdatedChart.id"> <div class="item" v-for="outdatedChart in outdatedCharts" v-bind:key="outdatedChart.id">
<div class="cover" :style="'background-image: url(' + outdatedChart.paths.cover + '), url(' + require('@/assets/img/defaultAlbumArt.jpg') + ');'"></div> <div class="cover" :style="'background-image: url(' + outdatedChart.cover + '), url(' + require('@/assets/img/defaultAlbumArt.jpg') + ');'"></div>
<div class="title">{{ outdatedChart.title }}</div> <div class="title">{{ outdatedChart.title }}</div>
</div> </div>
</div> </div>
<div class="check-result" v-if="okCharts.length > 0"> <div class="check-result" v-if="okCharts.length > 0">
<div class="list-title title-ok">OK</div> <div class="list-title title-ok">OK</div>
<div class="item" v-for="okChart in okCharts" v-bind:key="okChart.id"> <div class="item" v-for="okChart in okCharts" v-bind:key="okChart.id">
<div class="cover" :style="'background-image: url(' + okChart.paths.cover + '), url(' + require('@/assets/img/defaultAlbumArt.jpg') + ');'"></div> <div class="cover" :style="'background-image: url(' + okChart.cover + '), url(' + require('@/assets/img/defaultAlbumArt.jpg') + ');'"></div>
<div class="title">{{ okChart.title }}</div> <div class="title">{{ okChart.title }}</div>
</div> </div>
</div> </div>
...@@ -61,10 +61,10 @@ ...@@ -61,10 +61,10 @@
mounted: function() { mounted: function() {
let ssapi = new SSAPI(); let ssapi = new SSAPI();
ssapi.getTournamentMappool().then((data) => { ssapi.getPlaylistDetail(5).then((data) => {
this.$data.apiFinished = true; this.$data.apiFinished = true;
this.$data.tournamentCharts = data.data; this.$data.tournamentCharts = data.data.songs;
}); });
}, },
methods: { methods: {
...@@ -84,7 +84,7 @@ ...@@ -84,7 +84,7 @@
this.$data.missingCharts.push(chart); this.$data.missingCharts.push(chart);
} else { } else {
// Check MD5 // Check MD5
if(md5File.sync(files[0]) == chart.srtbMD5) { if(md5File.sync(files[0]) == chart.currentVersion) {
this.$data.okCharts.push(chart); this.$data.okCharts.push(chart);
} else { } else {
this.$data.outdatedCharts.push(chart); this.$data.outdatedCharts.push(chart);
...@@ -99,10 +99,10 @@ ...@@ -99,10 +99,10 @@
downloadCharts: function() { downloadCharts: function() {
// Add to Queue // Add to Queue
this.$data.missingCharts.forEach((chart) => { this.$data.missingCharts.forEach((chart) => {
this.$root.$emit('download', {id: chart.id, cover: chart.paths.cover, title: chart.title, artist: chart.artist, downloadPath: chart.paths.zip}); this.$root.$emit('download', {id: chart.id, cover: chart.cover, title: chart.title, artist: chart.artist, downloadPath: chart.zip});
}); });
this.$data.outdatedCharts.forEach((chart) => { this.$data.outdatedCharts.forEach((chart) => {
this.$root.$emit('download', {id: chart.id, cover: chart.paths.cover, title: chart.title, artist: chart.artist, downloadPath: chart.paths.zip}); this.$root.$emit('download', {id: chart.id, cover: chart.cover, title: chart.title, artist: chart.artist, downloadPath: chart.zip});
}); });
// Clear Analyzation data // Clear Analyzation data
......
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