Image.vue 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  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, Ref } from 'vue'
  38. import { useImageFetch } from '~/composables/data/useImageFetch'
  39. import ImageManager from '~/services/data/imageManager'
  40. const props = defineProps({
  41. /**
  42. * Id de l'image (null si aucune)
  43. */
  44. imageId: {
  45. type: Number as PropType<number | null>,
  46. required: false,
  47. default: null,
  48. },
  49. /**
  50. * Image par défaut
  51. */
  52. defaultImage: {
  53. type: String,
  54. required: false,
  55. },
  56. /**
  57. * Hauteur de l'image à l'écran (en px)
  58. */
  59. height: {
  60. type: Number,
  61. required: false,
  62. },
  63. /**
  64. * Largeur de l'image à l'écran (en px)
  65. */
  66. width: {
  67. type: Number,
  68. required: false,
  69. },
  70. /**
  71. * Icône à afficher en overlay au survol de la souris
  72. */
  73. overlayIcon: {
  74. type: String,
  75. required: false,
  76. default: null,
  77. },
  78. })
  79. const { fetch } = useImageFetch()
  80. const defaultImagePath = props.defaultImage ?? ImageManager.defaultImage
  81. const emit = defineEmits(['overlay-clicked'])
  82. const fileId = toRef(props, 'imageId')
  83. const {
  84. data: imageSrc,
  85. pending,
  86. refresh: refreshImage,
  87. } = (await fetch(fileId, defaultImagePath, props.height, props.width)) as any
  88. console.log(imageSrc.value)
  89. const refresh = () => {
  90. refreshImage()
  91. }
  92. defineExpose({ refresh })
  93. /**
  94. * Si l'id change, on recharge l'image
  95. */
  96. const unwatch: WatchStopHandle = watch(
  97. () => props.imageId,
  98. async (value, oldValue) => {
  99. refresh()
  100. },
  101. )
  102. /**
  103. * Lorsqu'on démonte le component, on supprime le watcher
  104. */
  105. onUnmounted(() => {
  106. unwatch()
  107. })
  108. </script>
  109. <style lang="scss">
  110. div.image-wrapper {
  111. display: block;
  112. position: relative;
  113. img {
  114. display: block;
  115. max-width: 100%;
  116. }
  117. .overlay {
  118. position: absolute;
  119. top: 0;
  120. bottom: 0;
  121. left: 0;
  122. right: 0;
  123. height: 100%;
  124. width: 100%;
  125. opacity: 0;
  126. display: flex;
  127. align-items: center;
  128. justify-content: center;
  129. transition: 0.3s ease;
  130. }
  131. .overlay:hover {
  132. opacity: 0.8;
  133. background-color: rgb(var(--v-theme-neutral-strong));
  134. cursor: pointer;
  135. }
  136. .overlay .v-icon {
  137. color: rgb(var(--v-theme-on-neutral-strong));
  138. font-size: 36px;
  139. }
  140. }
  141. </style>