Compare commits
59 Commits
drone-test
...
feat/botto
| Author | SHA1 | Date | |
|---|---|---|---|
| 2665a27803 | |||
| 74b96225c6 | |||
| f180b7f39b | |||
| a2fbfcb13c | |||
| d640f7f882 | |||
| d43c12b103 | |||
| 38c3792675 | |||
| ac2785abd5 | |||
| 1ff6a0e831 | |||
| 7a3b709404 | |||
|
|
d63cb4ac52 | ||
| b6ee1cf906 | |||
| 60201b1b67 | |||
| a8b8603649 | |||
| e193528fe9 | |||
| 73afb34964 | |||
| 65bbc453e6 | |||
|
|
188477ab64 | ||
|
|
a31bfb6b39 | ||
| 681ed69ef0 | |||
| b771428b4d | |||
| fc0103ee5d | |||
| 55067b81b8 | |||
| dfe2b5df09 | |||
| dc0c435163 | |||
| 9d1ac56b9a | |||
| fc2c3664d9 | |||
| 0bd45ed777 | |||
| 3912766982 | |||
| 3becce2a6c | |||
| 20b8692c91 | |||
| 14ac780aa5 | |||
| d836870612 | |||
| bc6f706e4a | |||
| 6ac6a9b039 | |||
| 85be80d712 | |||
| 105be1e411 | |||
| 010830243e | |||
| 923dc46dc7 | |||
| f2ef5366f5 | |||
| 20380a4587 | |||
| 069ef2c458 | |||
| 2f430b2d8f | |||
| f7a579a438 | |||
| b9ddd998bc | |||
| ae59d02df2 | |||
| ec205bab0c | |||
| ed49d825b8 | |||
| a9db8be46a | |||
| 1caa3c7fae | |||
| 2ea4bffd49 | |||
| 5ae52f59fc | |||
| a7e6d25d3f | |||
| 83751a4e3e | |||
| 0e9daab187 | |||
| 4390491873 | |||
| d620a4cc2e | |||
| 1fd48edd42 | |||
| 68e45303c6 |
33
.drone.yml
33
.drone.yml
@@ -1,7 +1,7 @@
|
||||
---
|
||||
kind: pipeline
|
||||
type: exec
|
||||
name: default
|
||||
type: docker
|
||||
name: seasoned build
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
@@ -9,11 +9,30 @@ platform:
|
||||
|
||||
steps:
|
||||
- name: frontend_install
|
||||
image: node:13.6.0
|
||||
commands:
|
||||
- yarn
|
||||
- name: frontend_build
|
||||
commands:
|
||||
- yarn build
|
||||
- node -v
|
||||
- yarn --version
|
||||
- name: deploy
|
||||
image: appleboy/drone-ssh
|
||||
pull: true
|
||||
secrets:
|
||||
- ssh_key
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
branch:
|
||||
- master
|
||||
- drone-test
|
||||
status: success
|
||||
settings:
|
||||
host: 10.0.0.114
|
||||
username: root
|
||||
key:
|
||||
from_secret: ssh_key
|
||||
command_timeout: 600s
|
||||
script:
|
||||
- /home/kevin/deploy/seasoned.sh
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
@@ -21,3 +40,5 @@ trigger:
|
||||
event:
|
||||
include:
|
||||
- pull_request
|
||||
- push
|
||||
|
||||
|
||||
10
.prettierrc
Normal file
10
.prettierrc
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "avoid",
|
||||
"vueIndentScriptAndStyle": false,
|
||||
"trailingComma": "none"
|
||||
}
|
||||
@@ -21,10 +21,6 @@
|
||||
<symbol id="icon_now_playing" viewBox="0 0 30 30">
|
||||
<title>Now Playing</title>
|
||||
<path d="M27.9847266,7.50322266 C25.9822852,4.03494141 22.749082,1.55390625 18.8806055,0.517382812 C15.0121875,-0.519257812 10.9716797,0.0127148437 7.50322266,2.01527344 C4.03482422,4.01777344 1.55390625,7.25097656 0.517382812,11.1194531 C-0.519140625,14.9878711 0.0128320312,19.0284961 2.01527344,22.4967773 C4.01765625,25.9650586 7.25097656,28.4460937 11.1193945,29.4826172 C12.4111523,29.8287891 13.7219531,30 15.0244336,30 C17.6224219,30 20.1866016,29.3186133 22.4968359,27.9847852 C25.9651172,25.9823437 28.4461523,22.7491406 29.4826758,18.8806641 C30.5192578,15.0121289 29.987168,10.9716211 27.9847266,7.50322266 Z M27.9743555,18.476543 C27.0457617,21.9421289 24.8231836,24.8387109 21.715957,26.6326172 C18.6088477,28.426582 14.989043,28.9030664 11.523457,27.9745898 C8.0578125,27.0459961 5.16128906,24.823418 3.36732422,21.7161914 C1.57341797,18.609082 1.096875,14.9892188 2.02552734,11.5235742 C2.95417969,8.05798828 5.17675781,5.16152344 8.28392578,3.3675 C10.35375,2.17248047 12.6505664,1.56210937 14.9782031,1.56210937 C16.1448047,1.56210937 17.3195508,1.71550781 18.4763672,2.02552734 C21.9419531,2.95412109 24.8385352,5.17669922 26.6324414,8.28392578 C28.4264063,11.3910937 28.9030078,15.0108984 27.9743555,18.476543 Z M22.1940234,13.5850781 L12.5538281,8.01925781 C12.0422461,7.72388672 11.4314648,7.72400391 10.9198828,8.01919922 C10.4083008,8.31451172 10.1028516,8.84355469 10.1028516,9.43423828 L10.1028516,20.5658789 C10.1028516,21.1565625 10.4082422,21.6855469 10.9198828,21.980918 C11.1756445,22.1286328 11.4561328,22.2024023 11.7367383,22.2024023 C12.0174023,22.2024023 12.2980078,22.128457 12.5537695,21.9808594 L22.194082,16.4150977 C22.7056055,16.119668 23.0109375,15.5906836 23.0109375,15 C23.0109375,14.409375 22.7055469,13.8803906 22.1940234,13.5850781 Z M21.4132031,15.0629297 L11.7729492,20.6286914 C11.7611719,20.6355469 11.7366211,20.649668 11.7005273,20.6286914 C11.6643164,20.6077734 11.6643164,20.5795312 11.6643164,20.5659375 L11.6643164,9.43429687 C11.6643164,9.42070312 11.6643164,9.39246094 11.7005273,9.37154297 C11.714707,9.36333984 11.7270703,9.36052734 11.7376172,9.36052734 C11.7540234,9.36052734 11.7658594,9.36738281 11.7730664,9.37154297 L21.4132617,14.9373633 C21.4250391,14.9441602 21.4494727,14.9582812 21.4494727,15.0001172 C21.4494727,15.0419531 21.4249219,15.0561328 21.4132031,15.0629297 Z M24.2169727,7.87734375 C22.3601953,5.47863281 19.5689648,3.86707031 16.5588867,3.45580078 C16.1321484,3.39738281 15.7380469,3.69638672 15.6796289,4.12371094 C15.6213281,4.55091797 15.920332,4.94455078 16.3475391,5.00296875 C18.9556641,5.35927734 21.3738867,6.75544922 22.9822266,8.83318359 C23.1360937,9.03193359 23.3668945,9.13599609 23.6001562,9.13599609 C23.7670898,9.13599609 23.9353125,9.08273437 24.0774609,8.97257813 C24.418418,8.70867187 24.4808789,8.21830078 24.2169727,7.87734375 Z" fill-rule="nonzero"></path>
|
||||
</symbol>
|
||||
<symbol id="icon_top_rated" viewBox="0 0 30 30">
|
||||
<title>Top Rated</title>
|
||||
<path d="M24.7750847,5.22491532 C24.7021599,5.15199056 24.6169531,5.09595364 24.52407,5.05757218 C24.4304192,5.01919073 24.3313951,5 24.2323709,5 L8.84447835,5.00076763 C8.41997947,5.00076763 8.07684927,5.34466546 8.07684927,5.76839671 C8.07684927,6.19289559 8.4207471,6.53602579 8.84447835,6.53602579 L22.3785467,6.53525816 L5.22510723,23.6894653 C4.92496426,23.9896082 4.92496426,24.4747498 5.22510723,24.7748928 C5.3747949,24.9245804 5.57130794,24.9998081 5.76782099,24.9998081 C5.96433403,24.9998081 6.16084708,24.9245804 6.31053475,24.7748928 L23.4647418,7.62068568 L23.4647418,21.1539864 C23.4647418,21.5784853 23.807872,21.9216155 24.2323709,21.9216155 C24.6568698,21.9216155 25,21.5784853 25,21.1539864 L25,5.76762908 C25,5.66860493 24.9808093,5.56958078 24.9424278,5.47593003 C24.9040464,5.38304691 24.8480094,5.29784008 24.7750847,5.22491532 Z"></path>
|
||||
</symbol>
|
||||
<symbol id="icon_popular" viewBox="0 0 30 30">
|
||||
<title>Popular</title>
|
||||
@@ -125,9 +121,8 @@
|
||||
l246.17-246.175C512.959,136.021,512.959,129.804,509.121,125.966z" fill-rule="nozero" transform="scale(1.4)"></path>
|
||||
</symbol>
|
||||
<div id="app"></div>
|
||||
<script type="text/javascript" src="dist/build.js"></script>
|
||||
<script type="text/javascript" src="/build.js"></script>
|
||||
</body>
|
||||
|
||||
<script src="https://cdn.ravenjs.com/3.23.1/vue/raven.min.js" crossorigin="anonymous"></script>
|
||||
<!-- <script>Raven.config('https://c1fa1a17de3d4b24abcd05161648fe4d@sentry.io/300063').install();</script> -->
|
||||
</html>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"@babel/runtime": "^7.4.5",
|
||||
"babel-loader": "^8.0.6",
|
||||
"cross-env": "^3.0.0",
|
||||
"css-loader": "^0.25.0",
|
||||
"css-loader": "^3.4.2",
|
||||
"documentation": "^11.0.0",
|
||||
"file-loader": "^0.9.0",
|
||||
"node-sass": "^4.5.0",
|
||||
|
||||
104
src/App.vue
104
src/App.vue
@@ -1,37 +1,32 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
|
||||
<!-- Header and hamburger navigation -->
|
||||
<navigation></navigation>
|
||||
|
||||
<!-- Header with search field -->
|
||||
|
||||
<!-- TODO move this to the navigation component -->
|
||||
<header class="header">
|
||||
<search-input v-model="query"></search-input>
|
||||
</header>
|
||||
<search-input v-model="query"></search-input>
|
||||
|
||||
<!-- Movie popup that will show above existing rendered content -->
|
||||
<movie-popup v-if="moviePopupIsVisible" :id="popupID" :type="popupType"></movie-popup>
|
||||
|
||||
<movie-popup
|
||||
v-if="moviePopupIsVisible"
|
||||
:id="popupID"
|
||||
:type="popupType"
|
||||
></movie-popup>
|
||||
|
||||
<darkmode-toggle />
|
||||
|
||||
<!-- Display the component assigned to the given route (default: home) -->
|
||||
<router-view class="content" :key="$route.fullPath"></router-view>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue'
|
||||
import Navigation from '@/components/Navigation'
|
||||
import MoviePopup from '@/components/MoviePopup'
|
||||
import SearchInput from '@/components/SearchInput'
|
||||
import DarkmodeToggle from '@/components/ui/darkmodeToggle'
|
||||
import Vue from "vue";
|
||||
import Navigation from "@/components/Navigation";
|
||||
import MoviePopup from "@/components/MoviePopup";
|
||||
import SearchInput from "@/components/SearchInput";
|
||||
import DarkmodeToggle from "@/components/ui/darkmodeToggle";
|
||||
|
||||
export default {
|
||||
name: 'app',
|
||||
name: "app",
|
||||
components: {
|
||||
Navigation,
|
||||
MoviePopup,
|
||||
@@ -40,39 +35,42 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
query: '',
|
||||
query: "",
|
||||
moviePopupIsVisible: false,
|
||||
popupID: 0,
|
||||
popupType: 'movie'
|
||||
}
|
||||
popupType: "movie"
|
||||
};
|
||||
},
|
||||
created(){
|
||||
let that = this
|
||||
created() {
|
||||
let that = this;
|
||||
Vue.prototype.$popup = {
|
||||
get isOpen() {
|
||||
return that.moviePopupIsVisible
|
||||
return that.moviePopupIsVisible;
|
||||
},
|
||||
open: (id, type) => {
|
||||
this.popupID = id || this.popupID
|
||||
this.popupType = type || this.popupType
|
||||
this.moviePopupIsVisible = true
|
||||
console.log('opened')
|
||||
this.popupID = id || this.popupID;
|
||||
this.popupType = type || this.popupType;
|
||||
this.moviePopupIsVisible = true;
|
||||
console.log("opened");
|
||||
},
|
||||
close: () => {
|
||||
this.moviePopupIsVisible = false
|
||||
console.log('closed')
|
||||
this.moviePopupIsVisible = false;
|
||||
console.log("closed");
|
||||
}
|
||||
}
|
||||
console.log('MoviePopup registered at this.$popup and has state: ', this.$popup.isOpen)
|
||||
};
|
||||
console.log(
|
||||
"MoviePopup registered at this.$popup and has state: ",
|
||||
this.$popup.isOpen
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "./src/scss/media-queries";
|
||||
@import "./src/scss/variables";
|
||||
.content {
|
||||
@include tablet-min{
|
||||
@include tablet-min {
|
||||
width: calc(100% - 95px);
|
||||
margin-top: $header-size;
|
||||
margin-left: 95px;
|
||||
@@ -86,38 +84,42 @@ export default {
|
||||
@import "./src/scss/variables";
|
||||
@import "./src/scss/media-queries";
|
||||
|
||||
*{
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
body{
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-family: "Roboto", sans-serif;
|
||||
line-height: 1.6;
|
||||
background: $background-color;
|
||||
color: $text-color;
|
||||
transition: background-color .5s ease, color .5s ease;
|
||||
&.hidden{
|
||||
transition: background-color 0.5s ease, color 0.5s ease;
|
||||
&.hidden {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
h1,h2,h3 {
|
||||
transition: color .5s ease;
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
transition: color 0.5s ease;
|
||||
}
|
||||
a:any-link {
|
||||
color: inherit;
|
||||
}
|
||||
input, textarea, button{
|
||||
font-family: 'Roboto', sans-serif;
|
||||
input,
|
||||
textarea,
|
||||
button {
|
||||
font-family: "Roboto", sans-serif;
|
||||
}
|
||||
figure{
|
||||
figure {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
img{
|
||||
img {
|
||||
display: block;
|
||||
// max-width: 100%;
|
||||
height: auto;
|
||||
@@ -127,16 +129,16 @@ img{
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.wrapper{
|
||||
.wrapper {
|
||||
position: relative;
|
||||
}
|
||||
.header{
|
||||
.header {
|
||||
position: fixed;
|
||||
z-index: 15;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@include tablet-min{
|
||||
@include tablet-min {
|
||||
width: calc(100% - 170px);
|
||||
margin-left: 95px;
|
||||
border-top: 0;
|
||||
@@ -146,14 +148,16 @@ img{
|
||||
}
|
||||
|
||||
// router view transition
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition-property: opacity;
|
||||
transition-duration: 0.25s;
|
||||
}
|
||||
.fade-enter-active {
|
||||
transition-delay: 0.25s;
|
||||
}
|
||||
.fade-enter, .fade-leave-active {
|
||||
opacity: 0
|
||||
.fade-enter,
|
||||
.fade-leave-active {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
31
src/api.js
31
src/api.js
@@ -252,6 +252,21 @@ const getRequestStatus = (id, type, authorization_token=undefined) => {
|
||||
.catch(err => Promise.reject(err))
|
||||
}
|
||||
|
||||
const watchLink = (title, year, authorization_token=undefined) => {
|
||||
const url = new URL('v1/plex/watch-link', SEASONED_URL)
|
||||
url.searchParams.append('title', title)
|
||||
url.searchParams.append('year', year)
|
||||
|
||||
const headers = {
|
||||
'Authorization': authorization_token,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
return fetch(url.href, { headers })
|
||||
.then(resp => resp.json())
|
||||
.then(response => response.link)
|
||||
}
|
||||
|
||||
// - - - Seasoned user endpoints - - -
|
||||
|
||||
const register = (username, password) => {
|
||||
@@ -271,7 +286,7 @@ const register = (username, password) => {
|
||||
})
|
||||
}
|
||||
|
||||
const login = (username, password) => {
|
||||
const login = (username, password, throwError=false) => {
|
||||
const url = new URL('v1/user/login', SEASONED_URL)
|
||||
const options = {
|
||||
method: 'POST',
|
||||
@@ -280,11 +295,14 @@ const login = (username, password) => {
|
||||
}
|
||||
|
||||
return fetch(url.href, options)
|
||||
.then(resp => resp.json())
|
||||
.catch(error => {
|
||||
console.error('Unexpected error occured before receiving response. Error:', error)
|
||||
// TODO log to sentry the issue here
|
||||
throw error
|
||||
.then(resp => {
|
||||
if (resp.status == 200)
|
||||
return resp.json();
|
||||
|
||||
if (throwError)
|
||||
throw resp;
|
||||
else
|
||||
console.error("Error occured when trying to sign in.\nError:", resp);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -458,6 +476,7 @@ export {
|
||||
searchTorrents,
|
||||
addMagnet,
|
||||
request,
|
||||
watchLink,
|
||||
getRequestStatus,
|
||||
linkPlexAccount,
|
||||
unlinkPlexAccount,
|
||||
|
||||
@@ -1,38 +1,74 @@
|
||||
<template>
|
||||
<section class="not-found">
|
||||
<h1 class="not-found__title">Page Not Found</h1>
|
||||
</section>
|
||||
<div>
|
||||
<section class="not-found">
|
||||
<h1 class="not-found__title">Page Not Found</h1>
|
||||
</section>
|
||||
<seasoned-button class="button" @click="goBack">go back to previous page</seasoned-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import store from '@/store'
|
||||
import SeasonedButton from '@/components/ui/SeasonedButton'
|
||||
|
||||
export default {
|
||||
components: { SeasonedButton },
|
||||
methods: {
|
||||
goBack() {
|
||||
this.$router.go(-1)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.$popup.isOpen == true)
|
||||
this.$popup.close()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "./src/scss/variables";
|
||||
@import "./src/scss/media-queries";
|
||||
|
||||
.button {
|
||||
font-size: 1.2rem;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: calc(50% + 46px);
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
@include mobile {
|
||||
top: 60%;
|
||||
left: 50%;
|
||||
font-size: 1rem;
|
||||
width: content;
|
||||
}
|
||||
}
|
||||
|
||||
.not-found {
|
||||
display: flex;
|
||||
height: calc(100vh - var(--header-size));
|
||||
width: 100%;
|
||||
background: url('~assets/pulp-fiction.jpg') no-repeat 50% 50%;
|
||||
background-size: cover;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
||||
&:before {
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
height: calc(100vh - var(--header-size));
|
||||
width: 100%;
|
||||
pointer-events: none;
|
||||
background: $background-40;
|
||||
}
|
||||
&__title {
|
||||
padding-top: 40vh;
|
||||
font-size: 2rem;
|
||||
margin-top: 30vh;
|
||||
font-size: 2.5rem;
|
||||
font-weight: 500;
|
||||
color: $text-color;
|
||||
position: relative;
|
||||
margin: 0;
|
||||
|
||||
@include tablet-min {
|
||||
font-size: 2.3rem;
|
||||
font-size: 3.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,74 +12,76 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LandingBanner from '@/components/LandingBanner'
|
||||
import ListHeader from '@/components/ListHeader'
|
||||
import ResultsList from '@/components/ResultsList'
|
||||
import Loader from '@/components/ui/Loader'
|
||||
import LandingBanner from "@/components/LandingBanner";
|
||||
import ListHeader from "@/components/ListHeader";
|
||||
import ResultsList from "@/components/ResultsList";
|
||||
import Loader from "@/components/ui/Loader";
|
||||
|
||||
import { getTmdbMovieListByName, getRequests } from '@/api'
|
||||
import { getTmdbMovieListByName, getRequests } from "@/api";
|
||||
|
||||
export default {
|
||||
name: 'home',
|
||||
name: "home",
|
||||
components: { LandingBanner, ResultsList, ListHeader, Loader },
|
||||
data(){
|
||||
data() {
|
||||
return {
|
||||
imageFile: 'dist/pulp-fiction.jpg',
|
||||
imageFile: "/pulp-fiction.jpg",
|
||||
requests: [],
|
||||
nowplaying: [],
|
||||
upcoming: [],
|
||||
popular: []
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
lists() {
|
||||
return [
|
||||
{
|
||||
title: 'Requests',
|
||||
route: 'request',
|
||||
title: "Requests",
|
||||
route: "request",
|
||||
data: this.requests
|
||||
},
|
||||
{
|
||||
title: 'Now playing',
|
||||
route: 'now_playing',
|
||||
title: "Now playing",
|
||||
route: "now_playing",
|
||||
data: this.nowplaying
|
||||
},
|
||||
{
|
||||
title: 'Upcoming',
|
||||
route: 'upcoming',
|
||||
title: "Upcoming",
|
||||
route: "upcoming",
|
||||
data: this.upcoming
|
||||
},
|
||||
{
|
||||
title: 'Popular',
|
||||
route: 'popular',
|
||||
title: "Popular",
|
||||
route: "popular",
|
||||
data: this.popular
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetchRequests() {
|
||||
getRequests()
|
||||
.then(results => this.requests = results.results)
|
||||
getRequests().then(results => (this.requests = results.results));
|
||||
},
|
||||
fetchNowPlaying() {
|
||||
getTmdbMovieListByName('now_playing')
|
||||
.then(results => this.nowplaying = results.results)
|
||||
getTmdbMovieListByName("now_playing").then(
|
||||
results => (this.nowplaying = results.results)
|
||||
);
|
||||
},
|
||||
fetchUpcoming() {
|
||||
getTmdbMovieListByName('upcoming')
|
||||
.then(results => this.upcoming = results.results)
|
||||
getTmdbMovieListByName("upcoming").then(
|
||||
results => (this.upcoming = results.results)
|
||||
);
|
||||
},
|
||||
fetchPopular() {
|
||||
getTmdbMovieListByName('popular')
|
||||
.then(results => this.popular = results.results)
|
||||
getTmdbMovieListByName("popular").then(
|
||||
results => (this.popular = results.results)
|
||||
);
|
||||
}
|
||||
},
|
||||
created(){
|
||||
this.fetchRequests()
|
||||
this.fetchNowPlaying()
|
||||
this.fetchUpcoming()
|
||||
this.fetchPopular()
|
||||
created() {
|
||||
this.fetchRequests();
|
||||
this.fetchNowPlaying();
|
||||
this.fetchUpcoming();
|
||||
this.fetchPopular();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<template>
|
||||
<header v-bind:style="{ 'background-image': 'url(' + imageFile + ')' }">
|
||||
<div class="container">
|
||||
<h1 class="title">Request new movies or tv shows for plex</h1>
|
||||
<strong class="subtitle">Made with Vue.js</strong>
|
||||
<h1 class="title">Request movies or tv shows</h1>
|
||||
<strong class="subtitle"
|
||||
>Create a profile to track and view requests</strong
|
||||
>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
@@ -17,15 +19,15 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
imageFile: 'dist/pulp-fiction.jpg'
|
||||
}
|
||||
imageFile: "/pulp-fiction.jpg"
|
||||
};
|
||||
},
|
||||
beforeMount() {
|
||||
if (this.image && this.image.length > 0) {
|
||||
this.imageFile = this.image
|
||||
this.imageFile = this.image;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -55,15 +57,15 @@ header {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: $background-70;
|
||||
transition: background-color .5s ease;
|
||||
transition: background-color 0.5s ease;
|
||||
}
|
||||
|
||||
|
||||
.container {
|
||||
text-align: center;
|
||||
position: relative;
|
||||
transition: color .5s ease;
|
||||
transition: color 0.5s ease;
|
||||
}
|
||||
|
||||
|
||||
.title {
|
||||
font-weight: 500;
|
||||
font-size: 22px;
|
||||
@@ -72,8 +74,8 @@ header {
|
||||
color: $text-color;
|
||||
margin: 0;
|
||||
|
||||
@include tablet-min{
|
||||
font-size: 28px;
|
||||
@include tablet-min {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,9 +86,9 @@ header {
|
||||
color: $text-color-70;
|
||||
margin: 5px 0;
|
||||
|
||||
@include tablet-min{
|
||||
font-size: 16px;
|
||||
@include tablet-min {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
<template>
|
||||
<header :class="{ 'sticky': sticky }">
|
||||
<header :class="{ sticky: sticky }">
|
||||
<h2>{{ title }}</h2>
|
||||
|
||||
<div v-if="info instanceof Array" class="flex flex-direction-column">
|
||||
<span v-for="item in info" class="info">{{ item }}</span>
|
||||
</div>
|
||||
<span v-else class="info">{{ info }}</span>
|
||||
<router-link v-if="link" :to="link" class='view-more' :aria-label="`View all ${title}`">
|
||||
<router-link
|
||||
v-if="link"
|
||||
:to="link"
|
||||
class="view-more"
|
||||
:aria-label="`View all ${title}`"
|
||||
>
|
||||
View All
|
||||
</router-link>
|
||||
</header>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -33,31 +38,38 @@ export default {
|
||||
required: false
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import './src/scss/variables';
|
||||
@import './src/scss/media-queries';
|
||||
@import './src/scss/main';
|
||||
@import "./src/scss/variables";
|
||||
@import "./src/scss/media-queries";
|
||||
@import "./src/scss/main";
|
||||
|
||||
header {
|
||||
width: 100%;
|
||||
min-height: 80px;
|
||||
min-height: 45px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-left: 0.75rem;
|
||||
padding-right: 0.75rem;
|
||||
|
||||
@include tablet-min {
|
||||
min-height: 65px;
|
||||
}
|
||||
|
||||
&.sticky {
|
||||
background-color: $background-color;
|
||||
|
||||
position: sticky;
|
||||
position: -webkit-sticky;
|
||||
top: $header-size;
|
||||
top: 0;
|
||||
z-index: 4;
|
||||
|
||||
@include tablet-min {
|
||||
top: $header-size;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
@@ -72,16 +84,16 @@ header {
|
||||
.view-more {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 300;
|
||||
letter-spacing: .5px;
|
||||
letter-spacing: 0.5px;
|
||||
color: $text-color-70;
|
||||
text-decoration: none;
|
||||
transition: color .5s ease;
|
||||
transition: color 0.5s ease;
|
||||
cursor: pointer;
|
||||
|
||||
&:after{
|
||||
&:after {
|
||||
content: " →";
|
||||
}
|
||||
&:hover{
|
||||
&:hover {
|
||||
color: $text-color;
|
||||
}
|
||||
}
|
||||
@@ -89,18 +101,17 @@ header {
|
||||
.info {
|
||||
font-size: 13px;
|
||||
font-weight: 300;
|
||||
letter-spacing: .5px;
|
||||
letter-spacing: 0.5px;
|
||||
color: $text-color;
|
||||
text-decoration: none;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@include tablet-min {
|
||||
padding-left: 1.25rem;;
|
||||
padding-left: 1.25rem;
|
||||
}
|
||||
@include desktop-lg-min {
|
||||
padding-left: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="page-container">
|
||||
<list-header :title="listTitle" :info="info" :sticky="true" />
|
||||
|
||||
<results-list :results="results" v-if="results" />
|
||||
@@ -13,96 +12,107 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import ListHeader from '@/components/ListHeader'
|
||||
import ResultsList from '@/components/ResultsList'
|
||||
import SeasonedButton from '@/components/ui/SeasonedButton'
|
||||
import Loader from '@/components/ui/Loader'
|
||||
import { getTmdbMovieListByName, getRequests } from '@/api'
|
||||
import store from '@/store'
|
||||
import ListHeader from "@/components/ListHeader";
|
||||
import ResultsList from "@/components/ResultsList";
|
||||
import SeasonedButton from "@/components/ui/SeasonedButton";
|
||||
import Loader from "@/components/ui/Loader";
|
||||
import { getTmdbMovieListByName, getRequests } from "@/api";
|
||||
import store from "@/store";
|
||||
|
||||
export default {
|
||||
components: { ListHeader, ResultsList, SeasonedButton, Loader },
|
||||
data() {
|
||||
return {
|
||||
legalTmdbLists: [ 'now_playing', 'upcoming', 'popular' ],
|
||||
legalTmdbLists: ["now_playing", "upcoming", "popular"],
|
||||
results: [],
|
||||
page: 1,
|
||||
totalPages: 0,
|
||||
totalResults: 0,
|
||||
loading: true
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
listTitle() {
|
||||
if (this.results.length === 0)
|
||||
return ''
|
||||
if (this.results.length === 0) return "";
|
||||
|
||||
const routeListName = this.$route.params.name
|
||||
console.log('routelistname', routeListName)
|
||||
return routeListName.includes('_') ? routeListName.split('_').join(' ') : routeListName
|
||||
const routeListName = this.$route.params.name;
|
||||
console.log("routelistname", routeListName);
|
||||
return routeListName.includes("_")
|
||||
? routeListName.split("_").join(" ")
|
||||
: routeListName;
|
||||
},
|
||||
info() {
|
||||
if (this.results.length === 0)
|
||||
return [null, null]
|
||||
return [this.pageCount, this.resultCount]
|
||||
if (this.results.length === 0) return [null, null];
|
||||
return [this.pageCount, this.resultCount];
|
||||
},
|
||||
resultCount() {
|
||||
const loadedResults = this.results.length
|
||||
const totalResults = this.totalResults < 10000 ? this.totalResults : '∞'
|
||||
return `${loadedResults} of ${totalResults} results`
|
||||
const loadedResults = this.results.length;
|
||||
const totalResults = this.totalResults < 10000 ? this.totalResults : "∞";
|
||||
return `${loadedResults} of ${totalResults} results`;
|
||||
},
|
||||
pageCount() {
|
||||
return `Page ${this.page} of ${this.totalPages}`
|
||||
return `Page ${this.page} of ${this.totalPages}`;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadMore() {
|
||||
console.log(this.$route)
|
||||
console.log(this.$route);
|
||||
this.loading = true;
|
||||
this.page++
|
||||
this.page++;
|
||||
|
||||
window.history.replaceState({}, 'search', `/#/${this.$route.fullPath}?page=${this.page}`)
|
||||
this.init()
|
||||
window.history.replaceState(
|
||||
{},
|
||||
"search",
|
||||
`/#/${this.$route.fullPath}?page=${this.page}`
|
||||
);
|
||||
this.init();
|
||||
},
|
||||
init() {
|
||||
const routeListName = this.$route.params.name
|
||||
const routeListName = this.$route.params.name;
|
||||
|
||||
if (routeListName === 'request') {
|
||||
getRequests(this.page)
|
||||
.then(results => {
|
||||
this.results = this.results.concat(...results.results)
|
||||
this.page = results.page
|
||||
this.totalPages = results.total_pages
|
||||
this.totalResults = results.total_results
|
||||
})
|
||||
if (routeListName === "request") {
|
||||
getRequests(this.page).then(results => {
|
||||
this.results = this.results.concat(...results.results);
|
||||
this.page = results.page;
|
||||
this.totalPages = results.total_pages;
|
||||
this.totalResults = results.total_results;
|
||||
});
|
||||
} else if (this.legalTmdbLists.includes(routeListName)) {
|
||||
getTmdbMovieListByName(routeListName, this.page)
|
||||
.then(results => {
|
||||
this.results = this.results.concat(...results.results)
|
||||
this.page = results.page
|
||||
this.totalPages = results.total_pages
|
||||
this.totalResults = results.total_results
|
||||
})
|
||||
getTmdbMovieListByName(routeListName, this.page).then(results => {
|
||||
this.results = this.results.concat(...results.results);
|
||||
this.page = results.page;
|
||||
this.totalPages = results.total_pages;
|
||||
this.totalResults = results.total_results;
|
||||
});
|
||||
} else {
|
||||
// TODO handle if list is not found
|
||||
console.log('404 this is not a tmdb list')
|
||||
console.log("404 this is not a tmdb list");
|
||||
}
|
||||
|
||||
this.loading = false
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.results.length === 0)
|
||||
this.init()
|
||||
if (this.results.length === 0) this.init();
|
||||
|
||||
store.dispatch('documentTitle/updateTitle', `${this.$router.history.current.name} ${this.$route.params.name}`)
|
||||
store.dispatch(
|
||||
"documentTitle/updateTitle",
|
||||
`${this.$router.history.current.name} ${this.$route.params.name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "./src/scss/media-queries";
|
||||
|
||||
@include mobile-only {
|
||||
.page-container {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.fullwidth-button {
|
||||
width: 100%;
|
||||
margin: 1rem 0;
|
||||
|
||||
@@ -1,52 +1,60 @@
|
||||
<template>
|
||||
<section class="movie">
|
||||
|
||||
<!-- HEADER w/ POSTER -->
|
||||
<header class="movie__header" :style="{ 'background-image': movie && backdrop !== null ? 'url(' + ASSET_URL + ASSET_SIZES[1] + backdrop + ')' : '' }" :class="compact ? 'compact' : ''" @click="compact=!compact">
|
||||
<div class="movie__wrap movie__wrap--header">
|
||||
<figure class="movie__poster">
|
||||
<img v-if="movie && poster === null"
|
||||
class="movies-item__img is-loaded"
|
||||
alt="movie poster image"
|
||||
src="~assets/no-image.png">
|
||||
<img v-else-if="poster === undefined"
|
||||
class="movies-item__img grey"
|
||||
alt="movie poster image">
|
||||
<!-- src="~assets/placeholder.png"> -->
|
||||
<img v-else
|
||||
class="movies-item__img is-loaded"
|
||||
alt="movie poster image"
|
||||
:src="ASSET_URL + ASSET_SIZES[0] + poster">
|
||||
</figure>
|
||||
<header
|
||||
ref="header"
|
||||
:class="compact ? 'compact' : ''"
|
||||
@click="compact = !compact"
|
||||
>
|
||||
<figure class="movie__poster">
|
||||
<img
|
||||
class="movie-item__img is-loaded"
|
||||
ref="poster-image"
|
||||
src="~assets/placeholder.png"
|
||||
/>
|
||||
</figure>
|
||||
|
||||
<div class="movie__title">
|
||||
<h1 v-if="movie">{{ movie.title }}</h1>
|
||||
<loading-placeholder v-else :count="1" />
|
||||
</div>
|
||||
</div>
|
||||
<h1 class="movie__title" v-if="movie">{{ movie.title }}</h1>
|
||||
<loading-placeholder v-else :count="1" />
|
||||
</header>
|
||||
|
||||
<!-- Siderbar and movie info -->
|
||||
<div class="movie__main">
|
||||
<div class="movie__wrap movie__wrap--main">
|
||||
|
||||
<!-- SIDEBAR ACTIONS -->
|
||||
<div class="movie__actions" v-if="movie">
|
||||
|
||||
<sidebar-list-element :iconRef="'#iconNot_exsits'" :active="matched"
|
||||
:iconRefActive="'#iconExists'" :textActive="'Already in plex 🎉'">
|
||||
|
||||
<sidebar-list-element
|
||||
:iconRef="'#iconNot_exsits'"
|
||||
:active="matched"
|
||||
:iconRefActive="'#iconExists'"
|
||||
:textActive="'Already in plex 🎉'"
|
||||
>
|
||||
Not yet in plex
|
||||
</sidebar-list-element>
|
||||
<sidebar-list-element @click="sendRequest" :iconRef="'#iconSent'"
|
||||
:active="requested" :textActive="'Requested to be downloaded'">
|
||||
|
||||
<sidebar-list-element
|
||||
@click="sendRequest"
|
||||
:iconRef="'#iconSent'"
|
||||
:active="requested"
|
||||
:textActive="'Requested to be downloaded'"
|
||||
>
|
||||
Request to be downloaded?
|
||||
</sidebar-list-element>
|
||||
<sidebar-list-element v-if="admin" @click="showTorrents=!showTorrents"
|
||||
:iconRef="'#icon_torrents'" :active="showTorrents"
|
||||
:supplementaryText="numberOfTorrentResults">
|
||||
|
||||
<sidebar-list-element
|
||||
v-if="isPlexAuthenticated && matched"
|
||||
@click="openInPlex"
|
||||
:iconString="'⏯ '"
|
||||
>
|
||||
Watch in plex now!
|
||||
</sidebar-list-element>
|
||||
|
||||
<sidebar-list-element
|
||||
v-if="admin"
|
||||
@click="showTorrents = !showTorrents"
|
||||
:iconRef="'#icon_torrents'"
|
||||
:active="showTorrents"
|
||||
:supplementaryText="numberOfTorrentResults"
|
||||
>
|
||||
Search for torrents
|
||||
</sidebar-list-element>
|
||||
<sidebar-list-element @click="openTmdb" :iconRef="'#icon_info'">
|
||||
@@ -56,83 +64,129 @@
|
||||
|
||||
<!-- Loading placeholder -->
|
||||
<div class="movie__actions text-input__loading" v-else>
|
||||
<div class="movie__actions-link" v-for="_ in admin ? Array(4) : Array(3)">
|
||||
<div class="movie__actions-text text-input__loading--line" style="margin:9px; margin-left: -3px;"></div>
|
||||
<div
|
||||
class="movie__actions-link"
|
||||
v-for="_ in admin ? Array(4) : Array(3)"
|
||||
>
|
||||
<div
|
||||
class="movie__actions-text text-input__loading--line"
|
||||
style="margin: 9px; margin-left: -3px"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- MOVIE INFO -->
|
||||
<div class="movie__info">
|
||||
<div class="movie__description" v-if="movie"> {{ movie.overview }}</div>
|
||||
|
||||
<!-- Loading placeholder -->
|
||||
<div
|
||||
class="movie__description noselect"
|
||||
@click="truncatedDescription = !truncatedDescription"
|
||||
v-if="!loading"
|
||||
>
|
||||
<span :class="truncatedDescription ? 'truncated' : null">{{
|
||||
movie.overview
|
||||
}}</span>
|
||||
<button class="truncate-toggle"><i>⬆</i></button>
|
||||
</div>
|
||||
<div v-else class="movie__description">
|
||||
<loading-placeholder :count="12" />
|
||||
<loading-placeholder :count="5" />
|
||||
</div>
|
||||
|
||||
<div class="movie__details" v-if="movie">
|
||||
<div v-if="movie.year" class="movie__details-block">
|
||||
<h2 class="movie__details-title">Release Date</h2>
|
||||
<div class="movie__details-text">{{ movie.year }}</div>
|
||||
<div v-if="movie.year">
|
||||
<h2 class="title">Release Date</h2>
|
||||
<div class="text">{{ movie.year }}</div>
|
||||
</div>
|
||||
|
||||
<div v-if="movie.rank" class="movie__details-block">
|
||||
<h2 class="movie__details-title">Rating</h2>
|
||||
<div class="movie__details-text">{{ movie.rank }}</div>
|
||||
<div v-if="movie.rating">
|
||||
<h2 class="title">Rating</h2>
|
||||
<div class="text">{{ movie.rating }}</div>
|
||||
</div>
|
||||
|
||||
<div v-if="movie.type == 'show'" class="movie__details-block">
|
||||
<h2 class="movie__details-title">Seasons</h2>
|
||||
<div class="movie__details-text">{{ movie.seasons }}</div>
|
||||
<div v-if="movie.type == 'show'">
|
||||
<h2 class="title">Seasons</h2>
|
||||
<div class="text">{{ movie.seasons }}</div>
|
||||
</div>
|
||||
|
||||
<div v-if="movie.genres" class="movie__details-block">
|
||||
<h2 class="movie__details-title">Genres</h2>
|
||||
<div class="movie__details-text">{{ nestedDataToString(movie.genres) }}</div>
|
||||
<div v-if="movie.genres">
|
||||
<h2 class="title">Genres</h2>
|
||||
<div class="text">{{ movie.genres.join(", ") }}</div>
|
||||
</div>
|
||||
|
||||
<div v-if="movie.type == 'show'">
|
||||
<h2 class="title">Production status</h2>
|
||||
<div class="text">{{ movie.production_status }}</div>
|
||||
</div>
|
||||
|
||||
<div v-if="movie.type == 'show'">
|
||||
<h2 class="title">Runtime</h2>
|
||||
<div class="text">{{ movie.runtime[0] }} minutes</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- TODO: change this classname, this is general -->
|
||||
|
||||
<div class="movie__admin" v-if="movie && movie.credits">
|
||||
<h2 class="movie__details-title">Cast</h2>
|
||||
<div style="display: flex; flex-wrap: wrap;">
|
||||
<person v-for="cast in movie.credits.cast" :info="cast"
|
||||
style="flex-basis: 0;"></person>
|
||||
</div>
|
||||
<div style="display: flex; flex-wrap: wrap">
|
||||
<person
|
||||
v-for="cast in movie.credits.cast"
|
||||
:info="cast"
|
||||
style="flex-basis: 0"
|
||||
></person>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- TORRENT LIST -->
|
||||
<TorrentList v-if="movie" :show="showTorrents" :query="title" :tmdb_id="id"
|
||||
:admin="admin"></TorrentList>
|
||||
<TorrentList
|
||||
v-if="movie"
|
||||
:show="showTorrents"
|
||||
:query="title"
|
||||
:tmdb_id="id"
|
||||
:admin="admin"
|
||||
></TorrentList>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import storage from '@/storage'
|
||||
import img from '@/directives/v-image'
|
||||
import TorrentList from './TorrentList'
|
||||
import Person from './Person'
|
||||
import SidebarListElement from './ui/sidebarListElem'
|
||||
import store from '@/store'
|
||||
import LoadingPlaceholder from './ui/LoadingPlaceholder'
|
||||
import storage from "@/storage";
|
||||
import img from "@/directives/v-image";
|
||||
import TorrentList from "./TorrentList";
|
||||
import Person from "./Person";
|
||||
import SidebarListElement from "./ui/sidebarListElem";
|
||||
import store from "@/store";
|
||||
import LoadingPlaceholder from "./ui/LoadingPlaceholder";
|
||||
|
||||
import { getMovie, getPerson, getShow, request, getRequestStatus } from '@/api'
|
||||
import {
|
||||
getMovie,
|
||||
getPerson,
|
||||
getShow,
|
||||
request,
|
||||
getRequestStatus,
|
||||
watchLink
|
||||
} from "@/api";
|
||||
|
||||
export default {
|
||||
props: ['id', 'type'],
|
||||
// props: ['id', 'type'],
|
||||
props: {
|
||||
id: {
|
||||
required: true,
|
||||
type: Number
|
||||
},
|
||||
type: {
|
||||
required: false,
|
||||
type: String
|
||||
}
|
||||
},
|
||||
components: { TorrentList, Person, LoadingPlaceholder, SidebarListElement },
|
||||
directives: { img: img }, // TODO decide to remove or use
|
||||
data(){
|
||||
return{
|
||||
ASSET_URL: 'https://image.tmdb.org/t/p/',
|
||||
ASSET_SIZES: ['w500', 'w780', 'original'],
|
||||
data() {
|
||||
return {
|
||||
ASSET_URL: "https://image.tmdb.org/t/p/",
|
||||
ASSET_SIZES: ["w500", "w780", "original"],
|
||||
movie: undefined,
|
||||
title: undefined,
|
||||
poster: undefined,
|
||||
@@ -140,92 +194,201 @@ export default {
|
||||
matched: false,
|
||||
userLoggedIn: storage.sessionId ? true : false,
|
||||
requested: false,
|
||||
admin: localStorage.getItem('admin') == "true" ? true : false,
|
||||
admin: localStorage.getItem("admin") == "true" ? true : false,
|
||||
showTorrents: false,
|
||||
compact: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
parseResponse(movie) {
|
||||
this.movie = { ...movie }
|
||||
this.title = movie.title
|
||||
this.poster = movie.poster
|
||||
this.backdrop = movie.backdrop
|
||||
this.matched = movie.exists_in_plex || false
|
||||
this.checkIfRequested(movie)
|
||||
.then(status => this.requested = status)
|
||||
|
||||
store.dispatch('documentTitle/updateTitle', movie.title)
|
||||
},
|
||||
async checkIfRequested(movie) {
|
||||
return await getRequestStatus(movie.id, movie.type)
|
||||
},
|
||||
nestedDataToString(data) {
|
||||
return data.join(', ')
|
||||
},
|
||||
sendRequest(){
|
||||
request(this.id, this.type, storage.token)
|
||||
.then(resp => {
|
||||
if (resp.success) {
|
||||
this.requested = true
|
||||
}
|
||||
})
|
||||
},
|
||||
openTmdb(){
|
||||
const tmdbType = this.type === 'show' ? 'tv' : this.type
|
||||
window.location.href = 'https://www.themoviedb.org/' + tmdbType + '/' + this.id
|
||||
},
|
||||
compact: false,
|
||||
loading: true,
|
||||
truncatedDescription: true
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
id: function(val){
|
||||
if (this.type === 'movie') {
|
||||
id: function (val) {
|
||||
if (this.type === "movie") {
|
||||
this.fetchMovie(val);
|
||||
} else {
|
||||
this.fetchShow(val)
|
||||
this.fetchShow(val);
|
||||
}
|
||||
},
|
||||
backdrop: function (backdrop) {
|
||||
if (backdrop != null) {
|
||||
const style = {
|
||||
backgroundImage:
|
||||
"url(" + this.ASSET_URL + this.ASSET_SIZES[1] + backdrop + ")"
|
||||
};
|
||||
|
||||
Object.assign(this.$refs.header.style, style);
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
numberOfTorrentResults: () => {
|
||||
let numTorrents = store.getters['torrentModule/resultCount']
|
||||
return numTorrents !== null ? numTorrents + ' results' : null
|
||||
let numTorrents = store.getters["torrentModule/resultCount"];
|
||||
return numTorrents !== null ? numTorrents + " results" : null;
|
||||
},
|
||||
isPlexAuthenticated: () => {
|
||||
return store.getters["userModule/isPlexAuthenticated"];
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
store.dispatch('documentTitle/updateTitle', this.prevDocumentTitle)
|
||||
},
|
||||
created(){
|
||||
this.prevDocumentTitle = store.getters['documentTitle/title']
|
||||
methods: {
|
||||
parseResponse(movie) {
|
||||
this.loading = false;
|
||||
this.movie = { ...movie };
|
||||
this.title = movie.title;
|
||||
this.poster = movie.poster;
|
||||
this.backdrop = movie.backdrop;
|
||||
this.matched = movie.exists_in_plex || false;
|
||||
this.checkIfRequested(movie).then(status => (this.requested = status));
|
||||
|
||||
if (this.type === 'movie') {
|
||||
store.dispatch("documentTitle/updateTitle", movie.title);
|
||||
this.setPosterSrc();
|
||||
},
|
||||
async checkIfRequested(movie) {
|
||||
return await getRequestStatus(movie.id, movie.type);
|
||||
},
|
||||
setPosterSrc() {
|
||||
const poster = this.$refs["poster-image"];
|
||||
if (this.poster == null) {
|
||||
poster.src = "/no-image.png";
|
||||
return;
|
||||
}
|
||||
|
||||
poster.src = `${this.ASSET_URL}${this.ASSET_SIZES[0]}${this.poster}`;
|
||||
},
|
||||
sendRequest() {
|
||||
request(this.id, this.type, storage.token).then(resp => {
|
||||
if (resp.success) {
|
||||
this.requested = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
openInPlex() {
|
||||
watchLink(this.title, this.movie.year, storage.token).then(
|
||||
watchLink => (window.location = watchLink)
|
||||
);
|
||||
},
|
||||
openTmdb() {
|
||||
const tmdbType = this.type === "show" ? "tv" : this.type;
|
||||
window.location.href =
|
||||
"https://www.themoviedb.org/" + tmdbType + "/" + this.id;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.prevDocumentTitle = store.getters["documentTitle/title"];
|
||||
|
||||
if (this.type === "movie") {
|
||||
getMovie(this.id, true)
|
||||
.then(this.parseResponse)
|
||||
.catch(error => {
|
||||
this.$router.push({ name: '404' });
|
||||
})
|
||||
} else if (this.type == 'person') {
|
||||
getPerson(this.id, true)
|
||||
this.$router.push({ name: "404" });
|
||||
});
|
||||
} else if (this.type == "person") {
|
||||
getPerson(this.id, true)
|
||||
.then(this.parseResponse)
|
||||
.catch(error => {
|
||||
this.$router.push({ name: '404' });
|
||||
})
|
||||
this.$router.push({ name: "404" });
|
||||
});
|
||||
} else {
|
||||
getShow(this.id, true)
|
||||
.then(this.parseResponse)
|
||||
.catch(error => {
|
||||
this.$router.push({ name: '404' });
|
||||
})
|
||||
this.$router.push({ name: "404" });
|
||||
});
|
||||
}
|
||||
|
||||
console.log('admin: ', this.admin)
|
||||
},
|
||||
beforeDestroy() {
|
||||
store.dispatch("documentTitle/updateTitle", this.prevDocumentTitle);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "./src/scss/loading-placeholder";
|
||||
@import "./src/scss/variables";
|
||||
@import "./src/scss/media-queries";
|
||||
@import "./src/scss/main";
|
||||
|
||||
header {
|
||||
$duration: 0.2s;
|
||||
height: 250px;
|
||||
transform: scaleY(1);
|
||||
transition: height $duration ease;
|
||||
transform-origin: top;
|
||||
position: relative;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% 50%;
|
||||
background-color: $background-color;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@include tablet-min {
|
||||
height: 350px;
|
||||
}
|
||||
&:before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: $background-dark-85;
|
||||
}
|
||||
@include mobile {
|
||||
&.compact {
|
||||
height: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.movie__poster {
|
||||
display: none;
|
||||
|
||||
@include desktop {
|
||||
background: $background-color;
|
||||
height: 0;
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: calc(45% - 40px);
|
||||
top: 40px;
|
||||
left: 40px;
|
||||
|
||||
> img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.truncate-toggle {
|
||||
border: none;
|
||||
background: none;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
color: $text-color;
|
||||
|
||||
> i {
|
||||
font-style: unset;
|
||||
font-size: 0.7rem;
|
||||
transition: 0.3s ease all;
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: "";
|
||||
flex: 1;
|
||||
border-bottom: 1px solid $text-color-50;
|
||||
}
|
||||
&::before {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
&::after {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.movie {
|
||||
&__wrap {
|
||||
@@ -238,7 +401,7 @@ export default {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: column;
|
||||
@include tablet-min{
|
||||
@include tablet-min {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
@@ -246,49 +409,6 @@ export default {
|
||||
color: $text-color;
|
||||
}
|
||||
}
|
||||
&__header {
|
||||
$duration: 0.2s;
|
||||
height: 250px;
|
||||
transform: scaleY(1);
|
||||
transition: height $duration ease;
|
||||
transform-origin: top;
|
||||
position: relative;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% 50%;
|
||||
background-color: $background-color;
|
||||
@include tablet-min {
|
||||
height: 350px;
|
||||
}
|
||||
&:before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: $background-dark-85;
|
||||
}
|
||||
&.compact {
|
||||
height: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
&__poster {
|
||||
display: none;
|
||||
@include tablet-min {
|
||||
background: $background-color;
|
||||
height: 0;
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: calc(45% - 40px);
|
||||
top: 40px;
|
||||
left: 40px;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
&__img {
|
||||
display: block;
|
||||
@@ -332,56 +452,67 @@ export default {
|
||||
|
||||
height: 100%;
|
||||
}
|
||||
&__actions {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
order: 2;
|
||||
padding: 20px;
|
||||
border-top: 1px solid $text-color-5;
|
||||
@include tablet-min {
|
||||
order: 1;
|
||||
width: 45%;
|
||||
padding: 185px 0 40px 40px;
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
&__info {
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
&__actions {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
order: 2;
|
||||
padding: 20px;
|
||||
border-top: 1px solid $text-color-5;
|
||||
@include tablet-min {
|
||||
order: 1;
|
||||
@include tablet-min {
|
||||
order: 2;
|
||||
padding: 40px;
|
||||
width: 55%;
|
||||
margin-left: 45%;
|
||||
width: 45%;
|
||||
padding: 185px 0 40px 40px;
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
&__info {
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
order: 1;
|
||||
@include tablet-min {
|
||||
order: 2;
|
||||
padding: 40px;
|
||||
width: 55%;
|
||||
margin-left: 45%;
|
||||
}
|
||||
}
|
||||
&__info {
|
||||
margin-left: 0;
|
||||
}
|
||||
&__description {
|
||||
font-weight: 300;
|
||||
font-size: 13px;
|
||||
line-height: 1.8;
|
||||
margin-bottom: 20px;
|
||||
|
||||
& .truncated {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
-webkit-line-clamp: 4;
|
||||
-webkit-box-orient: vertical;
|
||||
|
||||
& + .truncate-toggle > i {
|
||||
transform: rotateY(0deg) rotateZ(180deg);
|
||||
}
|
||||
}
|
||||
&__info {
|
||||
margin-left: 0;
|
||||
|
||||
@include tablet-min {
|
||||
margin-bottom: 30px;
|
||||
font-size: 14px;
|
||||
}
|
||||
&__description {
|
||||
font-weight: 300;
|
||||
font-size: 13px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
&__details {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
> div {
|
||||
margin-bottom: 20px;
|
||||
margin-right: 20px;
|
||||
@include tablet-min {
|
||||
margin-bottom: 30px;
|
||||
font-size: 14px;
|
||||
margin-right: 30px;
|
||||
}
|
||||
}
|
||||
&__details {
|
||||
&-block {
|
||||
float: left;
|
||||
}
|
||||
&-block:not(:last-child) {
|
||||
margin-bottom: 20px;
|
||||
margin-right: 20px;
|
||||
@include tablet-min {
|
||||
margin-bottom: 30px;
|
||||
margin-right: 30px;
|
||||
}
|
||||
}
|
||||
&-title {
|
||||
& .title {
|
||||
margin: 0;
|
||||
font-weight: 400;
|
||||
text-transform: uppercase;
|
||||
@@ -391,34 +522,35 @@ export default {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
&-text {
|
||||
& .text {
|
||||
font-weight: 300;
|
||||
font-size: 14px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
&__admin {
|
||||
}
|
||||
&__admin {
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
order: 2;
|
||||
@include tablet-min {
|
||||
order: 3;
|
||||
padding: 40px;
|
||||
padding-top: 0px;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
order: 2;
|
||||
@include tablet-min {
|
||||
order: 3;
|
||||
padding: 40px;
|
||||
padding-top: 0px;
|
||||
width: 100%;
|
||||
}
|
||||
&-title {
|
||||
margin: 0;
|
||||
font-weight: 400;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: $green;
|
||||
padding-bottom: 20px;
|
||||
@include tablet-min {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
&-title {
|
||||
margin: 0;
|
||||
font-weight: 400;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: $green;
|
||||
padding-bottom: 20px;
|
||||
@include tablet-min {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,122 +1,207 @@
|
||||
<template>
|
||||
<li class="movies-item" :class="{'shortList': shortList}">
|
||||
<a class="movies-item__link" :class="{'no-image': noImage}" @click.prevent="openMoviePopup(movie.id, movie.type)">
|
||||
<li class="movie-item" :class="{ shortList: shortList }">
|
||||
<figure class="movie-item__poster">
|
||||
<img
|
||||
class="movie-item__img"
|
||||
ref="poster-image"
|
||||
@click="openMoviePopup(movie.id, movie.type)"
|
||||
:alt="posterAltText"
|
||||
:data-src="poster"
|
||||
src="~assets/placeholder.png"
|
||||
/>
|
||||
|
||||
<!-- TODO change to picture element -->
|
||||
<figure class="movies-item__poster">
|
||||
<img v-if="!noImage" class="movies-item__img" src="~assets/placeholder.png" v-img="poster()" alt="">
|
||||
<img v-if="noImage" class="movies-item__img is-loaded" src="~assets/no-image.png" alt="">
|
||||
|
||||
<div v-if="movie.download" class="progress">
|
||||
<progress :value="movie.download.progress" max="100"></progress>
|
||||
<span>{{ movie.download.state }}: {{ movie.download.progress }}%</span>
|
||||
</div>
|
||||
</figure>
|
||||
<div class="movies-item__content">
|
||||
<p class="movies-item__title">{{ movie.title || movie.name }}</p>
|
||||
<p class="movies-item__title">{{ movie.year }}</p>
|
||||
<div v-if="movie.download" class="progress">
|
||||
<progress :value="movie.download.progress" max="100"></progress>
|
||||
<span>{{ movie.download.state }}: {{ movie.download.progress }}%</span>
|
||||
</div>
|
||||
</a>
|
||||
</figure>
|
||||
|
||||
<div class="movie-item__info">
|
||||
<p v-if="movie.title || movie.name">{{ movie.title || movie.name }}</p>
|
||||
<p v-if="movie.year">{{ movie.year }}</p>
|
||||
<p v-if="movie.type == 'person'">
|
||||
Known for: {{ movie.known_for_department }}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import img from '../directives/v-image'
|
||||
import img from "../directives/v-image";
|
||||
|
||||
export default {
|
||||
props: ['movie', 'shortList'],
|
||||
props: {
|
||||
movie: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
shortList: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
directives: {
|
||||
img: img
|
||||
},
|
||||
data(){
|
||||
data() {
|
||||
return {
|
||||
noImage: false
|
||||
poster: undefined,
|
||||
observed: false,
|
||||
posterSizes: [
|
||||
{
|
||||
id: "w500",
|
||||
minWidth: 500
|
||||
},
|
||||
{
|
||||
id: "w342",
|
||||
minWidth: 342
|
||||
},
|
||||
{
|
||||
id: "w185",
|
||||
minWidth: 185
|
||||
},
|
||||
{
|
||||
id: "w154",
|
||||
minWidth: 0
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
posterAltText: function () {
|
||||
const type = this.movie.type || "";
|
||||
const title = this.movie.title || this.movie.name;
|
||||
return this.movie.poster
|
||||
? `Poster for ${type} ${title}`
|
||||
: `Missing image for ${type} ${title}`;
|
||||
}
|
||||
},
|
||||
beforeMount() {
|
||||
if (this.movie.poster != null) {
|
||||
this.poster = "https://image.tmdb.org/t/p/w500" + this.movie.poster;
|
||||
} else {
|
||||
this.poster = "/no-image.png";
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const poster = this.$refs["poster-image"];
|
||||
if (poster == null) return;
|
||||
|
||||
const imageObserver = new IntersectionObserver((entries, imgObserver) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting && this.observed == false) {
|
||||
const lazyImage = entry.target;
|
||||
lazyImage.src = lazyImage.dataset.src;
|
||||
lazyImage.className = lazyImage.className + " is-loaded";
|
||||
this.observed = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
imageObserver.observe(poster);
|
||||
},
|
||||
methods: {
|
||||
// TODO handle missing images better and load diff sizes based on screen size
|
||||
poster() {
|
||||
if (this.movie.poster) {
|
||||
return 'https://image.tmdb.org/t/p/w500' + this.movie.poster
|
||||
} else {
|
||||
this.noImage = true
|
||||
}
|
||||
},
|
||||
openMoviePopup(id, type) {
|
||||
this.$popup.open(id, type)
|
||||
this.$popup.open(id, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
@import "./src/scss/variables";
|
||||
@import "./src/scss/media-queries";
|
||||
@import "./src/scss/main";
|
||||
|
||||
.movies-item {
|
||||
.movie-item {
|
||||
padding: 10px;
|
||||
width: 50%;
|
||||
background-color: $background-color;
|
||||
transition: background-color 0.5s ease;
|
||||
|
||||
@include tablet-min{
|
||||
@include tablet-min {
|
||||
padding: 15px;
|
||||
width: 33%;
|
||||
}
|
||||
@include tablet-landscape-min{
|
||||
@include tablet-landscape-min {
|
||||
padding: 15px;
|
||||
width: 25%;
|
||||
}
|
||||
@include desktop-min{
|
||||
@include desktop-min {
|
||||
padding: 15px;
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
@include desktop-lg-min{
|
||||
@include desktop-lg-min {
|
||||
padding: 15px;
|
||||
width: 12.5%;
|
||||
}
|
||||
|
||||
&__link{
|
||||
&:hover &__info > p {
|
||||
color: $text-color;
|
||||
}
|
||||
|
||||
&__poster {
|
||||
text-decoration: none;
|
||||
color: $text-color-70;
|
||||
font-weight: 300;
|
||||
|
||||
> img {
|
||||
width: 100%;
|
||||
opacity: 0;
|
||||
transform: scale(0.97) translateZ(0);
|
||||
transition: opacity 1s ease, transform 0.5s ease;
|
||||
&.is-loaded {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.03);
|
||||
box-shadow: 0 0 10px rgba($dark, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
&__content{
|
||||
|
||||
&__info {
|
||||
padding-top: 15px;
|
||||
}
|
||||
&__poster{
|
||||
transition: transform 0.5s ease, box-shadow 0.3s ease;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
&__img{
|
||||
width: 100%;
|
||||
opacity: 0;
|
||||
transform: scale(0.97) translateZ(0);
|
||||
transition: opacity 0.5s ease, transform 0.5s ease;
|
||||
&.is-loaded{
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
font-weight: 300;
|
||||
|
||||
> p {
|
||||
color: $text-color-70;
|
||||
margin: 0;
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.5px;
|
||||
transition: color 0.5s ease;
|
||||
cursor: pointer;
|
||||
@include mobile-ls-min {
|
||||
font-size: 12px;
|
||||
}
|
||||
@include tablet-min {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
&__link:not(.no-image):hover &__poster{
|
||||
transform: scale(1.03);
|
||||
box-shadow: 0 0 10px rgba($dark, 0.1);
|
||||
}
|
||||
|
||||
.no-image {
|
||||
background-color: var(--text-color);
|
||||
color: var(--background-color);
|
||||
width: 100%;
|
||||
height: 383px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
span {
|
||||
font-size: 1.5rem;
|
||||
width: 70%;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
&__title{
|
||||
margin: 0;
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.5px;
|
||||
transition: color 0.5s ease;
|
||||
cursor: pointer;
|
||||
@include mobile-ls-min{
|
||||
font-size: 12px;
|
||||
}
|
||||
@include tablet-min{
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
&__link:hover &__title{
|
||||
color: $text-color;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -155,11 +240,10 @@ export default {
|
||||
progress::-webkit-progress-value {
|
||||
background-color: $green-70;
|
||||
border-radius: 4px;
|
||||
|
||||
}
|
||||
progress::-moz-progress-bar {
|
||||
/* style rules */
|
||||
background-color: green;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,78 +1,93 @@
|
||||
<template>
|
||||
<div>
|
||||
<nav class="nav">
|
||||
<router-link class="nav__logo" :to="{name: 'home'}" exact title="Vue.js — TMDb App">
|
||||
<svg class="nav__logo-image">
|
||||
<use xlink:href="#svgLogo"></use>
|
||||
</svg>
|
||||
</router-link>
|
||||
<nav class="nav">
|
||||
<router-link
|
||||
class="nav__logo"
|
||||
:to="{ name: 'home' }"
|
||||
exact
|
||||
title="Vue.js — TMDb App"
|
||||
>
|
||||
<svg class="nav__logo-image">
|
||||
<use xlink:href="#svgLogo"></use>
|
||||
</svg>
|
||||
</router-link>
|
||||
|
||||
<div class="nav__hamburger" @click="toggleNav">
|
||||
<div v-for="_ in 3" class="bar"></div>
|
||||
</div>
|
||||
<div class="nav__hamburger" @click="toggleNav">
|
||||
<div v-for="_ in 3" class="bar"></div>
|
||||
</div>
|
||||
|
||||
<ul class="nav__list">
|
||||
<li class="nav__item" v-for="item in listTypes">
|
||||
<router-link class="nav__link" :to="'/list/' + item.route">
|
||||
<div class="nav__link-wrap">
|
||||
<svg class="nav__link-icon">
|
||||
<use :xlink:href="'#icon_' + item.route"></use>
|
||||
</svg>
|
||||
<span class="nav__link-title">{{ item.title }}</span>
|
||||
</div>
|
||||
</router-link>
|
||||
</li>
|
||||
<ul class="nav__list">
|
||||
<li class="nav__item" v-for="item in listTypes">
|
||||
<router-link class="nav__link" :to="'/list/' + item.route">
|
||||
<div class="nav__link-wrap">
|
||||
<svg class="nav__link-icon">
|
||||
<use :xlink:href="'#icon_' + item.route"></use>
|
||||
</svg>
|
||||
<span class="nav__link-title">{{ item.title }}</span>
|
||||
</div>
|
||||
</router-link>
|
||||
</li>
|
||||
|
||||
<li class="nav__item nav__item--profile">
|
||||
<router-link class="nav__link nav__link--profile" :to="{name: 'signin'}" v-if="!userLoggedIn">
|
||||
<div class="nav__link-wrap">
|
||||
<svg class="nav__link-icon">
|
||||
<use xlink:href="#iconLogin"></use>
|
||||
</svg>
|
||||
<span class="nav__link-title">Sign in</span>
|
||||
</div>
|
||||
</router-link>
|
||||
<li class="nav__item mobile-only"></li>
|
||||
|
||||
<router-link class="nav__link nav__link--profile" :to="{name: 'profile'}" v-if="userLoggedIn">
|
||||
<div class="nav__link-wrap">
|
||||
<svg class="nav__link-icon">
|
||||
<use xlink:href="#iconLogin"></use>
|
||||
</svg>
|
||||
<span class="nav__link-title">Profile</span>
|
||||
</div>
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<li class="nav__item nav__item--profile">
|
||||
<router-link
|
||||
class="nav__link nav__link--profile"
|
||||
:to="{ name: 'signin' }"
|
||||
v-if="!userLoggedIn"
|
||||
>
|
||||
<div class="nav__link-wrap">
|
||||
<svg class="nav__link-icon">
|
||||
<use xlink:href="#iconLogin"></use>
|
||||
</svg>
|
||||
<span class="nav__link-title">Sign in</span>
|
||||
</div>
|
||||
</router-link>
|
||||
|
||||
<div class="spacer"></div>
|
||||
</div>
|
||||
<router-link
|
||||
class="nav__link nav__link--profile"
|
||||
:to="{ name: 'profile' }"
|
||||
v-if="userLoggedIn"
|
||||
>
|
||||
<div class="nav__link-wrap">
|
||||
<svg class="nav__link-icon">
|
||||
<use xlink:href="#iconLogin"></use>
|
||||
</svg>
|
||||
<span class="nav__link-title">Profile</span>
|
||||
</div>
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import storage from '@/storage'
|
||||
import storage from "@/storage";
|
||||
|
||||
export default {
|
||||
data(){
|
||||
data() {
|
||||
return {
|
||||
listTypes: storage.homepageLists,
|
||||
userLoggedIn: localStorage.getItem('token') ? true : false
|
||||
}
|
||||
userLoggedIn: localStorage.getItem("token") ? true : false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
setUserStatus(){
|
||||
this.userLoggedIn = localStorage.getItem('token') ? true : false;
|
||||
setUserStatus() {
|
||||
this.userLoggedIn = localStorage.getItem("token") ? true : false;
|
||||
},
|
||||
toggleNav(){
|
||||
document.querySelector('.nav__hamburger').classList.toggle('nav__hamburger--active');
|
||||
document.querySelector('.nav__list').classList.toggle('nav__list--active');
|
||||
toggleNav() {
|
||||
document
|
||||
.querySelector(".nav__hamburger")
|
||||
.classList.toggle("nav__hamburger--active");
|
||||
document
|
||||
.querySelector(".nav__list")
|
||||
.classList.toggle("nav__list--active");
|
||||
}
|
||||
},
|
||||
created(){
|
||||
created() {
|
||||
// TODO move this to state manager
|
||||
eventHub.$on('setUserStatus', this.setUserStatus);
|
||||
eventHub.$on("setUserStatus", this.setUserStatus);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -83,45 +98,45 @@ export default {
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
@include mobile-only {
|
||||
width: 100%;
|
||||
height: $header-size;
|
||||
}
|
||||
}
|
||||
|
||||
.nav {
|
||||
transition: background .5s ease;
|
||||
transition: background 0.5s ease;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
height: var(--header-size);
|
||||
z-index: 10;
|
||||
display: block;
|
||||
color: $text-color;
|
||||
background-color: $background-color-secondary;
|
||||
|
||||
@include tablet-min{
|
||||
@include tablet-min {
|
||||
top: 0;
|
||||
bottom: unset;
|
||||
width: 95px;
|
||||
height: 100vh;
|
||||
}
|
||||
&__logo {
|
||||
width: 55px;
|
||||
width: 95px;
|
||||
height: $header-size;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: $background-nav-logo;
|
||||
@include tablet-min{
|
||||
width: 95px;
|
||||
|
||||
@include mobile-only {
|
||||
align-items: flex-start;
|
||||
padding-top: 0.5rem;
|
||||
width: 55px;
|
||||
}
|
||||
&-image{
|
||||
|
||||
&-image {
|
||||
width: 35px;
|
||||
height: 31px;
|
||||
fill: $green;
|
||||
transition: transform 0.5s ease;
|
||||
@include tablet-min{
|
||||
|
||||
@include tablet-min {
|
||||
width: 45px;
|
||||
height: 40px;
|
||||
}
|
||||
@@ -135,12 +150,12 @@ export default {
|
||||
position: fixed;
|
||||
width: 55px;
|
||||
height: 50px;
|
||||
top: 0;
|
||||
bottom: 1.5rem;
|
||||
right: 0;
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
border-left: 1px solid $background-color;
|
||||
@include tablet-min{
|
||||
@include tablet-min {
|
||||
display: none;
|
||||
}
|
||||
.bar {
|
||||
@@ -172,9 +187,9 @@ export default {
|
||||
}
|
||||
}
|
||||
&--active {
|
||||
.bar{
|
||||
.bar {
|
||||
&:nth-child(1),
|
||||
&:nth-child(3){
|
||||
&:nth-child(3) {
|
||||
width: 0;
|
||||
}
|
||||
&:nth-child(2) {
|
||||
@@ -198,15 +213,21 @@ export default {
|
||||
left: 0;
|
||||
top: 50px;
|
||||
border-top: 1px solid $background-color;
|
||||
|
||||
@include mobile-only {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: unset;
|
||||
bottom: var(--header-size);
|
||||
height: min-content;
|
||||
flex-wrap: wrap;
|
||||
font-size: 0;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
background-color: $background-95;
|
||||
text-align: left;
|
||||
&--active{
|
||||
|
||||
&--active {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
@@ -221,15 +242,15 @@ export default {
|
||||
}
|
||||
}
|
||||
&__item {
|
||||
transition: background .5s ease, color .5s ease, border .5s ease;
|
||||
transition: background 0.5s ease, color 0.5s ease, border 0.5s ease;
|
||||
background-color: $background-color-secondary;
|
||||
color: $text-color-70;
|
||||
|
||||
@include mobile-only {
|
||||
flex: 0 0 50%;
|
||||
flex: 0 0 33.3%;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid $background-color;
|
||||
&:nth-child(odd){
|
||||
&:nth-child(odd) {
|
||||
border-right: 1px solid $background-color;
|
||||
|
||||
&:last-child {
|
||||
@@ -251,7 +272,8 @@ export default {
|
||||
border-left: 1px solid $background-color;
|
||||
}
|
||||
}
|
||||
&:hover, .is-active {
|
||||
&:hover,
|
||||
.is-active {
|
||||
color: $text-color;
|
||||
background-color: $background-color;
|
||||
}
|
||||
@@ -299,14 +321,14 @@ export default {
|
||||
height: 20px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
}
|
||||
&-title {
|
||||
margin-top: 5px;
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
&:hover &-icon, &.is-active &-icon {
|
||||
&:hover &-icon,
|
||||
&.is-active &-icon {
|
||||
fill: $text-color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<h2 class="profile__title">{{ emoji }} Welcome {{ username }}</h2>
|
||||
|
||||
<div class="button--group">
|
||||
<seasoned-button @click="showSettings = !showSettings">{{ showSettings ? 'hide settings' : 'show settings' }}</seasoned-button>
|
||||
<seasoned-button @click="toggleSettings">{{ showSettings ? 'hide settings' : 'show settings' }}</seasoned-button>
|
||||
|
||||
<seasoned-button @click="logOut">Log out</seasoned-button>
|
||||
</div>
|
||||
@@ -63,11 +63,15 @@ export default {
|
||||
methods: {
|
||||
toggleSettings() {
|
||||
this.showSettings = this.showSettings ? false : true;
|
||||
|
||||
if (this.showSettings) {
|
||||
this.$router.replace({ query: { settings: true} })
|
||||
} else {
|
||||
this.$router.replace({ name: 'profile' })
|
||||
}
|
||||
},
|
||||
logOut(){
|
||||
localStorage.clear();
|
||||
eventHub.$emit('setUserStatus');
|
||||
this.$router.push({ name: 'home' });
|
||||
this.$router.push('logout')
|
||||
}
|
||||
},
|
||||
created(){
|
||||
@@ -76,6 +80,8 @@ export default {
|
||||
} else {
|
||||
this.userLoggedIn = true;
|
||||
|
||||
this.showSettings = window.location.toString().includes('settings=true')
|
||||
|
||||
getUserRequests()
|
||||
.then(results => {
|
||||
this.results = results.results
|
||||
|
||||
@@ -2,17 +2,14 @@
|
||||
<section>
|
||||
<h1>Register new user</h1>
|
||||
|
||||
<seasoned-input placeholder="username" icon="Email" type="username" :value.sync="username" />
|
||||
<seasoned-input placeholder="username" icon="Email" type="username" :value.sync="username" @enter="submit"/>
|
||||
|
||||
<seasoned-input placeholder="password" icon="Keyhole" type="password"
|
||||
:value.sync="password" @enter="requestNewUser"/>
|
||||
|
||||
<seasoned-input placeholder="repeat password" icon="Keyhole" type="password"
|
||||
:value.sync="passwordRepeat" @enter="requestNewUser"/>
|
||||
|
||||
<seasoned-button @click="requestNewUser">Register</seasoned-button>
|
||||
<seasoned-input placeholder="password" icon="Keyhole" type="password" :value.sync="password" @enter="submit"/>
|
||||
<seasoned-input placeholder="repeat password" icon="Keyhole" type="password" :value.sync="passwordRepeat" @enter="submit"/>
|
||||
|
||||
<seasoned-button @click="submit">Register</seasoned-button>
|
||||
<router-link class="link" to="/signin">Have a user? Sign in here</router-link>
|
||||
|
||||
<seasoned-messages :messages.sync="messages"></seasoned-messages>
|
||||
</section>
|
||||
</template>
|
||||
@@ -34,57 +31,47 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
requestNewUser(){
|
||||
let { username, password, passwordRepeat } = this
|
||||
submit() {
|
||||
this.messages = [];
|
||||
let { username, password, passwordRepeat } = this;
|
||||
|
||||
let verifyCredentials = this.checkCredentials(username, password, passwordRepeat);
|
||||
if (username == null || username.length == 0) {
|
||||
this.messages.push({ type: 'error', title: 'Missing username' })
|
||||
return
|
||||
} else if (password == null || password.length == 0) {
|
||||
this.messages.push({ type: 'error', title: 'Missing password' })
|
||||
return
|
||||
} else if (passwordRepeat == null || passwordRepeat.length == 0) {
|
||||
this.messages.push({ type: 'error', title: 'Missing repeat password' })
|
||||
return
|
||||
} else if (passwordRepeat != password) {
|
||||
this.messages.push({ type: 'error', title: 'Passwords do not match' })
|
||||
return
|
||||
}
|
||||
|
||||
if (verifyCredentials.verified) {
|
||||
this.registerUser(username, password)
|
||||
},
|
||||
registerUser(username, password) {
|
||||
register(username, password, true)
|
||||
.then(data => {
|
||||
if (data.success){
|
||||
localStorage.setItem('token', data.token);
|
||||
const jwtData = parseJwt(data.token)
|
||||
localStorage.setItem('username', jwtData['username']);
|
||||
localStorage.setItem('admin', jwtData['admin'] || false);
|
||||
|
||||
register(username, password)
|
||||
.then(data => {
|
||||
if (data.success){
|
||||
localStorage.setItem('token', data.token);
|
||||
localStorage.setItem('username', username);
|
||||
localStorage.setItem('admin', data.admin)
|
||||
|
||||
eventHub.$emit('setUserStatus');
|
||||
this.$router.push({ name: 'profile' })
|
||||
}
|
||||
eventHub.$emit('setUserStatus');
|
||||
this.$router.push({ name: 'profile' })
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
this.messages.push({ type: 'error', title: 'Unexpected error', message: error.message })
|
||||
if (error.status === 401) {
|
||||
this.messages.push({ type: 'error', title: 'Access denied', message: 'Incorrect username or password' })
|
||||
}
|
||||
else {
|
||||
this.messages.push({ type: 'error', title: 'Unexpected error', message: error.message })
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.messages.push({ type: 'warning', title: 'Parse error', message: verifyCredentials.reason })
|
||||
}
|
||||
},
|
||||
checkCredentials(username, password, passwordRepeat) {
|
||||
if (!username || username.length === 0) {
|
||||
return {
|
||||
verified: false,
|
||||
reason: 'Fill inn username'
|
||||
}
|
||||
}
|
||||
else if (!password || !passwordRepeat) {
|
||||
return {
|
||||
verified: false,
|
||||
reason: "Fill inn both password fields"
|
||||
}
|
||||
}
|
||||
else if (password !== passwordRepeat) {
|
||||
return {
|
||||
verified: false,
|
||||
reason: 'Passwords do not match'
|
||||
}
|
||||
}
|
||||
else {
|
||||
return {
|
||||
verified: true,
|
||||
reason: 'Verified credentials'
|
||||
}
|
||||
}
|
||||
},
|
||||
logOut(){
|
||||
localStorage.clear();
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="page-container">
|
||||
<list-header :title="title" :info="resultCount" :sticky="true" />
|
||||
|
||||
<results-list :results="results" />
|
||||
|
||||
|
||||
<div v-if="page < totalPages" class="fullwidth-button">
|
||||
<seasoned-button @click="loadMore">load more</seasoned-button>
|
||||
</div>
|
||||
|
||||
<div class="notFound" v-if="results.length == 0 && loading == false">
|
||||
<h1 class="notFound-title">No results for search: <b>{{ query }}</b></h1>
|
||||
<h1 class="notFound-title">
|
||||
No results for search: <b>{{ query }}</b>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<loader v-if="loading" />
|
||||
@@ -29,11 +31,11 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { searchTmdb } from '@/api'
|
||||
import ListHeader from '@/components/ListHeader'
|
||||
import ResultsList from '@/components/ResultsList'
|
||||
import SeasonedButton from '@/components/ui/SeasonedButton'
|
||||
import Loader from '@/components/ui/Loader'
|
||||
import { searchTmdb } from "@/api";
|
||||
import ListHeader from "@/components/ListHeader";
|
||||
import ResultsList from "@/components/ResultsList";
|
||||
import SeasonedButton from "@/components/ui/SeasonedButton";
|
||||
import Loader from "@/components/ui/Loader";
|
||||
|
||||
export default {
|
||||
components: { ListHeader, ResultsList, SeasonedButton, Loader },
|
||||
@@ -58,59 +60,74 @@ export default {
|
||||
totalPages: 0,
|
||||
results: [],
|
||||
totalResults: []
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
resultCount() {
|
||||
const loadedResults = this.results.length
|
||||
const totalResults = this.totalResults < 10000 ? this.totalResults : '∞'
|
||||
return `${loadedResults} of ${totalResults} results`
|
||||
const loadedResults = this.results.length;
|
||||
const totalResults = this.totalResults < 10000 ? this.totalResults : "∞";
|
||||
return `${loadedResults} of ${totalResults} results`;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
search(query=this.query, page=this.page, adult=this.adult, mediaType=this.mediaType) {
|
||||
searchTmdb(query, page, adult, mediaType)
|
||||
.then(this.parseResponse)
|
||||
search(
|
||||
query = this.query,
|
||||
page = this.page,
|
||||
adult = this.adult,
|
||||
mediaType = this.mediaType
|
||||
) {
|
||||
searchTmdb(query, page, adult, mediaType).then(this.parseResponse);
|
||||
},
|
||||
parseResponse(data) {
|
||||
if (this.results.length > 0) {
|
||||
this.results.push(...data.results)
|
||||
this.results.push(...data.results);
|
||||
} else {
|
||||
this.results = data.results
|
||||
this.results = data.results;
|
||||
}
|
||||
|
||||
this.totalPages = data.total_pages
|
||||
this.totalResults = data.total_results || data.results.length
|
||||
this.totalPages = data.total_pages;
|
||||
this.totalResults = data.total_results || data.results.length;
|
||||
|
||||
this.loading = false
|
||||
this.loading = false;
|
||||
},
|
||||
loadMore() {
|
||||
this.page++
|
||||
this.page++;
|
||||
|
||||
window.history.replaceState({}, 'search', `/#/search?query=${this.query}&page=${this.page}`)
|
||||
this.search()
|
||||
window.history.replaceState(
|
||||
{},
|
||||
"search",
|
||||
`/#/search?query=${this.query}&page=${this.page}`
|
||||
);
|
||||
this.search();
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const { query, page, adult, media_type } = this.$route.query
|
||||
const { query, page, adult, media_type } = this.$route.query;
|
||||
|
||||
if (!query) {
|
||||
// abort
|
||||
console.error('abort, no query')
|
||||
console.error("abort, no query");
|
||||
}
|
||||
this.query = decodeURIComponent(query)
|
||||
this.page = page || 1
|
||||
this.adult = adult || this.adult
|
||||
this.mediaType = media_type || this.mediaType
|
||||
this.title = `Search results: ${this.query}`
|
||||
this.query = decodeURIComponent(query);
|
||||
this.page = page || 1;
|
||||
this.adult = adult || this.adult;
|
||||
this.mediaType = media_type || this.mediaType;
|
||||
this.title = `Search results: ${this.query}`;
|
||||
|
||||
this.search()
|
||||
this.search();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "./src/scss/media-queries";
|
||||
|
||||
@include mobile-only {
|
||||
.page-container {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.fullwidth-button {
|
||||
width: 100%;
|
||||
margin: 1rem 0;
|
||||
@@ -118,5 +135,4 @@ export default {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,184 +1,290 @@
|
||||
<template>
|
||||
<div>
|
||||
|
||||
<div class="search">
|
||||
<input
|
||||
ref="input"
|
||||
type="text"
|
||||
placeholder="Search for a movie or show"
|
||||
autocorrect="off"
|
||||
autocapitalize="off"
|
||||
v-model="query"
|
||||
@input="handleInput"
|
||||
@click="focus = true"
|
||||
@keydown.escape="handleEscape"
|
||||
@keyup.enter="handleSubmit"
|
||||
@keydown.up="navigateUp"
|
||||
@keydown.down="navigateDown" />
|
||||
|
||||
<svg class="search--icon" fill="currentColor"><use xlink:href="#iconSearch"></use></svg>
|
||||
</div>
|
||||
<!-- <div> -->
|
||||
<div class="search">
|
||||
<input
|
||||
ref="input"
|
||||
type="text"
|
||||
placeholder="Search for movie or show"
|
||||
aria-label="Search input for finding a movie or show"
|
||||
autocorrect="off"
|
||||
autocapitalize="off"
|
||||
tabindex="1"
|
||||
v-model="query"
|
||||
@input="handleInput"
|
||||
@click="focus = true"
|
||||
@keydown.escape="handleEscape"
|
||||
@keyup.enter="handleSubmit"
|
||||
@keydown.up="navigateUp"
|
||||
@keydown.down="navigateDown"
|
||||
/>
|
||||
|
||||
<svg class="search-icon" fill="currentColor" @click="handleSubmit">
|
||||
<use xlink:href="#iconSearch"></use>
|
||||
</svg>
|
||||
</div>
|
||||
<!--
|
||||
<transition name="fade">
|
||||
<div class="dropdown" v-if="!disabled && focus && query.length > 0">
|
||||
<div class="dropdown--results">
|
||||
<div class="filter">
|
||||
<h2>Filter your search:</h2>
|
||||
|
||||
<ul v-for="(item, index) in elasticSearchResults"
|
||||
@click="$popup.open(item.id, item.type)"
|
||||
:class="{ active: index + 1 === selectedResult}">
|
||||
|
||||
{{ item.name }}
|
||||
</ul>
|
||||
<div class="filter-items">
|
||||
<toggle-button
|
||||
:options="searchTypes"
|
||||
:selected.sync="selectedSearchType"
|
||||
/>
|
||||
|
||||
<label
|
||||
>Adult
|
||||
<input type="checkbox" value="adult" v-model="adult" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<seasoned-button class="end-section" fullWidth="true"
|
||||
@click="focus = false" :active="elasticSearchResults.length + 1 === selectedResult">
|
||||
<hr />
|
||||
|
||||
<div class="dropdown-results" v-if="elasticSearchResults.length">
|
||||
<ul
|
||||
v-for="(item, index) in elasticSearchResults"
|
||||
@click="openResult(item, index + 1)"
|
||||
:class="{ active: index + 1 === selectedResult }"
|
||||
>
|
||||
{{
|
||||
item.name
|
||||
}}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div v-else class="dropdown">
|
||||
<div class="dropdown-results">
|
||||
<h2 class="not-found">
|
||||
No results for query: <b>{{ query }}</b>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<seasoned-button
|
||||
class="end-section"
|
||||
fullWidth="true"
|
||||
@click="focus = false"
|
||||
:active="elasticSearchResults.length + 1 === selectedResult"
|
||||
>
|
||||
close
|
||||
</seasoned-button>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
|
||||
|
||||
</div> -->
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SeasonedButton from '@/components/ui/SeasonedButton'
|
||||
import SeasonedButton from "@/components/ui/SeasonedButton";
|
||||
import ToggleButton from "@/components/ui/ToggleButton";
|
||||
|
||||
import { elasticSearchMoviesAndShows } from '@/api'
|
||||
import config from '@/config.json'
|
||||
import { elasticSearchMoviesAndShows } from "@/api";
|
||||
import config from "@/config.json";
|
||||
|
||||
export default {
|
||||
name: 'SearchInput',
|
||||
name: "SearchInput",
|
||||
components: {
|
||||
SeasonedButton
|
||||
SeasonedButton,
|
||||
ToggleButton
|
||||
},
|
||||
props: ['value'],
|
||||
props: ["value"],
|
||||
data() {
|
||||
return {
|
||||
adult: true,
|
||||
searchTypes: ["all", "movie", "show", "person"],
|
||||
selectedSearchType: "all",
|
||||
|
||||
query: this.value,
|
||||
focus: false,
|
||||
disabled: false,
|
||||
scrollListener: undefined,
|
||||
scrollDistance: 0,
|
||||
elasticSearchResults: '',
|
||||
elasticSearchResults: [],
|
||||
selectedResult: 0
|
||||
}
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
focus: function(val) {
|
||||
focus: function (val) {
|
||||
if (val === true) {
|
||||
window.addEventListener('scroll', this.disableFocus)
|
||||
window.addEventListener("scroll", this.disableFocus);
|
||||
} else {
|
||||
window.removeEventListener('scroll', this.disableFocus)
|
||||
this.scrollDistance = 0
|
||||
window.removeEventListener("scroll", this.disableFocus);
|
||||
this.scrollDistance = 0;
|
||||
}
|
||||
},
|
||||
adult: function (value) {
|
||||
this.handleInput();
|
||||
}
|
||||
},
|
||||
beforeMount() {
|
||||
const elasticUrl = config.ELASTIC_URL
|
||||
if (elasticUrl === undefined || elasticUrl === false || elasticUrl === '') {
|
||||
this.disabled = true
|
||||
const elasticUrl = config.ELASTIC_URL;
|
||||
if (elasticUrl === undefined || elasticUrl === false || elasticUrl === "") {
|
||||
this.disabled = true;
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
console.log('scroll eventlistener not removed, destroying!')
|
||||
window.removeEventListener('scroll', this.disableFocus)
|
||||
console.log("scroll eventlistener not removed, destroying!");
|
||||
window.removeEventListener("scroll", this.disableFocus);
|
||||
},
|
||||
methods: {
|
||||
navigateDown() {
|
||||
this.focus = true
|
||||
this.selectedResult++
|
||||
this.focus = true;
|
||||
this.selectedResult++;
|
||||
},
|
||||
navigateUp() {
|
||||
this.focus = true
|
||||
this.selectedResult--
|
||||
this.focus = true;
|
||||
this.selectedResult--;
|
||||
const input = this.$refs.input;
|
||||
const textLength = input.value.length
|
||||
const textLength = input.value.length;
|
||||
|
||||
setTimeout(() => {
|
||||
input.focus()
|
||||
input.setSelectionRange(textLength, textLength + 1)
|
||||
}, 1)
|
||||
input.focus();
|
||||
input.setSelectionRange(textLength, textLength + 1);
|
||||
}, 1);
|
||||
},
|
||||
handleInput(e){
|
||||
this.selectedResult = 0
|
||||
this.$emit('input', this.query);
|
||||
openResult(item, index) {
|
||||
this.selectedResult = index;
|
||||
this.$popup.open(item.id, item.type);
|
||||
},
|
||||
handleInput(e) {
|
||||
this.selectedResult = 0;
|
||||
this.$emit("input", this.query);
|
||||
|
||||
if (! this.focus) {
|
||||
if (!this.focus) {
|
||||
this.focus = true;
|
||||
}
|
||||
|
||||
elasticSearchMoviesAndShows(this.query)
|
||||
.then(resp => {
|
||||
const data = resp.hits.hits
|
||||
elasticSearchMoviesAndShows(this.query).then(resp => {
|
||||
const data = resp.hits.hits;
|
||||
|
||||
this.elasticSearchResults = data.map(item => {
|
||||
const index = item._index.slice(0, -1)
|
||||
if (index === 'movie' || item._source.original_title) {
|
||||
let results = data.map(item => {
|
||||
const index = item._index.slice(0, -1);
|
||||
if (index === "movie" || item._source.original_title) {
|
||||
return {
|
||||
name: item._source.original_title,
|
||||
id: item._source.id,
|
||||
type: 'movie'
|
||||
}
|
||||
} else if (index === 'show' || item._source.original_name) {
|
||||
adult: item._source.adult,
|
||||
type: "movie"
|
||||
};
|
||||
} else if (index === "show" || item._source.original_name) {
|
||||
return {
|
||||
name: item._source.original_name,
|
||||
id: item._source.id,
|
||||
type: 'show'
|
||||
}
|
||||
adult: item._source.adult,
|
||||
type: "show"
|
||||
};
|
||||
}
|
||||
})
|
||||
console.log(this.elasticSearchResults)
|
||||
})
|
||||
});
|
||||
results = this.removeDuplicates(results);
|
||||
this.elasticSearchResults = results;
|
||||
});
|
||||
},
|
||||
removeDuplicates(searchResults) {
|
||||
let filteredResults = [];
|
||||
searchResults.map(result => {
|
||||
const numberOfDuplicates = filteredResults.filter(
|
||||
filterItem => filterItem.id == result.id
|
||||
);
|
||||
if (numberOfDuplicates.length >= 1) {
|
||||
return null;
|
||||
}
|
||||
filteredResults.push(result);
|
||||
});
|
||||
|
||||
if (this.adult == false) {
|
||||
filteredResults = filteredResults.filter(
|
||||
result => result.adult == false
|
||||
);
|
||||
}
|
||||
|
||||
return filteredResults;
|
||||
},
|
||||
handleSubmit() {
|
||||
let searchResults = this.elasticSearchResults
|
||||
let searchResults = this.elasticSearchResults;
|
||||
|
||||
if (this.selectedResult > searchResults.length) {
|
||||
this.focus = false
|
||||
this.selectedResult = 0
|
||||
this.focus = false;
|
||||
this.selectedResult = 0;
|
||||
} else if (this.selectedResult > 0) {
|
||||
const resultItem = searchResults[this.selectedResult - 1]
|
||||
this.$popup.open(resultItem.id, resultItem.type)
|
||||
const resultItem = searchResults[this.selectedResult - 1];
|
||||
this.$popup.open(resultItem.id, resultItem.type);
|
||||
} else {
|
||||
const encodedQuery = encodeURI(this.query.replace('/ /g, "+"'))
|
||||
this.$router.push({ name: 'search', query: { query: encodedQuery }});
|
||||
this.focus = false
|
||||
this.selectedResult = 0
|
||||
const encodedQuery = encodeURI(this.query.replace('/ /g, "+"'));
|
||||
const media_type =
|
||||
this.selectedSearchType !== "all" ? this.selectedSearchType : null;
|
||||
this.$router.push({
|
||||
name: "search",
|
||||
query: { query: encodedQuery, adult: this.adult, media_type }
|
||||
});
|
||||
this.focus = false;
|
||||
this.selectedResult = 0;
|
||||
}
|
||||
},
|
||||
handleEscape() {
|
||||
if (this.$popup.isOpen) {
|
||||
console.log('THIS WAS FUCKOING OPEN!')
|
||||
console.log("THIS WAS FUCKOING OPEN!");
|
||||
} else {
|
||||
this.focus = false
|
||||
this.focus = false;
|
||||
}
|
||||
},
|
||||
disableFocus(_) {
|
||||
this.focus = false
|
||||
this.focus = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "./src/scss/variables";
|
||||
@import "./src/scss/media-queries";
|
||||
@import './src/scss/main';
|
||||
|
||||
@import "./src/scss/main";
|
||||
|
||||
.fade-enter-active {
|
||||
transition: opacity .2s;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
.fade-leave-active {
|
||||
transition: opacity .2s;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.filter {
|
||||
// background-color: rgba(004, 122, 125, 0.2);
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 1rem 2rem;
|
||||
|
||||
h2 {
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
&-items {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
> :not(:first-child) {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
display: block;
|
||||
height: 1px;
|
||||
border: 0;
|
||||
border-bottom: 1px solid $text-color-50;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
@@ -196,7 +302,11 @@ export default {
|
||||
width: calc(100%);
|
||||
}
|
||||
|
||||
&--results {
|
||||
.not-found {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
&-results {
|
||||
padding-left: 60px;
|
||||
width: 100%;
|
||||
|
||||
@@ -211,7 +321,7 @@ export default {
|
||||
width: calc(100% - 25px);
|
||||
max-width: fit-content;
|
||||
|
||||
list-style: none;
|
||||
list-style: none;
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
text-transform: capitalize;
|
||||
cursor: pointer;
|
||||
@@ -222,7 +332,9 @@ export default {
|
||||
overflow: hidden;
|
||||
color: $text-color-50;
|
||||
|
||||
&.active, &:hover, &:active {
|
||||
&.active,
|
||||
&:hover,
|
||||
&:active {
|
||||
color: $text-color;
|
||||
border-bottom: 2px solid $text-color;
|
||||
}
|
||||
@@ -235,16 +347,16 @@ export default {
|
||||
display: flex;
|
||||
position: fixed;
|
||||
flex-wrap: wrap;
|
||||
z-index: 5;
|
||||
z-index: 16;
|
||||
border: 0;
|
||||
background-color: $background-color-secondary;
|
||||
|
||||
// TODO check if this is for mobile
|
||||
width: calc(100% - 110px);
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 55px;
|
||||
|
||||
@include tablet-min{
|
||||
@include tablet-min {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
right: 0px;
|
||||
@@ -252,23 +364,26 @@ export default {
|
||||
|
||||
input {
|
||||
display: block;
|
||||
height: calc($header-size - 1.5rem);
|
||||
width: 100%;
|
||||
padding: 13px 20px 13px 45px;
|
||||
padding: 13px 0 13px 45px;
|
||||
outline: none;
|
||||
margin: 0;
|
||||
margin-bottom: auto;
|
||||
border: 0;
|
||||
background-color: $background-color-secondary;
|
||||
font-weight: 300;
|
||||
font-size: 19px;
|
||||
color: $text-color;
|
||||
transition: background-color .5s ease, color .5s ease;
|
||||
transition: background-color 0.5s ease, color 0.5s ease;
|
||||
|
||||
@include tablet-min {
|
||||
height: calc($header-size);
|
||||
padding: 13px 30px 13px 60px;
|
||||
}
|
||||
}
|
||||
|
||||
&--icon{
|
||||
&-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
fill: $text-color-50;
|
||||
@@ -278,10 +393,10 @@ export default {
|
||||
left: 15px;
|
||||
top: 15px;
|
||||
|
||||
@include tablet-min{
|
||||
@include tablet-min {
|
||||
top: 27px;
|
||||
left: 25px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
<seasoned-input placeholder="username"
|
||||
icon="Email"
|
||||
type="email"
|
||||
@enter="submit"
|
||||
:value.sync="username" />
|
||||
<seasoned-input placeholder="password" icon="Keyhole" type="password" :value.sync="password" @enter="signin"/>
|
||||
|
||||
<seasoned-button @click="signin">sign in</seasoned-button>
|
||||
<seasoned-input placeholder="password" icon="Keyhole" type="password" :value.sync="password" @enter="submit"/>
|
||||
|
||||
<seasoned-button @click="submit">sign in</seasoned-button>
|
||||
<router-link class="link" to="/register">Don't have a user? Register here</router-link>
|
||||
<seasoned-messages :messages.sync="messages"></seasoned-messages>
|
||||
|
||||
<seasoned-messages :messages.sync="messages"></seasoned-messages>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -39,11 +39,25 @@ export default {
|
||||
setValue(l, t) {
|
||||
this[l] = t
|
||||
},
|
||||
signin(){
|
||||
submit() {
|
||||
this.messages = [];
|
||||
let username = this.username;
|
||||
let password = this.password;
|
||||
|
||||
login(username, password)
|
||||
if (username == null || username.length == 0) {
|
||||
this.messages.push({ type: 'error', title: 'Missing username' })
|
||||
return
|
||||
}
|
||||
|
||||
if (password == null || password.length == 0) {
|
||||
this.messages.push({ type: 'error', title: 'Missing password' })
|
||||
return
|
||||
}
|
||||
|
||||
this.signin(username, password)
|
||||
},
|
||||
signin(username, password) {
|
||||
login(username, password, true)
|
||||
.then(data => {
|
||||
if (data.success){
|
||||
const jwtData = parseJwt(data.token)
|
||||
@@ -57,7 +71,7 @@ export default {
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.status === 401) {
|
||||
this.messages.push({ type: 'warning', title: 'Access denied', message: 'Incorrect username or password' })
|
||||
this.messages.push({ type: 'error', title: 'Access denied', message: 'Incorrect username or password' })
|
||||
}
|
||||
else {
|
||||
this.messages.push({ type: 'error', title: 'Unexpected error', message: error.message })
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="seasoned-button">
|
||||
<button type="button" class="button" @click="emit('click')" :class="{ active: active }"><slot></slot></button>
|
||||
</div>
|
||||
<button type="button" @click="emit('click')" :class="{ active: active }">
|
||||
<slot></slot>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -9,7 +9,11 @@
|
||||
export default {
|
||||
name: 'seasonedButton',
|
||||
props: {
|
||||
active: Boolean
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
emit() {
|
||||
@@ -23,32 +27,39 @@ export default {
|
||||
@import "./src/scss/variables";
|
||||
@import "./src/scss/media-queries";
|
||||
|
||||
.button{
|
||||
button {
|
||||
display: inline-block;
|
||||
border: 1px solid $text-color;
|
||||
text-transform: uppercase;
|
||||
font-weight: 300;
|
||||
font-size: 11px;
|
||||
line-height: 2;
|
||||
height: 45px;
|
||||
font-weight: 300;
|
||||
line-height: 1.5;
|
||||
letter-spacing: 0.5px;
|
||||
padding: 5px 20px 4px 20px;
|
||||
text-transform: uppercase;
|
||||
min-height: 45px;
|
||||
padding: 5px 10px 4px 10px;
|
||||
margin: 0;
|
||||
margin-right: 0.3rem;
|
||||
cursor: pointer;
|
||||
color: $text-color;
|
||||
background: $background-color-secondary;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
transition: background 0.5s ease, color 0.5s ease, border-color .5s ease;
|
||||
|
||||
@include tablet-min{
|
||||
font-size: 12px;
|
||||
@include desktop {
|
||||
font-size: 0.8rem;
|
||||
padding: 6px 20px 5px 20px;
|
||||
}
|
||||
|
||||
body:not(.touch) &:hover, &:focus, &:active, &.active {
|
||||
&:focus, &:active, &.active {
|
||||
background: $text-color;
|
||||
color: $background-color;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover {
|
||||
background: $text-color;
|
||||
color: $background-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
<div class="message" v-for="(message, index) in reversedMessages" :class="message.type || 'warning'" :key="index">
|
||||
<span class="pinstripe"></span>
|
||||
<div>
|
||||
<h2>{{ message.title || defaultTitles[message.type] }}</h2>
|
||||
<span>{{ message.message }}</span>
|
||||
<h2 class="title">{{ message.title || defaultTitles[message.type] }}</h2>
|
||||
<span v-if="message.message" class="message">{{ message.message }}</span>
|
||||
</div>
|
||||
|
||||
<button class="dismiss" @click="clicked(message)">X</button>
|
||||
@@ -41,14 +41,7 @@ export default {
|
||||
const removedMessage = [...this.messages].filter(mes => mes !== e)
|
||||
this.$emit('update:messages', removedMessage)
|
||||
}
|
||||
},
|
||||
// watch: {
|
||||
// messages(propState, oldState) {
|
||||
// const newMessage = propState.filter(msg => !this.localMessages.includes(msg))
|
||||
// console.log('newMessage', newMessage)
|
||||
// this.localMessages = this.localMessages.concat(newMessage)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -70,7 +63,6 @@ export default {
|
||||
.message {
|
||||
width: 100%;
|
||||
max-width: 35rem;
|
||||
min-height: 75px;
|
||||
|
||||
display: flex;
|
||||
margin-top: 1rem;
|
||||
@@ -78,12 +70,12 @@ export default {
|
||||
color: $text-color-70;
|
||||
|
||||
> div {
|
||||
margin: 6px 24px;
|
||||
margin: 10px 24px;
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
|
||||
h2 {
|
||||
.title {
|
||||
font-weight: 300;
|
||||
letter-spacing: 0.25px;
|
||||
margin: 0;
|
||||
@@ -91,10 +83,11 @@ export default {
|
||||
color: $text-color;
|
||||
transition: color .5s ease;
|
||||
}
|
||||
span {
|
||||
.message {
|
||||
font-weight: 300;
|
||||
color: $text-color-70;
|
||||
transition: color .5s ease;
|
||||
margin: 0.2rem 0 0.5rem;
|
||||
}
|
||||
|
||||
@include mobile-only {
|
||||
@@ -112,9 +105,8 @@ export default {
|
||||
}
|
||||
|
||||
.pinstripe {
|
||||
height: 100%;
|
||||
width: 0.5rem;
|
||||
// background-color: $color-error-highlight;
|
||||
background-color: $color-error-highlight;
|
||||
}
|
||||
|
||||
.dismiss {
|
||||
|
||||
@@ -1,42 +1,38 @@
|
||||
<template>
|
||||
|
||||
<div class="darkToggle">
|
||||
<span @click="toggleDarkmode()">{{ darkmodeToggleIcon }}</span>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
data() {
|
||||
return {
|
||||
darkmode: this.supported
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
toggleDarkmode() {
|
||||
this.darkmode = !this.darkmode;
|
||||
document.body.className = this.darkmode ? 'dark' : 'light'
|
||||
document.body.className = this.darkmode ? "dark" : "light";
|
||||
},
|
||||
supported() {
|
||||
const computedStyle = window.getComputedStyle(document.body)
|
||||
if (computedStyle['colorScheme'] != null)
|
||||
return computedStyle.colorScheme.includes('dark')
|
||||
return false
|
||||
const computedStyle = window.getComputedStyle(document.body);
|
||||
if (computedStyle["colorScheme"] != null)
|
||||
return computedStyle.colorScheme.includes("dark");
|
||||
return false;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
darkmodeToggleIcon() {
|
||||
return this.darkmode ? '🌝' : '🌚'
|
||||
return this.darkmode ? "🌝" : "🌚";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "./src/scss/media-queries";
|
||||
.darkToggle {
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
@@ -49,9 +45,13 @@ export default {
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
|
||||
@include mobile-only {
|
||||
margin-bottom: 5rem;
|
||||
}
|
||||
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div>
|
||||
<a @click="$emit('click')">
|
||||
<li>
|
||||
<figure :class="activeClassIfActive">
|
||||
<figure v-if="iconRef" :class="activeClassIfActive">
|
||||
<svg class="icon"><use :xlink:href="iconRefNameIfActive"/></svg>
|
||||
</figure>
|
||||
|
||||
@@ -23,7 +23,7 @@ export default {
|
||||
props: {
|
||||
iconRef: {
|
||||
type: String,
|
||||
required: true
|
||||
required: false
|
||||
},
|
||||
iconRefActive: {
|
||||
type: String,
|
||||
@@ -85,11 +85,11 @@ li {
|
||||
border-bottom: 1px solid $text-color-5;
|
||||
|
||||
&:hover {
|
||||
color: $text-color-70;
|
||||
color: $text-color;
|
||||
cursor: pointer;
|
||||
|
||||
.icon {
|
||||
fill: $text-color-70;
|
||||
fill: $text-color;
|
||||
cursor: pointer;
|
||||
transform: scale(1.1, 1.1);
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ const setDocumentTitle = (state) => {
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: {
|
||||
emoji: '🍕',
|
||||
titlePrefix: 'request',
|
||||
emoji: '',
|
||||
titlePrefix: 'seasoned',
|
||||
title: undefined
|
||||
},
|
||||
getters: {
|
||||
|
||||
@@ -69,6 +69,14 @@ export default {
|
||||
|
||||
ifMissingSettingsAndTokenExistsFetchSettings()
|
||||
return undefined
|
||||
},
|
||||
isPlexAuthenticated: (state) => {
|
||||
const settings = state.settings || getLocalStorageByKey('settings')
|
||||
if (settings == null)
|
||||
return false
|
||||
|
||||
const hasPlexId = settings['plex_userid']
|
||||
return hasPlexId != null ? true : false
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
@@ -101,4 +109,4 @@ export default {
|
||||
commit('SET_SETTINGS', settings)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,13 @@ let routes = [
|
||||
{
|
||||
name: 'activity',
|
||||
path: '/activity',
|
||||
meta: { requiresAuth: true },
|
||||
component: (resolve) => require(['./components/ActivityPage.vue'], resolve)
|
||||
},
|
||||
{
|
||||
name: 'profile',
|
||||
path: '/profile',
|
||||
meta: { requiresAuth: true },
|
||||
component: (resolve) => require(['./components/Profile.vue'], resolve)
|
||||
},
|
||||
{
|
||||
@@ -46,11 +48,13 @@ let routes = [
|
||||
{
|
||||
name: 'settings',
|
||||
path: '/settings',
|
||||
meta: { requiresAuth: true },
|
||||
component: (resolve) => require(['./components/Settings.vue'], resolve)
|
||||
},
|
||||
{
|
||||
name: 'signin',
|
||||
path: '/signin',
|
||||
alias: '/login',
|
||||
component: (resolve) => require(['./components/Signin.vue'], resolve)
|
||||
},
|
||||
// {
|
||||
@@ -65,6 +69,17 @@ let routes = [
|
||||
path: '/404',
|
||||
component: (resolve) => require(['./components/404.vue'], resolve)
|
||||
},
|
||||
{
|
||||
name: 'logout',
|
||||
path: '/logout',
|
||||
component: {
|
||||
template: '<div></div>',
|
||||
created() {
|
||||
localStorage.clear();
|
||||
this.$router.push({ name: 'home' });
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
redirect: '/'
|
||||
@@ -76,7 +91,7 @@ let routes = [
|
||||
];
|
||||
|
||||
const router = new VueRouter({
|
||||
mode: 'hash',
|
||||
mode: 'history',
|
||||
base: '/',
|
||||
routes,
|
||||
linkActiveClass: 'is-active'
|
||||
@@ -90,6 +105,13 @@ router.beforeEach((to, from, next) => {
|
||||
document.querySelector('.nav__hamburger').classList.remove('nav__hamburger--active');
|
||||
document.querySelector('.nav__list').classList.remove('nav__list--active');
|
||||
}
|
||||
|
||||
if (to.matched.some(record => record.meta.requiresAuth)) {
|
||||
if (localStorage.getItem('token') == null) {
|
||||
next({ path: '/signin' });
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
|
||||
.noselect {
|
||||
-webkit-touch-callout: none; /* iOS Safari */
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-khtml-user-select: none; /* Konqueror HTML */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* Internet Explorer/Edge */
|
||||
user-select: none; /* Non-prefixed version, currently */
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-khtml-user-select: none; /* Konqueror HTML */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* Internet Explorer/Edge */
|
||||
user-select: none; /* Non-prefixed version, currently */
|
||||
}
|
||||
|
||||
.end-section {
|
||||
@@ -47,4 +46,4 @@
|
||||
&-absolute {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,12 +18,12 @@
|
||||
|
||||
--background-nav-logo: #081c24;
|
||||
--color-green: #01d277;
|
||||
--color-green-90: rgba(1, 210, 119, .9);
|
||||
--color-green-70: rgba(1, 210, 119, .73);
|
||||
--color-green-90: rgba(1, 210, 119, 0.9);
|
||||
--color-green-70: rgba(1, 210, 119, 0.73);
|
||||
--color-teal: #091c24;
|
||||
--color-black: #081c24;
|
||||
--white: #fff;
|
||||
--white-70: rgba(255,255,255,0.7);
|
||||
--white-70: rgba(255, 255, 255, 0.7);
|
||||
|
||||
--color-warning: rgba(241, 188, 53, 0.7);
|
||||
--color-warning-highlight: #f1bc35;
|
||||
@@ -31,7 +31,7 @@
|
||||
--color-success-text: #fff;
|
||||
--color-success-highlight: rgb(0, 100, 66);
|
||||
--color-error: rgba(220, 48, 35, 0.8);
|
||||
--color-error-highlight: #DC3023;
|
||||
--color-error-highlight: #dc3023;
|
||||
|
||||
--header-size: 75px;
|
||||
}
|
||||
@@ -55,7 +55,7 @@
|
||||
|
||||
@include mobile-only {
|
||||
:root {
|
||||
--header-size: 50px;
|
||||
--header-size: calc(50px + 1.5rem);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,9 +67,9 @@ $green-90: var(--color-green-90);
|
||||
$green-70: var(--color-green-70);
|
||||
$teal: #091c24;
|
||||
$black: #081c24;
|
||||
$black-80: rgba(0,0,0,0.8);
|
||||
$black-80: rgba(0, 0, 0, 0.8);
|
||||
$white: #fff;
|
||||
$white-80: rgba(255,255,255,0.8);
|
||||
$white-80: rgba(255, 255, 255, 0.8);
|
||||
|
||||
$text-color: var(--text-color) !default;
|
||||
$text-color-70: var(--text-color-70) !default;
|
||||
|
||||
Reference in New Issue
Block a user