Image.vue 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. <!--
  2. Composant Image permettant d'afficher une image stockée sur les serveurs Opentalent à partir de son id.
  3. Permet d'afficher une image par défaut si l'image demandée n'est pas disponible ou invalide.
  4. -->
  5. <template>
  6. <main>
  7. <div class="image-wrapper" :style="{ width: width + 'px' }">
  8. <v-img
  9. :src="imageSrc ?? undefined"
  10. :lazy-src="defaultImagePath"
  11. :height="height"
  12. :width="width"
  13. aspect-ratio="1"
  14. >
  15. <template #placeholder>
  16. <v-row
  17. v-if="pending"
  18. class="fill-height ma-0"
  19. align="center"
  20. justify="center"
  21. >
  22. <v-progress-circular :indeterminate="true" color="neutral" />
  23. </v-row>
  24. </template>
  25. <div
  26. v-if="!pending && overlayIcon"
  27. class="overlay"
  28. @click="emit('overlay-clicked')"
  29. >
  30. <v-icon>{{ overlayIcon }}</v-icon>
  31. </div>
  32. </v-img>
  33. </div>
  34. </main>
  35. </template>
  36. <script setup lang="ts">
  37. import type { WatchStopHandle } from 'vue'
  38. import { useImageFetch } from '~/composables/data/useImageFetch'
  39. import ImageManager from '~/services/data/imageManager'
  40. import { IMAGE_SIZE } from '~/types/enum/enums'
  41. const props = defineProps({
  42. /**
  43. * Id de l'image (null si aucune)
  44. */
  45. imageId: {
  46. type: Number as PropType<number | null>,
  47. required: false,
  48. default: null,
  49. },
  50. /**
  51. * Image par défaut
  52. */
  53. defaultImage: {
  54. type: String,
  55. required: false,
  56. default: null,
  57. },
  58. /**
  59. * Hauteur de l'image à l'écran (en px)
  60. */
  61. height: {
  62. type: Number,
  63. required: false,
  64. default: null,
  65. },
  66. /**
  67. * Largeur de l'image à l'écran (en px)
  68. */
  69. width: {
  70. type: Number,
  71. required: false,
  72. default: null,
  73. },
  74. /**
  75. * Taille de l'image fetchée depuis l'API (prédimensionnement)
  76. */
  77. size: {
  78. type: String as PropType<IMAGE_SIZE>,
  79. required: false,
  80. default: IMAGE_SIZE.MD,
  81. },
  82. /**
  83. * Icône à afficher en overlay au survol de la souris
  84. */
  85. overlayIcon: {
  86. type: String,
  87. required: false,
  88. default: null,
  89. },
  90. })
  91. const { fetch } = useImageFetch()
  92. const defaultImagePath = props.defaultImage ?? ImageManager.defaultImage
  93. const emit = defineEmits(['overlay-clicked'])
  94. const fileId = toRef(props, 'imageId')
  95. const refresh = () => {
  96. refreshImage()
  97. }
  98. defineExpose({ refresh })
  99. interface FetchResult {
  100. data: Ref<string | null>
  101. pending: Ref<boolean>
  102. refresh: () => void
  103. }
  104. const {
  105. data: imageSrc,
  106. pending,
  107. refresh: refreshImage,
  108. } = (await fetch(fileId, props.size, defaultImagePath)) as FetchResult
  109. /**
  110. * Si l'id change, on recharge l'image
  111. */
  112. const unwatch: WatchStopHandle = watch(
  113. () => props.imageId,
  114. async () => {
  115. refresh()
  116. },
  117. )
  118. /**
  119. * Lorsqu'on démonte le component, on supprime le watcher
  120. */
  121. onUnmounted(() => {
  122. unwatch()
  123. })
  124. </script>
  125. <style lang="scss">
  126. div.image-wrapper {
  127. display: block;
  128. position: relative;
  129. img {
  130. display: block;
  131. max-width: 100%;
  132. }
  133. .overlay {
  134. position: absolute;
  135. top: 0;
  136. bottom: 0;
  137. left: 0;
  138. right: 0;
  139. height: 100%;
  140. width: 100%;
  141. opacity: 0;
  142. display: flex;
  143. align-items: center;
  144. justify-content: center;
  145. transition: 0.3s ease;
  146. }
  147. .overlay:hover {
  148. opacity: 0.8;
  149. background-color: rgb(var(--v-theme-neutral-strong));
  150. cursor: pointer;
  151. }
  152. .overlay .v-icon {
  153. color: rgb(var(--v-theme-on-neutral-strong));
  154. font-size: 36px;
  155. }
  156. }
  157. </style>