Commit 1bca3c81 authored by SpinShare's avatar SpinShare

added library

parent e501f626
......@@ -5,7 +5,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
<title>SpinSha.re</title>
<!-- Styles -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&display=swap" />
......
......@@ -105,71 +105,19 @@
&:focus {
outline: 0;
}
}
.user-row {
display: grid;
grid-template-rows: auto 1fr;
grid-gap: 5px;
& .user-header {
display: grid;
grid-template-columns: 1fr auto;
& .row-title {
letter-spacing: 0.25em;
font-size: 14px;
font-weight: bold;
text-transform: uppercase;
&.row-title-noactions {
margin: 10px 0px;
}
}
}
& .user-list {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 15px;
}
}
.user-item {
background: #383c3f;
transition: 0.2s ease-in-out transform, 0.2s ease-in-out box-shadow;
overflow: hidden;
border-radius: 6px;
display: grid;
padding: 10px;
grid-gap: 15px;
grid-template-columns: 32px 1fr;
& .user-avatar {
background: rgba(255,255,255,0.1);
background-size: cover;
background-position: center;
width: 32px;
height: 32px;
border-radius: 32px;
}
& .user-metadata {
display: flex;
align-items: center;
&.button-label {
background: transparent;
& .user-name {
font-weight: bold;
overflow: hidden;
white-space: nowrap;
&:hover {
background: rgba(255, 255, 255, 0.2);
color: #fff;
}
& .user-badge {
font-size: 18px;
margin-left: 10px;
&:active {
background: #fff;
color: #222;
cursor: pointer;
}
}
&:hover {
transform: scale(1.1);
cursor: pointer;
box-shadow: 0px 4px 20px 5px rgba(0, 0, 0, 0.4);
}
}
</style>
<template>
<div class="song-item" v-on:click="install()">
<div class="song-cover">
<div class="song-icon"><i class="mdi mdi-folder-music"></i></div>
</div>
<div class="song-metadata">
<div class="song-title">Install</div>
<div class="song-artist">Install a local .zip</div>
</div>
</div>
</template>
<script>
import { remote } from 'electron';
const { clipboard } = remote;
export default {
name: 'SongInstallItem',
data: function() {
return {
}
},
mounted: function() {
},
methods: {
install: function(e) {
}
}
}
</script>
<style scoped lang="less">
.song-item {
background: rgba(255,255,255,0.1);
transition: 0.2s ease-in-out transform, 0.2s ease-in-out box-shadow;
overflow: hidden;
border-radius: 6px;
& .song-cover {
background: rgba(255,255,255,0.1);
background-size: cover;
width: 100%;
padding-top: 100%;
position: relative;
background-position: center;
& .song-icon {
position: absolute;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
display: flex;
justify-content: center;
align-items: center;
font-size: 32px;
}
}
& .song-metadata {
padding: 15px;
& .song-title {
font-weight: bold;
overflow: hidden;
white-space: nowrap;
}
& .song-artist {
margin-top: 5px;
opacity: 0.6;
overflow: hidden;
white-space: nowrap;
}
& .song-difficulties {
margin-top: 10px;
height: 20px;
display: flex;
& img {
height: 18px;
margin-right: 10px;
opacity: 0.3;
&.active {
opacity: 1;
}
}
}
}
&:hover {
transform: scale(1.1);
cursor: pointer;
box-shadow: 0px 4px 20px 5px rgba(0, 0, 0, 0.4);
& .song-cover {
& .song-charter {
opacity: 1;
}
}
}
}
</style>
<template>
<div :class="'song-item-local ' + (isSpinShare ? '' : 'song-item-onlylocal')" v-on:contextmenu="showContextMenu($event)">
<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>{{ detail.charter ? detail.charter : "Unknown" }}</span></div>
</div>
</div>
<div class="song-metadata">
<div class="song-title">{{ detail.title ? detail.title : "Untitled" }}</div>
<div class="song-artist">{{ detail.artist ? detail.artist : "Unknown" }}</div>
</div>
</div>
</template>
<script>
import { remote } from 'electron';
const { clipboard } = remote;
export default {
name: 'SongLocalItem',
props: [
'id',
'detail',
'cover',
'isSpinShare'
],
data: function() {
return {
isContextMenuActive: false
}
},
mounted: function() {
},
methods: {
showContextMenu: function(e) {
this.$root.$emit('showContextMenu', {
x: e.pageX,
y: e.pageY,
items: [
{ icon: "delete", title: "Delete", method: function() { alert('TODO') }.bind(this) }
]});
}
}
}
</script>
<style scoped lang="less">
.song-item-local {
background: rgba(255,255,255,0.1);
transition: 0.2s ease-in-out transform, 0.2s ease-in-out box-shadow;
overflow: hidden;
border-radius: 6px;
& .song-cover {
background: rgba(255,255,255,0.1);
background-size: cover;
width: 100%;
padding-top: 100%;
position: relative;
background-position: center;
& .song-charter {
position: absolute;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
background: linear-gradient(180deg, rgba(0,0,0,0.2), rgba(0,0,0,0.8));
opacity: 0;
padding: 15px;
overflow: hidden;
display: grid;
transition: 0.2s ease-in-out opacity;
grid-template-columns: auto 1fr;
grid-gap: 10px;
align-items: flex-end;
& .song-charter-info {
display: grid;
align-items: center;
& .mdi {
font-size: 18px;
}
& span {
font-size: 12px;
color: transparent;
transition: 0.2s ease-in-out color;
overflow: hidden;
white-space: nowrap;
}
}
}
}
& .song-metadata {
padding: 15px;
& .song-title {
font-weight: bold;
overflow: hidden;
white-space: nowrap;
}
& .song-artist {
margin-top: 5px;
opacity: 0.6;
overflow: hidden;
white-space: nowrap;
}
}
&:not(.song-item-onlylocal):hover {
transform: scale(1.1);
cursor: pointer;
box-shadow: 0px 4px 20px 5px rgba(0, 0, 0, 0.4);
& .song-cover {
& .song-charter {
opacity: 1;
}
}
}
&.song-item-onlylocal {
opacity: 0.6;
}
}
</style>
<template>
<div class="song-row song-row-new">
<div class="song-header">
<div class="row-title">{{ title }}</div>
<div class="row-controls">
<div :class="'row-title ' + (noactions ? 'row-title-noactions' : '')">{{ title }}</div>
<div class="row-controls" v-if="!noactions">
<div class="item disabled row-controls-previous"><i class="mdi mdi-chevron-left"></i></div>
<div class="item row-controls-next"><i class="mdi mdi-chevron-right"></i></div>
</div>
......@@ -20,73 +20,74 @@
export default {
name: 'SongRow',
props: [
'title'
'title',
'noactions'
]
}
</script>
<style scoped lang="less">
.song-row {
display: grid;
grid-template-rows: auto 1fr;
grid-gap: 5px;
& .song-header {
.song-row {
display: grid;
grid-template-columns: 1fr auto;
& .row-title {
letter-spacing: 0.25em;
font-size: 14px;
font-weight: bold;
text-transform: uppercase;
grid-template-rows: auto 1fr;
grid-gap: 5px;
&.row-title-noactions {
margin: 10px 0px;
}
}
& .row-controls {
& .song-header {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 15px;
grid-template-columns: 1fr auto;
& .item {
width: 28px;
height: 28px;
font-size: 22px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
& .row-title {
letter-spacing: 0.25em;
font-size: 14px;
font-weight: bold;
text-transform: uppercase;
&.disabled {
opacity: 0.4;
&.row-title-noactions {
margin: 10px 0px;
}
}
& .row-controls {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 15px;
& .item {
width: 28px;
height: 28px;
font-size: 22px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
&:not(.disabled):hover {
background: rgba(255,255,255,0.2);
cursor: pointer;
&.disabled {
opacity: 0.4;
}
&:not(.disabled):hover {
background: rgba(255,255,255,0.2);
cursor: pointer;
}
}
}
}
}
& .song-list {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
grid-gap: 15px;
}
& .song-list-noresults {
display: none;
background: rgba(255,255,255,0.1);
border-radius: 6px;
padding: 25px;
opacity: 0.6;
text-align: center;
& .song-list {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
grid-gap: 15px;
}
& .song-list-noresults {
display: none;
background: rgba(255,255,255,0.1);
border-radius: 6px;
padding: 25px;
opacity: 0.6;
text-align: center;
&.active {
display: block;
&.active {
display: block;
}
}
}
}
}
</style>
<template>
<!-- <div class="song-item" v-on:contextmenu="showContextMenu($event)">
<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">
<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> -->
<div class="user-item">
<div class="user-avatar" :style="'background-image: url(' + avatar + '), url(' + require('@/assets/img/defaultAvatar.jpg') + ');'"></div>
<div class="user-metadata">
<div class="user-username">{{ username }}</div>
</div>
</div>
</template>
<script>
import { remote } from 'electron';
const { clipboard } = remote;
export default {
name: 'Useritem',
props: [
'id',
'avatar',
'username',
'isPatreon',
'isVerified'
],
data: function() {
return {
isContextMenuActive: false
}
},
mounted: function() {
},
methods: {
showContextMenu: function(e) {
this.$root.$emit('showContextMenu', {
x: e.pageX,
y: e.pageY,
items: [
{ icon: "eye", title: "Open", method: function() { alert('TODO') }.bind(this) },
{ icon: "link", title: "Copy Link", method: function() { clipboard.writeText('https://spinsha.re/song/' + this.$props.id) }.bind(this) },
{ icon: "download", title: "Download", method: function() { this.$root.$emit('download', this.$props.isVerified); }.bind(this) }
]});
}
}
}
</script>
<style scoped lang="less">
.user-item {
background: #383c3f;
transition: 0.2s ease-in-out transform, 0.2s ease-in-out box-shadow;
overflow: hidden;
border-radius: 6px;
display: grid;
padding: 10px;
grid-gap: 15px;
grid-template-columns: 32px 1fr;
& .user-avatar {
background: rgba(255,255,255,0.1);
background-size: cover;
background-position: center;
width: 32px;
height: 32px;
border-radius: 32px;
}
& .user-metadata {
display: flex;
align-items: center;
& .user-name {
font-weight: bold;
overflow: hidden;
white-space: nowrap;
}
& .user-badge {
font-size: 18px;
margin-left: 10px;
}
}
&:hover {
transform: scale(1.1);
cursor: pointer;
box-shadow: 0px 4px 20px 5px rgba(0, 0, 0, 0.4);
}
}
</style>
<template>
<div class="user-row">
<div class="user-header">
<div :class="'row-title ' + (noactions ? 'row-title-noactions' : '')">{{ title }}</div>
<div class="row-controls" v-if="!noactions">
<div class="item disabled row-controls-previous"><i class="mdi mdi-chevron-left"></i></div>
<div class="item row-controls-next"><i class="mdi mdi-chevron-right"></i></div>
</div>
</div>
<div class="user-list">
<slot></slot>
</div>
</div>
</template>
<script>
import { remote } from 'electron';
const { shell } = remote;
export default {
name: 'UserRow',
props: [
'title',
'noactions'
]
}
</script>
<style scoped lang="less">
.user-row {
display: grid;
grid-template-rows: auto 1fr;
grid-gap: 5px;
& .user-header {
display: grid;
grid-template-columns: 1fr auto;
& .row-title {
letter-spacing: 0.25em;
font-size: 14px;
font-weight: bold;
text-transform: uppercase;
&.row-title-noactions {
margin: 10px 0px;
}
}
}
& .user-list {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 15px;
}
}
</style>
......@@ -122,6 +122,22 @@ class SSAPI {
});
}
async searchAll() {
let apiPath = this.apiBase + "searchAll";
let supportedVersion = this.supportedVersion;
return axios.get(apiPath)
.then(function(response) {
if(response.data.version !== supportedVersion) {
throw new Error("Client is outdated!");
}
return response.data;
}).catch(function(error) {
throw new Error(error);
});
}
async search(_searchQuery) {
let apiPath = this.apiBase + "search/" + _searchQuery;
let supportedVersion = this.supportedVersion;
......
const glob = require('glob');
const fs = require('fs');
const path = require('path');
const rimraf = require('rimraf');
const unzipper = require('unzipper');
const uniqid = require('uniqid');
const UserSettings = require('./module.usersettings');
class SRXD {
constructor() {
......@@ -9,6 +12,8 @@ class SRXD {
this.srtbLocation = "";
this.songTrackInfo = {};
this.songLocation = "";
this.userSettings = new UserSettings();
}
// Extract a local backup folder
......@@ -79,11 +84,11 @@ class SRXD {
this.songLocation = "";
}
getSongDetail(srtbPath) {
getSongDetail(rootPath, songPath) {
let srtbPath = path.join(rootPath, songPath);
let srtbFile = JSON.parse( fs.readFileSync(srtbPath) );
let songTrackInfo = "";
let songOggInfo = "";
srtbFile.largeStringValuesContainer.values.forEach(function(value) {
if(value.key == "SO_TrackInfo_TrackInfo") {
......@@ -114,10 +119,10 @@ class SRXD {
else {return [];}
}
getSongCover(fileName) {
let fileExtension = this.getFileExtension(fileName, path.join(userSettings.get('gameDirectory'), "AlbumArt") );
let fileExtension = this.getFileExtension(fileName, path.join(this.userSettings.get('gameDirectory'), "AlbumArt") );
if(fileExtension.length > 0) {
let finalPath = path.join(userSettings.get('gameDirectory'), "AlbumArt", fileExtension[0]);
let finalPath = path.join(this.userSettings.get('gameDirectory'), "AlbumArt", fileExtension[0]);
let base64Data = "data:image/jpg;base64," + fs.readFileSync(finalPath, { encoding: 'base64' });
......@@ -133,9 +138,9 @@ class SRXD {
//Gets directory of files to delete
getSongAssetDirectory(fileName, fileType) {
let fileExtension = this.getFileExtension(fileName, path.join(userSettings.get('gameDirectory'), fileType));
let fileExtension = this.getFileExtension(fileName, path.join(this.userSettings.get('gameDirectory'), fileType));
if (fileExtension.join() != '') {
let finalPath = path.join(userSettings.get('gameDirectory'), fileType, fileExtension.join());
let finalPath = path.join(this.userSettings.get('gameDirectory'), fileType, fileExtension.join());
return finalPath;
}
else {return fileName;}
......
const { electron, remote } = require('electron');
const app = remote.app;
const path = require('path');
const fs = require('fs');
class UserSettings {
constructor() {
let defaults = {
showExplicit: false,
gameDirectory: detectGameDirectory(),
language: app.getLocale()
};
const userDataPath = app.getPath('userData');
this.path = path.join(userDataPath, 'UserSettings.json');
this.data = parseDataFile(this.path, defaults);
}
get(key) {
return this.data[key];
}
set(key, val) {
this.data[key] = val;
this.write();
}
clear(key) {
this.data[key] = null;
this.write();
}
write() {
fs.writeFileSync(this.path, JSON.stringify(this.data));
}
}
function parseDataFile(filePath, defaults) {
try {
return JSON.parse(fs.readFileSync(filePath));
} catch(error) {
return defaults;
}
}
// TODO: Mac/Linux Support
function detectGameDirectory() {
if(process.platform == "win32") {
return path.join(app.getPath("userData"), "../..", "LocalLow", "Super Spin Digital", "Spin Rhythm XD", "Custom");
} else {
console.error("Unsupported system");
}
}
module.exports = UserSettings;
\ No newline at end of file
<template>
<section class="section-library">
LIBRARY
<SongRow title="Installed Songs" noactions="true">
<SongInstallItem />
<SongLocalItem
v-for="song in librarySongs"
v-bind:key="song.detail.id"
v-bind="song" />
</SongRow>
</section>
</template>
<script>
import glob from 'glob';
import fs from 'fs';
import path from 'path';
import UserSettings from '@/modules/module.usersettings.js';
import SRXD from '@/modules/module.srxd.js';
import SongRow from '@/components/Song/SongRow.vue';
import SongLocalItem from '@/components/Song/SongLocalItem.vue';
import SongInstallItem from '@/components/Song/SongInstallItem.vue';
export default {
name: 'Library',
data: function() {
return {
librarySongs: []
}
},
components: {
SongRow,
SongLocalItem,
SongInstallItem
},
mounted: function() {
this.refreshLibrary();
},
methods: {
refreshLibrary: function() {
let userSettings = new UserSettings();
this.$data.librarySongs = [];
// Load local .srtb
glob(path.join(userSettings.get('gameDirectory'), "*.srtb"), (error, files) => {
files.forEach((file) => {
// Get Detail Data
let songDetail = this.getSongDetail(file);
let songCover = glob.sync(path.join(userSettings.get('gameDirectory'), "AlbumArt", songDetail.coverReference + ".*"))[0];
let songSpinShareReference = false;
if(file.split("/")[file.split("/").length - 1].replace(".srtb", "").includes("spinshare_")) {
songSpinShareReference = file.split("/")[file.split("/").length - 1].replace(".srtb", "");
}
if(songCover) {
songCover = "data:image/jpg;base64," + fs.readFileSync(songCover, { encoding: 'base64' });
}
let librarySong = {
detail: songDetail,
cover: songCover,
isSpinShare: songSpinShareReference
};
this.$data.librarySongs.push(librarySong)
});
});
},
getSongDetail: function(filePath) {
let srtbContent = JSON.parse( fs.readFileSync(filePath) );
let trackInfo = {};
let stringValueContainers = srtbContent['largeStringValuesContainer'].values;
stringValueContainers.forEach((stringValueContainer) => {
if(stringValueContainer.key == "SO_TrackInfo_TrackInfo") {
let rawTrackInfo = JSON.parse( stringValueContainer.val );
trackInfo = {
title: rawTrackInfo.title,
artist: rawTrackInfo.artistName,
charter: rawTrackInfo.charter,
coverReference: rawTrackInfo.albumArtReference.assetName
};
}
});
return trackInfo;
}
}
}
</script>
......
......@@ -4,22 +4,86 @@
<div class="show-all">
<div class="button button-label" v-on:click="searchAll()" locale="">Show all</div>
</div>
<input type="search" placeholder="Search for songs, tags &amp; profiles..." localeplaceholder="" v-on:change="search(this.value)">
<input type="search" placeholder="Search for songs, tags &amp; profiles..." v-on:input="search()" v-model="searchQuery">
</div>
<div class="search-results">
<UserRow title="Users" noactions="true" v-if="searchResultsUsers.length > 0">
<UserItem
v-for="user in searchResultsUsers"
v-bind:key="user.id"
v-bind="user" />
</UserRow>
<SongRow title="Songs" noactions="true" v-if="searchResultsSongs.length > 0">
<SongItem
v-for="song in searchResultsSongs"
v-bind:key="song.id"
v-bind="song" />
</SongRow>
<div class="search-results-noresults" v-if="searchResultsUsers.length == 0 && searchResultsSongs.length == 0 && apiFinished">
<div class="noresults-title">{{ searchQuery }}</div>
<div class="noresults-text">Your search did not match any songs or users. Make sure, that all words are spelled correctly or try a different query.</div>
</div>
</div>
</section>
</template>
<script>
import SSAPI from '@/modules/module.api.js';
import UserRow from '@/components/User/UserRow.vue';
import UserItem from '@/components/User/UserItem.vue';
import SongRow from '@/components/Song/SongRow.vue';
import SongItem from '@/components/Song/SongItem.vue';
export default {
name: 'Search',
components: {
UserRow,
UserItem,
SongRow,
SongItem
},
data: function() {
return {
searchQuery: "",
searchResultsUsers: [],
searchResultsSongs: [],
apiFinished: false
}
},
methods: {
searchAll: function() {
let ssapi = new SSAPI(process.env.NODE_ENV === 'development');
this.$data.searchQuery == "";
this.$data.apiFinished = false;
ssapi.searchAll().then((data) => {
if(data.status == 200) {
this.$data.searchResultsUsers = data.data.users;
this.$data.searchResultsSongs = data.data.songs;
this.$data.apiFinished = true;
}
});
},
search: function(searchQuery) {
search: function() {
let ssapi = new SSAPI(process.env.NODE_ENV === 'development');
this.$data.apiFinished = false;
if(this.$data.searchQuery != "") {
ssapi.search(this.$data.searchQuery).then((data) => {
if(data.status == 200) {
this.$data.searchResultsUsers = data.data.users;
this.$data.searchResultsSongs = data.data.songs;
this.$data.apiFinished = true;
}
});
} else {
this.$data.searchResultsUsers = [];
this.$data.searchResultsSongs = [];
this.$data.apiFinished = false;
}
}
}
}
......@@ -37,20 +101,28 @@
border-radius: 4px;
display: grid;
grid-template-columns: auto 1fr;
& .show-all {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
padding: 0px 10px;
}
input {
width: 100%;
font-family: 'Open Sans', sans-serif;
font-size: 14px;
background: transparent;
color: #fff;
background: rgba(255,255,255,0.2);
border-radius: 4px;
padding: 14px 28px;
border: 0px;
transition: 0.2s ease-in-out all;
&:hover {
background: rgba(255,255,255,0.4);
background: rgba(255,255,255,0.2);
color: #fff;
}
&:focus {
......@@ -67,26 +139,18 @@
grid-gap: 25px;
& .search-results-users {
display: none;
&.active {
display: grid;
}
display: grid;
}
& .search-results-songs {
display: none;
&.active {
display: grid;
}
display: grid;
}
& .search-results-noresults {
background: rgba(255,255,255,0.1);
border-radius: 6px;
padding: 25px;
display: none;
display: block;
& .noresults-title {
font-size: 24px;
......@@ -96,10 +160,6 @@
& .noresults-text {
opacity: 0.6;
}
&.active {
display: block;
}
}
}
}
......
......@@ -3,7 +3,8 @@
<div class="staff-promos">
<StaffPromoPlaceholder
v-if="isPromoLoading"
v-for="n in 2" />
v-for="n in 2"
v-bind:key="n" />
<StaffPromo
v-if="!isPromoLoading"
v-for="staffPromo in staffPromos"
......@@ -15,7 +16,8 @@
title="New Songs">
<SongItemPlaceholder
v-if="isNewSongsLoading"
v-for="n in 6" />
v-for="n in 6"
v-bind:key="n" />
<SongItem
v-if="!isNewSongsLoading"
v-for="song in newSongs"
......@@ -27,7 +29,8 @@
title="Popular Songs">
<SongItemPlaceholder
v-if="isPopularSongsLoading"
v-for="n in 6" />
v-for="n in 6"
v-bind:key="n" />
<SongItem
v-if="!isPopularSongsLoading"
v-for="song in popularSongs"
......@@ -60,7 +63,7 @@
}
},
mounted: function() {
let ssapi = new SSAPI(true);
let ssapi = new SSAPI(process.env.NODE_ENV === 'development');
ssapi.getPromos().then((data) => {
this.$data.isPromoLoading = false;
......
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