Commit 87f4a9dd authored by SpinShare's avatar SpinShare

cleanup, error handling, social icons

parent a6707980
...@@ -12,9 +12,6 @@ ...@@ -12,9 +12,6 @@
</template> </template>
<script> <script>
import { remote } from 'electron';
const { shell } = remote;
export default { export default {
name: 'SongItemPlaceholder', name: 'SongItemPlaceholder',
} }
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
<script> <script>
import { remote } from 'electron'; import { remote } from 'electron';
import path from 'path'; import path from 'path';
const { clipboard, shell } = remote; const { shell } = remote;
export default { export default {
name: 'SongLocalItem', name: 'SongLocalItem',
...@@ -51,8 +51,10 @@ ...@@ -51,8 +51,10 @@
}); });
}, },
openOnSpinShare: function() { openOnSpinShare: function() {
if(this.isSpinShare.includes("spinshare_")) { if(this.isSpinShare) {
this.$router.push({ name: 'SongDetail', params: { id: this.isSpinShare } }); if(this.isSpinShare.includes("spinshare_")) {
this.$router.push({ name: 'SongDetail', params: { id: this.isSpinShare } });
}
} }
}, },
openInExplorer: function() { openInExplorer: function() {
......
...@@ -17,9 +17,6 @@ ...@@ -17,9 +17,6 @@
</template> </template>
<script> <script>
import { remote } from 'electron';
const { clipboard } = remote;
import CollapsableText from '@/components/CollapsableText.vue'; import CollapsableText from '@/components/CollapsableText.vue';
export default { export default {
...@@ -34,10 +31,6 @@ ...@@ -34,10 +31,6 @@
'comment', 'comment',
'reviewDate' 'reviewDate'
], ],
mounted: function() {
},
methods: {
}
} }
</script> </script>
......
...@@ -15,9 +15,6 @@ ...@@ -15,9 +15,6 @@
</template> </template>
<script> <script>
import { remote } from 'electron';
const { shell } = remote;
export default { export default {
name: 'SongRow', name: 'SongRow',
props: [ props: [
......
...@@ -54,6 +54,7 @@ ...@@ -54,6 +54,7 @@
<style scoped lang="less"> <style scoped lang="less">
.staff-promo { .staff-promo {
background: #fff; background: #fff;
background-position: right center;
border-radius: 6px; border-radius: 6px;
padding: 50px; padding: 50px;
height: 256px; height: 256px;
......
<template> <template>
<div class="stream" v-if="isLive"> <div class="stream" v-if="isLive">
<div class="header">
<div class="viewers"><i class="mdi mdi-eye-outline"></i> {{ viewers }}</div>
<div class="title">{{ title }}</div>
</div>
<div class="video-container"> <div class="video-container">
<iframe src="https://player.twitch.tv/?channel=spinshare&enableExtensions=true&muted=true&player=popout&volume=1"></iframe> <iframe src="https://player.twitch.tv/?channel=spinshare&enableExtensions=true&muted=true&volume=1&parent=localhost"></iframe>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { remote } from 'electron';
const { shell } = remote;
export default { export default {
name: 'Stream', name: 'Stream',
props: [ props: [
...@@ -26,34 +19,14 @@ ...@@ -26,34 +19,14 @@
<style scoped lang="less"> <style scoped lang="less">
.stream { .stream {
margin-top: 50px; width: 720px;
margin: 0 auto;
margin-bottom: 50px;
& .header {
display: grid;
grid-template-columns: auto 1fr;
grid-gap: 15px;
margin-bottom: 15px;
& .viewers {
background: #ff294d;
color: #fff;
padding: 5px;
font-size: 12px;
font-weight: bold;
border-radius: 20px;
}
& .title {
letter-spacing: 0.25em;
font-size: 14px;
font-weight: bold;
text-transform: uppercase;
align-self: center;
}
}
& .video-container { & .video-container {
position: relative; position: relative;
width: 100%; width: 100%;
padding-top: 50%; padding-top: 56.25%;
& iframe { & iframe {
position: absolute; position: absolute;
......
...@@ -10,9 +10,6 @@ ...@@ -10,9 +10,6 @@
</template> </template>
<script> <script>
import { remote } from 'electron';
const { clipboard } = remote;
export default { export default {
name: 'Useritem', name: 'Useritem',
props: [ props: [
...@@ -21,11 +18,7 @@ ...@@ -21,11 +18,7 @@
'username', 'username',
'isPatreon', 'isPatreon',
'isVerified' 'isVerified'
], ]
mounted: function() {
},
methods: {
}
} }
</script> </script>
......
...@@ -10,9 +10,6 @@ ...@@ -10,9 +10,6 @@
</template> </template>
<script> <script>
import { remote } from 'electron';
const { shell } = remote;
export default { export default {
name: 'UserRow', name: 'UserRow',
props: [ props: [
......
...@@ -14,6 +14,7 @@ import ViewSongDetailReviews from '../views/SongDetailReviews.vue'; ...@@ -14,6 +14,7 @@ import ViewSongDetailReviews from '../views/SongDetailReviews.vue';
import ViewSongDetailSpinPlays from '../views/SongDetailSpinPlays.vue'; import ViewSongDetailSpinPlays from '../views/SongDetailSpinPlays.vue';
import ViewUserDetail from '../views/UserDetail.vue'; import ViewUserDetail from '../views/UserDetail.vue';
import ViewSettings from '../views/Settings.vue'; import ViewSettings from '../views/Settings.vue';
import ViewError from '../views/Error.vue';
Vue.use(VueRouter); Vue.use(VueRouter);
Vue.use(VueAxios, axios); Vue.use(VueAxios, axios);
...@@ -80,6 +81,10 @@ const routes = [{ ...@@ -80,6 +81,10 @@ const routes = [{
path: '/settings', path: '/settings',
name: 'Settings', name: 'Settings',
component: ViewSettings component: ViewSettings
}, {
path: '/error/:errorCode',
name: 'Error',
component: ViewError
}]; }];
const router = new VueRouter({ const router = new VueRouter({
......
<template>
<section class="section-error">
<header>
<div class="title">{{ errorCode }} - {{ errorTitle }}</div>
<div class="description">
{{ errorText }}
</div>
</header>
<div class="error-content">
<div class="button" v-on:click="retry()">Try again</div>
</div>
</section>
</template>
<script>
export default {
name: 'Error',
data: function() {
return {
errorCode: 500,
errorTitle: "",
errorText: "",
}
},
mounted: function() {
switch(status) {
default:
this.$data.errorCode = this.$route.params.errorCode;
this.$data.errorTitle = this.$t('connectionerror.server.title');
this.$data.errorText = this.$t('connectionerror.server.text');
break;
case 404:
case 403:
this.$data.errorCode = this.$route.params.errorCode;
this.$data.errorTitle = this.$t('connectionerror.notfound.title');
this.$data.errorText = this.$t('connectionerror.notfound.text');
break;
}
},
methods: {
retry: function() {
this.$router.back();
}
}
}
</script>
<style scoped lang="less">
.section-error {
& header {
background: rgba(0,0,0,0.3);
padding: 50px;
padding-bottom: 25px;
& .title {
font-size: 32px;
letter-spacing: 0.05em;
text-transform: uppercase;
margin-bottom: 15px;
font-family: 'Oswald', sans-serif;
}
& .actions {
display: grid;
grid-template-columns: auto auto auto auto 1fr;
grid-gap: 15px;
}
}
& .error-content {
padding: 25px 50px;
line-height: 1.5em;
display: flex;
}
}
</style>
\ No newline at end of file
...@@ -30,6 +30,10 @@ ...@@ -30,6 +30,10 @@
</template> </template>
</SongRow> </SongRow>
<div class="loading" v-if="!apiFinished">
<Loading />
</div>
<DeleteOverlay v-if="showDeleteOverlay" v-bind:deleteFiles="deleteFiles" /> <DeleteOverlay v-if="showDeleteOverlay" v-bind:deleteFiles="deleteFiles" />
</section> </section>
</template> </template>
...@@ -46,9 +50,10 @@ ...@@ -46,9 +50,10 @@
import SSAPI from '@/modules/module.api.js'; import SSAPI from '@/modules/module.api.js';
import SRXD from '@/modules/module.srxd.js'; import SRXD from '@/modules/module.srxd.js';
import DeleteOverlay from '@/components/Overlays/DeleteOverlay.vue';
import SongRow from '@/components/Song/SongRow.vue'; import SongRow from '@/components/Song/SongRow.vue';
import SongLocalItem from '@/components/Song/SongLocalItem.vue'; import SongLocalItem from '@/components/Song/SongLocalItem.vue';
import Loading from '@/components/Loading.vue';
import DeleteOverlay from '@/components/Overlays/DeleteOverlay.vue';
export default { export default {
name: 'Library', name: 'Library',
...@@ -57,12 +62,15 @@ ...@@ -57,12 +62,15 @@
librarySongs: [], librarySongs: [],
showDeleteOverlay: false, showDeleteOverlay: false,
deleteFiles: [], deleteFiles: [],
hasUnusedFiles: false hasUnusedFiles: false,
apiFinished: false,
useAPIForLibrary: false
} }
}, },
components: { components: {
SongRow, SongRow,
SongLocalItem, SongLocalItem,
Loading,
DeleteOverlay DeleteOverlay
}, },
mounted: function() { mounted: function() {
...@@ -96,32 +104,46 @@ ...@@ -96,32 +104,46 @@
this.$data.hasUnusedFiles = false; this.$data.hasUnusedFiles = false;
this.$data.librarySongs = []; this.$data.librarySongs = [];
this.$data.apiFinished = false;
await ssapi.ping().then((data) => {
this.$data.useAPIForLibrary = true;
console.log(data);
}).catch((error) => {
console.log(error);
this.$data.useAPIForLibrary = false;
console.log("ERROR WHILE PINGING API");
});
// Load local .srtb // Load local .srtb
glob(path.join(userSettings.get('gameDirectory'), "*.srtb"), (error, files) => { glob(path.join(userSettings.get('gameDirectory'), "*.srtb"), (error, files) => {
files.forEach((file) => { files.forEach((file) => {
// Get Detail Data // Get Detail Data
let songDetail = this.getSongDetail(file); this.getSongDetail(file).then((songDetail) => {
let songCover = glob.sync(path.join(userSettings.get('gameDirectory'), "AlbumArt", songDetail.coverReference + ".*"))[0]; let librarySong = {};
let songSpinShareReference = false; let fileReference = false;
if(file.split("/")[file.split("/").length - 1].replace(".srtb", "").includes("spinshare_")) { if(path.basename(file).includes("spinshare_")) {
songSpinShareReference = file.split("/")[file.split("/").length - 1].replace(".srtb", ""); fileReference = path.basename(file).replace(".srtb", "");
} }
if(songCover) { let songCover = glob.sync(path.join(userSettings.get('gameDirectory'), "AlbumArt", songDetail.coverReference + ".*"))[0];
songCover = "data:image/jpg;base64," + fs.readFileSync(songCover, { encoding: 'base64' });
} if(songCover) {
songCover = "data:image/jpg;base64," + fs.readFileSync(songCover, { encoding: 'base64' });
let librarySong = { }
file: file,
detail: songDetail, librarySong = {
cover: songCover, file: file,
modifiedDate: fs.statSync(file).mtime, detail: songDetail,
isSpinShare: songSpinShareReference cover: songCover,
}; modifiedDate: fs.statSync(file).mtime,
isSpinShare: fileReference
this.$data.librarySongs.push(librarySong); };
this.$data.librarySongs.push(librarySong);
this.$data.apiFinished = true;
});
}); });
// Order library by modifiedDate // Order library by modifiedDate
...@@ -146,10 +168,12 @@ ...@@ -146,10 +168,12 @@
install: function() { install: function() {
this.$parent.$parent.$emit('install'); this.$parent.$parent.$emit('install');
}, },
getSongDetail: function(filePath) { getSongDetail: async function(filePath) {
let srtbContent = JSON.parse( fs.readFileSync(filePath) ); let ssapi = new SSAPI(process.env.NODE_ENV === 'development');
let trackInfo = {}; let trackInfo = {};
let fileReference = path.basename(filePath).replace(".srtb", "");
let srtbContent = JSON.parse( fs.readFileSync(filePath) );
let stringValueContainers = srtbContent['largeStringValuesContainer'].values; let stringValueContainers = srtbContent['largeStringValuesContainer'].values;
stringValueContainers.forEach((stringValueContainer) => { stringValueContainers.forEach((stringValueContainer) => {
...@@ -163,7 +187,7 @@ ...@@ -163,7 +187,7 @@
}; };
} }
}); });
return trackInfo; return trackInfo;
}, },
getConnectedFiles: function(srtbFilePath) { getConnectedFiles: function(srtbFilePath) {
......
...@@ -74,7 +74,11 @@ ...@@ -74,7 +74,11 @@
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 {
this.$router.push({ name: 'Error', params: { errorCode: data.status } });
} }
}).catch(() => {
this.$router.push({ name: 'Error', params: { errorCode: 500 } });
}); });
}, },
search: function() { search: function() {
...@@ -97,7 +101,11 @@ ...@@ -97,7 +101,11 @@
this.$data.searchResultsSongs = data.data.songs; this.$data.searchResultsSongs = data.data.songs;
this.$data.apiFinished = true; this.$data.apiFinished = true;
} else {
this.$router.push({ name: 'Error', params: { errorCode: data.status } });
} }
}).catch(() => {
this.$router.push({ name: 'Error', params: { errorCode: 500 } });
}); });
} else { } else {
this.$data.searchResultsUsers = []; this.$data.searchResultsUsers = [];
......
...@@ -170,35 +170,47 @@ ...@@ -170,35 +170,47 @@
let userSettings = new UserSettings(); let userSettings = new UserSettings();
ssapi.getSongDetail(this.$route.params.id).then((data) => { ssapi.getSongDetail(this.$route.params.id).then((data) => {
this.$data.id = data.data.id; if(data.status == 200) {
this.$data.cover = data.data.paths.cover; this.$data.id = data.data.id;
this.$data.title = data.data.title; this.$data.cover = data.data.paths.cover;
this.$data.subtitle = data.data.subtitle; this.$data.title = data.data.title;
this.$data.artist = data.data.artist; this.$data.subtitle = data.data.subtitle;
this.$data.charter = data.data.charter; this.$data.artist = data.data.artist;
this.$data.hasEasyDifficulty = data.data.hasEasyDifficulty; this.$data.charter = data.data.charter;
this.$data.hasNormalDifficulty = data.data.hasNormalDifficulty; this.$data.hasEasyDifficulty = data.data.hasEasyDifficulty;
this.$data.hasHardDifficulty = data.data.hasHardDifficulty; this.$data.hasNormalDifficulty = data.data.hasNormalDifficulty;
this.$data.hasExpertDifficulty = data.data.hasExtremeDifficulty; this.$data.hasHardDifficulty = data.data.hasHardDifficulty;
this.$data.hasXDDifficulty = data.data.hasXDDifficulty; this.$data.hasExpertDifficulty = data.data.hasExtremeDifficulty;
if(data.data.tags != "") { this.$data.hasXDDifficulty = data.data.hasXDDifficulty;
this.$data.tags = data.data.tags; if(data.data.tags != "") {
this.$data.tags = data.data.tags;
}
this.$data.previewPath = data.data.paths.ogg;
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;
this.$data.fileReference = data.data.fileReference;
// Check if Song is already installed by searching for the srtb file
this.$data.isInstalled = fs.existsSync(path.join(userSettings.get('gameDirectory'), this.$data.fileReference + ".srtb"));
ssapi.getUserDetail(data.data.uploader).then((data) => {
if(data.status == 200) {
this.$data.uploader = data.data;
this.$data.apiFinished = true;
} else {
this.$router.push({ name: 'Error', params: { errorCode: data.status } });
}
}).catch((error) => {
this.$router.push({ name: 'Error', params: { errorCode: 500 } });
});
} else {
this.$router.push({ name: 'Error', params: { errorCode: data.status } });
} }
this.$data.previewPath = data.data.paths.ogg; }).catch((error) => {
this.$data.downloadPath = data.data.paths.zip; this.$router.push({ name: 'Error', params: { errorCode: 500 } });
this.$data.downloads = data.data.downloads;
this.$data.views = data.data.views;
this.$data.description = data.data.description;
this.$data.uploadDate = data.data.uploadDate;
this.$data.fileReference = data.data.fileReference;
// Check if Song is already installed by searching for the srtb file
this.$data.isInstalled = fs.existsSync(path.join(userSettings.get('gameDirectory'), this.$data.fileReference + ".srtb"));
ssapi.getUserDetail(data.data.uploader).then((data) => {
this.$data.uploader = data.data;
this.$data.apiFinished = true;
});
}); });
this.$on('closePlayOverlay', () => { this.$on('closePlayOverlay', () => {
......
...@@ -55,9 +55,13 @@ ...@@ -55,9 +55,13 @@
let ssapi = new SSAPI(process.env.NODE_ENV === 'development'); let ssapi = new SSAPI(process.env.NODE_ENV === 'development');
ssapi.getSongDetailReviews(this.$route.params.id).then((data) => { ssapi.getSongDetailReviews(this.$route.params.id).then((data) => {
this.$data.apiFinished = true; if(data.status == 200) {
this.$data.reviewAverage = data.data.average; this.$data.apiFinished = true;
this.$data.reviews = data.data.reviews ? data.data.reviews : []; this.$data.reviewAverage = data.data.average;
this.$data.reviews = data.data.reviews ? data.data.reviews : [];
} else {
this.$router.push({ name: 'Error', params: { errorCode: data.status } });
}
}); });
}, },
components: { components: {
......
...@@ -48,8 +48,12 @@ ...@@ -48,8 +48,12 @@
let ssapi = new SSAPI(process.env.NODE_ENV === 'development'); let ssapi = new SSAPI(process.env.NODE_ENV === 'development');
ssapi.getSongDetailSpinPlays(this.$route.params.id).then((data) => { ssapi.getSongDetailSpinPlays(this.$route.params.id).then((data) => {
this.$data.apiFinished = true; if(data.status == 200) {
this.$data.spinPlays = data.data.spinPlays ? data.data.spinPlays : []; this.$data.apiFinished = true;
this.$data.spinPlays = data.data.spinPlays ? data.data.spinPlays : [];
} else {
this.$router.push({ name: 'Error', params: { errorCode: data.status } });
}
}); });
}, },
components: { components: {
......
<template> <template>
<div class="frontpage"> <div class="frontpage">
<Stream v-bind="streamStatus" />
<div class="staff-promos"> <div class="staff-promos">
<StaffPromoPlaceholder <StaffPromoPlaceholder
v-if="isPromoLoading" v-if="isPromoLoading"
...@@ -12,11 +14,19 @@ ...@@ -12,11 +14,19 @@
v-bind="staffPromo" /> v-bind="staffPromo" />
</div> </div>
<Stream v-bind="streamStatus" /> <div class="social-buttons">
<div v-on:click="OpenDiscord()" class="item item-discord"><i class="mdi mdi-discord"></i></div>
<div v-on:click="OpenTwitter()" class="item item-twitter"><i class="mdi mdi-twitter"></i></div>
<div v-on:click="OpenYouTube()" class="item item-youtube"><i class="mdi mdi-youtube"></i></div>
<div v-on:click="OpenTwitch()" class="item item-twitch"><i class="mdi mdi-twitch"></i></div>
</div>
</div> </div>
</template> </template>
<script> <script>
import { remote } from 'electron';
const { shell } = remote;
import SSAPI from '@/modules/module.api.js'; import SSAPI from '@/modules/module.api.js';
import StaffPromo from '@/components/Startup/StaffPromo.vue'; import StaffPromo from '@/components/Startup/StaffPromo.vue';
import StaffPromoPlaceholder from '@/components/Startup/StaffPromoPlaceholder.vue'; import StaffPromoPlaceholder from '@/components/Startup/StaffPromoPlaceholder.vue';
...@@ -40,7 +50,10 @@ ...@@ -40,7 +50,10 @@
}); });
ssapi.getStreamStatus().then((data) => { ssapi.getStreamStatus().then((data) => {
this.$data.streamStatus = data; // Gracefully fail if Twitch API is ratelimited
if(data.status == 200) {
this.$data.streamStatus = data;
}
}); });
}, },
components: { components: {
...@@ -49,6 +62,18 @@ ...@@ -49,6 +62,18 @@
Stream Stream
}, },
methods: { methods: {
OpenDiscord: function() {
shell.openExternal("https://spinsha.re/discord");
},
OpenTwitter: function() {
shell.openExternal("https://twitter.com/WeAreSpinShare");
},
OpenYouTube: function() {
shell.openExternal("https://www.youtube.com/channel/UCh7ftpzIqlHw5p_-HXHwMBw");
},
OpenTwitch: function() {
shell.openExternal("https://twitch.tv/SpinShare");
}
} }
} }
</script> </script>
...@@ -65,4 +90,42 @@ ...@@ -65,4 +90,42 @@
display: none; display: none;
} }
} }
.social-buttons {
display: grid;
width: 550px;
margin: 0 auto;
margin-top: 25px;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-gap: 15px;
& .item {
height: 75px;
display: flex;
justify-content: center;
align-items: center;
font-size: 32px;
background: rgba(255,255,255,0.2);
border-radius: 6px;
transition: 0.2s ease-in-out all;
&.item-discord {
background: linear-gradient(135deg, #99aab5, #7289da);
}
&.item-twitter {
background: linear-gradient(135deg, #d0e6f7, #1da1f2);
}
&.item-youtube {
background: linear-gradient(135deg, #ff0000, #c20000);
}
&.item-twitch {
background: linear-gradient(135deg, #b9a3e3, #6441a5);
}
&:hover {
transform: scale(1.1);
box-shadow: 0px 4px 16px rgba(0,0,0,0.4);
cursor: pointer;
}
}
}
</style> </style>
\ No newline at end of file
...@@ -66,13 +66,17 @@ ...@@ -66,13 +66,17 @@
let ssapi = new SSAPI(process.env.NODE_ENV === 'development'); let ssapi = new SSAPI(process.env.NODE_ENV === 'development');
ssapi.getUserDetail(this.$route.params.id).then((data) => { ssapi.getUserDetail(this.$route.params.id).then((data) => {
this.$data.id = data.data.id; if(data.status == 200) {
this.$data.username = data.data.username; this.$data.id = data.data.id;
this.$data.isVerified = data.data.isVerified; this.$data.username = data.data.username;
this.$data.isPatreon = data.data.isPatreon; this.$data.isVerified = data.data.isVerified;
this.$data.avatar = data.data.avatar; this.$data.isPatreon = data.data.isPatreon;
this.$data.songs = data.data.songs; this.$data.avatar = data.data.avatar;
this.$data.apiFinished = true; this.$data.songs = data.data.songs;
this.$data.apiFinished = true;
} else {
this.$router.push({ name: 'Error', params: { errorCode: data.status } });
}
}); });
}, },
methods: { methods: {
......
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