218 lines
7 KiB
Vue
218 lines
7 KiB
Vue
<script setup lang="ts">
|
|
import type { MovieMainCrew } from "@/libs/apis/tmdb/schemas/credits.ts";
|
|
import type { TmdbGenreName } from "@/libs/apis/tmdb/schemas/genres.ts";
|
|
import type { ArtWorkTitle } from "@/libs/apis/tmdb/schemas/works.ts";
|
|
|
|
import { getMainMovieCrew } from "@/libs/apis/tmdb/crew.ts";
|
|
import { TmdbGenre } from "@/libs/apis/tmdb/schemas/genres.ts";
|
|
import { generateDitheredBufferFromUrl } from "@/libs/utils/images.ts";
|
|
import { convertToMillions } from "@/libs/utils/numbers.ts";
|
|
import { PrettyLogger } from "@/services/logger.ts";
|
|
import { RuntimeClient } from "@/services/runtime-client.ts";
|
|
import { useEntryStore } from "@/stores/entry.ts";
|
|
import { canvasFromPixelBuffer } from "@thi.ng/pixel";
|
|
import { Array as Arr, Effect, pipe, String as Str } from "effect";
|
|
import { ref, type Ref, type ShallowRef, useTemplateRef, watch } from "vue";
|
|
import { useRouter } from "vue-router";
|
|
|
|
// Types
|
|
interface AddEntryDisplayData {
|
|
budget: string;
|
|
genres: TmdbGenreName[];
|
|
hasUniqueOriginalTitle: boolean;
|
|
mainCrew: MovieMainCrew;
|
|
originalLanguage: string;
|
|
originalTitle: ArtWorkTitle;
|
|
overview: string;
|
|
popularity: number;
|
|
releaseDate: string;
|
|
revenue: string;
|
|
runtime: string;
|
|
tagline: string;
|
|
title: ArtWorkTitle;
|
|
}
|
|
|
|
// Magasins et routeur
|
|
const router = useRouter();
|
|
const entryStore = useEntryStore();
|
|
|
|
// Données
|
|
/** Ensemble des données affichées à l'Utilisateur au sein */
|
|
const displayData: Ref<AddEntryDisplayData | undefined> = ref<AddEntryDisplayData | undefined>();
|
|
/** Conteneur du canvas du poster de l'oeuvre courante avec tramage. */
|
|
const imageContainer: Readonly<ShallowRef<HTMLDivElement | null>> = useTemplateRef("imageContainer");
|
|
/** Canvas dans lequel est inséré le poster de l'oeuvre courante avec tramage. */
|
|
const ditheredPoster: Ref<HTMLCanvasElement | undefined> = ref<HTMLCanvasElement>();
|
|
|
|
// Cycles
|
|
watch(() => entryStore.diaryEntry, async (): Promise<void> => {
|
|
if (!entryStore.diaryEntry || !entryStore.tmdbData) {
|
|
console.error("AddEntryPage - Pas d'entrée dans le magasin !");
|
|
await router.push("/");
|
|
return;
|
|
}
|
|
|
|
// Récupère les données à afficher.
|
|
displayData.value = {
|
|
budget: entryStore.tmdbData.budget ? `${String(convertToMillions(entryStore.tmdbData.budget))} M$` : "?",
|
|
genres: Arr.map(entryStore.tmdbData.genres, (genre: TmdbGenre) => genre.name),
|
|
hasUniqueOriginalTitle:
|
|
Str.toLowerCase(entryStore.diaryEntry.originalTitle) !== Str.toLowerCase(entryStore.diaryEntry.title),
|
|
mainCrew: getMainMovieCrew(entryStore.tmdbData.credits.crew),
|
|
originalLanguage: entryStore.tmdbData.original_language,
|
|
originalTitle: entryStore.diaryEntry.originalTitle,
|
|
overview: entryStore.diaryEntry.overview,
|
|
popularity: entryStore.diaryEntry.popularity,
|
|
releaseDate: entryStore.diaryEntry.releaseDate,
|
|
revenue: entryStore.tmdbData.revenue ? `${String(convertToMillions(entryStore.tmdbData.revenue))} M$` : "?",
|
|
runtime: `${String(entryStore.tmdbData.runtime)} min`,
|
|
tagline: entryStore.tmdbData.tagline,
|
|
title: entryStore.diaryEntry.title,
|
|
};
|
|
|
|
// Applique le tramage à l'affiche de l'oeuvre.
|
|
await RuntimeClient.runPromise(Effect.gen(function*() {
|
|
if (!entryStore.diaryEntry?.posterUrl) return;
|
|
|
|
yield* pipe(
|
|
generateDitheredBufferFromUrl(`https://image.tmdb.org/t/p/w500${entryStore.diaryEntry.posterUrl}`),
|
|
Effect.tapError(Effect.logError),
|
|
Effect.tap(ditheredBuffer => {
|
|
const canvas = canvasFromPixelBuffer(ditheredBuffer, imageContainer.value, { pixelated: true });
|
|
ditheredPoster.value = canvas;
|
|
}),
|
|
Effect.provide(PrettyLogger),
|
|
);
|
|
}));
|
|
}, { immediate: true });
|
|
</script>
|
|
|
|
<template>
|
|
<div class="stack">
|
|
<h2>Ajouter une entrée</h2>
|
|
|
|
<aside class="go-back">
|
|
<a class="link-with-iconography" href="javascript:void(0)" @click="router.back">
|
|
<span class="iconography">←</span>
|
|
<span class="text">Retour à la recherche</span></a>
|
|
</aside>
|
|
|
|
<div class="container switcher">
|
|
<div ref="imageContainer" class="canvas-container"></div>
|
|
<div class="stack">
|
|
<h3>{{ entryStore.diaryEntry?.originalTitle }}</h3>
|
|
|
|
<div class="original-metadata">
|
|
<span v-if="displayData?.hasUniqueOriginalTitle">{{ displayData?.title }} | </span>
|
|
{{ displayData?.releaseDate }} | {{ displayData?.runtime }} | {{ displayData?.originalLanguage }} | {{
|
|
displayData?.popularity
|
|
}} | {{ displayData?.budget }} -> {{ displayData?.revenue }}
|
|
</div>
|
|
|
|
<div class="main-movie-crew cluster">
|
|
<div class="department">
|
|
<strong>RÉALISATION </strong>
|
|
<ul>
|
|
<li v-for="director of displayData?.mainCrew.directors" :key="director.id">{{ director.name }}</li>
|
|
<li v-if="displayData?.mainCrew.directors.length === 0">/</li>
|
|
</ul>
|
|
</div>
|
|
<div class="department">
|
|
<strong>ÉCRITURE </strong>
|
|
<ul>
|
|
<li v-for="writer of displayData?.mainCrew.writers" :key="writer.id">{{ writer.name }}</li>
|
|
<li v-if="displayData?.mainCrew.writers.length === 0">/</li>
|
|
</ul>
|
|
</div>
|
|
<div class="department">
|
|
<strong>MONTAGE </strong>
|
|
<ul>
|
|
<li v-for="editor of displayData?.mainCrew.editors" :key="editor.id">{{ editor.name }}</li>
|
|
<li v-if="displayData?.mainCrew.editors.length === 0">/</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<hr>
|
|
|
|
<p v-if="displayData?.tagline" class="tagline">{{ displayData?.tagline }}</p>
|
|
<p class="overview">{{ displayData?.overview }}</p>
|
|
|
|
<ul class="genres">
|
|
<strong>Genres</strong>
|
|
<li v-for="genre of displayData?.genres" :key="Str.toLowerCase(genre)">{{ genre }}</li>
|
|
</ul>
|
|
|
|
<hr>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped lang="css">
|
|
.go-back {
|
|
margin-block-end: var(--s1);
|
|
margin-inline-start: calc(var(--s1) * -1);
|
|
}
|
|
|
|
.container {
|
|
--threshold: 51rem;
|
|
|
|
gap: var(--s2);
|
|
max-inline-size: 70rem;
|
|
}
|
|
|
|
.canvas-container {
|
|
aspect-ratio: 0.6;
|
|
width: 400px;
|
|
max-width: 400px;
|
|
height: 600px;
|
|
max-height: 600px;
|
|
border: 4px double var(--root-text-color);
|
|
background: var(--bg25-tertiary);
|
|
|
|
> * {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
}
|
|
}
|
|
|
|
.main-movie-crew {
|
|
--layout-cluster-gap: 2ch;
|
|
|
|
align-items: start;
|
|
}
|
|
|
|
hr {
|
|
block-size: 1px;
|
|
background: var(--root-text-color);
|
|
}
|
|
|
|
.overview {
|
|
hyphens: auto;
|
|
text-align: justify;
|
|
}
|
|
|
|
.tagline {
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.genres {
|
|
display: inline-flex;
|
|
|
|
strong {
|
|
margin-inline-end: 1ch;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
li + li {
|
|
padding-inline-start: 1ch;
|
|
|
|
&::before {
|
|
content: "·";
|
|
padding-inline-end: 1ch;
|
|
}
|
|
}
|
|
}
|
|
</style>
|