This commit is contained in:
Travis Shivers 2020-05-27 14:34:29 -05:00
parent c2a3b0b2b5
commit 063aefc48e
31 changed files with 2904 additions and 1225 deletions

View File

@ -1,13 +1,24 @@
// Inspired by https://stackoverflow.com/questions/60187885/how-to-configure-vue-cli-4-with-eslint-airbnb-rules-typescript-stylelint-f
module.exports = {
root: true,
env: {
node: true,
},
extends: ['plugin:vue/essential', '@vue/airbnb'],
extends: ['plugin:vue/recommended', '@vue/airbnb'],
parserOptions: {
parser: 'babel-eslint',
ecmaVersion: 2020,
},
rules: {
'class-methods-use-this': 0,
'max-len': [
'error',
{
code: 100,
ignoreComments: true,
ignoreUrls: true,
},
],
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'import/no-extraneous-dependencies': ['error', { devDependencies: true }],

View File

@ -1,57 +1,93 @@
<template>
<v-app dark style="height:100%">
<leftsidebar></leftsidebar>
<rightsidebar></rightsidebar>
<v-app
dark
style="height:100%"
>
<leftsidebar />
<rightsidebar />
<v-app-bar app scroll-off-screen :scroll-threshold="1" style="z-index: 5">
<v-app-bar-nav-icon @click="SET_LEFT_SIDEBAR_OPEN"></v-app-bar-nav-icon>
<a href="https://synclounge.tv" target="_blank">
<v-app-bar
app
scroll-off-screen
:scroll-threshold="1"
style="z-index: 5"
>
<v-app-bar-nav-icon @click="SET_LEFT_SIDEBAR_OPEN" />
<a
href="https://synclounge.tv"
target="_blank"
>
<img
class="ma-1 hidden-xs-only"
style="height: 42px; width: auto; vertical-align: middle"
v-bind:src="logos.light.long"
/>
:src="logos.light.long"
>
<img
class="ma-1 hidden-sm-and-up"
style="height: 42px; width: auto; vertical-align: middle"
v-bind:src="logo"
/>
:src="logo"
>
</a>
<nowplayingchip class="pl-4" v-if="showNowPlaying"></nowplayingchip>
<v-spacer></v-spacer>
<nowplayingchip
v-if="showNowPlaying"
class="pl-4"
/>
<v-spacer />
<v-toolbar-items>
<v-btn
v-if="getShortLink != null"
v-clipboard="getShortLink"
color="primary"
dark
raised
v-if="getShortLink != null"
v-clipboard="getShortLink"
@success="sendNotification()"
>Invite</v-btn
>
<v-btn dark @click="goFullscreen" class="hidden-lg-and-up" icon>
Invite
</v-btn>
<v-btn
dark
class="hidden-lg-and-up"
icon
@click="goFullscreen"
>
<v-icon>fullscreen</v-icon>
</v-btn>
<v-btn
v-for="item in links"
:key="item.title"
small
tag="a"
class="hidden-sm-and-down"
text
:href="item.href"
:target="item.target"
>
{{ item.title }}
</v-btn>
<v-btn
small
tag="a"
class="hidden-sm-and-down"
text
v-for="item in links"
:key="item.title"
:href="item.href"
:target="item.target"
>{{ item.title }}</v-btn
@click="donateDialog = true"
>
<v-btn small tag="a" class="hidden-sm-and-down" text @click="donateDialog = true"
>Donate </v-btn
Donate
</v-btn>
<v-icon
v-if="showRightDrawerButton"
class="clickable"
@click="TOGGLE_RIGHT_SIDEBAR_OPEN"
>
<v-icon v-if="showRightDrawerButton" @click="TOGGLE_RIGHT_SIDEBAR_OPEN" class="clickable">{{
isRightSidebarOpen ? 'last_page' : 'first_page'
}}</v-icon>
{{
isRightSidebarOpen ? 'last_page' : 'first_page'
}}
</v-icon>
</v-toolbar-items>
</v-app-bar>
<v-content v-bind:style="mainStyle" app>
<v-content
:style="mainStyle"
app
>
<v-container
class="ma-0 pa-0"
align-start
@ -59,26 +95,50 @@
style="height: 100%; z-index: 250"
fluid
>
<v-flex xs12 v-if="configError">
<v-alert :dismissible="true" type="error" class="mt-0">{{ configError }}</v-alert>
<v-flex
v-if="configError"
xs12
>
<v-alert
:dismissible="true"
type="error"
class="mt-0"
>
{{ configError }}
</v-alert>
</v-flex>
<v-flex xs12 v-if="(loading || (getPlex && !getPlex.gotDevices)) && route.protected">
<v-flex
v-if="(loading || (getPlex && !getPlex.gotDevices)) && route.protected"
xs12
>
<v-container fill-height>
<v-layout justify-center align-center wrap row class="pt-4 text-center">
<v-flex xs8 md4>
<v-layout
justify-center
align-center
wrap
row
class="pt-4 text-center"
>
<v-flex
xs8
md4
>
<v-progress-circular
indeterminate
v-bind:size="60"
:size="60"
class="amber--text"
></v-progress-circular>
/>
</v-flex>
</v-layout>
</v-container>
</v-flex>
<div v-else :style="paddingStyle">
<div
v-else
:style="paddingStyle"
>
<v-breadcrumbs
:items="crumbs"
v-if="crumbs && crumbs.length > 0"
:items="crumbs"
class="text-xs-left"
style="justify-content: left"
>
@ -86,22 +146,37 @@
<v-icon>chevron_right</v-icon>
</template>
<template v-slot:item="props">
<v-breadcrumbs-item :to="props.item.to" :exact="true">{{
props.item.text
}}</v-breadcrumbs-item>
<v-breadcrumbs-item
:to="props.item.to"
:exact="true"
>
{{
props.item.text
}}
</v-breadcrumbs-item>
</template>
</v-breadcrumbs>
<router-view></router-view>
<router-view />
</div>
<v-snackbar color="green darken-2" bottom :timeout="4000" v-model="snackbar">
<div style="text-align:center; width:100%">{{ snackbarMsg }}</div>
<v-snackbar
v-model="snackbar"
color="green darken-2"
bottom
:timeout="4000"
>
<div style="text-align:center; width:100%">
{{ snackbarMsg }}
</div>
</v-snackbar>
<upnext></upnext>
<v-dialog v-model="donateDialog" max-width="650px">
<upnext />
<v-dialog
v-model="donateDialog"
max-width="650px"
>
<donate
:donateDialog="donateDialog"
:onClose="() => (this.donateDialog = false)"
></donate>
:donate-dialog="donateDialog"
:on-close="() => (this.donateDialog = false)"
/>
</v-dialog>
</v-container>
</v-content>
@ -175,6 +250,14 @@ export default {
fscreen.requestFullscreen(document.body);
},
},
watch: {
showRightDrawerButton() {
// TODO: fix this is hacky
if (this.showRightDrawerButton) {
this.SET_RIGHT_SIDEBAR_OPEN(true);
}
},
},
created() {
this.configFetchPromise = this.fetchConfig();
},
@ -276,14 +359,6 @@ export default {
this.loading = false;
},
watch: {
showRightDrawerButton() {
// TODO: fix this is hacky
if (this.showRightDrawerButton) {
this.SET_RIGHT_SIDEBAR_OPEN(true);
}
},
},
computed: {
...mapGetters([
'getPlex',

View File

@ -1,30 +1,75 @@
<template>
<v-layout row wrap justify-center>
<v-flex xs12 lg10 style="background: rgba(0,0,0,0.1); border-radius: 10px" class="pa-4">
<v-layout row wrap justify-center>
<v-flex xs12 md8 lg4 xl6>
<img style="width:100%" v-bind:src="logo" />
<v-layout
row
wrap
justify-center
>
<v-flex
xs12
lg10
style="background: rgba(0,0,0,0.1); border-radius: 10px"
class="pa-4"
>
<v-layout
row
wrap
justify-center
>
<v-flex
xs12
md8
lg4
xl6
>
<img
style="width:100%"
:src="logo"
>
</v-flex>
</v-layout>
<v-stepper
style="background: rgba(0,0,0,0.3); border-radius: 20px"
v-model="e1"
style="background: rgba(0,0,0,0.3); border-radius: 20px"
dark
class="ma-4"
>
<v-stepper-header>
<v-stepper-step step="1" :complete="true">Select a client</v-stepper-step>
<v-divider></v-divider>
<v-stepper-step step="2" :complete="false">Join a room</v-stepper-step>
<v-divider></v-divider>
<v-stepper-step step="3">Sync</v-stepper-step>
<v-stepper-step
step="1"
:complete="true"
>
Select a client
</v-stepper-step>
<v-divider />
<v-stepper-step
step="2"
:complete="false"
>
Join a room
</v-stepper-step>
<v-divider />
<v-stepper-step step="3">
Sync
</v-stepper-step>
</v-stepper-header>
</v-stepper>
<v-layout row wrap justify-center>
<v-flex xs12 class="ml-4">
<h2 class="text-xs-left">Connect to a SyncLounge room</h2>
<v-layout
row
wrap
justify-center
>
<v-flex
xs12
class="ml-4"
>
<h2 class="text-xs-left">
Connect to a SyncLounge room
</h2>
</v-flex>
<v-flex xs12 class="ml-4">
<v-flex
xs12
class="ml-4"
>
<p>
It's time to connect to SyncLounge. From the list select a server which is closest to
your location. Once you've chosen one that works for you it's time to create a room for
@ -32,17 +77,23 @@
</p>
</v-flex>
<v-flex
v-if="!getConnected && this.GET_RECENT_ROOMS.length > 0"
xs12
class="nicelist pa-4"
v-if="!getConnected && this.GET_RECENT_ROOMS.length > 0"
style="color:white !important;"
>
<v-subheader>Recent Rooms</v-subheader>
<v-list class="pa-0">
<template v-for="(item, index) in GET_RECENT_ROOMS.slice(0, 3)">
<v-list-item :key="index" @click="recentConnect(item)">
<v-list-item
:key="index"
@click="recentConnect(item)"
>
<v-list-item-avatar>
<img :src="logos.light.small" style="width: 32px; height: auto" />
<img
:src="logos.light.small"
style="width: 32px; height: auto"
>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>{{ item.name || item.server || 'Custom' }}</v-list-item-title>
@ -52,8 +103,17 @@
</v-list-item-subtitle>
</v-list-item-content>
<v-list-item-action>
<v-tooltip top color="light-blue darken-4">
<v-icon color="white" dark @click.stop="REMOVE_RECENT_ROOM(item)">close</v-icon>
<v-tooltip
top
color="light-blue darken-4"
>
<v-icon
color="white"
dark
@click.stop="REMOVE_RECENT_ROOM(item)"
>
close
</v-icon>
>Remove
</v-tooltip>
</v-list-item-action>
@ -61,36 +121,80 @@
</template>
</v-list>
</v-flex>
<v-flex xs12 class="nicelist pa-4" v-if="!getConnected" style="color:white !important">
<v-flex
v-if="!getConnected"
xs12
class="nicelist pa-4"
style="color:white !important"
>
<v-subheader>Select a server</v-subheader>
<v-layout row wrap justify-center align-center>
<v-flex pa-2 xs12 md3 lg2 v-for="server in GET_SYNCLOUNGE_SERVERS" :key="server.url">
<v-card height="300px" style="border-radius: 20px">
<v-layout row wrap justify-start align-center style="height: 100%">
<v-flex xs12 class="text-center pa-2" style="height: 80px">
<v-layout
row
wrap
justify-center
align-center
>
<v-flex
v-for="server in GET_SYNCLOUNGE_SERVERS"
:key="server.url"
pa-2
xs12
md3
lg2
>
<v-card
height="300px"
style="border-radius: 20px"
>
<v-layout
row
wrap
justify-start
align-center
style="height: 100%"
>
<v-flex
xs12
class="text-center pa-2"
style="height: 80px"
>
<img
:src="server.image"
style="max-height: 100%; vertical-align: middle; max-width: 80%; border-radius: 7px"
/>
>
</v-flex>
<v-flex xs12 class="text-center">
<v-flex
xs12
class="text-center"
>
<h2>{{ server.name }}</h2>
<h4>{{ server.location }}</h4>
</v-flex>
<v-flex xs12 class="text-center" v-if="server.url !== 'custom'">
<v-flex
v-if="server.url !== 'custom'"
xs12
class="text-center"
>
<div v-if="results[server.url]">
<div v-if="results[server.url].alive">
Ping:
<span
class="thick--text"
:class="connectionQualityClass(results[server.url].latency)"
>{{ results[server.url].latency }}ms</span
>
>{{ results[server.url].latency }}ms</span>
</div>
<div
v-else
class="text-center red--text"
>
error
</div>
<div v-else class="text-center red--text">error</div>
</div>
</v-flex>
<v-flex xs12 class="text-center">
<v-flex
xs12
class="text-center"
>
<div v-if="server.url !== 'custom'">
<div v-if="results[server.url]">
<div v-if="results[server.url].alive">
@ -99,23 +203,31 @@
<span
class="thick--text"
:class="loadQualityClass(results[server.url].result)"
>{{ results[server.url].result || 'Unknown' }}</span
>
>{{ results[server.url].result || 'Unknown' }}</span>
</div>
</div>
<div v-else class="text-center red--text">error</div>
<div
v-else
class="text-center red--text"
>
error
</div>
</div>
</div>
</v-flex>
<v-flex xs12 class="text-center pt-1 mt-4">
<v-flex
xs12
class="text-center pt-1 mt-4"
>
<v-btn
color="primary"
:disabled="connectionPending"
@click="serverSelected(server)"
style="width: 80%; border-radius: 7px; margin: auto; position: absolute; bottom: 5px; left: 0; right: 0;"
>Connect</v-btn
@click="serverSelected(server)"
>
Connect
</v-btn>
</v-flex>
</v-layout>
</v-card>
@ -126,69 +238,124 @@
name="input-2"
label="Custom Server"
:value="GET_CUSTOM_SERVER_USER_INPUTTED_URL"
@change="SET_CUSTOM_SERVER_USER_INPUTTED_URL"
class="input-group pt-input"
></v-text-field>
<v-layout row wrap v-if="selectedServer.url == 'custom'">
@change="SET_CUSTOM_SERVER_USER_INPUTTED_URL"
/>
<v-layout
v-if="selectedServer.url == 'custom'"
row
wrap
>
<v-flex xs12>
<v-btn
class="pt-orange white--text pa-0 ma-0"
color="primary"
primary
style="width:100%"
v-on:click.native="attemptConnectCustom()"
>Connect</v-btn
@click.native="attemptConnectCustom()"
>
Connect
</v-btn>
</v-flex>
</v-layout>
<v-layout row wrap v-if="connectionPending && !serverError" class="pt-3">
<v-layout
v-if="connectionPending && !serverError"
row
wrap
class="pt-3"
>
<v-flex xs12>
<div style="width:100%;text-align:center">
<v-progress-circular
indeterminate
v-bind:size="50"
:size="50"
class="amber--text"
style="display:inline-block"
></v-progress-circular>
/>
</div>
</v-flex>
</v-layout>
<v-layout class="pt-3 text-center" row wrap v-if="serverError">
<v-flex xs12 class="red--text">
<v-icon class="red--text">info</v-icon>
<v-layout
v-if="serverError"
class="pt-3 text-center"
row
wrap
>
<v-flex
xs12
class="red--text"
>
<v-icon class="red--text">
info
</v-icon>
{{ serverError }}
</v-flex>
</v-layout>
</v-flex>
<v-flex xs12 v-if="getConnected" class="text-center">
<v-layout row wrap>
<v-flex xs12 md6 offset-md3>
<v-flex
v-if="getConnected"
xs12
class="text-center"
>
<v-layout
row
wrap
>
<v-flex
xs12
md6
offset-md3
>
<v-text-field
v-model="room"
origin="center center"
:maxlength="25"
name="input-2"
label="Room name"
:autofocus="true"
v-on:keyup.enter.native="joinRoom()"
v-model="room"
></v-text-field>
@keyup.enter.native="joinRoom()"
/>
</v-flex>
<v-flex xs12 md6 offset-md3>
<v-flex
xs12
md6
offset-md3
>
<v-text-field
v-model="password"
transition="v-scale-transition"
origin="center center"
name="input-2"
label="Room password"
v-on:keyup.enter.native="joinRoom()"
v-model="password"
></v-text-field>
@keyup.enter.native="joinRoom()"
/>
</v-flex>
<v-flex xs12 md6 offset-md3>
<v-btn block color="primary" v-on:click.native="joinRoom()">Join</v-btn>
<v-flex
xs12
md6
offset-md3
>
<v-btn
block
color="primary"
@click.native="joinRoom()"
>
Join
</v-btn>
</v-flex>
<v-layout class="pt-3 text-center" row wrap v-if="roomError">
<v-flex xs12 class="red--text">
<v-icon class="red--text">info</v-icon>
<v-layout
v-if="roomError"
class="pt-3 text-center"
row
wrap
>
<v-flex
xs12
class="red--text"
>
<v-icon class="red--text">
info
</v-icon>
{{ roomError }}
</v-flex>
</v-layout>
@ -206,8 +373,8 @@ import axios from 'axios';
import { mapGetters, mapMutations, mapActions } from 'vuex';
export default {
name: 'Joinroom',
props: ['object'],
name: 'joinroom',
data() {
return {
selectedServer: '',

View File

@ -3,28 +3,42 @@
<div>
<h4>Search</h4>
<div>
<v-layout class="mb-3" v-if="!selectedItem && !browsingServer" row wrap>
<v-flex xs10 lg4>
<v-layout
v-if="!selectedItem && !browsingServer"
class="mb-3"
row
wrap
>
<v-flex
xs10
lg4
>
<v-text-field
id="testing"
v-model="searchWord"
name="searchInput"
label="Search"
:hint="searchStatus"
id="testing"
persistent-hint
single-line
prepend-icon="search"
v-model="searchWord"
></v-text-field>
/>
</v-flex>
<v-flex xs2>
<v-icon
v-if="results.length > 0"
v-on:click="results = []; searchWord = ''; searching = false"
class="clickable red--text pt-3"
>clear</v-icon>
@click="results = []; searchWord = ''; searching = false"
>
clear
</v-icon>
</v-flex>
</v-layout>
<v-layout row wrap v-if="searching || results.length > 0">
<v-layout
v-if="searching || results.length > 0"
row
wrap
>
<v-chip
v-for="server in getPlex.servers"
:key="server.machineIdentifier"
@ -32,120 +46,190 @@
class="green darken-3 white--text"
>
<v-avatar>
<v-icon v-if="!heardBack(server)">clear</v-icon>
<v-icon v-if="heardBack(server)">check_circle</v-icon>
<v-icon v-if="!heardBack(server)">
clear
</v-icon>
<v-icon v-if="heardBack(server)">
check_circle
</v-icon>
</v-avatar>
{{ server.name }}
</v-chip>
</v-layout>
<v-progress-circular v-if="searching" indeterminate class="amber--text"></v-progress-circular>
<v-progress-circular
v-if="searching"
indeterminate
class="amber--text"
/>
<div v-if="results.length > 0">
<v-layout v-if="filteredMovies && filteredMovies.length > 0" row wrap>
<v-layout
v-if="filteredMovies && filteredMovies.length > 0"
row
wrap
>
<!--Movies-->
<v-flex xs12 lg12>
<v-flex
xs12
lg12
>
<v-subheader>Movies ({{ filteredMovies.length }})</v-subheader>
</v-flex>
<v-flex
v-for="movie in filteredMovies"
:key="movie.key"
xs6
md3
xl1
lg1
class="pb-3 ma-2"
v-for="movie in filteredMovies"
:key="movie.key"
>
<plexthumb
:content="movie"
:server="movie.server"
showServer
show-server
search
@contentSet="setContent(movie)"
></plexthumb>
/>
</v-flex>
</v-layout>
<v-layout v-if="filteredShows && filteredShows.length > 0" row wrap>
<v-layout
v-if="filteredShows && filteredShows.length > 0"
row
wrap
>
<!--Shows-->
<v-flex xs12 lg12>
<v-flex
xs12
lg12
>
<v-subheader>TV Shows ({{ filteredShows.length }})</v-subheader>
</v-flex>
<v-flex xs6 md3 xl1 lg1 class="pb-3 ma-2" v-for="show in filteredShows" :key="show.key">
<v-flex
v-for="show in filteredShows"
:key="show.key"
xs6
md3
xl1
lg1
class="pb-3 ma-2"
>
<plexthumb
:content="show"
:server="show.server"
showServer
show-server
search
@contentSet="setContent(show)"
></plexthumb>
/>
</v-flex>
</v-layout>
<v-layout v-if="filteredEpisodes && filteredEpisodes.length > 0" row wrap>
<v-layout
v-if="filteredEpisodes && filteredEpisodes.length > 0"
row
wrap
>
<!--Episodes-->
<v-flex xs12 lg12>
<v-flex
xs12
lg12
>
<v-subheader>TV Episodes ({{ filteredEpisodes.length }})</v-subheader>
</v-flex>
<v-flex
v-for="episode in filteredEpisodes"
:key="episode.key"
xs6
md3
xl2
lg2
class="pb-3 ma-2"
v-for="episode in filteredEpisodes"
:key="episode.key"
>
<plexthumb
:content="episode"
:server="episode.server"
showServer
show-server
type="art"
search
@contentSet="setContent(episode)"
></plexthumb>
/>
</v-flex>
</v-layout>
</div>
</div>
<v-divider></v-divider>
<div class="pt-4" v-if="GET_LASTSERVER && results.length == 0">
<v-divider />
<div
v-if="GET_LASTSERVER && results.length == 0"
class="pt-4"
>
<h4 v-if="subsetOnDeck().length > 0">
Continue watching from {{ GET_LASTSERVER.name }}
<span
style="float:right; font-size:5rem; user-select: none;"
>
<v-icon
@click="onDeckDown"
style="margin-right: 15px;cursor: pointer"
:style="onDeckDownStyle"
@click="onDeckDown"
>angle-left</v-icon>
<v-icon @click="onDeckUp" :style="onDeckUpStyle" style="cursor: pointer">angle-right</v-icon>
<v-icon
:style="onDeckUpStyle"
style="cursor: pointer"
@click="onDeckUp"
>angle-right</v-icon>
</span>
</h4>
<v-layout v-if="onDeck" row justify-center>
<v-flex xs12 md3 class="pb-3 pa-2" v-for="content in subsetOnDeck()" :key="content.key">
<v-layout
v-if="onDeck"
row
justify-center
>
<v-flex
v-for="content in subsetOnDeck()"
:key="content.key"
xs12
md3
class="pb-3 pa-2"
>
<plexthumb
:content="content"
:server="GET_LASTSERVER"
type="art"
@contentSet="setContent(content)"
></plexthumb>
/>
</v-flex>
</v-layout>
</div>
<v-divider></v-divider>
<div class="pt-4" v-if="results.length == 0">
<h4>Browse
<v-icon @click="PLEX_GET_DEVICES(true)" class="pl-2" small>refresh</v-icon>
<v-divider />
<div
v-if="results.length == 0"
class="pt-4"
>
<h4>
Browse
<v-icon
class="pl-2"
small
@click="PLEX_GET_DEVICES(true)"
>
refresh
</v-icon>
</h4>
<v-layout row wrap>
<v-flex xs12 v-if="Object.keys(getPlex.servers).length === 0">
<v-layout
row
wrap
>
<v-flex
v-if="Object.keys(getPlex.servers).length === 0"
xs12
>
<h5>No Plex Media Servers found. Make sure your server owner has shared their libraries with you!</h5>
</v-flex>
<v-flex
v-for="server in getPlex.servers"
:key="server.clientIdentifier"
xs12
lg4
md6
xl3
v-for="server in getPlex.servers"
:key="server.clientIdentifier"
class="pa-2"
>
<router-link :to="'/browse/' + server.clientIdentifier">
@ -157,21 +241,43 @@
:title="server.name"
>
<v-container fill-height>
<v-layout row justify-center align-center>
<v-layout
row
justify-center
align-center
>
<v-flex xs4>
<v-img :src="logos.plex.standard" height="110px" contain></v-img>
<v-img
:src="logos.plex.standard"
height="110px"
contain
/>
</v-flex>
<v-flex xs8 class="pl-2">
<v-flex
xs8
class="pl-2"
>
<div>
<h1 style="white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">{{ server.name }}</h1>
<h4 style="opacity: 0.9">v{{ server.productVersion }}</h4>
<h1 style="white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">
{{ server.name }}
</h1>
<h4 style="opacity: 0.9">
v{{ server.productVersion }}
</h4>
<div>Owned by {{ ownerOfServer(server) }}</div>
<div v-if="!isConnectable(server)" class="red--text">Unable to connect</div>
<div
v-if="!isConnectable(server)"
class="red--text"
>
Unable to connect
</div>
<div
v-if="!isConnectable(server)"
class="red--text"
style="font-size: 10px"
>Try disabling your adblocker</div>
>
Try disabling your adblocker
</div>
</div>
</v-flex>
</v-layout>
@ -192,10 +298,10 @@ import plexthumb from './plexbrowser/plexthumb.vue';
const _ = require('lodash');
export default {
name: 'Plexbrowser',
components: {
plexthumb,
},
name: 'plexbrowser',
mounted() {
this.updateOnDeck();
},

View File

@ -1,57 +1,124 @@
<template>
<span>
<span v-on:click="reset()" style="cursor: pointer !important"> {{ content.title }}<span
v-if="browsingContent"
> > </span> </span>
<v-layout v-if="!contents && !browsingContent" row>
<v-flex xs12 style="position:relative">
<v-progress-circular style="left: 50%; top:50%" v-bind:size="60" indeterminate class="amber--text"></v-progress-circular>
</v-flex>
</v-layout>
<div v-if="contents && !browsingContent" class="mt-3">
<v-card horizontal height="25em" :img="getArtUrl">
<v-card-row class="hidden-sm-and-down" :img="getThumb" height="100%"></v-card-row>
<v-card-column style="background: rgba(0, 0, 0, .4)">
<v-card-row height="11em" class="white--text">
<v-card-text>
<h3> {{ content.parentTitle }}</h3>
<h6>{{ content.title }}</h6>
<h4> {{content.year}} </h4>
<p> {{ contents.MediaContainer.size }} tracks </p>
</v-card-text>
</v-card-row>
<v-card-row actions class="pa-4" style="background: rgba(0,0,0,0.4)">
<v-btn style="width:15%" v-on:click.native="playMedia(content)" raised large class="primary white--text">
<v-icon light>play_arrow</v-icon> Play
</v-btn>
</v-card-row>
</v-card-column>
</v-card>
<h4 class="mt-3"> Tracks </h4>
<v-divider></v-divider>
<div>
<v-layout class="row mt-3" v-for="content in contents.MediaContainer.Metadata" :key="content" row wrap>
<v-flex xs1 md1>
<v-icon class="click-cursor" large light v-on:click="playMedia(content)">play_arrow</v-icon>
</v-flex>
<v-flex xs4 md3 class="click-cursor" v-on:click="playMedia(content)">
<span class="soft-text">{{ content.index }}</span>. {{ content.title }}
</v-flex>
<v-flex xs4 md2>
<span class="soft-text">{{ getDuration(content) }}</span>
</v-flex>
<v-flex md3>
<span class="soft-text" v-for="media in content.Media" :key="media">
{{ media.audioCodec }} ({{ media.audioChannels }}ch) <span v-if="content.Media.length > 1">,</span>
</span>
</v-flex>
</v-layout>
</div>
</div>
<plexcontent v-if="browsingContent && browsingContent.type != 'show'" :content="browsingContent"
:server="server" :library="library"
></plexcontent>
</span>
<span>
<span
style="cursor: pointer !important"
@click="reset()"
> {{ content.title }}<span
v-if="browsingContent"
> > </span> </span>
<v-layout
v-if="!contents && !browsingContent"
row
>
<v-flex
xs12
style="position:relative"
>
<v-progress-circular
style="left: 50%; top:50%"
:size="60"
indeterminate
class="amber--text"
/>
</v-flex>
</v-layout>
<div
v-if="contents && !browsingContent"
class="mt-3"
>
<v-card
horizontal
height="25em"
:img="getArtUrl"
>
<v-card-row
class="hidden-sm-and-down"
:img="getThumb"
height="100%"
/>
<v-card-column style="background: rgba(0, 0, 0, .4)">
<v-card-row
height="11em"
class="white--text"
>
<v-card-text>
<h3> {{ content.parentTitle }}</h3>
<h6>{{ content.title }}</h6>
<h4> {{ content.year }} </h4>
<p> {{ contents.MediaContainer.size }} tracks </p>
</v-card-text>
</v-card-row>
<v-card-row
actions
class="pa-4"
style="background: rgba(0,0,0,0.4)"
>
<v-btn
style="width:15%"
raised
large
class="primary white--text"
@click.native="playMedia(content)"
>
<v-icon light>play_arrow</v-icon> Play
</v-btn>
</v-card-row>
</v-card-column>
</v-card>
<h4 class="mt-3"> Tracks </h4>
<v-divider />
<div>
<v-layout
v-for="content in contents.MediaContainer.Metadata"
:key="content"
class="row mt-3"
row
wrap
>
<v-flex
xs1
md1
>
<v-icon
class="click-cursor"
large
light
@click="playMedia(content)"
>play_arrow</v-icon>
</v-flex>
<v-flex
xs4
md3
class="click-cursor"
@click="playMedia(content)"
>
<span class="soft-text">{{ content.index }}</span>. {{ content.title }}
</v-flex>
<v-flex
xs4
md2
>
<span class="soft-text">{{ getDuration(content) }}</span>
</v-flex>
<v-flex md3>
<span
v-for="media in content.Media"
:key="media"
class="soft-text"
>
{{ media.audioCodec }} ({{ media.audioChannels }}ch) <span v-if="content.Media.length > 1">,</span>
</span>
</v-flex>
</v-layout>
</div>
</div>
<plexcontent
v-if="browsingContent && browsingContent.type != 'show'"
:content="browsingContent"
:server="server"
:library="library"
/>
</span>
</template>
<script>
@ -70,22 +137,11 @@ humanizeDuration.languages.shortEn = {
};
export default {
props: ['library', 'server', 'content'],
components: {
plexcontent,
plexthumb,
},
created() {
// Hit the PMS endpoing /library/sections
this.server.getSeriesContent(this.content.key, 0, 500, 1, (result) => {
if (result) {
this.contents = result;
this.setBackground();
} else {
this.status = 'Error loading content!';
}
});
},
props: ['library', 'server', 'content'],
data() {
return {
browsingContent: null,
@ -94,15 +150,6 @@ export default {
status: 'loading..',
};
},
watch: {
browsingContent() {
if (!this.browsingContent) {
this.$store.commit('SET_BACKGROUND', null);
}
},
},
mounted() {},
beforeDestroy() {},
computed: {
getArtUrl() {
const w = Math.round(Math.max(document.documentElement.clientWidth, window.innerWidth || 0));
@ -128,6 +175,26 @@ export default {
);
},
},
watch: {
browsingContent() {
if (!this.browsingContent) {
this.$store.commit('SET_BACKGROUND', null);
}
},
},
created() {
// Hit the PMS endpoing /library/sections
this.server.getSeriesContent(this.content.key, 0, 500, 1, (result) => {
if (result) {
this.contents = result;
this.setBackground();
} else {
this.status = 'Error loading content!';
}
});
},
mounted() {},
beforeDestroy() {},
methods: {
setContent(content) {
this.browsingContent = content;

View File

@ -1,42 +1,107 @@
<template>
<span>
<span v-on:click="reset()" style="cursor: pointer !important"> {{ content.title }}<span
v-if="browsingContent"
> > </span> </span>
<v-layout v-if="!contents && !browsingContent" row>
<v-flex xs12 style="position:relative">
<v-progress-circular style="left: 50%; top:50%" v-bind:size="60" indeterminate class="amber--text"></v-progress-circular>
</v-flex>
</v-layout>
<div v-if="contents && !browsingContent" class="mt-3">
<v-card horizontal height="25em" :img="getArtUrl">
<v-card-row class="hidden-sm-and-down" :img="getThumb" height="100%"></v-card-row>
<v-card-column style="background: rgba(0, 0, 0, .4)">
<v-card-row height="11em" class="white--text">
<v-card-text>
<h3> {{ content.title }}</h3>
<p> {{ contents.MediaContainer.summary }} </p>
</v-card-text>
</v-card-row>
<v-card-row actions>
<v-chip v-for="genre in content.Genre" :key="genre" v-tooltip:top="{ html: 'Genre' }" label> {{ genre.tag }}</v-chip>
<v-chip v-for="country in content.Country" :key="country" v-tooltip:top="{ html: 'Country' }"> {{ country.tag }}</v-chip>
</v-card-row>
</v-card-column>
</v-card>
<h4 class="mt-3"> Albums </h4>
<v-divider></v-divider>
<div>
<v-layout class="row mt-3" row wrap>
<v-flex xs6 md3 xl1 lg2 class="pb-3" v-for="content in contents.MediaContainer.Metadata" :key="content">
<plexthumb :content="content" :server="server" type="thumb" fullTitle @contentSet="setContent(content)"></plexthumb>
</v-flex>
</v-layout>
</div>
<span>
<span
style="cursor: pointer !important"
@click="reset()"
> {{ content.title }}<span
v-if="browsingContent"
> > </span> </span>
<v-layout
v-if="!contents && !browsingContent"
row
>
<v-flex
xs12
style="position:relative"
>
<v-progress-circular
style="left: 50%; top:50%"
:size="60"
indeterminate
class="amber--text"
/>
</v-flex>
</v-layout>
<div
v-if="contents && !browsingContent"
class="mt-3"
>
<v-card
horizontal
height="25em"
:img="getArtUrl"
>
<v-card-row
class="hidden-sm-and-down"
:img="getThumb"
height="100%"
/>
<v-card-column style="background: rgba(0, 0, 0, .4)">
<v-card-row
height="11em"
class="white--text"
>
<v-card-text>
<h3> {{ content.title }}</h3>
<p> {{ contents.MediaContainer.summary }} </p>
</v-card-text>
</v-card-row>
<v-card-row actions>
<v-chip
v-for="genre in content.Genre"
:key="genre"
v-tooltip:top="{ html: 'Genre' }"
label
> {{ genre.tag }}</v-chip>
<v-chip
v-for="country in content.Country"
:key="country"
v-tooltip:top="{ html: 'Country' }"
> {{ country.tag }}</v-chip>
</v-card-row>
</v-card-column>
</v-card>
<h4 class="mt-3"> Albums </h4>
<v-divider />
<div>
<v-layout
class="row mt-3"
row
wrap
>
<v-flex
v-for="content in contents.MediaContainer.Metadata"
:key="content"
xs6
md3
xl1
lg2
class="pb-3"
>
<plexthumb
:content="content"
:server="server"
type="thumb"
full-title
@contentSet="setContent(content)"
/>
</v-flex>
</v-layout>
</div>
<plexalbum v-if="browsingContent && browsingContent.type == 'album'" :content="browsingContent" :server="server" :library="library"></plexalbum>
<plexcontent v-if="browsingContent && browsingContent.type != 'album'" :content="browsingContent" :server="server" :library="library"></plexcontent>
</span>
</div>
<plexalbum
v-if="browsingContent && browsingContent.type == 'album'"
:content="browsingContent"
:server="server"
:library="library"
/>
<plexcontent
v-if="browsingContent && browsingContent.type != 'album'"
:content="browsingContent"
:server="server"
:library="library"
/>
</span>
</template>
<script>
@ -45,23 +110,12 @@ import plexalbum from './plexalbum';
import plexthumb from './plexthumb.vue';
export default {
props: ['library', 'server', 'content'],
components: {
plexcontent,
plexthumb,
plexalbum,
},
created() {
// Hit the PMS endpoing /library/sections
this.server.getSeriesContent(this.content.key, 0, 500, 1, (result) => {
if (result) {
this.contents = result;
this.setBackground();
} else {
this.status = 'Error loading content!';
}
});
},
props: ['library', 'server', 'content'],
data() {
return {
browsingContent: null,
@ -70,15 +124,6 @@ export default {
status: 'loading..',
};
},
watch: {
browsingContent() {
if (!this.browsingContent) {
this.$store.commit('SET_BACKGROUND', null);
}
},
},
mounted() {},
beforeDestroy() {},
computed: {
getArtUrl() {
const w = Math.round(Math.max(document.documentElement.clientWidth, window.innerWidth || 0));
@ -100,6 +145,26 @@ export default {
);
},
},
watch: {
browsingContent() {
if (!this.browsingContent) {
this.$store.commit('SET_BACKGROUND', null);
}
},
},
created() {
// Hit the PMS endpoing /library/sections
this.server.getSeriesContent(this.content.key, 0, 500, 1, (result) => {
if (result) {
this.contents = result;
this.setBackground();
} else {
this.status = 'Error loading content!';
}
});
},
mounted() {},
beforeDestroy() {},
methods: {
setContent(content) {
this.browsingContent = content;

View File

@ -1,13 +1,22 @@
<template>
<div ref="root" class="slcontent">
<v-layout v-if="!contents" row>
<v-flex xs12 style="position:relative">
<div
ref="root"
class="slcontent"
>
<v-layout
v-if="!contents"
row
>
<v-flex
xs12
style="position:relative"
>
<v-progress-circular
style="left: 50%; top:50%"
v-bind:size="60"
:size="60"
indeterminate
class="amber--text"
></v-progress-circular>
/>
</v-flex>
</v-layout>
<div v-if="contents">
@ -19,65 +28,101 @@
class="darken-2 white--text"
>
<div style=" height: 100%">
<v-container style="background-color: rgba(0, 0, 0, 0.8); height: 100%" fluid>
<v-layout row wrap>
<v-flex xs12 md3>
<v-layout row wrap>
<v-flex md12 class="pa-2">
<v-img :src="thumb" height="30vh" contain></v-img>
<v-container
style="background-color: rgba(0, 0, 0, 0.8); height: 100%"
fluid
>
<v-layout
row
wrap
>
<v-flex
xs12
md3
>
<v-layout
row
wrap
>
<v-flex
md12
class="pa-2"
>
<v-img
:src="thumb"
height="30vh"
contain
/>
</v-flex>
<v-flex xs8 md12 class="text-center hidden-sm-and-down ">
<v-flex
xs8
md12
class="text-center hidden-sm-and-down "
>
<div v-if="playable">
<v-btn
block
v-if="
playable &&
contents.Media.length == 1 &&
(contents.viewOffset == 0 || !contents.viewOffset)
"
v-on:click.native="playMedia(contents)"
block
class="primary white--text"
@click.native="playMedia(contents)"
>
<v-icon> play_arrow </v-icon>
</v-btn>
<v-btn
block
v-else
@click.native.stop="dialog = true"
block
class="primary white--text"
@click.native.stop="dialog = true"
>
<v-icon> play_arrow </v-icon>
</v-btn>
</div>
<div v-if="!playable" class="pa-2">
<div
v-if="!playable"
class="pa-2"
>
Now playing on {{ chosenClient.name }} from {{ server.name }}
</div>
<v-btn
v-if="!playable"
style="background-color: #cc3f3f"
v-on:click.native="pressStop()"
class="white--text"
@click.native="pressStop()"
>
<v-icon></v-icon> Stop
<v-icon /> Stop
</v-btn>
<div v-if="!playable">
<v-btn
v-if="me.role !== 'host'"
:disabled="manualSyncQueued"
color="blue"
v-on:click.native="doManualSync"
v-if="me.role !== 'host'"
>Manual sync</v-btn
@click.native="doManualSync"
>
Manual sync
</v-btn>
</div>
</v-flex>
</v-layout>
</v-flex>
<v-flex xs12 md9 sm12 class="pa-2">
<v-flex
xs12
md9
sm12
class="pa-2"
>
<h1>
{{ title }}
<span style="float: right">
<v-menu>
<v-btn icon class="ma-0 pa-0" dark>
<v-btn
icon
class="ma-0 pa-0"
dark
>
<v-icon>more_vert</v-icon>
</v-btn>
<v-list>
@ -104,13 +149,27 @@
contents.year
}})
</h4>
<h2 v-if="contents.type === 'episode'">{{ contents.title }}</h2>
<h3 v-else>{{ contents.year }}</h3>
<v-layout row wrap>
<v-flex xs12 md6 style="opacity:0.5">
<h2 v-if="contents.type === 'episode'">
{{ contents.title }}
</h2>
<h3 v-else>
{{ contents.year }}
</h3>
<v-layout
row
wrap
>
<v-flex
xs12
md6
style="opacity:0.5"
>
{{ length }}
</v-flex>
<v-flex xs12 sm6>
<v-flex
xs12
sm6
>
<div class="text-xs-right">
<v-chip
v-if="
@ -120,41 +179,57 @@
outlined
left
>
{{ contents.Media[0].videoResolution.toUpperCase() }}</v-chip
{{ contents.Media[0].videoResolution.toUpperCase() }}
</v-chip>
<v-chip
v-if="contents.contentRating"
color="grey darken-2"
small
label
>
<v-chip v-if="contents.contentRating" color="grey darken-2" small label>
{{ contents.contentRating }}</v-chip
>
<v-chip v-if="contents.studio" color="grey darken-2" small label>
{{ contents.studio }}</v-chip
{{ contents.contentRating }}
</v-chip>
<v-chip
v-if="contents.studio"
color="grey darken-2"
small
label
>
{{ contents.studio }}
</v-chip>
</div>
</v-flex>
<v-flex xs12 class="text-center hidden-md-and-up ">
<v-flex
xs12
class="text-center hidden-md-and-up "
>
<div v-if="playable">
<v-btn
block
v-if="
playable &&
contents.Media.length == 1 &&
(contents.viewOffset == 0 || !contents.viewOffset)
"
v-on:click.native="playMedia(contents)"
block
class="primary white--text"
@click.native="playMedia(contents)"
>
<v-icon> play_arrow </v-icon>
</v-btn>
<v-btn
block
v-else
@click.native.stop="dialog = true"
block
class="primary white--text"
@click.native.stop="dialog = true"
>
<v-icon> play_arrow </v-icon>
</v-btn>
</div>
<div v-else>
<v-layout row wrap>
<v-layout
row
wrap
>
<v-flex xs12>
<div class="pa-2">
Now playing on {{ chosenClient.name }} from {{ server.name }}
@ -164,66 +239,109 @@
<v-btn
block
style="background-color: #cc3f3f"
v-on:click.native="pressStop()"
class="white--text"
@click.native="pressStop()"
>
<v-icon></v-icon> Stop
<v-icon /> Stop
</v-btn>
</v-flex>
<v-flex xs12>
<v-btn
v-if="me.role !== 'host'"
block
:disabled="manualSyncQueued"
color="blue"
v-on:click.native="doManualSync"
v-if="me.role !== 'host'"
>Manual sync</v-btn
@click.native="doManualSync"
>
Manual sync
</v-btn>
</v-flex>
</v-layout>
</div>
</v-flex>
<div style="width: 100%">
<p
v-if="hidden"
class="pt-3"
style="font-style: italic"
v-if="hidden"
v-on:click="hiddenOverride = true"
@click="hiddenOverride = true"
>
Summary automatically hidden for unwatched content. Click to unhide.
</p>
<p class="pt-3" style="font-style: italic" v-else>{{ contents.summary }}</p>
<p
v-else
class="pt-3"
style="font-style: italic"
>
{{ contents.summary }}
</p>
</div>
<v-layout
v-if="contents.type === 'movie'"
row
wrap
class="hidden-sm-and-down"
justify-start
align-start
v-if="contents.type === 'movie'"
>
<v-flex lg3 xl2 v-if="contents.Role && contents.Role.length > 0">
<v-subheader class="white--text"> Featuring </v-subheader>
<div v-for="actor in contents.Role.slice(0, 6)" :key="actor.tag">
<v-flex
v-if="contents.Role && contents.Role.length > 0"
lg3
xl2
>
<v-subheader class="white--text">
Featuring
</v-subheader>
<div
v-for="actor in contents.Role.slice(0, 6)"
:key="actor.tag"
>
{{ actor.tag }}
<span style="opacity:0.7;font-size:80%"> {{ actor.role }} </span>
</div>
</v-flex>
<v-flex lg3 xl2 v-if="contents.Director && contents.Director.length > 0">
<v-subheader class="white--text"> Director </v-subheader>
<div v-for="director in contents.Director.slice(0, 3)" :key="director.tag">
<v-flex
v-if="contents.Director && contents.Director.length > 0"
lg3
xl2
>
<v-subheader class="white--text">
Director
</v-subheader>
<div
v-for="director in contents.Director.slice(0, 3)"
:key="director.tag"
>
{{ director.tag }}
</div>
</v-flex>
<v-flex lg3 xl2 v-if="contents.Producer && contents.Producer.length > 0">
<v-subheader class="white--text"> Producers </v-subheader>
<div v-for="producer in contents.Producer.slice(0, 3)" :key="producer.tag">
<v-flex
v-if="contents.Producer && contents.Producer.length > 0"
lg3
xl2
>
<v-subheader class="white--text">
Producers
</v-subheader>
<div
v-for="producer in contents.Producer.slice(0, 3)"
:key="producer.tag"
>
{{ producer.tag }}
</div>
</v-flex>
<v-flex lg3 xl2 v-if="contents.Writer && contents.Writer.length > 0">
<v-subheader class="white--text"> Writers </v-subheader>
<div v-for="writer in contents.Writer.slice(0, 3)" :key="writer.tag">
<v-flex
v-if="contents.Writer && contents.Writer.length > 0"
lg3
xl2
>
<v-subheader class="white--text">
Writers
</v-subheader>
<div
v-for="writer in contents.Writer.slice(0, 3)"
:key="writer.tag"
>
{{ writer.tag }}
</div>
</v-flex>
@ -231,49 +349,68 @@
</v-layout>
</v-flex>
</v-layout>
<v-divider></v-divider>
<v-divider />
<div
v-if="subsetParentData(6).length >= 0 && contents.type == 'episode'"
class="hidden-xs-only"
>
<v-subheader
>Also in Season {{ contents.parentIndex }} of
{{ contents.grandparentTitle }}</v-subheader
<v-subheader>
Also in Season {{ contents.parentIndex }} of
{{ contents.grandparentTitle }}
</v-subheader>
<v-layout
v-if="parentData"
row
wrap
justify-start
>
<v-layout v-if="parentData" row wrap justify-start>
<v-flex
v-for="ep in subsetParentData(6)"
:key="ep.key"
xs6
md2
xl2
lg2
class="pb-3"
v-for="ep in subsetParentData(6)"
:key="ep.key"
>
<plexthumb
bottomOnly
bottom-only
:content="ep"
:img="getLittleThumb(ep)"
:class="{ highlightBorder: ep.index === contents.index }"
style="margin:3%"
:server="server"
spoilerFilter
></plexthumb>
spoiler-filter
/>
</v-flex>
</v-layout>
</div>
<div v-if="relatedItems.length > 0">
<v-subheader>Related Movies</v-subheader>
<v-container fill-height fluid>
<v-layout row wrap justify-space-around align-center>
<v-flex xs4 md1 class="ma-1" v-for="movie in relatedItems" :key="movie.key">
<v-container
fill-height
fluid
>
<v-layout
row
wrap
justify-space-around
align-center
>
<v-flex
v-for="movie in relatedItems"
:key="movie.key"
xs4
md1
class="ma-1"
>
<plexthumb
:content="movie"
:img="getLittleThumb(movie)"
style="margin:3%"
:server="server"
type="thumb"
></plexthumb>
/>
</v-flex>
</v-layout>
</v-container>
@ -289,18 +426,29 @@
width="500px"
>
<v-card style="overflow: hidden">
<v-card-title class="headline">Playback Settings</v-card-title>
<v-card-title class="headline">
Playback Settings
</v-card-title>
<v-checkbox
v-if="contents.viewOffset && contents.viewOffset > 0"
v-bind:label="'Resume from ' + getDuration(contents.viewOffset)"
v-model="resumeFrom"
:label="'Resume from ' + getDuration(contents.viewOffset)"
color="orange lighten-2"
class="pa-0 ma-0 ml-3"
v-model="resumeFrom"
></v-checkbox>
<div v-for="(media, index) in contents.Media" :key="media.Part[0].key">
<v-layout row wrap class="pa-2">
/>
<div
v-for="(media, index) in contents.Media"
:key="media.Part[0].key"
>
<v-layout
row
wrap
class="pa-2"
>
<v-flex xs8>
<div class="pl-2">{{ media.videoResolution }}p {{ getDuration(media.duration) }}</div>
<div class="pl-2">
{{ media.videoResolution }}p {{ getDuration(media.duration) }}
</div>
<div class="pl-4 soft-text">
<div>Video Codec: {{ media.videoCodec }} ({{ media.bitrate }}kbps)</div>
<div>Audio Streams: {{ audioStreams(media.Part[0].Stream) }}</div>
@ -308,7 +456,10 @@
</div>
</v-flex>
<v-flex xs4>
<v-btn class="primary white--text" @click.native.stop="playMedia(contents, index)">
<v-btn
class="primary white--text"
@click.native.stop="playMedia(contents, index)"
>
Play
</v-btn>
</v-flex>
@ -328,7 +479,6 @@ export default {
components: {
plexthumb,
},
created() {},
data() {
return {
browsingContent: null,
@ -349,17 +499,6 @@ export default {
eventbus: window.eventbus,
};
},
mounted() {
this.getNewData();
this.fullheight = this.$refs.root.offsetHeight;
this.fullwidth = this.$refs.root.offsetWidth;
},
beforeDestroy() {},
watch: {
ratingKey() {
this.getNewData();
},
},
computed: {
plex() {
return this.$store.getters.getPlex;
@ -471,6 +610,18 @@ export default {
);
},
},
watch: {
ratingKey() {
this.getNewData();
},
},
created() {},
mounted() {
this.getNewData();
this.fullheight = this.$refs.root.offsetHeight;
this.fullwidth = this.$refs.root.offsetWidth;
},
beforeDestroy() {},
methods: {
getNewData() {
this.server.getMediaByRatingKey(this.ratingKey).then(async (result) => {

View File

@ -1,7 +1,5 @@
<template>
<span ref="root">
</span>
<span ref="root" />
</template>
<script>
@ -17,6 +15,16 @@ export default {
plexseries,
plexcontent,
},
data() {
return {
contents: null,
};
},
computed: {
ratingKey() {
return this.$route.params.ratingKey;
},
},
created() {
this.server.getMediaByRatingKey(this.content.ratingKey).then((result) => {
if (result) {
@ -34,20 +42,10 @@ export default {
}
});
},
data() {
return {
contents: null,
};
},
mounted() {
},
beforeDestroy() {
},
computed: {
ratingKey() {
return this.$route.params.ratingKey;
},
},
methods: {

View File

@ -1,18 +1,58 @@
<template>
<span style="max-height: 90%">
<v-layout v-if="!contents && !browsingContent" row justify-center align-start>
<v-flex xs12 style="position:relative">
<v-progress-circular style="left: 50%; top:50%" v-bind:size="60" indeterminate class="amber--text"></v-progress-circular>
<v-layout
v-if="!contents && !browsingContent"
row
justify-center
align-start
>
<v-flex
xs12
style="position:relative"
>
<v-progress-circular
style="left: 50%; top:50%"
:size="60"
indeterminate
class="amber--text"
/>
</v-flex>
</v-layout>
<div v-if="!browsingContent && contents" class="mt-3" style="height:90vh; overflow-y: auto">
<v-layout class="row" row wrap>
<v-flex xs3 sm3 md1 lg1 class="ma-1" v-for="content in contents.MediaContainer.Metadata" :key="content.key">
<plexthumb :content="content" :server="server" type="thumb" style="margin:7%" @contentSet="setContent(content)"></plexthumb>
<div
v-if="!browsingContent && contents"
class="mt-3"
style="height:90vh; overflow-y: auto"
>
<v-layout
class="row"
row
wrap
>
<v-flex
v-for="content in contents.MediaContainer.Metadata"
:key="content.key"
xs3
sm3
md1
lg1
class="ma-1"
>
<plexthumb
:content="content"
:server="server"
type="thumb"
style="margin:7%"
@contentSet="setContent(content)"
/>
</v-flex>
</v-layout>
<v-layout row>
<v-flex xs12 v-if="contents && !browsingContent && !stopNewContent" v-observe-visibility="getMoreContent" justify-center>
<v-flex
v-if="contents && !browsingContent && !stopNewContent"
v-observe-visibility="getMoreContent"
xs12
justify-center
>
Loading...
</v-flex>
</v-layout>
@ -30,7 +70,6 @@ import plexartist from './plexartist';
const _ = require('lodash');
export default {
props: ['library'],
components: {
plexcontent,
plexseries,
@ -38,10 +77,7 @@ export default {
plexalbum,
plexartist,
},
created() {
// Hit the PMS endpoing /library/sections
this.getMoreContent();
},
props: ['library'],
data() {
return {
browsingContent: null,
@ -55,16 +91,20 @@ export default {
status: 'loading..',
searchPhrase: null,
};
},
mounted() {
},
beforeDestroy() {
},
computed: {
server() {
return this.plex.servers[this.$route.params.machineIdentifier];
},
},
created() {
// Hit the PMS endpoing /library/sections
this.getMoreContent();
},
mounted() {
},
beforeDestroy() {
},
methods: {
setContent(content) {

View File

@ -1,53 +1,118 @@
<template>
<span>
<v-layout v-if="!contents" row>
<v-flex xs12 style="position:relative">
<v-progress-circular style="left: 50%; top:50%" v-bind:size="60" indeterminate class="amber--text"></v-progress-circular>
<v-layout
v-if="!contents"
row
>
<v-flex
xs12
style="position:relative"
>
<v-progress-circular
style="left: 50%; top:50%"
:size="60"
indeterminate
class="amber--text"
/>
</v-flex>
</v-layout>
<div v-else class="mt-3">
<div
v-else
class="mt-3"
>
<v-flex xs12>
<v-card class="darken-2 white--text" :img="getArtUrl">
<v-container style="background: rgba(0, 0, 0, .6);" class="pa-0 ma-0" fluid grid-list-lg>
<v-layout row style="height:100%">
<v-flex xs12 md3 class="hidden-sm-and-down">
<v-img
:src="getThumb"
class="ma-0 pa-0"
height="25em"
contain
></v-img>
</v-flex>
<v-flex xs12 md9 style="position:relative" class="ma-2">
<v-card
class="darken-2 white--text"
:img="getArtUrl"
>
<v-container
style="background: rgba(0, 0, 0, .6);"
class="pa-0 ma-0"
fluid
grid-list-lg
>
<v-layout
row
style="height:100%"
>
<v-flex
xs12
md3
class="hidden-sm-and-down"
>
<v-img
:src="getThumb"
class="ma-0 pa-0"
height="25em"
contain
/>
</v-flex>
<v-flex
xs12
md9
style="position:relative"
class="ma-2"
>
<div>
<h1> {{ contents.MediaContainer.title1 }}</h1>
<h3 style="font-weight:bold">{{ contents.MediaContainer.title2 }}</h3>
<p> {{ contents.MediaContainer.size }} episodes </p>
<v-divider />
<p
style="font-style: italic"
class="pt-3; overflow: hidden"
> {{ contents.summary }} </p>
<div>
<h1> {{ contents.MediaContainer.title1 }}</h1>
<h3 style="font-weight:bold">{{ contents.MediaContainer.title2 }}</h3>
<p> {{ contents.MediaContainer.size }} episodes </p>
<v-divider></v-divider>
<p style="font-style: italic" class="pt-3; overflow: hidden"> {{ contents.summary }} </p>
<div>
<div style="float:right" class="pa-4">
<v-chip v-if="contents.MediaContainer.grandparentContentRating" label color="grey"> {{ contents.MediaContainer.grandparentContentRating }}</v-chip>
<v-chip v-if="contents.MediaContainer.grandparentStudio" secondary color="grey"> {{ contents.MediaContainer.grandparentStudio }}</v-chip>
</div>
<div
style="float:right"
class="pa-4"
>
<v-chip
v-if="contents.MediaContainer.grandparentContentRating"
label
color="grey"
> {{ contents.MediaContainer.grandparentContentRating }}</v-chip>
<v-chip
v-if="contents.MediaContainer.grandparentStudio"
secondary
color="grey"
> {{ contents.MediaContainer.grandparentStudio }}</v-chip>
</div>
</div>
</v-flex>
</v-layout>
</v-container>
</v-card>
</v-flex>
<h4 class="mt-3"> Episodes </h4>
<v-divider></v-divider>
<div>
<v-layout class="row mt-3" row wrap>
<v-flex xs6 md2 class="pb-3" v-for="content in contents.MediaContainer.Metadata" :key="content.key">
<plexthumb :content="content" :server="plexserver" type="thumb" style="margin:2%" fullTitle @contentSet="setContent(content)"></plexthumb>
</v-flex>
</v-layout>
</div>
</div>
</v-flex>
</v-layout>
</v-container>
</v-card>
</v-flex>
<h4 class="mt-3"> Episodes </h4>
<v-divider />
<div>
<v-layout
class="row mt-3"
row
wrap
>
<v-flex
v-for="content in contents.MediaContainer.Metadata"
:key="content.key"
xs6
md2
class="pb-3"
>
<plexthumb
:content="content"
:server="plexserver"
type="thumb"
style="margin:2%"
full-title
@contentSet="setContent(content)"
/>
</v-flex>
</v-layout>
</div>
</span>
</div>
</span>
</template>
<script>
@ -55,23 +120,11 @@ import plexcontent from './plexcontent';
import plexthumb from './plexthumb.vue';
export default {
props: ['library', 'server', 'content'],
components: {
plexcontent,
plexthumb,
},
created() {
// Hit the PMS endpoing /library/sections
this.plexserver.getSeriesChildren(this.$route.params.ratingKey, 0, 500, 1, this.$route.params.sectionId)
.then((result) => {
if (result) {
this.contents = result;
this.setBackground();
} else {
this.status = 'Error loading content!';
}
});
},
props: ['library', 'server', 'content'],
data() {
return {
browsingContent: null,
@ -80,15 +133,6 @@ export default {
status: 'loading..',
};
},
watch: {
browsingContent() {
if (!this.browsingContent) {
this.$store.commit('SET_BACKGROUND', null);
}
},
},
mounted() {},
beforeDestroy() {},
computed: {
getArtUrl() {
const w = Math.round(Math.max(document.documentElement.clientWidth, window.innerWidth || 0));
@ -105,6 +149,27 @@ export default {
);
},
},
watch: {
browsingContent() {
if (!this.browsingContent) {
this.$store.commit('SET_BACKGROUND', null);
}
},
},
created() {
// Hit the PMS endpoing /library/sections
this.plexserver.getSeriesChildren(this.$route.params.ratingKey, 0, 500, 1, this.$route.params.sectionId)
.then((result) => {
if (result) {
this.contents = result;
this.setBackground();
} else {
this.status = 'Error loading content!';
}
});
},
mounted() {},
beforeDestroy() {},
methods: {
setContent(content) {
this.browsingContent = content;

View File

@ -1,44 +1,101 @@
<template>
<span>
<v-layout v-if="!contents" row>
<v-flex xs12 style="position:relative">
<v-progress-circular style="left: 50%; top:50%" v-bind:size="60" indeterminate class="amber--text"></v-progress-circular>
<v-layout
v-if="!contents"
row
>
<v-flex
xs12
style="position:relative"
>
<v-progress-circular
style="left: 50%; top:50%"
:size="60"
indeterminate
class="amber--text"
/>
</v-flex>
</v-layout>
<div v-if="contents" class="mt-3">
<v-flex xs12 style="background: rgba(0, 0, 0, .4);">
<v-card class="darken-2 white--text" :img="getArtUrl">
<v-container style="background:rgba(0,0,0,0.6)" class="pa-3 ma-0" fluid grid-list-lg>
<v-layout row style="height:100%">
<v-flex xs12 md3 class="hidden-sm-and-down">
<div
v-if="contents"
class="mt-3"
>
<v-flex
xs12
style="background: rgba(0, 0, 0, .4);"
>
<v-card
class="darken-2 white--text"
:img="getArtUrl"
>
<v-container
style="background:rgba(0,0,0,0.6)"
class="pa-3 ma-0"
fluid
grid-list-lg
>
<v-layout
row
style="height:100%"
>
<v-flex
xs12
md3
class="hidden-sm-and-down"
>
<v-img
:src="thumb"
class="ma-0 pa-0 hidden-sm-and-down"
height="25em"
contain
></v-img>
/>
</v-flex>
<v-flex xs12 md9 class="ma-2">
<v-flex
xs12
md9
class="ma-2"
>
<div>
<h1> {{ contents.parentTitle }}</h1>
<h3 style="font-weight:bold">{{ contents.title }}</h3>
<p> {{ getSeasons }} - {{ contents.parentYear }} </p>
<v-divider></v-divider>
<p style="font-style: italic" class="pt-3; overflow: hidden"> {{ contents.summary }} </p>
<v-divider />
<p
style="font-style: italic"
class="pt-3; overflow: hidden"
> {{ contents.summary }} </p>
<div>
<v-chip v-for="genre in genres" :key="genre.tag" label color="grey">
<v-chip
v-for="genre in genres"
:key="genre.tag"
label
color="grey"
>
{{ genre.tag }}
</v-chip>
</div>
<v-subheader class="white--text"> Featuring </v-subheader>
<v-layout row wrap v-if="seriesData">
<v-flex v-for="role in roles" :key="role.tag" xs12 md6 lg4>
<v-layout
v-if="seriesData"
row
wrap
>
<v-flex
v-for="role in roles"
:key="role.tag"
xs12
md6
lg4
>
<v-chip style="border: none; background: none; color: white">
<v-avatar>
<img :src="role.thumb">
</v-avatar>
{{ role.tag }}
<div style="opacity:0.7;font-size:80% " class="pa-2"> {{role.role}} </div>
<div
style="opacity:0.7;font-size:80% "
class="pa-2"
> {{ role.role }} </div>
</v-chip>
</v-flex>
</v-layout>
@ -49,9 +106,26 @@
</v-card>
</v-flex>
<h4 class="mt-3"> Seasons </h4>
<v-layout class="row" row wrap>
<v-flex xs4 md2 xl1 lg1 class="pb-3" v-for="content in contents.Metadata" :key="content.key">
<plexthumb :content="content" :server="plexserver" type="thumb" style="margin:7%"></plexthumb>
<v-layout
class="row"
row
wrap
>
<v-flex
v-for="content in contents.Metadata"
:key="content.key"
xs4
md2
xl1
lg1
class="pb-3"
>
<plexthumb
:content="content"
:server="plexserver"
type="thumb"
style="margin:7%"
/>
</v-flex>
</v-layout>
</div>
@ -63,29 +137,11 @@ import plexseason from './plexseason.vue';
import plexthumb from './plexthumb.vue';
export default {
props: [],
components: {
plexseason,
plexthumb,
},
created() {
// Hit the PMS endpoing /library/sections
this.plexserver.getSeriesChildren(this.$route.params.ratingKey, this.startingIndex, this.size, 1, this.$route.params.sectionId).then((result) => {
if (result) {
this.contents = result.MediaContainer;
this.setBackground();
} else {
this.status = 'Error loading libraries!';
}
}).catch((e, data) => {
});
this.plexserver.getSeriesData(this.$route.params.ratingKey).then((res) => {
if (res) {
this.seriesData = res;
}
}).catch((e, data) => {
});
},
props: [],
data() {
return {
browsingContent: null,
@ -96,12 +152,6 @@ export default {
seriesData: null,
status: 'loading..',
};
},
mounted() {
},
beforeDestroy() {
},
computed: {
getArtUrl(object) {
@ -132,6 +182,30 @@ export default {
const h = Math.round(Math.max(document.documentElement.clientHeight, window.innerHeight || 0));
return this.plexserver.getUrlForLibraryLoc(this.contents.thumb || this.contents.parentThumb || this.contents.grandparentThumb, w / 1, h / 1);
},
},
created() {
// Hit the PMS endpoing /library/sections
this.plexserver.getSeriesChildren(this.$route.params.ratingKey, this.startingIndex, this.size, 1, this.$route.params.sectionId).then((result) => {
if (result) {
this.contents = result.MediaContainer;
this.setBackground();
} else {
this.status = 'Error loading libraries!';
}
}).catch((e, data) => {
});
this.plexserver.getSeriesData(this.$route.params.ratingKey).then((res) => {
if (res) {
this.seriesData = res;
}
}).catch((e, data) => {
});
},
mounted() {
},
beforeDestroy() {
},
methods: {
setBackground() {

View File

@ -1,58 +1,174 @@
<template>
<span v-if="server">
<v-layout v-if="!libraries && !browsingLibrary && !selectedItem" row align-center>
<v-flex xs12 style="position: relative">
<v-progress-circular style="left:50%; top:50%" v-bind:size="60" indeterminate class="amber--text"></v-progress-circular>
<v-layout
v-if="!libraries && !browsingLibrary && !selectedItem"
row
align-center
>
<v-flex
xs12
style="position: relative"
>
<v-progress-circular
style="left:50%; top:50%"
:size="60"
indeterminate
class="amber--text"
/>
</v-flex>
</v-layout>
<div v-if="!browsingLibrary && !selectedItem && libraries" class="mt-3">
<div v-if="!libraries && !browsingLibrary">
<v-progress-circular active large></v-progress-circular>
</div>
<h4> Libraries </h4>
<v-layout row wrap v-if="libraries && !browsingLibrary">
<v-flex xs12 md3 xl2 lg2 v-for="library in filteredLibraries" class="pa-1" :key="library.name">
<v-card v-on:click.native="setLibrary(library)" :img="getArtLibrary(library)" flat class="clickable text-center" style="max-width:100%; cursor: pointer; border-radius: 0px !important">
<div style="position:relative; width:100%; background: rgba(0,0,0,0.4); height:8em" class="hidden-xs-only">
<img style="height: 70%; display: block; margin-left: auto; margin-right: auto " :src="getThumb(library)" />
</div>
<div style="background: rgba(0,0,0,0.7); position:relative; width:100%;" class="text-center pa-1">
<h2 class="truncate text-xs-left text-sm-center">{{ library.title }}</h2>
</div>
</v-card>
</v-flex>
</v-layout>
<v-divider v-if="subsetOnDeck(onDeckItemsPer).length > 0" class="mt-3 ma-2"></v-divider>
<v-layout row wrap v-if="subsetOnDeck(onDeckItemsPer).length > 0">
<v-flex xs6>
<h4>On Deck</h4>
</v-flex>
<v-flex xs6>
<span style="float:right; user-select: none;">
<v-icon @click="onDeckDown" style="margin-right: 15px;cursor: pointer" :style="onDeckDownStyle">fa-angle-left</v-icon><v-icon @click="onDeckUp" :style="onDeckUpStyle" style="cursor: pointer">fa-angle-right</v-icon>
</span>
</v-flex>
</v-layout>
<v-layout v-if="onDeck" row wrap>
<v-flex xs12 sm6 md4 lg3 class="pb-3 pa-1" v-for="content in subsetOnDeck()" :key="content.key">
<plexthumb :content="content" :server="server" type="art" @contentSet="setContent(content)"></plexthumb>
</v-flex>
</v-layout>
<v-divider v-if="subsetRecentlyAdded(recentItemsPer).length > 0" class="mt-3 ma-2"></v-divider>
<v-layout row wrap v-if="subsetRecentlyAdded(recentItemsPer).length > 0">
<v-flex xs6>
<h4>Recently Added</h4>
</v-flex>
<v-flex xs6>
<span style="float:right; user-select: none;"> <v-icon fa @click="recentlyAddedDown" style="margin-right: 15px;cursor: pointer;" :style="recentlyAddedDownStyle">fa-angle-left</v-icon><v-icon fa :style="recentlyAddedUpStyle" @click="recentlyAddedUp" style="cursor: pointer">fa-angle-right</v-icon>
</span>
</v-flex>
</v-layout>
<v-layout v-if="recentlyAdded" class="row pt-2" row wrap justify-space-between>
<v-flex xs4 sm2 md1 xl1 lg1 class="pb-3 pa-3" v-for="content in subsetRecentlyAdded(recentItemsPer)" :key="content.key">
<plexthumb :content="content" :server="server" type="thumb" fullTitle locked @contentSet="setContent(content)"></plexthumb>
</v-flex>
</v-layout>
<div
v-if="!browsingLibrary && !selectedItem && libraries"
class="mt-3"
>
<div v-if="!libraries && !browsingLibrary">
<v-progress-circular
active
large
/>
</div>
<h4> Libraries </h4>
<v-layout
v-if="libraries && !browsingLibrary"
row
wrap
>
<v-flex
v-for="library in filteredLibraries"
:key="library.name"
xs12
md3
xl2
lg2
class="pa-1"
>
<v-card
:img="getArtLibrary(library)"
flat
class="clickable text-center"
style="max-width:100%; cursor: pointer; border-radius: 0px !important"
@click.native="setLibrary(library)"
>
<div
style="position:relative; width:100%; background: rgba(0,0,0,0.4); height:8em"
class="hidden-xs-only"
>
<img
style="height: 70%; display: block; margin-left: auto; margin-right: auto "
:src="getThumb(library)"
>
</div>
<div
style="background: rgba(0,0,0,0.7); position:relative; width:100%;"
class="text-center pa-1"
>
<h2 class="truncate text-xs-left text-sm-center">{{ library.title }}</h2>
</div>
</v-card>
</v-flex>
</v-layout>
<v-divider
v-if="subsetOnDeck(onDeckItemsPer).length > 0"
class="mt-3 ma-2"
/>
<v-layout
v-if="subsetOnDeck(onDeckItemsPer).length > 0"
row
wrap
>
<v-flex xs6>
<h4>On Deck</h4>
</v-flex>
<v-flex xs6>
<span style="float:right; user-select: none;">
<v-icon
style="margin-right: 15px;cursor: pointer"
:style="onDeckDownStyle"
@click="onDeckDown"
>fa-angle-left</v-icon><v-icon
:style="onDeckUpStyle"
style="cursor: pointer"
@click="onDeckUp"
>fa-angle-right</v-icon>
</span>
</v-flex>
</v-layout>
<v-layout
v-if="onDeck"
row
wrap
>
<v-flex
v-for="content in subsetOnDeck()"
:key="content.key"
xs12
sm6
md4
lg3
class="pb-3 pa-1"
>
<plexthumb
:content="content"
:server="server"
type="art"
@contentSet="setContent(content)"
/>
</v-flex>
</v-layout>
<v-divider
v-if="subsetRecentlyAdded(recentItemsPer).length > 0"
class="mt-3 ma-2"
/>
<v-layout
v-if="subsetRecentlyAdded(recentItemsPer).length > 0"
row
wrap
>
<v-flex xs6>
<h4>Recently Added</h4>
</v-flex>
<v-flex xs6>
<span style="float:right; user-select: none;"> <v-icon
fa
style="margin-right: 15px;cursor: pointer;"
:style="recentlyAddedDownStyle"
@click="recentlyAddedDown"
>fa-angle-left</v-icon><v-icon
fa
:style="recentlyAddedUpStyle"
style="cursor: pointer"
@click="recentlyAddedUp"
>fa-angle-right</v-icon>
</span>
</v-flex>
</v-layout>
<v-layout
v-if="recentlyAdded"
class="row pt-2"
row
wrap
justify-space-between
>
<v-flex
v-for="content in subsetRecentlyAdded(recentItemsPer)"
:key="content.key"
xs4
sm2
md1
xl1
lg1
class="pb-3 pa-3"
>
<plexthumb
:content="content"
:server="server"
type="thumb"
full-title
locked
@contentSet="setContent(content)"
/>
</v-flex>
</v-layout>
</div>
</span>
</template>
@ -66,8 +182,6 @@ export default {
components: {
plexthumb,
},
created() {
},
data() {
return {
browsingLibrary: null,
@ -82,25 +196,6 @@ export default {
onDeckOffset: 0,
recentlyAddedOffset: 0,
};
},
async mounted() {
this.server.getAllLibraries().then((data) => {
if (data) {
this.libraries = data;
} else {
this.status = 'Error loading libraries!';
}
});
this.server.getRecentlyAddedAll(0, 12).then((result) => {
if (result) {
this.recentlyAdded = result;
this.setBackground();
}
});
this.updateOnDeck();
},
beforeDestroy() {
},
computed: {
recentItemsPer() {
@ -170,6 +265,27 @@ export default {
}
},
},
created() {
},
async mounted() {
this.server.getAllLibraries().then((data) => {
if (data) {
this.libraries = data;
} else {
this.status = 'Error loading libraries!';
}
});
this.server.getRecentlyAddedAll(0, 12).then((result) => {
if (result) {
this.recentlyAdded = result;
this.setBackground();
}
});
this.updateOnDeck();
},
beforeDestroy() {
},
methods: {
setContent(content) {

View File

@ -1,7 +1,7 @@
<template>
<div
class="portrait"
ref="root"
class="portrait"
style="cursor: pointer"
@mouseover="hovering = true"
@mouseout="hovering = false"
@ -9,9 +9,9 @@
<router-link :to="link">
<v-card
flat
v-on:click.native="emitContentClicked(content)"
class="grey darken-4 elevation-20"
style="border-radius: 0px !important"
@click.native="emitContentClicked(content)"
>
<v-img
data-tilt
@ -20,19 +20,23 @@
:height="calculatedHeight"
:src="getImg(content)"
>
<v-container class="pa-0 ma-0" fill-height fluid style="position:relative">
<v-container
class="pa-0 ma-0"
fill-height
fluid
style="position:relative"
>
<v-layout>
<v-flex xs12>
<small
class="ma-1"
v-if="showServer !== undefined"
class="ma-1"
style="position:absolute; top:0;text-align:right;right:0;background: rgba(0, 0, 0, .5)"
>
{{ server.name }}</small
>
{{ server.name }}</small>
<div
class="pt-content-unwatched pt-orange unwatched"
v-if="showUnwatchedFlag && showServer == undefined"
class="pt-content-unwatched pt-orange unwatched"
>
<span class="pa-2 black--text">
<span>
@ -41,8 +45,8 @@
</span>
</div>
<div
style="position:absolute; right:0; background-color: rgba(43, 43, 191, 0.8)"
v-if="content.Media && content.Media.length != 1 && showServer == undefined"
style="position:absolute; right:0; background-color: rgba(43, 43, 191, 0.8)"
>
<span class="pa-2 black--text">
<span>
@ -50,16 +54,26 @@
</span>
</span>
</div>
<v-container fill-height fluid class="pa-0" style="max-width:100%">
<v-layout row wrap justify-end align-end>
<v-container
fill-height
fluid
class="pa-0"
style="max-width:100%"
>
<v-layout
row
wrap
justify-end
align-end
>
<v-flex xs12>
<v-progress-linear
v-if="showProgressBar"
style="width:100%"
class="pa-0 mb-0 ma-0 pt-content-progress"
v-if="showProgressBar"
height="1"
:value="unwatchedPercent"
></v-progress-linear>
/>
</v-flex>
</v-layout>
</v-container>
@ -68,12 +82,33 @@
</v-container>
</v-img>
</v-card>
<v-layout align-end row wrap class="text-xs-left pa-1 white--text" style="max-width: 100%">
<v-flex xs12 v-if="!bottomOnly" style="max-width: 100%">
<div class="truncate" style="font-size:1rem">{{ getTitle(content) }}</div>
<v-layout
align-end
row
wrap
class="text-xs-left pa-1 white--text"
style="max-width: 100%"
>
<v-flex
v-if="!bottomOnly"
xs12
style="max-width: 100%"
>
<div
class="truncate"
style="font-size:1rem"
>
{{ getTitle(content) }}
</div>
</v-flex>
<v-flex xs12 style="font-size:0.8rem" ref="bottomText">
<div class="truncate soft-text">{{ getUnder(content) }}</div>
<v-flex
ref="bottomText"
xs12
style="font-size:0.8rem"
>
<div class="truncate soft-text">
{{ getUnder(content) }}
</div>
</v-flex>
</v-layout>
</router-link>
@ -84,6 +119,7 @@
import VanillaTilt from 'vanilla-tilt';
export default {
components: {},
props: [
'library',
'showServer',
@ -98,10 +134,6 @@ export default {
'bottomOnly',
'spoilerFilter',
],
components: {},
created() {
window.addEventListener('resize', this.handleResize);
},
data() {
return {
fullheight: null,
@ -112,36 +144,6 @@ export default {
hovering: false,
};
},
mounted() {
this.fullheight = this.$refs.root.offsetHeight;
// console.log(this.$refs)
this.fullwidth = this.$refs.root.offsetWidth;
if (this.$refs.topText) {
this.toptextheight = this.$refs.topText.offsetHeight;
}
if (this.$refs.bottomText) {
this.bottomtextheight = this.$refs.bottomText.offsetHeight;
}
if (this.type === 'thumb') {
VanillaTilt.init(this.$refs.root, {
reverse: true, // reverse the tilt direction
max: 7, // max tilt rotation (degrees)
perspective: 1000, // Transform perspective, the lower the more extreme the tilt gets.
scale: 1.01, // 2 = 200%, 1.5 = 150%, etc..
speed: 100, // Speed of the enter/exit transition
transition: true, // Set a transition on enter/exit.
axis: null, // What axis should be disabled. Can be X or Y.
reset: true, // If the tilt effect has to be reset on exit.
easing: 'cubic-bezier(.03,.98,.52,.99)', // Easing on enter/exit.
glare: false, // if it should have a "glare" effect
'max-glare': 0.15, // the maximum "glare" opacity (1 = 100%, 0.5 = 50%)
'glare-prerender': false, // false = VanillaTilt creates the glare elements for you, otherwise
});
}
},
beforeDestroy() {
window.removeEventListener('resize', this.handleResize);
},
computed: {
plex() {
return this.$store.getters.getPlex;
@ -336,6 +338,39 @@ export default {
return (this.content.viewedLeafCount / this.content.leafCount) * 100;
},
},
created() {
window.addEventListener('resize', this.handleResize);
},
mounted() {
this.fullheight = this.$refs.root.offsetHeight;
// console.log(this.$refs)
this.fullwidth = this.$refs.root.offsetWidth;
if (this.$refs.topText) {
this.toptextheight = this.$refs.topText.offsetHeight;
}
if (this.$refs.bottomText) {
this.bottomtextheight = this.$refs.bottomText.offsetHeight;
}
if (this.type === 'thumb') {
VanillaTilt.init(this.$refs.root, {
reverse: true, // reverse the tilt direction
max: 7, // max tilt rotation (degrees)
perspective: 1000, // Transform perspective, the lower the more extreme the tilt gets.
scale: 1.01, // 2 = 200%, 1.5 = 150%, etc..
speed: 100, // Speed of the enter/exit transition
transition: true, // Set a transition on enter/exit.
axis: null, // What axis should be disabled. Can be X or Y.
reset: true, // If the tilt effect has to be reset on exit.
easing: 'cubic-bezier(.03,.98,.52,.99)', // Easing on enter/exit.
glare: false, // if it should have a "glare" effect
'max-glare': 0.15, // the maximum "glare" opacity (1 = 100%, 0.5 = 50%)
'glare-prerender': false, // false = VanillaTilt creates the glare elements for you, otherwise
});
}
},
beforeDestroy() {
window.removeEventListener('resize', this.handleResize);
},
methods: {
emitContentClicked(content) {
this.$emit('contentSet', content);

View File

@ -1,7 +1,14 @@
<template>
<v-list-item :style="styleObj" class="pa-1">
<v-list-item
:style="styleObj"
class="pa-1"
>
<v-list-item-avatar>
<img class="clientLogo" :class="platformClass" :src="url" />
<img
class="clientLogo"
:class="platformClass"
:src="url"
>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>
@ -12,7 +19,9 @@
:color="label[1]"
small
label
>{{ label[0] }}</v-chip>
>
{{ label[0] }}
</v-chip>
</v-list-item-title>
<v-list-item-subtitle>{{ object.product }} - last seen {{ lastSeenAgo }}</v-list-item-subtitle>
</v-list-item-content>
@ -23,9 +32,47 @@
const moment = require('moment');
export default {
name: 'Plexclient',
props: ['object', 'selected', 'startup', 'sidebar'],
name: 'plexclient',
methods: {},
data: () => ({
platformMap: {
android: 'android',
'apple tv': 'atv',
chrome: 'chrome',
chromecast: 'chromecast',
dlna: 'dlna',
firefox: 'firefox',
'internet explorer': 'ie',
ios: 'ios',
ipad: 'ios',
iphone: 'ios',
kodi: 'kodi',
linux: 'linux',
nexus: 'android',
macos: 'macos',
'microsoft edge': 'msedge',
opera: 'opera',
osx: 'macos',
playstation: 'playstation',
'plex home theater': 'plex',
'plex media player': 'plex',
plexamp: 'plexamp',
plextogether: 'synclounge',
roku: 'roku',
safari: 'safari',
samsung: 'samsung',
synclounge: 'synclounge',
tivo: 'tivo',
tizen: 'samsung',
tvos: 'atv',
vizio: 'opera',
wiiu: 'wiiu',
windows: 'windows',
'windows phone': 'wp',
xbmc: 'xbmc',
xbox: 'xbox',
},
}),
computed: {
tooltipMsg() {
return `${this.object.name} running ${this.object.product} on ${this.object.device}`;
@ -84,44 +131,6 @@ export default {
};
},
},
data: () => ({
platformMap: {
android: 'android',
'apple tv': 'atv',
chrome: 'chrome',
chromecast: 'chromecast',
dlna: 'dlna',
firefox: 'firefox',
'internet explorer': 'ie',
ios: 'ios',
ipad: 'ios',
iphone: 'ios',
kodi: 'kodi',
linux: 'linux',
nexus: 'android',
macos: 'macos',
'microsoft edge': 'msedge',
opera: 'opera',
osx: 'macos',
playstation: 'playstation',
'plex home theater': 'plex',
'plex media player': 'plex',
plexamp: 'plexamp',
plextogether: 'synclounge',
roku: 'roku',
safari: 'safari',
samsung: 'samsung',
synclounge: 'synclounge',
tivo: 'tivo',
tizen: 'samsung',
tvos: 'atv',
vizio: 'opera',
wiiu: 'wiiu',
windows: 'windows',
'windows phone': 'wp',
xbmc: 'xbmc',
xbox: 'xbox',
},
}),
methods: {},
};
</script>

View File

@ -1,35 +1,43 @@
<template>
<div>
<div class="pt-1 text-xs-left">
<h4 style="text-align:initial">Blocked Plex Servers</h4>
<small
>Used for autoplay functionality. Use this list to block SyncLounge from searching certain
servers when attempting to autoplay content.</small
>
<h4 style="text-align:initial">
Blocked Plex Servers
</h4>
<small>Used for autoplay functionality. Use this list to block SyncLounge from searching certain
servers when attempting to autoplay content.</small>
<v-select
v-model="BLOCKEDSERVERS"
label="Select"
:items="localServersList"
v-model="BLOCKEDSERVERS"
item-value="id"
item-text="name"
multiple
hint="Blocked Servers"
persistent-hint
></v-select>
/>
</div>
<v-layout row wrap>
<v-flex xs12> </v-flex>
<v-layout
row
wrap
>
<v-flex xs12 />
</v-layout>
<v-divider></v-divider>
<v-divider />
<div class="pt-4 text-xs-left">
<h4 style="text-align:initial">Change display name</h4>
<v-checkbox label="Enabled" v-model="HIDEUSERNAME"></v-checkbox>
<h4 style="text-align:initial">
Change display name
</h4>
<v-checkbox
v-model="HIDEUSERNAME"
label="Enabled"
/>
<v-text-field
v-if="HIDEUSERNAME"
:value="GET_ALTUSERNAME"
@change="SET_ALTUSERNAME"
label="Alternative username"
></v-text-field>
@change="SET_ALTUSERNAME"
/>
<small>By default SyncLounge uses your Plex.tv username when you join a room.</small>
</div>
</div>
@ -39,7 +47,7 @@
import { mapGetters, mapMutations } from 'vuex';
export default {
name: 'plexsettings',
name: 'Plexsettings',
methods: {
...mapMutations('settings', ['SET_HIDEUSERNAME', 'SET_ALTUSERNAME', 'SET_BLOCKEDSERVERS']),
},

View File

@ -1,19 +1,23 @@
<template>
<div style="width:100%; position: relative">
<div style="position: relative" @mouseover="hovered = true" @mouseout="hovered = false">
<div
style="position: relative"
@mouseover="hovered = true"
@mouseout="hovered = false"
>
<videoplayer
v-if="playingMetadata && chosenServer && GET_SLPLAYERQUALITY && ready"
@playerMounted="playerMounted()"
@timelineUpdate="timelineUpdate"
@playbackEnded="stopPlayback()"
:metadata="playingMetadata"
:server="chosenServer"
:src="getSourceByLabel(GET_SLPLAYERQUALITY)"
:initUrl="getSourceByLabel(GET_SLPLAYERQUALITY).initUrl"
:init-url="getSourceByLabel(GET_SLPLAYERQUALITY).initUrl"
:params="getSourceByLabel(GET_SLPLAYERQUALITY).params"
:initialOffset="offset"
:createdAt="playerCreatedAt"
></videoplayer>
:initial-offset="offset"
:created-at="playerCreatedAt"
@playerMounted="playerMounted()"
@timelineUpdate="timelineUpdate"
@playbackEnded="stopPlayback()"
/>
<div v-if="playingMetadata && chosenServer">
<transition name="fade">
<div v-show="hovered">
@ -27,10 +31,17 @@
:src="thumbUrl"
class="elevation-20"
style="height: 80px; width: auto; vertical-align: middle; margin-left: auto; margin-right: auto;"
/>
>
<v-flex class="pl-3">
<v-container class="pa-0" fill-height>
<v-layout column wrap justify-space-apart>
<v-container
class="pa-0"
fill-height
>
<v-layout
column
wrap
justify-space-apart
>
<v-flex>
<h1>{{ getTitle(playingMetadata) }}</h1>
</v-flex>
@ -52,23 +63,36 @@
>
<v-flex class="text-xs-right pa-1">
<div class="hidden-xs-only">
<v-tooltip bottom color="accent" v-if="GET_ME && GET_ME.role !== 'host'">
<v-tooltip
v-if="GET_ME && GET_ME.role !== 'host'"
bottom
color="accent"
>
<v-icon
color="white"
class="clickable"
:disabled="GET_MANUAL_SYNC_QUEUED"
v-on:click="TRIGGER_MANUAL_SYNC"
>compare_arrows</v-icon
@click="TRIGGER_MANUAL_SYNC"
>
compare_arrows
</v-icon>
Manual Sync
</v-tooltip>
<v-icon color="white" class="clickable pl-3" v-on:click="dialog = !dialog"
>settings</v-icon
<v-icon
color="white"
class="clickable pl-3"
@click="dialog = !dialog"
>
settings
</v-icon>
<router-link to="/browse">
<v-icon color="white" class="pl-3" v-on:click.native="stopPlayback()"
>close</v-icon
<v-icon
color="white"
class="pl-3"
@click.native="stopPlayback()"
>
close
</v-icon>
</router-link>
</div>
</v-flex>
@ -79,28 +103,34 @@
class="hoverBar"
style="height: 120px; width: 100%; pointer-events: none; position: absolute; top: 0;"
>
<v-flex xs12> </v-flex>
<v-flex xs12 />
</v-layout>
</div>
</transition>
</div>
<div class="messages-wrapper" v-if="$vuetify.breakpoint.mdAndDown">
<messages></messages>
<div
v-if="$vuetify.breakpoint.mdAndDown"
class="messages-wrapper"
>
<messages />
</div>
<v-dialog v-model="dialog" width="350">
<v-dialog
v-model="dialog"
width="350"
>
<v-card>
<v-card-title>Playback Settings </v-card-title>
<v-card-text>
<v-select
:value="GET_SLPLAYERQUALITY"
@input="changeQuality"
:items="qualitiesSelect"
item-text="text"
item-value="id"
persistent-hint
label="Quality"
hint="Select a different quality"
></v-select>
@input="changeQuality"
/>
<v-select
v-model="chosenAudioTrackIndex"
:select-text="'Default'"
@ -110,30 +140,36 @@
persistent-hint
hint="Select a different audio track"
:items="audioTrackSelect"
></v-select>
/>
<v-select
v-model="chosenSubtitleIndex"
persistent-hint
label="Subtitles"
item-text="text"
item-value="id"
hint="Select a different subtitle track"
v-model="chosenSubtitleIndex"
:select-text="'Default'"
:items="subtitleTrackSelect"
></v-select>
/>
<v-select
v-if="mediaIndexSelect.length > 1"
v-model="chosenMediaIndex"
persistent-hint
item-text="text"
item-value="id"
hint="Select a different version of the content you're playing"
v-model="chosenMediaIndex"
label="Version"
:items="mediaIndexSelect"
></v-select>
/>
</v-card-text>
<v-card-actions>
<v-btn class="blue--text darken-1" text @click.native="dialog = false">Close</v-btn>
<v-btn
class="blue--text darken-1"
text
@click.native="dialog = false"
>
Close
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
@ -144,15 +180,25 @@
class="pa-3 hidden-sm-and-up"
>
<v-flex xs12>
<v-layout row wrap>
<v-layout
row
wrap
>
<img
:src="thumbUrl"
class="elevation-20"
style="height: 80px; width: auto; vertical-align: middle; margin-left: auto; margin-right: auto"
/>
>
<v-flex class="pl-2">
<v-container class="pa-0" fill-height>
<v-layout column wrap justify-space-apart>
<v-container
class="pa-0"
fill-height
>
<v-layout
column
wrap
justify-space-apart
>
<v-flex>
<h1>{{ getTitle(playingMetadata) }}</h1>
</v-flex>
@ -165,27 +211,40 @@
</v-layout>
</v-container>
</v-flex>
<v-layout row wrap class="">
<v-layout
row
wrap
class=""
>
<v-flex xs12>
<v-btn
v-if="GET_ME.role !== 'host'"
block
:disabled="GET_MANUAL_SYNC_QUEUED"
color="blue"
v-on:click.native="TRIGGER_MANUAL_SYNC"
v-if="GET_ME.role !== 'host'"
>Manual sync</v-btn
@click.native="TRIGGER_MANUAL_SYNC"
>
Manual sync
</v-btn>
</v-flex>
<v-flex xs12>
<v-btn block color="primary" v-on:click.native="dialog = !dialog"
>Playback Settings</v-btn
<v-btn
block
color="primary"
@click.native="dialog = !dialog"
>
Playback Settings
</v-btn>
</v-flex>
<v-flex xs12>
<router-link to="/browse">
<v-btn block color="error" v-on:click.native="stopPlayback()"
>Stop playback</v-btn
<v-btn
block
color="error"
@click.native="stopPlayback()"
>
Stop playback
</v-btn>
</router-link>
</v-flex>
</v-layout>
@ -217,108 +276,11 @@ const request = require('request');
const parseXMLString = require('xml2js').parseString;
export default {
name: 'ptplayer',
name: 'Ptplayer',
components: {
videoplayer,
messages,
},
mounted() {
// Check if we have params
if (this.$route.query.start) {
// We need to auto play
const { query } = this.$route;
this.chosenKey = query.key.replace('/library/metadata/', '');
this.chosenMediaIndex = query.mediaIndex || 0;
this.chosenServer = this.getPlex.servers[query.chosenServer];
this.offset = query.playertime;
}
// Similuate a real plex client
this.commandListener = this.eventbus.$on('command', (data) => {
if (this.destroyed) {
return;
}
if (data.command === '/player/timeline/poll') {
const key = this.chosenKey;
let ratingKey = null;
if (key) {
ratingKey = `/library/metadata/${key}`;
}
let machineIdentifier = null;
if (this.chosenServer) {
machineIdentifier = this.chosenServer.clientIdentifier;
}
const playerdata = {
key,
ratingKey,
time: this.playertime,
type: 'video',
machineIdentifier,
duration: this.playerduration,
state: this.playerstatus,
};
this.lastSentTimeline = playerdata;
if (this.playertime) {
this.eventbus.$emit('ptplayer-poll', (err, time) => {
if (err) {
return data.callback(this.playertime);
}
// let difference = Math.abs(time - this.playertime)
// console.log('Poll time was out by', difference)
playerdata.time = time;
this.playertime = time;
data.callback(playerdata);
});
} else {
data.callback(playerdata);
}
return;
}
if (data.command === '/player/playback/play') {
this.eventbus.$emit('player-press-play', (res) => data.callback(res));
return;
}
if (data.command === '/player/playback/pause') {
this.eventbus.$emit('player-press-pause', (res) => data.callback(res));
return;
}
if (data.command === '/player/playback/playMedia') {
this.chosenKey = data.params.key.replace('/library/metadata/', '');
this.chosenMediaIndex = data.params.mediaIndex || 0;
this.chosenServer = this.getPlex.servers[data.params.machineIdentifier];
this.playertime = data.params.offset || this.$route.query.playertime || 0;
this.offset = this.playertime;
this.$nextTick(() => {
this.changedPlaying(true);
});
return true;
}
if (data.command === '/player/playback/stop') {
this.ready = false;
this.chosenKey = null;
this.chosenServer = null;
this.playerduration = null;
this.playertime = 0;
this.bufferedTile = null;
this.playingMetadata = null;
this.$router.push('/browse');
return data.callback(true);
}
if (data.command === '/player/playback/seekTo') {
this.eventbus.$emit('player-seek', {
time: data.params.offset,
soft: data.params.softSeek,
callback: async (promise) => {
await promise.catch(() => {
data.callback(false);
});
data.callback(true);
},
});
}
});
},
data() {
return {
hovered: false,
@ -444,6 +406,103 @@ export default {
});
},
},
mounted() {
// Check if we have params
if (this.$route.query.start) {
// We need to auto play
const { query } = this.$route;
this.chosenKey = query.key.replace('/library/metadata/', '');
this.chosenMediaIndex = query.mediaIndex || 0;
this.chosenServer = this.getPlex.servers[query.chosenServer];
this.offset = query.playertime;
}
// Similuate a real plex client
this.commandListener = this.eventbus.$on('command', (data) => {
if (this.destroyed) {
return;
}
if (data.command === '/player/timeline/poll') {
const key = this.chosenKey;
let ratingKey = null;
if (key) {
ratingKey = `/library/metadata/${key}`;
}
let machineIdentifier = null;
if (this.chosenServer) {
machineIdentifier = this.chosenServer.clientIdentifier;
}
const playerdata = {
key,
ratingKey,
time: this.playertime,
type: 'video',
machineIdentifier,
duration: this.playerduration,
state: this.playerstatus,
};
this.lastSentTimeline = playerdata;
if (this.playertime) {
this.eventbus.$emit('ptplayer-poll', (err, time) => {
if (err) {
return data.callback(this.playertime);
}
// let difference = Math.abs(time - this.playertime)
// console.log('Poll time was out by', difference)
playerdata.time = time;
this.playertime = time;
data.callback(playerdata);
});
} else {
data.callback(playerdata);
}
return;
}
if (data.command === '/player/playback/play') {
this.eventbus.$emit('player-press-play', (res) => data.callback(res));
return;
}
if (data.command === '/player/playback/pause') {
this.eventbus.$emit('player-press-pause', (res) => data.callback(res));
return;
}
if (data.command === '/player/playback/playMedia') {
this.chosenKey = data.params.key.replace('/library/metadata/', '');
this.chosenMediaIndex = data.params.mediaIndex || 0;
this.chosenServer = this.getPlex.servers[data.params.machineIdentifier];
this.playertime = data.params.offset || this.$route.query.playertime || 0;
this.offset = this.playertime;
this.$nextTick(() => {
this.changedPlaying(true);
});
return true;
}
if (data.command === '/player/playback/stop') {
this.ready = false;
this.chosenKey = null;
this.chosenServer = null;
this.playerduration = null;
this.playertime = 0;
this.bufferedTile = null;
this.playingMetadata = null;
this.$router.push('/browse');
return data.callback(true);
}
if (data.command === '/player/playback/seekTo') {
this.eventbus.$emit('player-seek', {
time: data.params.offset,
soft: data.params.softSeek,
callback: async (promise) => {
await promise.catch(() => {
data.callback(false);
});
data.callback(true);
},
});
}
});
},
computed: {
...mapGetters(['getPlex', 'getChosenClient', 'GET_MANUAL_SYNC_QUEUED', 'GET_ME']),
...mapGetters('settings', ['GET_SLPLAYERQUALITY', 'GET_SLPLAYERFORCETRANSCODE']),

View File

@ -3,6 +3,8 @@
<videojs-player
ref="videoPlayer"
:options="playerOptions"
style="background-color:transparent !important;"
class="ptplayer"
@play="onPlayerPlay($event)"
@pause="onPlayerPause($event)"
@loadeddata="onPlayerLoadeddata($event)"
@ -17,11 +19,11 @@
@statechanged="playerStateChanged($event)"
@volumechange="volumeChange($event)"
@ready="playerReadied($event)"
style="background-color:transparent !important;"
class="ptplayer"
/>
<div
v-if="!src"
class="center"
>
</videojs-player>
<div class="center" v-if="!src">
Waiting...
</div>
</div>
@ -35,7 +37,6 @@ const request = require('request');
export default {
props: ['server', 'metadata', 'initialOffset', 'src', 'initUrl', 'stopUrl', 'params', 'sources'],
created() {},
data() {
return {
eventbus: window.EventBus,
@ -56,6 +57,7 @@ export default {
playbackRate: 1,
};
},
created() {},
mounted() {
this.source = this.src;
this.initReqSent = true;

View File

@ -1,51 +1,88 @@
<template>
<div>
<div style="text-align:center">
<h4 style="text-align:initial">Plex Client Polling Interval</h4>
<h4 style="text-align:initial">
Plex Client Polling Interval
</h4>
<div> {{ GET_CLIENTPOLLINTERVAL }}</div>
<v-slider class="pa-0 ma-0" :value="GET_CLIENTPOLLINTERVAL" @change="SET_CLIENTPOLLINTERVAL" :min="100" :max="10000"
<v-slider
class="pa-0 ma-0"
:value="GET_CLIENTPOLLINTERVAL"
:min="100"
:max="10000"
hint="Sets how frequently SyncLounge will poll external plex clients for new information in milliseconds. Default is 1000ms (1 second)"
persistent-hint
>
</v-slider>
@change="SET_CLIENTPOLLINTERVAL"
/>
</div>
<v-divider></v-divider>
<div style="text-align:center" class="pt-4">
<h4 style="text-align:initial">Sync Flexibility</h4>
<v-divider />
<div
style="text-align:center"
class="pt-4"
>
<h4 style="text-align:initial">
Sync Flexibility
</h4>
<div> {{ GET_SYNCFLEXIBILITY }}</div>
<v-slider class="pa-0 ma-0" :value="GET_SYNCFLEXIBILITY" @change="SET_SYNCFLEXIBILITY" :min="0" :max="10000"
<v-slider
class="pa-0 ma-0"
:value="GET_SYNCFLEXIBILITY"
:min="0"
:max="10000"
hint="Sets the acceptable distance away from the host in milliseconds. Default is 3000ms (3 seconds)."
persistent-hint
>
</v-slider>
@change="SET_SYNCFLEXIBILITY"
/>
</div>
<v-divider></v-divider>
<div style="text-align:center" class="pt-4">
<h4 style="text-align:initial">Syncing Method</h4>
<v-divider />
<div
style="text-align:center"
class="pt-4"
>
<h4 style="text-align:initial">
Syncing Method
</h4>
<v-radio-group v-model="syncmode">
<v-radio label="Clean Seek" class="pa-0 ma-0" value="cleanseek"></v-radio>
<v-radio label="Skip Ahead" class="pa-0 ma-0" value="skipahead"
persistent-hint
hint="Sets the syncing method used when we need to get back in line with the host."
></v-radio>
<v-radio
label="Clean Seek"
class="pa-0 ma-0"
value="cleanseek"
/>
<v-radio
label="Skip Ahead"
class="pa-0 ma-0"
value="skipahead"
persistent-hint
hint="Sets the syncing method used when we need to get back in line with the host."
/>
</v-radio-group>
</div>
<div style="text-align:center" class="pt-4">
<h4 style="text-align:initial">Autoplay</h4>
<div
style="text-align:center"
class="pt-4"
>
<h4 style="text-align:initial">
Autoplay
</h4>
<v-switch
label="Enabled"
hint="If enabled SyncLounge will attempt to automatically play the same content as the host."
:input-value="GET_AUTOPLAY"
@change="SET_AUTOPLAY"
></v-switch>
/>
</div>
<div style="text-align:center" class="pt-4">
<h4 style="text-align:initial">SLPlayer Force Transcode</h4>
<v-switch
label="Enabled"
:input-value="GET_SLPLAYERFORCETRANSCODE"
@change="SET_SLPLAYERFORCETRANSCODE"
></v-switch>
<div
style="text-align:center"
class="pt-4"
>
<h4 style="text-align:initial">
SLPlayer Force Transcode
</h4>
<v-switch
label="Enabled"
:input-value="GET_SLPLAYERFORCETRANSCODE"
@change="SET_SLPLAYERFORCETRANSCODE"
/>
<small>WARNING: EXPERIMENTAL SETTING! DO NOT CHANGE IF YOU DO NOT UNDERSTAND THE RAMIFICATIONS.</small>
</div>
</div>
@ -55,7 +92,7 @@
import { mapGetters, mapMutations } from 'vuex';
export default {
name: 'settings',
name: 'Settings',
computed: {
...mapGetters('settings', [
'GET_AUTOPLAY',

View File

@ -1,22 +1,46 @@
<template>
<v-row justify="center">
<v-col lg="8" style="background: rgba(0,0,0,0.1); border-radius: 10px" class="pa-4">
<v-col
lg="8"
style="background: rgba(0,0,0,0.1); border-radius: 10px"
class="pa-4"
>
<v-row justify="center">
<v-col md="8" lg="4">
<img style="width:100%" :src="logo" />
<v-col
md="8"
lg="4"
>
<img
style="width:100%"
:src="logo"
>
</v-col>
</v-row>
<v-stepper
style="background: rgba(0,0,0,0.3); color: white !important; border-radius: 20px"
v-model="e1"
style="background: rgba(0,0,0,0.3); color: white !important; border-radius: 20px"
class="ma-4"
>
<v-stepper-header dark>
<v-stepper-step step="1" dark :complete="true">Select a client</v-stepper-step>
<v-divider></v-divider>
<v-stepper-step step="2" dark :complete="false">Join a room</v-stepper-step>
<v-divider></v-divider>
<v-stepper-step step="3">Sync</v-stepper-step>
<v-stepper-step
step="1"
dark
:complete="true"
>
Select a client
</v-stepper-step>
<v-divider />
<v-stepper-step
step="2"
dark
:complete="false"
>
Join a room
</v-stepper-step>
<v-divider />
<v-stepper-step step="3">
Sync
</v-stepper-step>
</v-stepper-header>
</v-stepper>
<div v-if="!getChosenClient">
@ -26,41 +50,79 @@
</v-col>
</v-row>
<v-row class="ml-4">
<v-col class="pt-0"
>Choose a client from the list below. Once you've found the client you would like to
use, click the connect button. SyncLounge will test to see if it can connect with the
client and will let you know if it cannot.</v-col
<v-col
class="pt-0"
>
Choose a client from the list below. Once you've found the client you would like to
use, click the connect button. SyncLounge will test to see if it can connect with the
client and will let you know if it cannot.
</v-col>
</v-row>
<div v-if="plex && !plex.gotDevices" class="text-center pa-4">
<v-progress-circular indeterminate color="primary"></v-progress-circular>
<div
v-if="plex && !plex.gotDevices"
class="text-center pa-4"
>
<v-progress-circular
indeterminate
color="primary"
/>
</div>
<v-row v-else justify="center" class="ml-4 mr-4">
<v-col md="6" lg="6" v-if="!doReverse">
<v-row
v-else
justify="center"
class="ml-4 mr-4"
>
<v-col
v-if="!doReverse"
md="6"
lg="6"
>
<v-subheader>
Plex Players {{ playercount }}
<v-icon @click="PLEX_GET_DEVICES()" class="pl-2" small>refresh</v-icon>
<v-icon
class="pl-2"
small
@click="PLEX_GET_DEVICES()"
>
refresh
</v-icon>
</v-subheader>
<v-list dense style="background: none">
<v-list
dense
style="background: none"
>
<plexclient
v-for="i in recentClients"
:key="i.clientIdentifier"
:selected="isClientSelected(i)"
:object="i"
style="cursor: pointer"
@click.native="
previewClient(i);
gotResponse = true;
"
:selected="isClientSelected(i)"
:object="i"
style="cursor: pointer"
></plexclient>
/>
</v-list>
</v-col>
<v-col md="6" lg="6">
<div v-if="testClient" class="pa-2">
<v-col
md="6"
lg="6"
>
<div
v-if="testClient"
class="pa-2"
>
<v-subheader>Selected Player</v-subheader>
<v-row>
<v-col md="3" class="text-center" style="position: relative">
<img :src="url" style="height: 100px; width: auto; vertical-align: middle" />
<v-col
md="3"
class="text-center"
style="position: relative"
>
<img
:src="url"
style="height: 100px; width: auto; vertical-align: middle"
>
</v-col>
<v-col md="9">
<div class="selected-player-details pl-1">
@ -81,7 +143,10 @@
<label>Platform</label>
<span style="opacity:0.8">{{ testClient.platform }}</span>
</div>
<div class="red--text text--lighten-1" v-if="testClientErrorMsg">
<div
v-if="testClientErrorMsg"
class="red--text text--lighten-1"
>
{{ testClientErrorMsg }}
</div>
</div>
@ -89,20 +154,32 @@
</v-row>
<v-row class="pt-2">
<v-col>
<div v-if="!gotResponse" class="center spinner-orange">
<div
v-if="!gotResponse"
class="center spinner-orange"
>
<div style="width:100%;text-align:center">
<v-progress-circular
indeterminate
v-bind:size="50"
:size="50"
class="amber--text"
style="display:inline-block"
></v-progress-circular>
/>
</div>
</div>
<div v-if="gotResponse">
<v-btn block color="primary" v-on:click.native="clientClicked()">Connect</v-btn>
<v-btn
block
color="primary"
@click.native="clientClicked()"
>
Connect
</v-btn>
</div>
<div v-if="testClient.product.indexOf('Web') > -1" class="warning--text">
<div
v-if="testClient.product.indexOf('Web') > -1"
class="warning--text"
>
Note: Plex Web is currently not supported.
</div>
<div
@ -135,20 +212,27 @@
</v-row>
</div>
</v-col>
<v-col md="6" lg="7" v-if="doReverse">
<v-col
v-if="doReverse"
md="6"
lg="7"
>
<v-subheader>Plex Players {{ playercount }}</v-subheader>
<v-list dense style="background: none">
<v-list
dense
style="background: none"
>
<plexclient
v-for="i in recentClients"
:key="i.clientIdentifier"
:selected="isClientSelected(i)"
:object="i"
style="cursor: pointer"
@click.native="
previewClient(i);
gotResponse = true;
"
:selected="isClientSelected(i)"
:object="i"
style="cursor: pointer"
></plexclient>
/>
</v-list>
</v-col>
</v-row>
@ -164,8 +248,11 @@ import { mapActions, mapGetters, mapState } from 'vuex';
import plexclient from './plexclient.vue';
export default {
name: 'Walkthrough',
components: {
plexclient,
},
props: ['object'],
name: 'walkthrough',
data() {
return {
testClient: null,
@ -212,9 +299,6 @@ export default {
},
};
},
components: {
plexclient,
},
computed: {
...mapState(['plex']),
...mapGetters(['getChosenClient']),

View File

@ -1,50 +1,94 @@
<template>
<v-layout wrap row class="text-center" justify-center align-center>
<v-flex v-if="!loading" xs12 md5>
<v-card style="background: rgba(0,0,0,0.3)" class="pa-1">
<v-container fill-height>
<v-layout row wrap>
<v-flex xs12 md3 class="text-center">
<img :src="logos.light.small" style="width: 90%" />
</v-flex>
<v-flex md9>
<h1 class="white--text pa-1"> Welcome to SyncLounge!</h1>
<div class="pt-2">
<div>
<span style="font-weight:900">{{ owner }}</span> has invited you to join the room
<span style="font-weight:900">{{ room }}</span>
</div>
<v-layout
wrap
row
class="text-center"
justify-center
align-center
>
<v-flex
v-if="!loading"
xs12
md5
>
<v-card
style="background: rgba(0,0,0,0.3)"
class="pa-1"
>
<v-container fill-height>
<v-layout
row
wrap
>
<v-flex
xs12
md3
class="text-center"
>
<img
:src="logos.light.small"
style="width: 90%"
>
</v-flex>
<v-flex md9>
<h1 class="white--text pa-1">
Welcome to SyncLounge!
</h1>
<div class="pt-2">
<div>
<span style="font-weight:900">{{ owner }}</span> has invited you to join the room
<span style="font-weight:900">{{ room }}</span>
</div>
<v-layout wrap row class="pa-4 pt-2" justify-center align-center>
<v-flex xs12 md8 class="text-center">
<v-btn class="center" style="background-color: #E5A00D" @click.native="letsGo()">Accept Invite</v-btn>
</v-flex>
</v-layout>
</div>
<v-layout
wrap
row
class="pa-4 pt-2"
justify-center
align-center
>
<v-flex
xs12
md8
class="text-center"
>
<v-btn
class="center"
style="background-color: #E5A00D"
@click.native="letsGo()"
>
Accept Invite
</v-btn>
</v-flex>
</v-layout>
</v-container>
<v-divider></v-divider>
<p style="opacity:0.7" class="text-center pt-3">
SyncLounge is a tool to sync Plex content with your family and friends. For more info click <a target="_blank" href="https://github.com/samcm/synclounge"> here</a>.
</p>
</v-card>
</v-flex>
<v-flex v-else>
<v-progress-circular indeterminate color="primary"></v-progress-circular>
</v-flex>
</v-layout>
</v-flex>
</v-layout>
</v-container>
<v-divider />
<p
style="opacity:0.7"
class="text-center pt-3"
>
SyncLounge is a tool to sync Plex content with your family and friends. For more info click <a
target="_blank"
href="https://github.com/samcm/synclounge"
> here</a>.
</p>
</v-card>
</v-flex>
<v-flex v-else>
<v-progress-circular
indeterminate
color="primary"
/>
</v-flex>
</v-layout>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
name: 'join',
mounted() {
this.password = this.$route.query.password || '';
this.room = this.$route.query.room;
this.server = this.$route.query.server;
this.owner = this.$route.query.owner;
},
name: 'Join',
data() {
return {
server: null,
@ -62,6 +106,12 @@ export default {
}
},
},
mounted() {
this.password = this.$route.query.password || '';
this.room = this.$route.query.room;
this.server = this.$route.query.server;
this.owner = this.$route.query.owner;
},
computed: {
...mapGetters('settings', [
'GET_PLEX_AUTH_TOKEN',

View File

@ -1,33 +1,84 @@
<template>
<v-flex xs12>
<v-layout row wrap justify-start align-center v-if="message.user.username === 'You'">
<v-flex xs10 class="pr-2 ml-3 mb-1">
<!-- <v-flex><span style="opacity:0.6;font-size:60%; float:right"> {{ message.time}}</span></v-flex> -->
<div class="pt-1 pb-1 pl-2 pr-2 messageBubble me" style="">{{ message.msg }}</div>
<v-layout
v-if="message.user.username === 'You'"
row
wrap
justify-start
align-center
>
<v-flex
xs10
class="pr-2 ml-3 mb-1"
>
<!-- <v-flex><span style="opacity:0.6;font-size:60%; float:right"> {{ message.time}}</span></v-flex> -->
<div
class="pt-1 pb-1 pl-2 pr-2 messageBubble me"
style=""
>
{{ message.msg }}
</div>
</v-flex>
<v-flex xs1 class="text-center pt-1">
<v-tooltip bottom color="rgb(44, 44, 49)" multi-line>
<v-flex
xs1
class="text-center pt-1"
>
<v-tooltip
bottom
color="rgb(44, 44, 49)"
multi-line
>
<span slot="activator">
<img :src="message.user.thumb || message.user.avatarUrl" class="mt-2 messageAvatar" />
<img
:src="message.user.thumb || message.user.avatarUrl"
class="mt-2 messageAvatar"
>
</span>
{{ message.time }}
</v-tooltip>
</v-flex>
</v-layout>
<v-layout row wrap justify-start align-center v-else>
<v-flex xs1 class="text-center pt-1 mr-2 ml-2">
<v-tooltip bottom color="rgb(44, 44, 49)" multi-line>
<v-layout
v-else
row
wrap
justify-start
align-center
>
<v-flex
xs1
class="text-center pt-1 mr-2 ml-2"
>
<v-tooltip
bottom
color="rgb(44, 44, 49)"
multi-line
>
<span slot="activator">
<img :src="message.user.thumb || message.user.avatarUrl" class="mt-2 messageAvatar" />
<img
:src="message.user.thumb || message.user.avatarUrl"
class="mt-2 messageAvatar"
>
</span>
{{ message.time }}
</v-tooltip>
</v-flex>
<v-flex xs10 class="pr-2 mr-2 mb-2">
<!-- <v-flex><span style="opacity:0.6;font-size:60%; float:right"> {{ message.time}}</span></v-flex> -->
<b class="pl-1" style="opacity:1;font-size:60%; float:left"> {{ message.user.username }}</b>
<v-flex
xs10
class="pr-2 mr-2 mb-2"
>
<!-- <v-flex><span style="opacity:0.6;font-size:60%; float:right"> {{ message.time}}</span></v-flex> -->
<b
class="pl-1"
style="opacity:1;font-size:60%; float:left"
> {{ message.user.username }}</b>
<div class="pt-1 pb-1 pl-2 pr-2 messageBubble" style="">{{ message.msg }}</div>
<div
class="pt-1 pb-1 pl-2 pr-2 messageBubble"
style=""
>
{{ message.msg }}
</div>
</v-flex>
</v-layout>
</v-flex>

View File

@ -1,23 +1,43 @@
<template>
<v-layout row wrap style="height: 100%;">
<v-flex xs12 style="height: calc(100% - 96px)">
<v-divider class="hidden-md-and-down"></v-divider>
<v-subheader class="md-4">Chat</v-subheader>
<v-layout row wrap id="chatbox" v-if="messages.length > 0" style="max-height: calc(100% - 32px); overflow-y: scroll">
<message :message="msg" :id="getMsgId(msg)" v-for="(msg, index) in messages" :key="index"></message>
<v-layout
row
wrap
style="height: 100%;"
>
<v-flex
xs12
style="height: calc(100% - 96px)"
>
<v-divider class="hidden-md-and-down" />
<v-subheader class="md-4">
Chat
</v-subheader>
<v-layout
v-if="messages.length > 0"
id="chatbox"
row
wrap
style="max-height: calc(100% - 32px); overflow-y: scroll"
>
<message
v-for="(msg, index) in messages"
:id="getMsgId(msg)"
:key="index"
:message="msg"
/>
</v-layout>
</v-flex>
<v-flex xs12>
<v-text-field
v-model="messageToBeSent"
append-outer-icon="send"
@click:append-outer="sendMessage()"
:label="chatboxLabel"
hide-details
single-line
class="ml-2 mr-2 pr-1"
v-on:keyup.enter.native="sendMessage()"
v-model="messageToBeSent"
></v-text-field>
@click:append-outer="sendMessage()"
@keyup.enter.native="sendMessage()"
/>
<!-- <v-btn block color="primary" @click="sendMessage()" :disabled="messageToBeSent.length === 0">Send<v-icon right>send</v-icon></v-btn> -->
</v-flex>
</v-layout>
@ -38,6 +58,14 @@ export default {
messageToBeSent: '',
};
},
computed: {
messages() {
return this.$store.getters.getMessages;
},
chatboxLabel() {
return 'Message';
},
},
watch: {
messages() {
const options = {
@ -50,14 +78,6 @@ export default {
this.$scrollTo('#lastMessage', 5, options);
},
},
computed: {
messages() {
return this.$store.getters.getMessages;
},
chatboxLabel() {
return 'Message';
},
},
methods: {
getMsgId(msg) {
if (this.messages && msg === this.messages[this.messages.length - 1]) {

View File

@ -1,65 +1,116 @@
<template>
<v-row class="pt-2 pa-4" justify="center">
<v-row
class="pt-2 pa-4"
justify="center"
>
<v-col md="8">
<v-card style="background: rgba(0,0,0,0.3)" class="pa-4">
<v-layout row wrap justify-center align-center v-if="ready">
<v-flex xs12 sm8 lg4>
<v-card
style="background: rgba(0,0,0,0.3)"
class="pa-4"
>
<v-layout
v-if="ready"
row
wrap
justify-center
align-center
>
<v-flex
xs12
sm8
lg4
>
<h1 class="text-center pa-2">
Hello
<span style="font-weight: 700">{{ plex.user.username }}</span
>!
<span style="font-weight: 700">{{ plex.user.username }}</span>!
</h1>
<p>
Would you like to change your display name when using SyncLounge? By default your
Plex.tv username will be used. You can always change this setting later.
</p>
<v-checkbox
v-model="HIDEUSERNAME"
class="pt-2"
label="Change my display name"
v-model="HIDEUSERNAME"
></v-checkbox>
/>
<v-text-field
v-if="HIDEUSERNAME"
:value="GET_ALTUSERNAME"
@change="SET_ALTUSERNAME"
label="Alternative display name"
></v-text-field>
@change="SET_ALTUSERNAME"
/>
<div class="text-xs-right">
<v-btn @click="letsGo" color="primary">Get started</v-btn>
<v-btn
color="primary"
@click="letsGo"
>
Get started
</v-btn>
</div>
</v-flex>
</v-layout>
<div v-else>
<h1 v-if="!GET_PLEX_AUTH_TOKEN" class="center-text pa-4">
<h1
v-if="!GET_PLEX_AUTH_TOKEN"
class="center-text pa-4"
>
To use SyncLounge you need to sign in with your Plex account.
</h1>
<div v-if="!preAuth || checkingAuth">
<v-layout wrap row style="position:relative">
<v-flex xs12 md4 offset-md4>
<v-layout
wrap
row
style="position:relative"
>
<v-flex
xs12
md4
offset-md4
>
<div style="width:100%;text-align:center">
<v-progress-circular
indeterminate
v-bind:size="50"
:size="50"
class="amber--text"
style="display:inline-block"
></v-progress-circular>
/>
</div>
</v-flex>
</v-layout>
</div>
<div v-if="preAuth && !checkingAuth && !authError" class="text-center">
<v-btn class="primary" @click="openPopup()">Sign in with Plex</v-btn>
<div
v-if="preAuth && !checkingAuth && !authError"
class="text-center"
>
<v-btn
class="primary"
@click="openPopup()"
>
Sign in with Plex
</v-btn>
</div>
<div v-if="authError" class="text-center error">
<div
v-if="authError"
class="text-center error"
>
<p>You are not authorized to access this server</p>
</div>
<v-row justify="center" class="pt-4 pa-2">
<v-col md="8" class="center-text">
<v-row
justify="center"
class="pt-4 pa-2"
>
<v-col
md="8"
class="center-text"
>
<p style="opacity:0.7">
Your Plex account is used to fetch the details of your Plex devices. None of your
private details are sent to our servers. If you would like to install and run
SyncLounge yourself have a look
<a target="_blank" href="https://github.com/samcm/SyncLounge">here</a>
<a
target="_blank"
href="https://github.com/samcm/SyncLounge"
>here</a>
for details.
</p>
</v-col>
@ -75,7 +126,7 @@ import axios from 'axios';
import { mapActions, mapGetters, mapMutations } from 'vuex';
export default {
name: 'signin',
name: 'Signin',
data() {
return {
pin: null,

View File

@ -1,7 +1,16 @@
<template>
<v-layout wrap row>
<v-flex xs12 md8 offset-md2>
<h1 class="white--text center-text pa-4">Successfully signed out</h1>
<v-layout
wrap
row
>
<v-flex
xs12
md8
offset-md2
>
<h1 class="white--text center-text pa-4">
Successfully signed out
</h1>
</v-flex>
</v-layout>
</template>
@ -10,7 +19,7 @@
import { mapMutations } from 'vuex';
export default {
name: 'signout',
name: 'Signout',
mounted() {
this.SET_PLEX_AUTH_TOKEN(null);
this.$store.state.plex = null;

View File

@ -2,39 +2,85 @@
<v-card>
<v-card-title class="title">
Donate
<v-spacer></v-spacer>
<img :src="logos.light.small" style="height: 50px"/>
<v-spacer />
<img
:src="logos.light.small"
style="height: 50px"
>
</v-card-title>
<v-divider></v-divider>
<v-divider />
<v-card-text>
<p class="pa-2"> All donations to SyncLounge go directly towards running the SyncLounge public servers and the continued development of the application. </p>
<p class="pa-2">
All donations to SyncLounge go directly towards running the SyncLounge public servers and the continued development of the application.
</p>
<v-subheader> How to donate </v-subheader>
<v-layout row justify-center align-center class="pa-0 ma-1">
<v-flex xs4 class="text-center">
<v-btn block color="primary" class="white--text" target="_blank" href="https://paypal.me/PlexTogether">
Paypal
</v-btn>
</v-flex>
</v-layout>
<v-layout
row
justify-center
align-center
class="pa-0 ma-1"
>
<v-flex
xs4
class="text-center"
>
<v-btn
block
color="primary"
class="white--text"
target="_blank"
href="https://paypal.me/PlexTogether"
>
Paypal
</v-btn>
</v-flex>
</v-layout>
<div class="text-center pa-2">
<v-layout row justify-center align-center v-for="(address, coin) in addresses" :key="coin" class="pa-0 ma-1">
<v-flex xs2 style="font-weight: 600">
<v-layout
v-for="(address, coin) in addresses"
:key="coin"
row
justify-center
align-center
class="pa-0 ma-1"
>
<v-flex
xs2
style="font-weight: 600"
>
{{ coin }}
</v-flex>
<v-flex xs8>
{{ address }}
</v-flex>
<v-flex xs2 class="text-center">
<v-icon v-clipboard="address" v-on:click.native="sendNotification()" class="mr-2 primary--text click-cursor">content_copy</v-icon>
<v-flex
xs2
class="text-center"
>
<v-icon
v-clipboard="address"
class="mr-2 primary--text click-cursor"
@click.native="sendNotification()"
>
content_copy
</v-icon>
</v-flex>
</v-layout>
</div>
<v-divider></v-divider>
<p class="pa-2 soft-text mb-0 pb-0" >If you make a donation, stop by the Discord and message samcm#2715 to get your Donator role. Thankyou!</p>
<v-divider />
<p class="pa-2 soft-text mb-0 pb-0">
If you make a donation, stop by the Discord and message samcm#2715 to get your Donator role. Thankyou!
</p>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="primary" flat @click.stop="onClose()">Close</v-btn>
<v-spacer />
<v-btn
color="primary"
flat
@click.stop="onClose()"
>
Close
</v-btn>
</v-card-actions>
</v-card>
</template>

View File

@ -3,24 +3,34 @@
app
temporary
:value="isLeftSidebarOpen"
@input="SET_LEFT_SIDEBAR_OPEN"
disable-route-watcher
@input="SET_LEFT_SIDEBAR_OPEN"
>
<v-list-item v-if="plex && plex.user">
<v-list-item-avatar>
<img class="pa-1" :src="plex.user.thumb" />
<img
class="pa-1"
:src="plex.user.thumb"
>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title style="font-weight: bold">{{ plex.user.username }}</v-list-item-title>
<v-list-item-title style="font-weight: bold">
{{ plex.user.username }}
</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-divider></v-divider>
<v-divider />
<v-list dense nav>
<v-list
dense
nav
>
<v-subheader>Preferences</v-subheader>
<v-list-item @click.stop="ptsettingstoggle = !ptsettingstoggle">
<v-list-item-icon>
<v-icon color="white">settings</v-icon>
<v-icon color="white">
settings
</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>SyncLounge Settings</v-list-item-title>
@ -32,17 +42,26 @@
@click.stop="plexsettingstoggle = !plexsettingstoggle"
>
<v-list-item-icon>
<v-icon color="white">settings</v-icon>
<v-icon color="white">
settings
</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>Plex Settings</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-subheader v-if="plex && plex.gotDevices">Account</v-subheader>
<v-list-item :router="true" to="/signout">
<v-subheader v-if="plex && plex.gotDevices">
Account
</v-subheader>
<v-list-item
:router="true"
to="/signout"
>
<v-list-item-icon>
<v-icon color="white">cancel</v-icon>
<v-icon color="white">
cancel
</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>Sign out</v-list-item-title>
@ -50,27 +69,42 @@
</v-list-item>
<v-subheader>About</v-subheader>
<v-list-item href="https://synclounge.tv/" target="_blank">
<v-list-item
href="https://synclounge.tv/"
target="_blank"
>
<v-list-item-icon>
<v-icon color="white">info</v-icon>
<v-icon color="white">
info
</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>SyncLounge v{{appVersion}}</v-list-item-title>
<v-list-item-title>SyncLounge v{{ appVersion }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item href="https://discord.gg/fKQB3yt" target="_blank">
<v-list-item
href="https://discord.gg/fKQB3yt"
target="_blank"
>
<v-list-item-icon>
<v-icon color="white">chat</v-icon>
<v-icon color="white">
chat
</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>Discord</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item href="https://github.com/samcm/synclounge" target="_blank">
<v-list-item
href="https://github.com/samcm/synclounge"
target="_blank"
>
<v-list-item-icon>
<v-icon color="white">code</v-icon>
<v-icon color="white">
code
</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>GitHub</v-list-item-title>
@ -79,44 +113,70 @@
<v-list-item @click.stop="donateDialog = true">
<v-list-item-icon>
<v-icon color="white">favorite</v-icon>
<v-icon color="white">
favorite
</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>Donate</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-spacer></v-spacer>
<v-spacer />
</v-list>
<template v-slot:append>
<v-divider></v-divider>
<div class="text-center pa-2" style="opacity: 0.7; font-size: 12px">
<v-divider />
<div
class="text-center pa-2"
style="opacity: 0.7; font-size: 12px"
>
<div>Build #{{ hash }}</div>
<div>Last updated {{ updatedAt }}</div>
</div>
</template>
<v-dialog v-model="ptsettingstoggle" width="350">
<v-card style="background-color: #151924" class="pa-3">
<v-dialog
v-model="ptsettingstoggle"
width="350"
>
<v-card
style="background-color: #151924"
class="pa-3"
>
<div class="text-center">
<h2>SyncLounge Settings</h2>
</div>
<v-divider class="mt-2 mb-2"></v-divider>
<ptsettings class="darken-4 pa-1"></ptsettings>
<v-divider class="mt-2 mb-2" />
<ptsettings class="darken-4 pa-1" />
</v-card>
</v-dialog>
<v-dialog v-model="plexsettingstoggle" width="350">
<v-card style="background-color: #151924" class="pa-3">
<v-dialog
v-model="plexsettingstoggle"
width="350"
>
<v-card
style="background-color: #151924"
class="pa-3"
>
<div class="text-center">
<h2>Plex Settings</h2>
</div>
<v-divider class="mt-2 mb-2"></v-divider>
<plexsettings class="darken-4 pa-1" v-if="validPlex && plex.gotDevices"></plexsettings>
<v-divider class="mt-2 mb-2" />
<plexsettings
v-if="validPlex && plex.gotDevices"
class="darken-4 pa-1"
/>
</v-card>
</v-dialog>
<v-dialog v-model="donateDialog" max-width="650px">
<donate :donateDialog="donateDialog" :onClose="() => this.donateDialog = false"></donate>
<v-dialog
v-model="donateDialog"
max-width="650px"
>
<donate
:donate-dialog="donateDialog"
:on-close="() => this.donateDialog = false"
/>
</v-dialog>
</v-navigation-drawer>
</template>

View File

@ -59,12 +59,6 @@ window.EventBus.$on('command', (data) => {
});
Vue.mixin({
methods: {
sinceNow(x) {
const time = moment(x);
return time.fromNow();
},
},
computed: {
appVersion() {
return this.$store.getters.appVersion;
@ -115,6 +109,12 @@ Vue.mixin({
};
},
},
methods: {
sinceNow(x) {
const time = moment(x);
return time.fromNow();
},
},
});
router.beforeEach((to, from, next) => {

View File

@ -1,12 +1,34 @@
<template>
<router-link :to="href">
<v-card color="blue darken-4" hover style="height: 100%; width: 230px">
<v-layout row wrap justify-center align-center>
<v-flex md2 class="hidden-xs-only text-center">
<img :src="thumb" style="height: 52px; vertical-align: middle" />
<v-card
color="blue darken-4"
hover
style="height: 100%; width: 230px"
>
<v-layout
row
wrap
justify-center
align-center
>
<v-flex
md2
class="hidden-xs-only text-center"
>
<img
:src="thumb"
style="height: 52px; vertical-align: middle"
>
</v-flex>
<v-flex md10 xs12 class="pl-3 pa-1 text-xs-left" style="overflow: hidden; white-space: nowrap; line-height: 24px">
<div style="font-size: 18px">Now Playing</div>
<v-flex
md10
xs12
class="pl-3 pa-1 text-xs-left"
style="overflow: hidden; white-space: nowrap; line-height: 24px"
>
<div style="font-size: 18px">
Now Playing
</div>
<div><small><b>{{ getTitle }}</b> - {{ getUnder }}</small></div>
<!-- <div class="hidden-xs-only soft-text" style="font-size: 12px">Click for more info</div> -->
</v-flex>
@ -24,15 +46,6 @@ export default {
return {
};
},
async mounted() {
},
watch: {
},
methods: {
},
computed: {
item() {
@ -114,5 +127,14 @@ export default {
}
},
},
watch: {
},
async mounted() {
},
methods: {
},
};
</script>

View File

@ -1,66 +1,107 @@
<template>
<v-navigation-drawer
:value="isRightSidebarOpen"
@input="SET_RIGHT_SIDEBAR_OPEN"
style="padding: 0; z-index: 6"
app
right
@input="SET_RIGHT_SIDEBAR_OPEN"
>
<v-container class="pa-0 pb-0" fill-height>
<v-layout v-if="getRoom" row wrap style="background: rgba(30, 31, 50,0.6)">
<v-flex xs12 style="height: 50vh">
<v-container
class="pa-0 pb-0"
fill-height
>
<v-layout
v-if="getRoom"
row
wrap
style="background: rgba(30, 31, 50,0.6)"
>
<v-flex
xs12
style="height: 50vh"
>
<v-flex xs12>
<v-card
style="background: linear-gradient(180deg,#1f1c2c,#182848); border-radius: 7px"
class="pa-2 ma-3"
>
<v-layout row wrap justify-space-between align-center>
<v-layout
row
wrap
justify-space-between
align-center
>
<v-flex xs6>
<h3 class="mb-0 pb-0 pa-0">{{ getRoom }}</h3>
<h3 class="mb-0 pb-0 pa-0">
{{ getRoom }}
</h3>
</v-flex>
<v-flex xs2>
<v-menu>
<v-btn icon class="ma-0 pa-0" dark>
<v-btn
icon
class="ma-0 pa-0"
dark
>
<v-icon>more_vert</v-icon>
</v-btn>
<v-list>
<v-list-item @click="handleDisconnect()">
<v-list-item-title class="user-menu-list">Leave Room</v-list-item-title>
<v-list-item-title class="user-menu-list">
Leave Room
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-flex>
<v-flex xs12>
<div v-if="getUsers.length != 1" class="participant-count">
<div
v-if="getUsers.length != 1"
class="participant-count"
>
{{ getUsers.length }} people
</div>
<div v-else class="participant-count">It's just you, invite some friends</div>
<div
v-else
class="participant-count"
>
It's just you, invite some friends
</div>
</v-flex>
<v-flex xs12>
<v-layout row wrap>
<v-layout
row
wrap
>
<v-flex xs6>
<v-switch
v-if="isHost(me)"
class="pa-0 mt-2 party-pausing-label"
label="Party Pausing"
v-if="isHost(me)"
:input-value="getPartyPausing"
@change="updatePartyPausing"
></v-switch>
<v-tooltip bottom color="rgb(44, 44, 49)" v-else>
/>
<v-tooltip
v-else
bottom
color="rgb(44, 44, 49)"
>
<v-btn
v-if="playerState(getHostUser) !== 'stop'"
color="primary"
:disabled="!canPause"
style="min-width: 0; float: right;"
@click="sendPartyPauseLocal(playerState(getHostUser) === 'play_arrow')"
v-if="playerState(getHostUser) !== 'stop'"
>
<v-icon v-if="playerState(getHostUser) === 'play_arrow'">pause</v-icon>
<v-icon v-else>play_arrow</v-icon>
<v-icon v-if="playerState(getHostUser) === 'play_arrow'">
pause
</v-icon>
<v-icon v-else>
play_arrow
</v-icon>
</v-btn>
<span
>Party Pausing is currently {{ canPause ? 'enabled' : 'disabled' }} by the
host</span
>
<span>Party Pausing is currently {{ canPause ? 'enabled' : 'disabled' }} by the
host</span>
</v-tooltip>
</v-flex>
</v-layout>
@ -68,15 +109,24 @@
</v-layout>
</v-card>
<v-card
v-if="me.role !== 'host' && this.$route.path.indexOf('/player') === -1"
style="background: #E5A00D; border-radius: 7px"
class="pa-2 ma-3"
v-if="me.role !== 'host' && this.$route.path.indexOf('/player') === -1"
>
<v-layout row wrap justify-space-between align-center>
<v-flex xs12 class="text-center">
<span class="mb-0 pb-0 pa-0" style="color: rgb(44, 44, 49); "
>Waiting for {{ getHostUser.username }} to start</span
>
<v-layout
row
wrap
justify-space-between
align-center
>
<v-flex
xs12
class="text-center"
>
<span
class="mb-0 pb-0 pa-0"
style="color: rgb(44, 44, 49); "
>Waiting for {{ getHostUser.username }} to start</span>
</v-flex>
</v-layout>
</v-card>
@ -90,21 +140,37 @@
style="background: linear-gradient(180deg,#1f1c2c,#182848)!important; border-radius: 7px"
class="pa-1 ml-3 mr-3"
>
<v-list-item style="height:4em" class="pl-1 pr-1 mb-0" tag="div">
<v-list-item
style="height:4em"
class="pl-1 pr-1 mb-0"
tag="div"
>
<v-list-item-avatar>
<img v-bind:src="getHostUser.avatarUrl" :style="getImgStyle(getHostUser)" />
<img
:src="getHostUser.avatarUrl"
:style="getImgStyle(getHostUser)"
>
<v-icon
v-if="getHostUser.playerState !== 'playing'"
style="font-size: 26px; opacity: 0.8; position: absolute;background-color: rgba(0,0,0,0.5)"
>{{ playerState(getHostUser) }}</v-icon
>
{{ playerState(getHostUser) }}
</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-tooltip bottom color="rgb(44, 44, 49)" multi-line class="userlist">
<v-tooltip
bottom
color="rgb(44, 44, 49)"
multi-line
class="userlist"
>
<span>
<v-list-item-title>
{{ getHostUser.username }}
<span style="opacity: 0.6" v-if="getHostUser.uuid === me.uuid">(you)</span>
<span
v-if="getHostUser.uuid === me.uuid"
style="opacity: 0.6"
>(you)</span>
</v-list-item-title>
<v-list-item-subtitle style="opacity:0.6;color:white;font-size:70%">{{
getTitle(getHostUser)
@ -112,48 +178,83 @@
</span>
Watching on {{ getHostUser.playerProduct || 'Unknown Plex Client' }}
<span v-if="getPlex.servers[getHostUser.machineIdentifier]">
<br />
<br>
via {{ getPlex.servers[getHostUser.machineIdentifier].name }}
</span>
</v-tooltip>
</v-list-item-content>
<v-list-item-action>
<v-tooltip bottom color="rgb(44, 44, 49)" multi-line class="userlist">
<v-icon style="color: #E5A00D">star</v-icon>Host
<v-tooltip
bottom
color="rgb(44, 44, 49)"
multi-line
class="userlist"
>
<v-icon style="color: #E5A00D">
star
</v-icon>Host
</v-tooltip>
</v-list-item-action>
</v-list-item>
<div class="pl-1 pr-1 pt-1 mt-0 pb-0 mb-0">
<span style="float: left; font-size:70%" class="ptuser-time pl-1">{{
<span
style="float: left; font-size:70%"
class="ptuser-time pl-1"
>{{
getCurrent(getHostUser)
}}</span>
<span style="float: right; font-size:70%" class="ptuser-maxTime pr-1">{{
<span
style="float: right; font-size:70%"
class="ptuser-maxTime pr-1"
>{{
getMax(getHostUser)
}}</span>
<v-progress-linear
class="pt-content-progress"
:height="2"
:value="percent(getHostUser)"
></v-progress-linear>
/>
</div>
</v-card>
<div v-for="user in getUsers" v-bind:key="user.username">
<div class="pa-1 ml-3 mr-3" v-if="!isHost(user)">
<v-list-item style="height:4em" class="pb-0 mb-0" tag="div">
<v-list-item-avatar v-on:dblclick="transferHost(user.username)">
<img v-bind:src="user.avatarUrl" :style="getImgStyle(user)" />
<div
v-for="user in getUsers"
:key="user.username"
>
<div
v-if="!isHost(user)"
class="pa-1 ml-3 mr-3"
>
<v-list-item
style="height:4em"
class="pb-0 mb-0"
tag="div"
>
<v-list-item-avatar @dblclick="transferHost(user.username)">
<img
:src="user.avatarUrl"
:style="getImgStyle(user)"
>
<v-icon
v-if="user.playerState !== 'playing'"
style="font-size: 26px; opacity: 0.8; position: absolute;background-color: rgba(0,0,0,0.7)"
>{{ playerState(user) }}</v-icon
>
{{ playerState(user) }}
</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-tooltip bottom color="rgb(44, 44, 49)" multi-line class="userlist">
<v-tooltip
bottom
color="rgb(44, 44, 49)"
multi-line
class="userlist"
>
<span>
<v-list-item-title>
{{ user.username }}
<span style="opacity: 0.6" v-if="user.uuid === me.uuid">(you)</span>
<span
v-if="user.uuid === me.uuid"
style="opacity: 0.6"
>(you)</span>
</v-list-item-title>
<v-list-item-subtitle style="opacity:0.6;color:white;font-size:70%">{{
getTitle(user)
@ -161,22 +262,41 @@
</span>
Watching on {{ user.playerProduct || 'Unknown Plex Client' }}
<span v-if="getPlex.servers[user.machineIdentifier]">
<br />
<br>
via {{ getPlex.servers[user.machineIdentifier].name }}
</span>
</v-tooltip>
</v-list-item-content>
<v-list-item-action>
<v-tooltip bottom color="rgb(44, 44, 49)" multi-line class="userlist">
<v-icon v-if="isHost(user)" style="color: #E5A00D">star</v-icon>Host
<v-tooltip
bottom
color="rgb(44, 44, 49)"
multi-line
class="userlist"
>
<v-icon
v-if="isHost(user)"
style="color: #E5A00D"
>
star
</v-icon>Host
</v-tooltip>
<v-menu v-if="user.uuid !== me.uuid && isHost(me)" :offset-y="true">
<v-btn icon class="ma-0 pa-0" dark>
<v-menu
v-if="user.uuid !== me.uuid && isHost(me)"
:offset-y="true"
>
<v-btn
icon
class="ma-0 pa-0"
dark
>
<v-icon>more_vert</v-icon>
</v-btn>
<v-list>
<v-list-item @click="transferHost(user.username)">
<v-list-item-title class="user-menu-list">Make Host</v-list-item-title>
<v-list-item-title class="user-menu-list">
Make Host
</v-list-item-title>
</v-list-item>
<!-- <v-list-item @click.stop="openInviteDialog(user)">
<v-list-item-title class="user-menu-list">Invite to a Plex Server</v-list-item-title>
@ -186,24 +306,33 @@
</v-list-item-action>
</v-list-item>
<div class="pl-0 pr-0 pt-1 mt-0 pb-0 mb-0">
<span style="float: left; font-size:70%" class="ptuser-time pl-1">{{
<span
style="float: left; font-size:70%"
class="ptuser-time pl-1"
>{{
getCurrent(user)
}}</span>
<span style="float: right; font-size:70%" class="ptuser-maxTime pr-1">{{
<span
style="float: right; font-size:70%"
class="ptuser-maxTime pr-1"
>{{
getMax(user)
}}</span>
<v-progress-linear
class="pt-content-progress"
:height="2"
:value="percent(user)"
></v-progress-linear>
/>
</div>
</div>
</div>
</v-list>
</v-flex>
<v-flex xs12 style="position: relative; height: 50vh; max-height: 50vh">
<messages v-if="$vuetify.breakpoint.lgAndUp"></messages>
<v-flex
xs12
style="position: relative; height: 50vh; max-height: 50vh"
>
<messages v-if="$vuetify.breakpoint.lgAndUp" />
</v-flex>
</v-layout>
</v-container>
@ -227,11 +356,6 @@ export default {
partyPauseCooldownRunning: false,
};
},
mounted() {
setInterval(() => {
this.now = new Date().getTime();
}, 250);
},
watch: {
getUsers: {
deep: true,
@ -240,6 +364,11 @@ export default {
},
},
},
mounted() {
setInterval(() => {
this.now = new Date().getTime();
}, 250);
},
computed: {
...mapState(['me', 'isRightSidebarOpen']),
...mapGetters(['getPlex', 'getPartyPausing', 'getUsers', 'getRoom', 'getHostUser']),

View File

@ -1,34 +1,92 @@
<template>
<v-bottom-sheet v-model="sheet" hide-overlay>
<v-card v-if="ready && content && !content.loading" style="max-width: 100%; margin-left: auto; margin-right: auto" class="white--text pa-0" :img="background">
<v-container fluid align-center justify-start v-show="ready" class="pa-0" style="background: rgba(0,0,0,0.7);">
<v-bottom-sheet
v-model="sheet"
hide-overlay
>
<v-card
v-if="ready && content && !content.loading"
style="max-width: 100%; margin-left: auto; margin-right: auto"
class="white--text pa-0"
:img="background"
>
<v-container
v-show="ready"
fluid
align-center
justify-start
class="pa-0"
style="background: rgba(0,0,0,0.7);"
>
<v-card-title class="pa-0">
<v-spacer></v-spacer>
<v-spacer />
</v-card-title>
<v-layout row wrap justify-start align-start class="pa-0">
<v-container fluid class="pa-1">
<v-layout
row
wrap
justify-start
align-start
class="pa-0"
>
<v-container
fluid
class="pa-1"
>
<v-layout row>
<v-flex xs3 sm2>
<v-flex
xs3
sm2
>
<v-img
:src="thumb"
:src="thumb"
height="125px"
contain
></v-img>
/>
</v-flex>
<v-flex>
<div>
<h2 style="width: 100%">Coming up next<v-icon style="float: right" @click="sheet = false" class="clickable ma-2">close</v-icon></h2>
<div class="headline">{{ getTitle }}</div>
<h2 style="width: 100%">
Coming up next<v-icon
style="float: right"
class="clickable ma-2"
@click="sheet = false"
>
close
</v-icon>
</h2>
<div class="headline">
{{ getTitle }}
</div>
<div>{{ getUnder }}</div>
<v-layout row wrap>
<v-flex xs12 md6 class="text-xs-left">
<v-layout
row
wrap
>
<v-flex
xs12
md6
class="text-xs-left"
>
<h5>From {{ plexserver.name }}</h5>
</v-flex>
<v-flex xs12 md6 class="text-xs-right">
<v-flex
xs12
md6
class="text-xs-right"
>
<div class="text-xs-right">
<span>{{ (Math.round(timer / 1000) * 100) / 100 }}s</span>
<v-btn @click="pressPlay" color="primary">Play Now</v-btn>
<v-btn flat @click="sheet = false">Cancel</v-btn>
<v-btn
color="primary"
@click="pressPlay"
>
Play Now
</v-btn>
<v-btn
flat
@click="sheet = false"
>
Cancel
</v-btn>
</div>
</v-flex>
</v-layout>
@ -37,7 +95,11 @@
</v-layout>
</v-container>
</v-layout>
<div :style="{ width: percent + '%'}" class="primary" style="height: 3px"></div>
<div
:style="{ width: percent + '%'}"
class="primary"
style="height: 3px"
/>
<!-- <v-progress-linear :value="percent" class="pa-0 ma-0" height="3"></v-progress-linear> -->
</v-container>
</v-card>
@ -59,43 +121,6 @@ export default {
ready: false,
};
},
async mounted() {
window.EventBus.$on('upnext', (data) => {
console.log('Upnext event', data);
this.content = data;
this.ready = true;
this.startTimer();
});
},
watch: {
},
methods: {
pressPlay() {
this.chosenClient.playMedia({
ratingKey: this.item.ratingKey,
mediaIndex: null,
server: this.plexserver,
offset: 0,
});
},
startTimer() {
this.timer = this.maxTimer;
const data = this.item;
this.sheet = true;
const ticker = setInterval(() => {
console.log('tick');
this.timer -= 30;
if (this.timer < 1) {
if (this.sheet) {
this.pressPlay(data.item);
}
clearInterval(ticker);
this.sheet = false;
}
}, 30);
},
},
computed: {
percent() {
return (this.timer / this.maxTimer) * 100;
@ -175,5 +200,42 @@ export default {
}
},
},
watch: {
},
async mounted() {
window.EventBus.$on('upnext', (data) => {
console.log('Upnext event', data);
this.content = data;
this.ready = true;
this.startTimer();
});
},
methods: {
pressPlay() {
this.chosenClient.playMedia({
ratingKey: this.item.ratingKey,
mediaIndex: null,
server: this.plexserver,
offset: 0,
});
},
startTimer() {
this.timer = this.maxTimer;
const data = this.item;
this.sheet = true;
const ticker = setInterval(() => {
console.log('tick');
this.timer -= 30;
if (this.timer < 1) {
if (this.sheet) {
this.pressPlay(data.item);
}
clearInterval(ticker);
this.sheet = false;
}
}, 30);
},
},
};
</script>

View File

@ -1,6 +1,16 @@
const path = require('path');
module.exports = {
lintOnSave: process.env.NODE_ENV !== 'production',
productionSourceMap: false,
transpileDependencies: ['vuetify'],
configureWebpack: {
devtool: 'source-map',
devtool: process.env.NODE_ENV === 'production' ? false : 'cheap-eval-source-map',
resolve: {
alias: {
// Alias @ to /src folder for ES/TS imports
'@': path.join(__dirname, '/src'),
},
},
},
};