You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

248 lines
9.0 KiB

<template>
<transition appear name="fade">
<div v-if="tmdbId !== undefined" class="modal p-8 overflow-auto items-center z-50" @click.self="close">
<transition name="slide" appear
@before-enter="lockScroll(true)" @enter-cancelled="lockScroll(false)"
@leave="lockScroll(false)" @leave-cancelled="lockScroll(true)">
<div v-if="movie !== undefined" class="relative w-full max-w-6xl bg-white md:bg-none overflow-hidden rounded-xl flex-col flex-shrink-0 shadow-xl">
<div class="bg-center bg-cover bg-no-repeat" :class="{ 'text-white': isDark }"
:style="movie.backdropPath ? `background: url('/api/tmdb/image/w1280${movie.backdropPath}')` : ''">
<div class="movie-modal-content z-10" :style="backdropOverlayStyle">
<div class="flex p-4 items-center">
<movie-poster v-if="movie.posterPath" :src="movie.posterPath"
class="w-3/12 flex-shrink-0 rounded-xl shadow-md md:shadow-xl" @onLoad="onPosterLoad"/>
<div class="p-4 flex flex-col justify-center">
<h2 class="text-lg md:text-4xl">
<span class="font-bold">{{ movie.title }}</span>
<span class="opacity-70" v-if="releaseYear"> ({{ releaseYear }})</span>
</h2>
<div class="flex font-light dot-separated">
<span v-if="releaseDate">{{ releaseDate }}</span>
<span v-if="runtime">{{ runtime }}</span>
</div>
<div v-if="movie?.overview" class="mt-4 hidden md:block">
<p class="">{{movie?.overview}}</p>
</div>
<div class="mt-4 hidden md:block">
<a class="inline-block py-2 px-6 rounded-full shadow-md hover:shadow-lg focus:outline-none ring-0 transition-transform transform hover:scale-105" :class="{ 'bg-white text-black': isDark, 'bg-black text-white': !isDark }"
v-if="movie.plexLink !== null" :href="movie.plexLink" target="_blank">Watch Now</a>
<template v-else-if="movie.requestedBy !== null">
<button class="inline-block py-2 px-6 rounded-full shadow-md hover:shadow-lg disabled:opacity-50 disabled:cursor-default focus:outline-none ring-0 transition-transform transform hover:scale-105" :class="{ 'bg-white text-black': isDark, 'bg-black text-white': !isDark }"
v-if="movie.requestedBy.id == userId" @click="cancel">Cancel Request</button>
<button class="inline-block py-2 px-6 rounded-full shadow-md hover:shadow-lg disabled:opacity-50 disabled:cursor-default focus:outline-none ring-0 transition-transform transform hover:scale-105" :class="{ 'bg-white text-black': isDark, 'bg-black text-white': !isDark }"
v-else disabled>Requested by Another User</button>
</template>
<button v-else class="inline-block py-2 w-36 rounded-full shadow-md hover:shadow-lg disabled:opacity-50 disabled:cursor-default focus:outline-none ring-0 transition-transform transform hover:scale-105" :class="{ 'bg-white text-black': isDark, 'bg-black text-white': !isDark }"
:disabled="isRequesting" @click="request">{{isRequesting ? "Processing..." : "Request"}}</button>
</div>
</div>
</div>
</div>
</div>
<div class="p-4 space-y-4 md:hidden z-20" style="box-shadow: 0px -10px 10px rgba(0, 0, 0, 0.10);" >
<div v-if="movie?.overview">
<h3 class="font-bold mb-2">Overview</h3>
<p>{{ movie.overview }}</p>
</div>
<div class="text-center">
<a class="inline-block py-2 px-8 rounded-full shadow-sm bg-red-500 hover:bg-black transition-colors text-white focus:outline-none ring-0"
v-if="movie.plexLink !== null" :href="movie.plexLink">Watch Now</a>
<template v-if="movie.requestedBy !== null">
<button class="inline-block py-2 px-8 rounded-full bg-red-500 text-white disabled:opacity-50" @click="cancel"
v-if="movie.requestedBy.id == userId">Cancel Request</button>
<button class="inline-block py-2 px-8 rounded-full bg-red-500 text-white disabled:opacity-50" disabled
v-else>Requested by Another User</button>
</template>
<button v-else class="inline-block py-2 px-8 rounded-full shadow-sm bg-red-500 hover:bg-black transition-colors text-white disabled:opacity-50 disabled:cursor-default focus:outline-none ring-0 disabled:bg-black"
:disabled="isRequesting" @click="request">{{isRequesting ? "Processing..." : "Request"}}</button>
</div>
</div>
</div>
</transition>
</div>
</transition>
</template>
<script lang="ts">
import type { IMovieDetails } from "@autoplex-api/request";
import { Status } from "@autoplex/restful";
import { defineComponent } from "vue";
import { getAverageRgb } from "../../util";
import { useStore, Mutation, Action } from "../../store";
import MoviePoster from "../MoviePoster.vue";
import { mapGetters } from "vuex";
export default defineComponent({
components: {
MoviePoster
},
computed: {
...mapGetters(["userId"]),
backdropOverlayStyle(): string {
let { r, g, b } = this.rgb;
return `background-image: linear-gradient(to right, rgba(${r}, ${g}, ${b}, 1.0), rgba(${r}, ${g}, ${b}, 0.84)`;
},
isDark(): boolean {
let { r, g, b } = this.rgb;
return (r*0.299 + g*0.587 + b*0.114) <= 186;
// r /= 255;
// g /= 255;
// b /= 255;
// r = r <= 0.03928 ? r/12.92 : Math.pow((r+0.055)/1.055, 2.4);
// g = g <= 0.03928 ? r/12.92 : Math.pow((r+0.055)/1.055, 2.4);
// b = b <= 0.03928 ? r/12.92 : Math.pow((r+0.055)/1.055, 2.4);
// let L = 0.2126*r + 0.7152*g + 0.0722*b;
// return L <= 0.179;
},
releaseYear(): string {
if (!this.movie || !this.movie.releaseDate) {
return "";
}
return this.movie.releaseDate.slice(0, 4);
},
releaseDate(): string {
if (!this.movie || !this.movie.releaseDate) {
return "";
}
let [year, month, day] = this.movie.releaseDate.split('-');
return `${month}/${day}/${year}`;
},
runtime(): string {
if (!this.movie || !this.movie.runtime) {
return "";
}
let hours = Math.floor(this.movie.runtime / 60);
let minutes = Math.floor(this.movie.runtime % 60);
return (hours > 0 ? `${hours}h ` : "") + `${minutes}m`;
}
},
data() {
return {
rgb: {r: 0, g: 0, b: 0},
movie: <IMovieDetails|undefined>undefined,
isPosterLoaded: false,
isRequesting: false
}
},
methods: {
lockScroll(lock: boolean) {
useStore().commit(Mutation.LockScroll, lock);
},
close() {
this.movie = undefined;
this.$emit("onClose");
},
onKeyPress(event: KeyboardEvent) {
if (event.key != "Escape") {
return;
}
this.close();
},
onPosterLoad(img: HTMLImageElement) {
this.isPosterLoaded = true;
this.computeBackdropColor(img);
},
computeBackdropColor(img: HTMLImageElement) {
// This is being weird and this is the only way I can fix it...
let rgb: {[k: string]: number} = {r: 0, g: 0, b: 0};
try {
rgb = getAverageRgb(img);
} catch(e) {
return { r: 0, g: 0, b: 0 };
}
// Adjust contrast
let c = 60;
let f = 259*(c + 255) / (255*(259 - c));
for (let k in rgb) {
rgb[k] = Math.min(Math.max(f*(rgb[k] - 128) + 128, 0), 255);
}
// Adjust brightness
for (let k in rgb) {
rgb[k] *= 0.90;
}
this.rgb = <any>rgb;
},
async fetchMovieDetails() {
if (this.tmdbId === undefined) {
return;
}
let [status, response] = await this.$store.dispatch(Action.MovieDetails, this.tmdbId);
if (status != Status.Ok) {
this.close();
return;
}
this.movie = response.result;
},
async cancel() {
if (this.isRequesting || this.movie == null || this.movie.ticketId == null || this.movie.requestedBy?.id != this.userId) {
return;
}
this.isRequesting = true;
let [status, response] = await this.$store.dispatch(Action.CancelMovieRequest, this.movie.ticketId);
this.isRequesting = false;
if (status != Status.Ok) {
console.error("Failed to cancel movie request");
return;
}
this.movie.ticketId = null;
this.movie.requestedBy = null;
this.$emit("onCancel", this.movie);
},
async request() {
if (this.isRequesting || this.movie == null || this.movie.tmdbId == null) {
return;
}
this.isRequesting = true;
let [status, response] = await this.$store.dispatch(Action.CreateMovieRequest, this.movie.tmdbId);
this.isRequesting = false;
if (status == Status.Forbidden) {
console.error("Failed to add movie: quota has been met");
return;
}
this.movie.ticketId = response.result.ticketId;
this.movie.requestedBy = {
id : this.$store.state.user!.id,
isAdmin: this.$store.state.user!.isAdmin,
name : this.$store.state.user!.name,
}
}
},
mounted() {
document.addEventListener("keydown", this.onKeyPress);
this.fetchMovieDetails();
},
beforeUnmount() {
document.removeEventListener("keydown", this.onKeyPress);
},
props: {
tmdbId: {
type: [Number, String],
required: false
}
},
watch: {
tmdbId() {
this.fetchMovieDetails();
}
}
});
</script>
<style lang="postcss">
.dot-separated > *:not(:first-child)::before {
@apply px-2;
content: '\2022'
}
.modal button {
@apply ring-0;
}
.modal button:focus-visible {
@apply ring-2 ring-indigo-500 ring-offset-2;
}
</style>