Image.vue 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  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. class="fill-height ma-0"
  18. align="center"
  19. justify="center"
  20. v-if="pending"
  21. >
  22. <v-progress-circular
  23. :indeterminate="true"
  24. color="neutral"
  25. />
  26. </v-row>
  27. </template>
  28. <div v-if="!pending && overlayIcon" class="overlay" @click="emit('overlay-clicked')">
  29. <v-icon>{{ overlayIcon }}</v-icon>
  30. </div>
  31. </v-img>
  32. </div>
  33. </main>
  34. </template>
  35. <script setup lang="ts">
  36. import {useImageFetch} from "~/composables/data/useImageFetch";
  37. import ImageManager from "~/services/data/imageManager";
  38. import type {WatchStopHandle} from "@vue/runtime-core";
  39. import type {Ref} from "@vue/reactivity";
  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 { data: imageSrc, pending, refresh: refreshImage } = await fetch(fileId, defaultImagePath, props.height, props.width) as any
  84. const refresh = () => {
  85. refreshImage()
  86. }
  87. defineExpose({ refresh })
  88. /**
  89. * Si l'id change, on recharge l'image
  90. */
  91. const unwatch: WatchStopHandle = watch(() => props.imageId, async (value, oldValue) => {
  92. refresh()
  93. })
  94. /**
  95. * Lorsqu'on démonte le component, on supprime le watcher
  96. */
  97. onUnmounted(() => {
  98. unwatch()
  99. })
  100. </script>
  101. <style lang="scss">
  102. div.image-wrapper {
  103. display: block;
  104. position: relative;
  105. img {
  106. display: block;
  107. max-width: 100%;
  108. }
  109. .overlay {
  110. position: absolute;
  111. top: 0;
  112. bottom: 0;
  113. left: 0;
  114. right: 0;
  115. height: 100%;
  116. width: 100%;
  117. opacity: 0;
  118. display: flex;
  119. align-items: center;
  120. justify-content: center;
  121. transition: .3s ease;
  122. }
  123. .overlay:hover {
  124. opacity: 0.8;
  125. background-color: rgb(var(--v-theme-neutral-strong));
  126. cursor: pointer;
  127. }
  128. .overlay .v-icon {
  129. color: rgb(var(--v-theme-on-neutral-strong));
  130. font-size: 36px;
  131. }
  132. }
  133. </style>