Commit b736144a authored by SpinShare's avatar SpinShare

new user profile design

parent f923ad92
This diff is collapsed.
...@@ -34,6 +34,7 @@ ...@@ -34,6 +34,7 @@
"vue-i18n": "^8.20.0", "vue-i18n": "^8.20.0",
"vue-observe-visibility": "^0.4.6", "vue-observe-visibility": "^0.4.6",
"vue-router": "^3.3.4", "vue-router": "^3.3.4",
"vue-tilt.js": "^1.1.1",
"vuex": "^3.5.1" "vuex": "^3.5.1"
}, },
"devDependencies": { "devDependencies": {
......
<template>
<div class="card-overlay">
<div class="close" v-on:click="close()"><i class="mdi mdi-close"></i></div>
<div class="overlay-content">
<img :src="card.icon" class="card-icon" v-tilt="{glare: true, 'max-glare': 0.8, reverse: true, max: 15}" />
<div class="card-title">{{ card.title }}</div>
<div class="card-description">{{ card.description }}</div>
<div class="card-given">{{ renderDate(card.givenDateFormat) }}</div>
<div class="button" v-on:click="close()">Close</div>
</div>
</div>
</template>
<script>
import { remote, ipcRenderer } from 'electron';
import { path } from 'path';
import moment from 'moment';
const { shell } = remote;
export default {
name: 'CardOverlay',
props: [
'card'
],
mounted: function() {
ipcRenderer.on("overlays-close", () => {
this.close();
});
},
methods: {
close() {
this.$parent.$emit('cardClose');
},
renderDate( date ) {
return moment( date ).format(this.$t('locale.dateFormat'));
}
}
}
</script>
<style scoped lang="less">
.card-overlay {
display: flex;
position: fixed;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
z-index: 1200;
background: rgba(0,0,0,0.6);
justify-content: center;
align-items: center;
& .close {
font-size: 32px;
color: #fff;
position: absolute;
top: 40px;
right: 40px;
cursor: pointer;
transition: 0.2s ease-in-out opacity;
&:hover {
opacity: 0.6;
}
}
& .overlay-content {
max-width: 320px;
text-align: center;
& img {
width: 200px;
height: 200px;
margin-bottom: 50px;
}
& .card-title {
font-size: 24px;
font-weight: bold;
margin-bottom: 10px;
}
& .card-description {
line-height: 1.5em;
opacity: 0.75;
margin-bottom: 10px;
}
& .card-given {
margin-bottom: 25px;
font-weight: bold;
}
& .button {
display: inline-block;
}
}
}
</style>
...@@ -52,6 +52,9 @@ ...@@ -52,6 +52,9 @@
"userdetail.uploaded.header": "Uploaded Songs", "userdetail.uploaded.header": "Uploaded Songs",
"userdetail.uploaded.noresults": "This user did not upload any songs yet.", "userdetail.uploaded.noresults": "This user did not upload any songs yet.",
"userdetail.actions.reportButton": "Report", "userdetail.actions.reportButton": "Report",
"userdetail.charts.noresults": "This user has no charts yet.",
"userdetail.reviews.noresults": "This user has no reviews yet.",
"userdetail.spinplays.noresults": "This user did not release any SpinPlays yet.",
"settings.header": "Settings", "settings.header": "Settings",
"settings.client.header": "SpinShare", "settings.client.header": "SpinShare",
......
...@@ -4,10 +4,12 @@ import router from './router'; ...@@ -4,10 +4,12 @@ import router from './router';
import store from './store'; import store from './store';
import i18n from './i18n'; import i18n from './i18n';
import moment from 'moment' import moment from 'moment'
import VueTilt from 'vue-tilt.js'
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
import VueObserveVisibility from 'vue-observe-visibility' import VueObserveVisibility from 'vue-observe-visibility'
import { VTooltip, VPopover, VClosePopover } from 'v-tooltip' import { VTooltip, VPopover, VClosePopover } from 'v-tooltip'
Vue.use(VueTilt);
Vue.use(VueObserveVisibility); Vue.use(VueObserveVisibility);
Vue.directive('tooltip', VTooltip); Vue.directive('tooltip', VTooltip);
......
...@@ -5,7 +5,7 @@ class SSAPI { ...@@ -5,7 +5,7 @@ class SSAPI {
if(process.env.NODE_ENV !== 'development') { if(process.env.NODE_ENV !== 'development') {
return false; return false;
} else { } else {
return false; return true;
} }
} }
...@@ -210,6 +210,54 @@ class SSAPI { ...@@ -210,6 +210,54 @@ class SSAPI {
}); });
} }
async getUserCharts(_userId) {
let apiPath = this.apiBase + "user/" + _userId + "/charts";
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 getUserReviews(_userId) {
let apiPath = this.apiBase + "user/" + _userId + "/reviews";
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 getUserSpinPlays(_userId) {
let apiPath = this.apiBase + "user/" + _userId + "/spinplays";
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 searchAll() { async searchAll() {
let apiPath = this.apiBase + "searchAll"; let apiPath = this.apiBase + "searchAll";
let supportedVersion = this.supportedVersion; let supportedVersion = this.supportedVersion;
......
...@@ -13,6 +13,9 @@ import ViewSongDetail from '../views/SongDetail.vue'; ...@@ -13,6 +13,9 @@ import ViewSongDetail from '../views/SongDetail.vue';
import ViewSongDetailReviews from '../views/SongDetailReviews.vue'; 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 ViewUserDetailCharts from '../views/UserDetailCharts.vue';
import ViewUserDetailReviews from '../views/UserDetailReviews.vue';
import ViewUserDetailSpinPlays from '../views/UserDetailSpinPlays.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';
...@@ -74,10 +77,33 @@ const routes = [{ ...@@ -74,10 +77,33 @@ const routes = [{
component: ViewSongDetailSpinPlays component: ViewSongDetailSpinPlays
} }
] ]
},, { }, {
path: '/user/:id', path: '/user/:id',
name: 'UserDetail', name: 'UserDetail',
component: ViewUserDetail component: ViewUserDetail,
redirect: {
name: 'UserDetailCharts'
},
children: [
{
alias: '',
path: 'charts',
name: 'UserDetailCharts',
component: ViewUserDetailCharts
},
{
alias: '',
path: 'reviews',
name: 'UserDetailReviews',
component: ViewUserDetailReviews
},
{
alias: '',
path: 'spinplays',
name: 'UserDetailSpinPlays',
component: ViewUserDetailSpinPlays
}
]
}, { }, {
path: '/settings', path: '/settings',
name: 'Settings', name: 'Settings',
......
This diff is collapsed.
<template>
<section class="section-userdetail-charts">
<SongRow
v-if="apiFinished && charts.length > 0">
<template v-slot:song-list>
<SongItem
v-for="song in charts"
v-bind:key="song.id"
v-bind="song" />
</template>
</SongRow>
<div class="song-list-noresults" v-if="apiFinished && charts.length < 1">
<div class="noresults-text">{{ this.$t('userdetail.charts.noresults') }}</div>
</div>
<Loading v-if="!apiFinished" style="padding: 50px 0px;" />
</section>
</template>
<script>
import { remote } from 'electron';
const { clipboard, shell } = remote;
import SSAPI from '@/modules/module.api.js';
import SongRow from '@/components/Song/SongRow.vue';
import SongItem from '@/components/Song/SongItem.vue';
import Loading from '@/components/Loading.vue';
export default {
name: 'UserDetailCharts',
components: {
SongRow,
SongItem,
Loading,
},
data: function() {
return {
apiFinished: false,
charts: [],
}
},
mounted: function() {
let ssapi = new SSAPI();
ssapi.getUserCharts(this.$route.params.id).then((data) => {
if(data.status == 200) {
this.$data.charts = data.data;
this.$data.apiFinished = true;
} else {
this.$router.push({ name: 'Error', params: { errorCode: data.status } });
}
});
},
methods: {
}
}
</script>
<style scoped lang="less">
.section-userdetail-charts {
padding: 50px;
& .song-list-noresults {
display: block;
background: rgba(255,255,255,0.1);
border-radius: 6px;
padding: 25px;
opacity: 0.6;
text-align: center;
}
}
</style>
\ No newline at end of file
<template>
<section class="section-userdetail-reviews">
<div class="reviews" v-if="apiFinished">
<div class="review-list" v-if="reviews.length > 0">
<router-link :to="{ name: 'SongDetail', params: { id: review.song.id } }" class="review" v-for="review in reviews" :key="review.id">
<div class="metadata">
<div class="avatar" :style="'background-image: url(' + review.user.coverReference + '), url(' + require('@/assets/img/defaultAvatar.jpg') + ');'"></div>
<div class="text">
<div class="username">{{ review.user.username }}</div>
<div class="subline">
<i :class="review.recommended ? 'mdi mdi-thumb-up positive' : 'mdi mdi-thumb-down negative'"></i>
<span>{{ renderDate(review.reviewDate.date ) }}</span>
</div>
</div>
</div>
<div class="comment" v-if="review.comment != ''">{{ review.comment }}</div>
</router-link>
</div>
<div class="song-list-noresults" v-if="apiFinished && reviews.length < 1">
<div class="noresults-text">{{ this.$t('userdetail.reviews.noresults') }}</div>
</div>
</div>
<Loading v-if="!apiFinished" style="padding: 50px 0px;" />
</section>
</template>
<script>
import { remote } from 'electron';
import moment from 'moment';
const { clipboard, shell } = remote;
import SSAPI from '@/modules/module.api.js';
import Loading from '@/components/Loading.vue';
export default {
name: 'UserDetailReviews',
components: {
Loading,
},
data: function() {
return {
apiFinished: false,
reviews: [],
}
},
mounted: function() {
let ssapi = new SSAPI();
ssapi.getUserReviews(this.$route.params.id).then((data) => {
if(data.status == 200) {
console.log(data.data);
this.$data.reviews = data.data;
this.$data.apiFinished = true;
} else {
this.$router.push({ name: 'Error', params: { errorCode: data.status } });
}
});
},
methods: {
renderDate( date ) {
return moment( date ).format(this.$t('locale.dateFormat'));
}
}
}
</script>
<style scoped lang="less">
.section-userdetail-reviews {
padding: 50px;
& .review-list {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 25px;
& .review {
background: rgba(255,255,255,0.1);
border-radius: 4px;
padding: 20px;
display: block;
transition: 0.2s ease-in-out opacity;
& .metadata {
display: grid;
grid-template-columns: auto 1fr auto;
grid-gap: 15px;
& .avatar {
width: 48px;
height: 48px;
border-radius: 48px;
background-position: center;
background-size: cover;
display: block;
color: #fff;
text-decoration: none;
transition: 0.2s ease-in-out opacity;
&:hover {
opacity: 0.6;
}
}
& .text {
& .username {
font-size: 16px;
font-weight: bold;
margin-bottom: 5px;
display: block;
color: #fff;
text-decoration: none;
}
& .subline {
display: grid;
grid-gap: 5px;
grid-template-columns: auto 1fr;
align-items: center;
color: #fff;
text-decoration: none;
& i.positive { color: #62d38a; }
& i.negative { color: #f73c56; }
& span {
opacity: 0.6;
}
}
}
}
& .comment {
margin-top: 15px;
line-height: 1.5em;
word-break: normal;
color: #fff;
text-decoration: none;
}
&:hover {
opacity: 0.4;
}
}
}
& .song-list-noresults {
display: block;
background: rgba(255,255,255,0.1);
border-radius: 6px;
padding: 25px;
opacity: 0.6;
text-align: center;
}
}
</style>
\ No newline at end of file
<template>
<section class="section-userdetail-spinplays">
<div class="spinplays" v-if="apiFinished">
<div class="video-list" v-if="spinplays.length > 0">
<div class="spinplay" v-for="spinplay in spinplays" :key="spinplay.id">
<div v-on:click="OpenExternal(spinplay.videoUrl)" class="thumbnail" :style="'background-image: url(' + spinplay.videoThumbnail + ');'"></div>
</div>
</div>
<div class="song-list-noresults" v-if="apiFinished && spinplays.length < 1">
<div class="noresults-text">{{ this.$t('userdetail.spinplays.noresults') }}</div>
</div>
</div>
<Loading v-if="!apiFinished" style="padding: 50px 0px;" />
</section>
</template>
<script>
import { remote } from 'electron';
const { clipboard, shell } = remote;
import SSAPI from '@/modules/module.api.js';
import Loading from '@/components/Loading.vue';
export default {
name: 'UserDetailSpinPlays',
components: {
Loading,
},
data: function() {
return {
apiFinished: false,
spinplays: [],
}
},
mounted: function() {
let ssapi = new SSAPI();
ssapi.getUserSpinPlays(this.$route.params.id).then((data) => {
if(data.status == 200) {
this.$data.spinplays = data.data;
this.$data.apiFinished = true;
} else {
this.$router.push({ name: 'Error', params: { errorCode: data.status } });
}
});
},
methods: {
OpenExternal( url ) {
shell.openExternal(url);
}
}
}
</script>
<style scoped lang="less">
.section-userdetail-spinplays {
padding: 50px;
& .spinplays {
& .video-list {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-gap: 15px;
& .spinplay {
background: rgba(255,255,255,0.1);
border-radius: 4px;
overflow: hidden;
color: #fff;
text-decoration: none;
& .thumbnail {
width: 100%;
display: block;
padding-top: 56.25%;
background: rgba(255,255,255,0.1);
background-position: center;
background-size: cover;
transition: 0.2s ease-in-out opacity;
&:hover {
opacity: 0.6;
cursor: pointer;
}
}
}
}
}
& .song-list-noresults {
display: block;
background: rgba(255,255,255,0.1);
border-radius: 6px;
padding: 25px;
opacity: 0.6;
text-align: center;
}
}
</style>
\ No newline at end of file
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