Commit b736144a authored by SpinShare's avatar SpinShare

new user profile design

parent f923ad92
......@@ -1180,6 +1180,12 @@
"@types/node": "*"
}
},
"@types/json-schema": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz",
"integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==",
"dev": true
},
"@types/minimatch": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
......@@ -1405,6 +1411,24 @@
"integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==",
"dev": true
},
"ajv": {
"version": "6.12.4",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz",
"integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true
},
"cacache": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/cacache/-/cacache-13.0.1.tgz",
......@@ -1431,13 +1455,49 @@
"unique-filename": "^1.1.1"
},
"dependencies": {
"rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"fs-minipass": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
"dev": true,
"requires": {
"glob": "^7.1.3"
"minipass": "^3.0.0"
}
},
"minipass-collect": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz",
"integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==",
"dev": true,
"requires": {
"minipass": "^3.0.0"
}
},
"minipass-flush": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz",
"integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==",
"dev": true,
"requires": {
"minipass": "^3.0.0"
}
},
"minipass-pipeline": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz",
"integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==",
"dev": true,
"requires": {
"minipass": "^3.0.0"
}
},
"p-map": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz",
"integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==",
"dev": true,
"requires": {
"aggregate-error": "^3.0.0"
}
}
}
......@@ -1473,9 +1533,9 @@
}
},
"make-dir": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz",
"integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
"dev": true,
"requires": {
"semver": "^6.0.0"
......@@ -1520,6 +1580,15 @@
"find-up": "^4.0.0"
}
},
"rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"dev": true,
"requires": {
"glob": "^7.1.3"
}
},
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
......@@ -1543,20 +1612,63 @@
}
},
"terser-webpack-plugin": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.5.tgz",
"integrity": "sha512-WlWksUoq+E4+JlJ+h+U+QUzXpcsMSSNXkDy9lBVkSqDn1w23Gg29L/ary9GeJVYCGiNJJX7LnVc4bwL1N3/g1w==",
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.8.tgz",
"integrity": "sha512-/fKw3R+hWyHfYx7Bv6oPqmk4HGQcrWLtV3X6ggvPuwPNHSnzvVV51z6OaaCOus4YLjutYGOz3pEpbhe6Up2s1w==",
"dev": true,
"requires": {
"cacache": "^13.0.1",
"find-cache-dir": "^3.2.0",
"jest-worker": "^25.1.0",
"p-limit": "^2.2.2",
"schema-utils": "^2.6.4",
"serialize-javascript": "^2.1.2",
"find-cache-dir": "^3.3.1",
"jest-worker": "^25.4.0",
"p-limit": "^2.3.0",
"schema-utils": "^2.6.6",
"serialize-javascript": "^4.0.0",
"source-map": "^0.6.1",
"terser": "^4.4.3",
"terser": "^4.6.12",
"webpack-sources": "^1.4.3"
},
"dependencies": {
"jest-worker": {
"version": "25.5.0",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.5.0.tgz",
"integrity": "sha512-/dsSmUkIy5EBGfv/IjjqmFxrNAUpBERfGs1oHROyD7yxjG/w+t0GOJDX8O1k32ySmd7+a5IhnJU2qQFcJ4n1vw==",
"dev": true,
"requires": {
"merge-stream": "^2.0.0",
"supports-color": "^7.0.0"
}
},
"schema-utils": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz",
"integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.5",
"ajv": "^6.12.4",
"ajv-keywords": "^3.5.2"
}
},
"serialize-javascript": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
"integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
"dev": true,
"requires": {
"randombytes": "^2.1.0"
}
},
"terser": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz",
"integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==",
"dev": true,
"requires": {
"commander": "^2.20.0",
"source-map": "~0.6.1",
"source-map-support": "~0.5.12"
}
}
}
}
}
......@@ -1890,9 +2002,9 @@
"integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg=="
},
"aggregate-error": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz",
"integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
"integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
"dev": true,
"requires": {
"clean-stack": "^2.0.0",
......@@ -2534,9 +2646,9 @@
}
},
"bl": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
"integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==",
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz",
"integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==",
"dev": true,
"requires": {
"readable-stream": "^2.3.5",
......@@ -3815,9 +3927,9 @@
},
"dependencies": {
"dot-prop": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz",
"integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==",
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.1.tgz",
"integrity": "sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ==",
"dev": true,
"requires": {
"is-obj": "^1.0.0"
......@@ -3941,9 +4053,9 @@
"dev": true
},
"copy-webpack-plugin": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.1.1.tgz",
"integrity": "sha512-P15M5ZC8dyCjQHWwd4Ia/dm0SgVvZJMYeykVIVYXbGyqO4dWB5oyPHp9i7wjwo5LhtlhKbiBCdS2NvM07Wlybg==",
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.1.2.tgz",
"integrity": "sha512-Uh7crJAco3AjBvgAy9Z75CjK8IG+gxaErro71THQ+vv/bl4HaQcpkexAY8KVW/T6D2W2IRr+couF/knIRkZMIQ==",
"dev": true,
"requires": {
"cacache": "^12.0.3",
......@@ -3956,7 +4068,7 @@
"normalize-path": "^3.0.0",
"p-limit": "^2.2.1",
"schema-utils": "^1.0.0",
"serialize-javascript": "^2.1.2",
"serialize-javascript": "^4.0.0",
"webpack-log": "^2.0.0"
},
"dependencies": {
......@@ -4005,6 +4117,15 @@
"ajv-errors": "^1.0.0",
"ajv-keywords": "^3.1.0"
}
},
"serialize-javascript": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
"integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
"dev": true,
"requires": {
"randombytes": "^2.1.0"
}
}
}
},
......@@ -6073,15 +6194,6 @@
"universalify": "^0.1.0"
}
},
"fs-minipass": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
"dev": true,
"requires": {
"minipass": "^3.0.0"
}
},
"fs-write-stream-atomic": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz",
......@@ -7857,33 +7969,6 @@
"integrity": "sha512-yV+gqbd5vaOYjqlbk16EG89xB5udgjqQF3C5FAORDg4f/IS1Yc5ERCv5e/57yBcfJYw05V5JyIXabhwb75Xxow==",
"dev": true
},
"jest-worker": {
"version": "25.2.6",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.2.6.tgz",
"integrity": "sha512-FJn9XDUSxcOR4cwDzRfL1z56rUofNTFs539FGASpd50RHdb6EVkhxQqktodW2mI49l+W3H+tFJDotCHUQF6dmA==",
"dev": true,
"requires": {
"merge-stream": "^2.0.0",
"supports-color": "^7.0.0"
},
"dependencies": {
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"supports-color": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
"integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
}
}
},
"js-message": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/js-message/-/js-message-1.0.5.tgz",
......@@ -8636,33 +8721,6 @@
}
}
},
"minipass-collect": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz",
"integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==",
"dev": true,
"requires": {
"minipass": "^3.0.0"
}
},
"minipass-flush": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz",
"integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==",
"dev": true,
"requires": {
"minipass": "^3.0.0"
}
},
"minipass-pipeline": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.2.tgz",
"integrity": "sha512-3JS5A2DKhD2g0Gg8x3yamO0pj7YeKGwVlDS90pF++kxptwx/F+B//roxf9SqYil5tQo65bijy+dAuAFZmYOouA==",
"dev": true,
"requires": {
"minipass": "^3.0.0"
}
},
"mississippi": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
......@@ -9298,15 +9356,6 @@
"p-limit": "^1.1.0"
}
},
"p-map": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz",
"integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==",
"dev": true,
"requires": {
"aggregate-error": "^3.0.0"
}
},
"p-retry": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz",
......@@ -12976,6 +13025,11 @@
"spdx-expression-parse": "^3.0.0"
}
},
"vanilla-tilt": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/vanilla-tilt/-/vanilla-tilt-1.7.0.tgz",
"integrity": "sha512-u9yUhpSasFeqQCuiTon+RSb0aHzcj9stvWVXQIswzKL5oG491lkYk7U1GmhOAEZt7yPT6EiYZRJhIh2MFBncOA=="
},
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
......@@ -13371,6 +13425,14 @@
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
"dev": true
},
"vue-tilt.js": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/vue-tilt.js/-/vue-tilt.js-1.1.1.tgz",
"integrity": "sha512-Tsc+01E9tO5TprUFplYEScU+DxQVY1sWiGlsqYb03R1fpSM5SWzBDeq1cb3Mj1vl+VbD/rlUM0vj5xW0DzUTRw==",
"requires": {
"vanilla-tilt": "^1.7.0"
}
},
"vuex": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.5.1.tgz",
......
......@@ -34,6 +34,7 @@
"vue-i18n": "^8.20.0",
"vue-observe-visibility": "^0.4.6",
"vue-router": "^3.3.4",
"vue-tilt.js": "^1.1.1",
"vuex": "^3.5.1"
},
"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 @@
"userdetail.uploaded.header": "Uploaded Songs",
"userdetail.uploaded.noresults": "This user did not upload any songs yet.",
"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.client.header": "SpinShare",
......
......@@ -4,10 +4,12 @@ import router from './router';
import store from './store';
import i18n from './i18n';
import moment from 'moment'
import VueTilt from 'vue-tilt.js'
import { ipcRenderer } from 'electron';
import VueObserveVisibility from 'vue-observe-visibility'
import { VTooltip, VPopover, VClosePopover } from 'v-tooltip'
Vue.use(VueTilt);
Vue.use(VueObserveVisibility);
Vue.directive('tooltip', VTooltip);
......
......@@ -5,7 +5,7 @@ class SSAPI {
if(process.env.NODE_ENV !== 'development') {
return false;
} else {
return false;
return true;
}
}
......@@ -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() {
let apiPath = this.apiBase + "searchAll";
let supportedVersion = this.supportedVersion;
......
......@@ -13,6 +13,9 @@ import ViewSongDetail from '../views/SongDetail.vue';
import ViewSongDetailReviews from '../views/SongDetailReviews.vue';
import ViewSongDetailSpinPlays from '../views/SongDetailSpinPlays.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 ViewTournament from '../views/Tournament.vue';
import ViewError from '../views/Error.vue';
......@@ -74,10 +77,33 @@ const routes = [{
component: ViewSongDetailSpinPlays
}
]
},, {
}, {
path: '/user/:id',
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',
name: 'Settings',
......
<template>
<section class="section-user-detail">
<div class="user-detail-background" :style="'background-image: url(' + avatar + '), url(' + require('@/assets/img/defaultAvatar.jpg') + ');'" v-if="apiFinished">
<div class="user-detail-dim">
<div class="user-report" v-if="apiFinished">
<button class="button-report button" v-on:click="OpenReport">{{ $t('userdetail.actions.reportButton') }}</button>
</div>
<div class="user-detail">
<header v-if="apiFinished">
<div class="detail">
<div class="user-avatar" :style="'background-image: url(' + avatar + '), url(' + require('@/assets/img/defaultAvatar.jpg') + ');'"></div>
<div class="user-meta-data">
<div class="user-name">{{ username }}</div>
<div class="user-badge user-badge-verified" v-if="isVerified">
<i class="mdi mdi-check-decagram"></i>
</div>
<div class="user-badge user-badge-patreon" v-if="isPatreon">
<i class="mdi mdi-patreon"></i>
<div class="user-data">
<div class="user-name">{{ username }} <i class="mdi mdi-patreon" v-if="isVerified"></i></div>
<div class="user-actions">
<button class="button" v-on:click="OpenReport()">{{ $t('userdetail.actions.reportButton') }}</button>
</div>
</div>
</div>
<div class="cards" v-if="cards.length > 0">
<div class="card" v-for="card in cards" :key="card.id" :style="'background-image: url(' + card.icon + ')'" v-on:click="OpenCardOverlay(card)"></div>
</div>
<div class="tabs">
<router-link :to="{ name: 'UserDetailCharts', params: { id: id } }" class="tab" exact>Charts ({{ songs }})</router-link>
<router-link :to="{ name: 'UserDetailReviews', params: { id: id } }" class="tab" exact>Reviews ({{ reviews }})</router-link>
<router-link :to="{ name: 'UserDetailSpinPlays', params: { id: id } }" class="tab" exact>SpinPlays ({{ spinplays }})</router-link>
</div>
<SongRow
class="song-row-user"
v-if="apiFinished && songs.length > 0">
<template v-slot:song-list>
<SongItem
v-for="song in songs"
v-bind:key="song.id"
v-bind="song" />
</template>
</SongRow>
</header>
<transition name="fade">
<CardOverlay v-if="showCardOverlay" v-bind:card="cardToShow" />
</transition>
<router-view v-if="apiFinished"></router-view>
<Loading v-if="!apiFinished" />
</section>
......@@ -36,19 +34,22 @@
<script>
import { remote } from 'electron';
import moment from 'moment';
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';
import CardOverlay from '@/components/Overlays/CardOverlay.vue';
export default {
name: 'UserDetail',
components: {
SongRow,
SongItem,
Loading
Loading,
CardOverlay
},
data: function() {
return {
......@@ -58,7 +59,12 @@
isVerified: false,
isPatreon: false,
avatar: "",
songs: []
cards: [],
songs: 0,
reviews: 0,
spinplays: 0,
showCardOverlay: false,
cardToShow: null
}
},
mounted: function() {
......@@ -71,16 +77,28 @@
this.$data.isVerified = data.data.isVerified;
this.$data.isPatreon = data.data.isPatreon;
this.$data.avatar = data.data.avatar;
this.$data.cards = data.data.cards;
this.$data.songs = data.data.songs;
this.$data.reviews = data.data.reviews;
this.$data.spinplays = data.data.spinplays;
this.$data.apiFinished = true;
} else {
this.$router.push({ name: 'Error', params: { errorCode: data.status } });
}
});
this.$on('cardClose', () => {
this.$data.showCardOverlay = false;
this.$data.cardToShow = null;
});
},
methods: {
OpenReport: function() {
shell.openExternal("https://spinsha.re/report/user/" + this.$data.id);
},
OpenCardOverlay: function(cardToShow) {
this.$data.showCardOverlay = true;
this.$data.cardToShow = cardToShow;
}
}
}
......@@ -88,80 +106,127 @@
<style scoped lang="less">
.section-user-detail {
& .user-detail-background {
background-size: cover;
background-position: center;
& .user-detail-dim {
backdrop-filter: blur(10px);
background: linear-gradient(180deg, rgba(0,0,0,0.4), #212629);
& header {
background: rgba(0,0,0,0.5);
padding: 25px 50px;
padding-bottom: 0px;
& .user-detail {
padding: 50px;
& .detail {
margin-bottom: 15px;
display: grid;
grid-template-columns: 1fr;
justify-items: center;
grid-gap: 25px;
grid-template-columns: 80px 1fr;
grid-gap: 15px;
& .user-avatar {
width: 200px;
height: 200px;
align-self: center;
background: #eee;
width: 80px;
height: 80px;
border-radius: 50%;
background-size: cover;
background-position: center;
}
& .user-meta-data {
background-size: cover;
position: relative;
overflow: hidden;
& .user-avatar-change {
position: absolute;
top: 0px;
left: 0px;
bottom: 0px;
right: 0px;
background: rgba(0,0,0,0.6);
display: flex;
height: 48px;
justify-content: center;
align-items: center;
opacity: 0;
transition: 0.2s ease opacity;
cursor: pointer;
& .user-name {
font-weight: bold;
font-size: 48px;
& .mdi {
font-size: 28px;
}
& .user-badge {
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
margin-left: 20px;
& input {
position: absolute;
top: 0px;
left: 0px;
bottom: 0px;
right: 0px;
cursor: pointer;
opacity: 0;
}
}
&:hover {
& .user-avatar-change {
opacity: 1;
}
}
}
& .user-report {
position: absolute;
top: 25px;
right: 25px;
& .user-data {
display: grid;
grid-template-rows: 1fr 40px;
& .user-name {
font-size: 22px;
margin-bottom: 22px;
}
& .user-actions {
& .button {
padding: 15px 30px;
font-size: 16px;
transition: 0.2s ease-in-out all;
margin-right: 7px;
}
}
}
}
& .cards {
margin-bottom: 25px;
display: flex;
&.button-primary {
background: #fff;
color: #222;
& .card {
width: 100px;
height: 100px;
margin-right: 10px;
background-position: center;
background-size: cover;
opacity: 1;
transition: 0.2s ease-in-out opacity;
&:hover {
background: #fff;
color: #222;
opacity: 0.6;
cursor: pointer;
}
}
}
& .tabs {
display: flex;
&:hover {
color: #fff;
opacity: 0.6;
& .tab {
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;
background: rgba(0,0,0,0.6);
color: rgba(255,255,255,0.4);
transition: 0.2s ease-in-out all;
text-decoration: none;
&:not(.active):hover {
cursor: pointer;
background: #272c2e;
color: rgba(255,255,255,0.75);
}
&.router-link-active {
opacity: 1;
color: rgba(255,255,255,1);
background: #212629;
}
}
}
}
& .song-row-user {
display: grid;
padding: 50px;
display: grid;
}
}
</style>
\ No newline at end of file
<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