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.
 
 
 
 
 
 

221 lines
7.2 KiB

<template>
<transition appear name="fade">
<div v-if="tmdbId !== undefined" class="modal p-8 overflow-auto items-center" @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" :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">
<button class="py-2 px-4 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.ticketId !== null">View Request Status</button>
<button v-else class="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 ? "Requesting..." : "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">
<button class="py-2 px-4 rounded-full bg-red-500 text-white disabled:opacity-50"
v-if="movie.ticketId !== null">View Request Status</button>
<button v-else class="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 ? "Requesting..." : "Request"}}</button>
</div>
</div>
</div>
</transition>
</div>
</transition>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { IApiDataResponse, IApiMovieDetails } from "@common/api_schema";
import { authFetch } from "../../routes";
import { getAverageRgb } from "../../util";
import { useStore, Mutation, Action } from "../../store";
import MoviePoster from "../MoviePoster.vue";
export default defineComponent({
components: {
MoviePoster
},
computed: {
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: <IApiMovieDetails|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 response = await (authFetch(`/api/movie/details/${this.tmdbId}`));
if (response.status != 200) {
this.close();
return;
}
let movie = <IApiDataResponse<IApiMovieDetails>> await response.json();
this.movie = movie.data;
},
async request() {
if (this.isRequesting || this.movie == null || this.movie.tmdbId == null) {
return;
}
this.isRequesting = true;
let response = await this.$store.dispatch(Action.RequestMovie, this.movie.tmdbId);
this.isRequesting = false;
if (response.status == "Forbidden") {
console.log("Failed to add movie: quota has been met");
return;
}
console.log(response);
this.movie.ticketId = response.data.ticketId;
}
},
mounted() {
document.addEventListener("keydown", this.onKeyPress);
this.fetchMovieDetails();
},
beforeUnmount() {
document.removeEventListener("keydown", this.onKeyPress);
},
props: {
tmdbId: {
type: [Number, String],
required: false
}
},
watch: {
tmdbId(newId: number|string|undefined, oldId: number|string|undefined) {
this.fetchMovieDetails();
}
}
});
</script>
<style lang="postcss">
.modal {
@apply fixed inset-0 flex flex-col h-screen;
background: rgba(0, 0, 0, 0.5);
}
.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>