Merge pull request #48 from KevinMidboe/refactor/image-loading

Refactor/image loading
This commit is contained in:
2020-02-20 00:22:14 +01:00
committed by GitHub
2 changed files with 320 additions and 185 deletions

View File

@@ -2,28 +2,32 @@
<section class="movie">
<!-- HEADER w/ POSTER -->
<header class="movie__header" :style="{ 'background-image': movie && backdrop !== null ? 'url(' + ASSET_URL + ASSET_SIZES[1] + backdrop + ')' : '' }" :class="compact ? 'compact' : ''" @click="compact=!compact">
<div class="movie__wrap movie__wrap--header">
<figure class="movie__poster">
<header ref="header" :class="compact ? 'compact' : ''" @click="compact=!compact">
<figure class="movie__poster">
<img class="movie-item__img is-loaded"
ref="poster-image"
src="~assets/placeholder.png">
<!-- -->
<!-- <img v-else class="movie-item__img is-loaded" src="~assets/no-image.png" ref="image" alt="No image - linked image unavailable"> -->
</figure>
<!-- <figure class="movie__poster">
<img v-if="movie && poster === null"
class="movies-item__img is-loaded"
alt="movie poster image"
src="~assets/no-image.png">
<img v-else-if="poster === undefined"
class="movies-item__img grey"
alt="movie poster image">
<!-- src="~assets/placeholder.png"> -->
alt="movie poster image"
src="~assets/placeholder.png">
<img v-else
class="movies-item__img is-loaded"
alt="movie poster image"
:src="ASSET_URL + ASSET_SIZES[0] + poster">
</figure>
</figure> -->
<div class="movie__title">
<h1 v-if="movie">{{ movie.title }}</h1>
<loading-placeholder v-else :count="1" />
</div>
</div>
<h1 class="movie__title" v-if="movie">{{ movie.title }}</h1>
<loading-placeholder v-else :count="1" />
</header>
<!-- Siderbar and movie info -->
@@ -64,32 +68,48 @@
<!-- MOVIE INFO -->
<div class="movie__info">
<div class="movie__description" v-if="movie"> {{ movie.overview }}</div>
<!-- Loading placeholder -->
<div class="movie__description noselect"
@click="truncatedDescription=!truncatedDescription"
v-if="!loading">
<span :class="truncatedDescription ? 'truncated':null">{{ movie.overview }}</span>
<button class="truncate-toggle"><i></i></button>
</div>
<div v-else class="movie__description">
<loading-placeholder :count="12" />
<loading-placeholder :count="5" />
</div>
<div class="movie__details" v-if="movie">
<div v-if="movie.year" class="movie__details-block">
<h2 class="movie__details-title">Release Date</h2>
<div class="movie__details-text">{{ movie.year }}</div>
<div v-if="movie.year">
<h2 class="title">Release Date</h2>
<div class="text">{{ movie.year }}</div>
</div>
<div v-if="movie.rank" class="movie__details-block">
<h2 class="movie__details-title">Rating</h2>
<div class="movie__details-text">{{ movie.rank }}</div>
<div v-if="movie.rating">
<h2 class="title">Rating</h2>
<div class="text">{{ movie.rating }}</div>
</div>
<div v-if="movie.type == 'show'" class="movie__details-block">
<h2 class="movie__details-title">Seasons</h2>
<div class="movie__details-text">{{ movie.seasons }}</div>
<div v-if="movie.type == 'show'">
<h2 class="title">Seasons</h2>
<div class="text">{{ movie.seasons }}</div>
</div>
<div v-if="movie.genres" class="movie__details-block">
<h2 class="movie__details-title">Genres</h2>
<div class="movie__details-text">{{ nestedDataToString(movie.genres) }}</div>
<div v-if="movie.genres">
<h2 class="title">Genres</h2>
<div class="text">{{ movie.genres.join(', ') }}</div>
</div>
<div v-if="movie.type == 'show'">
<h2 class="title">Production status</h2>
<div class="text">{{ movie.production_status }}</div>
</div>
<div v-if="movie.type == 'show'">
<h2 class="title">Runtime</h2>
<div class="text">{{ movie.runtime[0] }} minutes</div>
</div>
</div>
@@ -126,7 +146,17 @@ import LoadingPlaceholder from './ui/LoadingPlaceholder'
import { getMovie, getPerson, getShow, request, getRequestStatus } from '@/api'
export default {
props: ['id', 'type'],
// props: ['id', 'type'],
props: {
id: {
required: true,
type: Number
},
type: {
required: false,
type: String
}
},
components: { TorrentList, Person, LoadingPlaceholder, SidebarListElement },
directives: { img: img }, // TODO decide to remove or use
data(){
@@ -142,11 +172,40 @@ export default {
requested: false,
admin: localStorage.getItem('admin') == "true" ? true : false,
showTorrents: false,
compact: false
compact: false,
loading: true,
truncatedDescription: true
}
},
watch: {
id: function(val){
if (this.type === 'movie') {
this.fetchMovie(val);
} else {
this.fetchShow(val)
}
},
backdrop: function(backdrop) {
if (backdrop != null) {
const style = {
backgroundImage: 'url(' + this.ASSET_URL + this.ASSET_SIZES[1] + backdrop + ')'
}
Object.assign(this.$refs.header.style, style)
}
}
},
computed: {
numberOfTorrentResults: () => {
let numTorrents = store.getters['torrentModule/resultCount']
return numTorrents !== null ? numTorrents + ' results' : null
}
},
methods: {
parseResponse(movie) {
setTimeout(() => {
this.loading = false
this.movie = { ...movie }
this.title = movie.title
this.poster = movie.poster
@@ -155,13 +214,22 @@ export default {
this.checkIfRequested(movie)
.then(status => this.requested = status)
store.dispatch('documentTitle/updateTitle', movie.title)
this.setPosterSrc()
}, 1000)
},
async checkIfRequested(movie) {
return await getRequestStatus(movie.id, movie.type)
},
nestedDataToString(data) {
return data.join(', ')
setPosterSrc() {
const poster = this.$refs['poster-image']
if (this.poster == null) {
poster.src = '/dist/no-image.png'
return
}
poster.src = `${this.ASSET_URL}${this.ASSET_SIZES[0]}${this.poster}`
},
sendRequest(){
request(this.id, this.type, storage.token)
@@ -176,25 +244,7 @@ export default {
window.location.href = 'https://www.themoviedb.org/' + tmdbType + '/' + this.id
},
},
watch: {
id: function(val){
if (this.type === 'movie') {
this.fetchMovie(val);
} else {
this.fetchShow(val)
}
}
},
computed: {
numberOfTorrentResults: () => {
let numTorrents = store.getters['torrentModule/resultCount']
return numTorrents !== null ? numTorrents + ' results' : null
}
},
beforeDestroy() {
store.dispatch('documentTitle/updateTitle', this.prevDocumentTitle)
},
created(){
created() {
this.prevDocumentTitle = store.getters['documentTitle/title']
if (this.type === 'movie') {
@@ -216,8 +266,9 @@ export default {
this.$router.push({ name: '404' });
})
}
console.log('admin: ', this.admin)
},
beforeDestroy() {
store.dispatch('documentTitle/updateTitle', this.prevDocumentTitle)
}
}
</script>
@@ -226,6 +277,89 @@ export default {
@import "./src/scss/loading-placeholder";
@import "./src/scss/variables";
@import "./src/scss/media-queries";
@import "./src/scss/main";
header {
$duration: 0.2s;
height: 250px;
transform: scaleY(1);
transition: height $duration ease;
transform-origin: top;
position: relative;
background-size: cover;
background-repeat: no-repeat;
background-position: 50% 50%;
background-color: $background-color;
display: flex;
align-items: center;
@include tablet-min {
height: 350px;
}
&:before {
content: "";
display: block;
position: absolute;
top: 0;
left: 0;
z-index: 0;
width: 100%;
height: 100%;
background: $background-dark-85;
}
@include mobile {
&.compact {
height: 100px;
}
}
}
.movie__poster {
display: none;
@include desktop {
background: $background-color;
height: 0;
display: block;
position: absolute;
width: calc(45% - 40px);
top: 40px;
left: 40px;
> img {
width: 100%;
}
}
}
.truncate-toggle {
border: none;
background: none;
width: 100%;
display: flex;
align-items: center;
text-align: center;
color: $text-color;
> i {
font-style: unset;
font-size: 0.7rem;
transition: 0.3s ease all;
transform: rotateY(180deg)
}
&::before, &::after {
content: '';
flex: 1;
border-bottom: 1px solid $text-color-50;
}
&::before {
margin-right: 1rem;
}
&::after {
margin-left: 1rem;
}
}
.movie {
&__wrap {
@@ -246,49 +380,6 @@ export default {
color: $text-color;
}
}
&__header {
$duration: 0.2s;
height: 250px;
transform: scaleY(1);
transition: height $duration ease;
transform-origin: top;
position: relative;
background-size: cover;
background-repeat: no-repeat;
background-position: 50% 50%;
background-color: $background-color;
@include tablet-min {
height: 350px;
}
&:before {
content: "";
display: block;
position: absolute;
top: 0;
left: 0;
z-index: 0;
width: 100%;
height: 100%;
background: $background-dark-85;
}
&.compact {
height: 100px;
}
}
&__poster {
display: none;
@include tablet-min {
background: $background-color;
height: 0;
display: block;
position: absolute;
width: calc(45% - 40px);
top: 40px;
left: 40px;
}
}
&__img {
display: block;
@@ -364,37 +455,49 @@ export default {
font-size: 13px;
line-height: 1.8;
margin-bottom: 20px;
& .truncated {
display: -webkit-box;
overflow: hidden;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
& + .truncate-toggle > i {
transform: rotateY(0deg) rotateZ(180deg);
}
}
@include tablet-min {
margin-bottom: 30px;
font-size: 14px;
}
}
&__details {
&-block {
float: left;
}
&-block:not(:last-child) {
display: flex;
flex-wrap: wrap;
> div {
margin-bottom: 20px;
margin-right: 20px;
@include tablet-min {
margin-bottom: 30px;
margin-right: 30px;
}
}
&-title {
margin: 0;
font-weight: 400;
text-transform: uppercase;
font-size: 14px;
color: $green;
@include tablet-min {
font-size: 16px;
& .title {
margin: 0;
font-weight: 400;
text-transform: uppercase;
font-size: 14px;
color: $green;
@include tablet-min {
font-size: 16px;
}
}
& .text {
font-weight: 300;
font-size: 14px;
margin-top: 5px;
}
}
&-text {
font-weight: 300;
font-size: 14px;
margin-top: 5px;
}
}
&__admin {

View File

@@ -1,21 +1,24 @@
<template>
<li class="movies-item" :class="{'shortList': shortList}">
<a class="movies-item__link" :class="{'no-image': !movie}" @click.prevent="openMoviePopup(movie.id, movie.type)">
<li class="movie-item" :class="{'shortList': shortList}">
<figure class="movie-item__poster">
<img class="movie-item__img is-loaded"
ref="poster-image"
@click="openMoviePopup(movie.id, movie.type)"
:alt="posterAltText"
:data-src="poster"
src="~assets/placeholder.png">
<!-- TODO change to picture element -->
<figure class="movies-item__poster">
<img v-if="movie.poster" class="movies-item__img is-loaded" ref="image" src="~assets/placeholder.png" :alt="`${movie.title} poster image`">
<div v-if="movie.download" class="progress">
<progress :value="movie.download.progress" max="100"></progress>
<span>{{ movie.download.state }}: {{ movie.download.progress }}%</span>
</div>
</figure>
<div class="movies-item__content">
<p class="movies-item__title">{{ movie.title || movie.name }}</p>
<p class="movies-item__title">{{ movie.year }}</p>
<div v-if="movie.download" class="progress">
<progress :value="movie.download.progress" max="100"></progress>
<span>{{ movie.download.state }}: {{ movie.download.progress }}%</span>
</div>
</a>
</figure>
<div class="movie-item__info">
<p v-if="movie.title || movie.name">{{ movie.title || movie.name }}</p>
<p v-if="movie.year">{{ movie.year }}</p>
<p v-if="movie.type == 'person'">Known for: {{ movie.known_for_department }}</p>
</div>
</li>
</template>
@@ -38,6 +41,8 @@ export default {
},
data(){
return {
poster: undefined,
observed: false,
posterSizes: [{
id: 'w500',
minWidth: 500
@@ -54,37 +59,35 @@ export default {
}
},
computed: {
posterUrl: function() {
if (this.movie.poster == null)
return "~assets/no-image.png"
const correctWidth = this.posterQualityIdentifierFromPosterWidth
return `https://image.tmdb.org/t/p/${correctWidth}${this.movie.poster}`
},
posterQualityIdentifierFromPosterWidth: function() {
const posterWidth = this.$refs.image.clientHeight
if (posterWidth > this.posterSizes[0].minWidth)
return this.posterSizes[0].id
const widthCandidates = this.posterSizes.filter(size => posterWidth < size.minWidth ? size.id : null)
return widthCandidates[widthCandidates.length - 1].id
posterAltText: function() {
const type = this.movie.type || ''
const title = this.movie.title || this.movie.name
return this.movie.poster ? `Poster for ${type} ${title}` : `Missing image for ${type} ${title}`
}
},
beforeMount() {
if (this.movie.poster != null) {
this.poster = 'https://image.tmdb.org/t/p/w500' + this.movie.poster
} else {
this.poster = '/dist/no-image.png'
}
},
mounted() {
if (this.$refs.image == undefined)
const poster = this.$refs['poster-image']
if (poster == null)
return
const imageObserver = new IntersectionObserver((entries, imgObserver) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
if (entry.isIntersecting && this.observed == false) {
const lazyImage = entry.target
lazyImage.src = this.posterUrl
lazyImage.class
lazyImage.src = lazyImage.dataset.src
this.observed = true
}
})
});
imageObserver.observe(this.$refs.image);
imageObserver.observe(poster);
},
methods: {
openMoviePopup(id, type) {
@@ -94,74 +97,103 @@ export default {
}
</script>
<style lang="scss">
<style lang="scss" scoped>
@import "./src/scss/variables";
@import "./src/scss/media-queries";
@import "./src/scss/main";
.movies-item {
.movie-item {
padding: 10px;
width: 50%;
background-color: $background-color;
transition: background-color 0.5s ease;
@include tablet-min{
@include tablet-min {
padding: 15px;
width: 33%;
}
@include tablet-landscape-min{
@include tablet-landscape-min {
padding: 15px;
width: 25%;
}
@include desktop-min{
@include desktop-min {
padding: 15px;
width: 20%;
}
@include desktop-lg-min{
@include desktop-lg-min {
padding: 15px;
width: 12.5%;
}
&__link{
&:hover &__info > p {
color: $text-color;
}
&__poster {
text-decoration: none;
color: $text-color-70;
font-weight: 300;
> img {
width: 100%;
opacity: 0;
transform: scale(0.97) translateZ(0);
transition: opacity 0.5s ease, transform 0.5s ease;
&.is-loaded{
opacity: 1;
transform: scale(1);
}
&:hover {
transform: scale(1.03);
box-shadow: 0 0 10px rgba($dark, 0.1);
}
}
}
&__content{
&__info {
padding-top: 15px;
}
&__poster{
transition: transform 0.5s ease, box-shadow 0.3s ease;
transform: translateZ(0);
}
&__img{
width: 100%;
opacity: 0;
transform: scale(0.97) translateZ(0);
transition: opacity 0.5s ease, transform 0.5s ease;
&.is-loaded{
opacity: 1;
transform: scale(1);
font-weight: 300;
> p {
color: $text-color-70;
margin: 0;
font-size: 11px;
letter-spacing: 0.5px;
transition: color 0.5s ease;
cursor: pointer;
@include mobile-ls-min{
font-size: 12px;
}
@include tablet-min{
font-size: 14px;
}
}
}
&__link:not(.no-image):hover &__poster{
transform: scale(1.03);
box-shadow: 0 0 10px rgba($dark, 0.1);
}
.no-image {
background-color: var(--text-color);
color: var(--background-color);
width: 100%;
height: 383px;
display: flex;
align-items: center;
justify-content: center;
span {
font-size: 1.5rem;
width: 70%;
text-align: center;
text-transform: uppercase;
}
&__title{
margin: 0;
font-size: 11px;
letter-spacing: 0.5px;
transition: color 0.5s ease;
cursor: pointer;
@include mobile-ls-min{
font-size: 12px;
}
@include tablet-min{
font-size: 14px;
}
}
&__link:hover &__title{
color: $text-color;
&:hover {
transform: scale(1);
}
}
</style>