feat(card): add cards and proper sliders

This commit is contained in:
MrTimscampi 2020-09-06 04:25:15 +02:00
parent ec5938e1ab
commit f2227cb176
13 changed files with 293 additions and 34 deletions

View File

@ -1,6 +1,7 @@
{
"editor.formatOnSave": true,
"i18n-ally.keepFulfilled": true,
"i18n-ally.keystyle": "flat",
"i18n-ally.localesPaths": "locales",
"i18n-ally.sortKeys": true,
"vetur.format.defaultFormatter.html": "prettier"

View File

@ -1,13 +1,34 @@
<template>
<v-card :to="`../item/${item.Id}`">
<v-img class="cardImage" :src="imageLink(item.Id)" />
<v-card-title>
<span>{{ item.Name }}</span>
</v-card-title>
<v-card-subtitle>
<span>{{ item.ProductionYear }}</span>
</v-card-subtitle>
</v-card>
<nuxt-link
:to="`/item/${item.Id}`"
style="text-decoration: none; color: inherit"
>
<div class="card-box">
<div :class="cardType">
<button
class="card-content card-content-button d-flex justify-center align-center"
:style="{ backgroundImage: `url('${imageLink(item.Id)}')` }"
>
<v-icon
v-if="!item.ImageTags.Primary"
size="96"
color="grey lighten-3"
>
{{ itemIcon }}
</v-icon>
</button>
<div class="card-overlay d-flex justify-center align-center">
<v-btn fab color="primary" :to="`/item/${item.Id}/play`">
<v-icon size="36">mdi-play</v-icon>
</v-btn>
</div>
</div>
<div class="card-text">
<div class="card-title mt-1">{{ item.Name }}</div>
<div class="card-subtitle grey--text">{{ item.ProductionYear }}</div>
</div>
</div>
</nuxt-link>
</template>
<script lang="ts">
@ -23,6 +44,70 @@ export default Vue.extend({
Name: 'Missing Name'
};
}
},
shape: {
type: String,
required: false,
default: () => {
return false;
}
}
},
computed: {
cardType: {
get() {
// If the shape is forced externally, use that instead
if (this.shape) {
return this.shape;
}
// Otherwise, figure out the shape based on the type of the item
switch (this.item.Type) {
case 'Book':
case 'BoxSet':
case 'Movie':
case 'MusicArtist':
case 'Person':
case 'Series':
return 'portrait-card';
case 'Audio':
case 'Folder':
case 'MusicAlbum':
case 'PhotoAlbum':
case 'Playlist':
return 'square-card';
default:
return '';
}
}
},
itemIcon: {
get() {
switch (this.item.Type) {
case 'Audio':
return 'mdi-music-note';
case 'Book':
return 'mdi-book-open-page-variant';
case 'BoxSet':
return 'mdi-folder-multiple';
case 'Folder':
return 'mdi-folder';
case 'Movie':
return 'mdi-filmstrip';
case 'MusicAlbum':
return 'mdi-album';
case 'MusicArtist':
case 'Person':
return 'mdi-account';
case 'PhotoAlbum':
return 'mdi-image-multiple';
case 'Playlist':
return 'mdi-playlist-play';
case 'Series':
return 'mdi-television-classic';
default:
return '';
}
}
}
},
methods: {
@ -34,7 +119,71 @@ export default Vue.extend({
</script>
<style scoped>
.card {
width: 12em;
.card-box {
cursor: pointer;
padding: 0;
margin: 0.6em;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
.portrait-card {
position: relative;
padding-bottom: 150%;
contain: strict;
}
.thumb-card {
position: relative;
padding-bottom: 56.25%;
contain: strict;
}
.square-card {
position: relative;
padding-bottom: 100%;
contain: strict;
}
.card-content {
overflow: hidden;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: 0 !important;
height: 100%;
width: 100%;
contain: strict;
border-radius: 0.3em;
background-color: #00455c;
background-size: cover;
background-repeat: no-repeat;
background-clip: content-box;
background-position: center center;
-webkit-tap-highlight-color: transparent;
}
.card-overlay {
position: absolute;
background: radial-gradient(
farthest-corner at 50% 50%,
rgba(0, 0, 0, 0.5) 50%,
rgba(0, 0, 0, 0.7) 100%
);
transition: opacity 0.2s;
top: 0;
left: 0;
right: 0;
bottom: 0;
opacity: 0;
}
.card-box:hover .card-overlay {
opacity: 1;
}
.card-text {
text-align: center;
padding: 0 0.25em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.a {
text-decoration: none;
}
</style>

View File

@ -1,14 +1,36 @@
<template>
<v-col v-show="items.length > 0">
<v-col v-show="items.length > 0" class="home-section">
<h1 class="text-h5">
<span>{{ section.name }}</span>
</h1>
<v-slide-group>
<v-slide-item v-for="item in items" :key="item.Id">
<card :to="`/item/${item.Id}`" class="card mt-5" :item="item" />
</v-slide-item>
</v-slide-group>
<vueper-slides
:bullets="false"
:bullets-outside="false"
:arrows-outside="false"
:visible-slides="8"
:slide-multiple="true"
:breakpoints="breakpoints"
fixed-height="true"
>
<vueper-slide v-for="item in items" :key="item.Id">
<template v-slot:content>
<card :shape="section.shape" :item="item" />
</template>
</vueper-slide>
<template v-slot:arrow-left>
<v-btn icon large>
<v-icon>mdi-arrow-left</v-icon>
</v-btn>
</template>
<template v-slot:arrow-right>
<v-btn icon large>
<v-icon>mdi-arrow-right</v-icon>
</v-btn>
</template>
</vueper-slides>
</v-col>
</template>
@ -25,7 +47,21 @@ export default Vue.extend({
},
data() {
return {
items: [] as BaseItemDto[]
items: [] as BaseItemDto[],
breakpoints: {
600: {
visibleSlides: 3
},
960: {
visibleSlides: 4
},
1264: {
visibleSlides: 6
},
1904: {
visibleSlides: 8
}
}
};
},
async created() {
@ -90,3 +126,32 @@ export default Vue.extend({
}
});
</script>
<style scoped>
h1 {
margin-left: 0.4em;
margin-bottom: 0.25em;
}
</style>
<style>
.home-section .vueperslides__track {
position: relative;
cursor: default !important;
}
.home-section .vueperslides__arrows {
display: flex;
position: absolute;
top: -2.75em;
right: 0;
align-items: center;
}
.home-section .vueperslides__arrow {
position: relative;
display: inline-flex;
transform: none;
}
.home-section .vueperslides__arrow--prev {
margin-right: 0.75em;
}
</style>

View File

@ -69,7 +69,7 @@
<user-button v-if="$auth.loggedIn" />
</v-app-bar>
<v-main>
<v-container>
<v-container fluid>
<nuxt />
</v-container>
</v-main>

View File

@ -1,15 +1,19 @@
{
"badRequest": "Bad request. Try again",
"continueListening": "Continue listening",
"continueWatching": "Continue watching",
"home": "Home",
"incorrectUsernameOrPassword": "Incorrect username or password",
"login": "Login",
"logout": "Logout",
"more": "More",
"nextUp": "",
"play": "Play",
"serverAddressMustBeUrl": "Server address must be a valid URL",
"serverAddressRequired": "Server address is required",
"serverNotFound": "Server not found",
"settings": "Settings",
"unexpectedError": "Unexpected error",
"upNext": "Up next",
"usernameRequired": "Username is required"
}

View File

@ -1,15 +1,19 @@
{
"badRequest": "Mauvaise requête. Réessayez",
"continueListening": "Poursuivre l'écoute",
"continueWatching": "Poursuivre le visionnage",
"home": "Accueil",
"incorrectUsernameOrPassword": "Nom d'utilisateur ou mot de passe incorrect",
"login": "Connexion",
"logout": "Se déconnecter",
"more": "Plus",
"nextUp": "",
"play": "Lire",
"serverAddressMustBeUrl": "L'adresse du serveur doit être une URL valide",
"serverAddressRequired": "L'adresse du serveur est requise",
"serverNotFound": "Serveur introuvable",
"settings": "Paramètres",
"unexpectedError": "Erreur inattendue",
"upNext": "À suivre",
"usernameRequired": "Nom d'utilisateur requis"
}

View File

@ -38,12 +38,16 @@ const config: NuxtConfig = {
** https://nuxtjs.org/guide/plugins
*/
plugins: [
// Components
'plugins/components/vueperSlides.ts',
// Utility
'plugins/snackbar.ts',
'plugins/user.ts',
// API
'plugins/displayPreferenceApi.ts',
'plugins/imageApi.ts',
'plugins/itemsApi.ts',
'plugins/snackbar.ts',
'plugins/tvShowsApi.ts',
'plugins/user.ts',
'plugins/userApi.ts',
'plugins/userLibraryApi.ts',
'plugins/userViewsApi.ts'

View File

@ -31,7 +31,8 @@
"lodash": "^4.17.20",
"nuxt": "^2.14.0",
"nuxt-i18n": "^6.13.12",
"nuxt-vuex-localstorage": "^1.2.7"
"nuxt-vuex-localstorage": "^1.2.7",
"vueperslides": "^2.10.7"
},
"devDependencies": {
"@commitlint/cli": "^9.1.2",

View File

@ -12,24 +12,28 @@
<script lang="ts">
import Vue from 'vue';
import { pickBy } from 'lodash';
import { getShapeFromCollectionType } from '~/utils/items';
export default Vue.extend({
data() {
return {
homeSections: [
{
name: 'Continue Watching',
name: this.$t('continueWatching'),
libraryId: '',
shape: 'thumb-card',
type: 'resume'
},
{
name: 'Continue Listening',
name: this.$t('continueListening'),
libraryId: '',
shape: 'square-card',
type: 'resumeaudio'
},
{
name: 'Next Up',
name: this.$t('upNext'),
libraryId: '',
shape: 'thumb-card',
type: 'nextup'
}
]
@ -83,6 +87,7 @@ export default Vue.extend({
latestMediaSections.push({
name: `Latest ${userView.Name}`,
libraryId: userView.Id || '',
shape: getShapeFromCollectionType(userView.CollectionType),
type: 'latestmedia'
});
}
@ -95,6 +100,7 @@ export default Vue.extend({
homeSections.push({
name: 'Continue Watching',
libraryId: '',
shape: 'thumb-card',
type: 'resume'
});
break;
@ -102,6 +108,7 @@ export default Vue.extend({
homeSections.push({
name: 'Continue Listening',
libraryId: '',
shape: 'square-card',
type: 'resumeaudio'
});
break;
@ -109,6 +116,7 @@ export default Vue.extend({
homeSections.push({
name: 'Next Up',
libraryId: '',
shape: 'thumb-card',
type: 'nextup'
});
break;

View File

@ -8,15 +8,8 @@
</v-col>
</v-row>
<v-row>
<v-col cols="12">
<div class="d-flex flex-wrap">
<card
v-for="item in items"
:key="item.Id"
class="card mt-5"
:item="item"
/>
</div>
<v-col v-for="item in items" :key="item.Id" cols="2">
<card :item="item" />
</v-col>
</v-row>
</v-container>

View File

@ -0,0 +1,6 @@
import Vue from 'vue';
import { VueperSlides, VueperSlide } from 'vueperslides';
import 'vueperslides/dist/vueperslides.css';
Vue.component('vueper-slides', VueperSlides);
Vue.component('vueper-slide', VueperSlide);

View File

@ -24,3 +24,22 @@ export function getLibraryIcon(libraryType: string | undefined | null): string {
return 'mdi-folder';
}
}
export function getShapeFromCollectionType(
collectionType: string | null | undefined
): string {
switch (collectionType) {
case 'boxsets':
case 'movies':
case 'tvshows':
case 'books':
return 'portrait-card';
case 'livetv':
return 'thumb-card';
case 'folders':
case 'playlists':
case 'music':
default:
return 'square-card';
}
}

View File

@ -12534,6 +12534,11 @@ vue@^2.6.12:
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.12.tgz#f5ebd4fa6bd2869403e29a896aed4904456c9123"
integrity sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg==
vueperslides@^2.10.7:
version "2.10.7"
resolved "https://registry.yarnpkg.com/vueperslides/-/vueperslides-2.10.7.tgz#56f5f139cbf044afada541cf1bfbcb845db22c34"
integrity sha512-CS4W1s45qraNCM6vdIFbAe6SAPEIAXpk7YdcJWheRpD+bU6HLwfMieK9cRELJBxemHoZND0TbaRQWxp3t1MEZA==
vuetify-loader@^1.4.3:
version "1.6.0"
resolved "https://registry.yarnpkg.com/vuetify-loader/-/vuetify-loader-1.6.0.tgz#05df0805b3ab2ff0de198109d34f9da3f69da667"