Image.vue 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  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. },
  57. /**
  58. * Hauteur de l'image à l'écran (en px)
  59. */
  60. height: {
  61. type: Number,
  62. required: false,
  63. },
  64. /**
  65. * Largeur de l'image à l'écran (en px)
  66. */
  67. width: {
  68. type: Number,
  69. required: false,
  70. },
  71. /**
  72. * Taille de l'image fetchée depuis l'API (prédimensionnement)
  73. */
  74. size: {
  75. type: String as PropType<IMAGE_SIZE>,
  76. required: false,
  77. default: IMAGE_SIZE.MD,
  78. },
  79. /**
  80. * Icône à afficher en overlay au survol de la souris
  81. */
  82. overlayIcon: {
  83. type: String,
  84. required: false,
  85. default: null,
  86. },
  87. })
  88. const { fetch } = useImageFetch()
  89. const defaultImagePath = props.defaultImage ?? ImageManager.defaultImage
  90. const emit = defineEmits(['overlay-clicked'])
  91. const fileId = toRef(props, 'imageId')
  92. const {
  93. data: imageSrc,
  94. pending,
  95. refresh: refreshImage,
  96. } = (await fetch(fileId, props.size, defaultImagePath)) as any
  97. const refresh = () => {
  98. refreshImage()
  99. }
  100. defineExpose({ refresh })
  101. /**
  102. * Si l'id change, on recharge l'image
  103. */
  104. const unwatch: WatchStopHandle = watch(
  105. () => props.imageId,
  106. async () => {
  107. refresh()
  108. },
  109. )
  110. /**
  111. * Lorsqu'on démonte le component, on supprime le watcher
  112. */
  113. onUnmounted(() => {
  114. unwatch()
  115. })
  116. </script>
  117. <style lang="scss">
  118. div.image-wrapper {
  119. display: block;
  120. position: relative;
  121. img {
  122. display: block;
  123. max-width: 100%;
  124. }
  125. .overlay {
  126. position: absolute;
  127. top: 0;
  128. bottom: 0;
  129. left: 0;
  130. right: 0;
  131. height: 100%;
  132. width: 100%;
  133. opacity: 0;
  134. display: flex;
  135. align-items: center;
  136. justify-content: center;
  137. transition: 0.3s ease;
  138. }
  139. .overlay:hover {
  140. opacity: 0.8;
  141. background-color: rgb(var(--v-theme-neutral-strong));
  142. cursor: pointer;
  143. }
  144. .overlay .v-icon {
  145. color: rgb(var(--v-theme-on-neutral-strong));
  146. font-size: 36px;
  147. }
  148. }
  149. </style>