This commit is contained in:
parent
3f546c4396
commit
5688e10d0d
42 changed files with 635 additions and 295 deletions
|
|
@ -1,13 +1,218 @@
|
|||
<script setup lang="ts">
|
||||
import { useEntryStore } from "@/stores/entry";
|
||||
import { onMounted } from "vue";
|
||||
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();
|
||||
|
||||
onMounted(() => {
|
||||
console.debug(entryStore.entry);
|
||||
console.debug(entryStore.tmdbData);
|
||||
});
|
||||
// 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></template>
|
||||
<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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue