| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- <!--
- Composant Image permettant d'afficher une image stockée sur les serveurs Opentalent à partir de son id.
- Permet d'afficher une image par défaut si l'image demandée n'est pas disponible ou invalide.
- -->
- <template>
- <main>
- <div class="image-wrapper" :style="{ width: width + 'px' }">
- <v-img
- :src="imageSrc ?? undefined"
- :lazy-src="defaultImagePath"
- :height="height"
- :width="width"
- aspect-ratio="1"
- >
- <template #placeholder>
- <v-row
- v-if="pending"
- class="fill-height ma-0"
- align="center"
- justify="center"
- >
- <v-progress-circular :indeterminate="true" color="neutral" />
- </v-row>
- </template>
- <div
- v-if="!pending && overlayIcon"
- class="overlay"
- @click="emit('overlay-clicked')"
- >
- <v-icon>{{ overlayIcon }}</v-icon>
- </div>
- </v-img>
- </div>
- </main>
- </template>
- <script setup lang="ts">
- import type { WatchStopHandle } from 'vue'
- import { useImageFetch } from '~/composables/data/useImageFetch'
- import ImageManager from '~/services/data/imageManager'
- import { IMAGE_SIZE } from '~/types/enum/enums'
- const props = defineProps({
- /**
- * Id de l'image (null si aucune)
- */
- imageId: {
- type: Number as PropType<number | null>,
- required: false,
- default: null,
- },
- /**
- * Image par défaut
- */
- defaultImage: {
- type: String,
- required: false,
- default: null,
- },
- /**
- * Hauteur de l'image à l'écran (en px)
- */
- height: {
- type: Number,
- required: false,
- default: null,
- },
- /**
- * Largeur de l'image à l'écran (en px)
- */
- width: {
- type: Number,
- required: false,
- default: null,
- },
- /**
- * Taille de l'image fetchée depuis l'API (prédimensionnement)
- */
- size: {
- type: String as PropType<IMAGE_SIZE>,
- required: false,
- default: IMAGE_SIZE.MD,
- },
- /**
- * Icône à afficher en overlay au survol de la souris
- */
- overlayIcon: {
- type: String,
- required: false,
- default: null,
- },
- })
- const { fetch } = useImageFetch()
- const defaultImagePath = props.defaultImage ?? ImageManager.defaultImage
- const emit = defineEmits(['overlay-clicked'])
- const fileId = toRef(props, 'imageId')
- const refresh = () => {
- refreshImage()
- }
- defineExpose({ refresh })
- interface FetchResult {
- data: Ref<string | null>
- pending: Ref<boolean>
- refresh: () => void
- }
- const {
- data: imageSrc,
- pending,
- refresh: refreshImage,
- } = (await fetch(fileId, props.size, defaultImagePath)) as FetchResult
- /**
- * Si l'id change, on recharge l'image
- */
- const unwatch: WatchStopHandle = watch(
- () => props.imageId,
- async () => {
- refresh()
- },
- )
- /**
- * Lorsqu'on démonte le component, on supprime le watcher
- */
- onUnmounted(() => {
- unwatch()
- })
- </script>
- <style lang="scss">
- div.image-wrapper {
- display: block;
- position: relative;
- img {
- display: block;
- max-width: 100%;
- }
- .overlay {
- position: absolute;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
- height: 100%;
- width: 100%;
- opacity: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: 0.3s ease;
- }
- .overlay:hover {
- opacity: 0.8;
- background-color: rgb(var(--v-theme-neutral-strong));
- cursor: pointer;
- }
- .overlay .v-icon {
- color: rgb(var(--v-theme-on-neutral-strong));
- font-size: 36px;
- }
- }
- </style>
|