Olivier Massot 9 месяцев назад
Родитель
Сommit
8bf8b622db

+ 11 - 8
components/Ui/Image.vue

@@ -39,7 +39,7 @@ Permet d'afficher une image par défaut si l'image demandée n'est pas disponibl
 import { useImageFetch } from '~/composables/data/useImageFetch'
 import { useImageFetch } from '~/composables/data/useImageFetch'
 import ImageManager from '~/services/data/imageManager'
 import ImageManager from '~/services/data/imageManager'
 import type { WatchStopHandle } from '@vue/runtime-core'
 import type { WatchStopHandle } from '@vue/runtime-core'
-import type { Ref } from '@vue/reactivity'
+import { IMAGE_SIZE } from '~/types/enum/enums'
 
 
 const props = defineProps({
 const props = defineProps({
   /**
   /**
@@ -71,6 +71,14 @@ const props = defineProps({
     type: Number,
     type: Number,
     required: false,
     required: false,
   },
   },
+  /**
+   * 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
    * Icône à afficher en overlay au survol de la souris
    */
    */
@@ -93,12 +101,7 @@ const {
   data: imageSrc,
   data: imageSrc,
   pending,
   pending,
   refresh: refreshImage,
   refresh: refreshImage,
-} = (await fetch(fileId, defaultImagePath, props.height, props.width)) as any
-
-// watch(imageSrc, (value) => {
-//   console.log('img', imageSrc.value)
-// })
-
+} = (await fetch(fileId, props.size, defaultImagePath)) as any
 
 
 const refresh = () => {
 const refresh = () => {
   refreshImage()
   refreshImage()
@@ -110,7 +113,7 @@ defineExpose({ refresh })
  */
  */
 const unwatch: WatchStopHandle = watch(
 const unwatch: WatchStopHandle = watch(
   () => props.imageId,
   () => props.imageId,
-  async (value, oldValue) => {
+  async () => {
     refresh()
     refresh()
   },
   },
 )
 )

+ 5 - 3
components/Ui/Input/Image.vue

@@ -100,9 +100,10 @@ import File from '~/models/Core/File'
 import type { PropType } from '@vue/runtime-core'
 import type { PropType } from '@vue/runtime-core'
 import { useEntityManager } from '~/composables/data/useEntityManager'
 import { useEntityManager } from '~/composables/data/useEntityManager'
 import { useImageManager } from '~/composables/data/useImageManager'
 import { useImageManager } from '~/composables/data/useImageManager'
-import { FILE_VISIBILITY, TYPE_ALERT } from '~/types/enum/enums'
+import { FILE_VISIBILITY, IMAGE_SIZE, TYPE_ALERT } from '~/types/enum/enums'
 import { usePageStore } from '~/stores/page'
 import { usePageStore } from '~/stores/page'
 import FileUtils from '~/services/utils/fileUtils'
 import FileUtils from '~/services/utils/fileUtils'
+import { useImageFetch } from '~/composables/data/useImageFetch'
 
 
 const props = defineProps({
 const props = defineProps({
   /**
   /**
@@ -257,7 +258,7 @@ const loadImage = async (fileId: number) => {
 
 
   currentImage.value.name = file.value.name
   currentImage.value.name = file.value.name
   currentImage.value.id = file.value.id
   currentImage.value.id = file.value.id
-  currentImage.value.src = (await imageManager.get(fileId)) as string
+  currentImage.value.src = await imageManager.get(fileId, IMAGE_SIZE.RAW)
 }
 }
 
 
 /**
 /**
@@ -414,13 +415,14 @@ const save = async () => {
   } else if (currentImage.value.id) {
   } else if (currentImage.value.id) {
     // L'image existante a été modifiée
     // L'image existante a été modifiée
     await saveExistingImage()
     await saveExistingImage()
-    uiImage.value.refresh()
   } else {
   } else {
     // On a reset l'image
     // On a reset l'image
     emit('update:modelValue', null)
     emit('update:modelValue', null)
   }
   }
 
 
   showModal.value = false
   showModal.value = false
+
+  uiImage.value.refresh()
   pageStore.loading = false
   pageStore.loading = false
 }
 }
 
 

+ 4 - 5
composables/data/useImageFetch.ts

@@ -7,9 +7,8 @@ import { IMAGE_SIZE } from '~/types/enum/enums'
 interface useImageFetchReturnType {
 interface useImageFetchReturnType {
   fetch: (
   fetch: (
     id: Ref<number | null>,
     id: Ref<number | null>,
+    size?: IMAGE_SIZE,
     defaultImage?: string | null,
     defaultImage?: string | null,
-    height?: number,
-    width?: number,
   ) => AsyncData<string | ArrayBuffer | null, Error | null>
   ) => AsyncData<string | ArrayBuffer | null, Error | null>
 }
 }
 
 
@@ -21,12 +20,12 @@ export const useImageFetch = (): useImageFetchReturnType => {
 
 
   const fetch = (
   const fetch = (
     id: Ref<number | null>, // If id is null, fetch shall return the default image url
     id: Ref<number | null>, // If id is null, fetch shall return the default image url
-    defaultImage: string | null = null,
-    size: IMAGE_SIZE = IMAGE_SIZE.MD
+    size: IMAGE_SIZE = IMAGE_SIZE.MD,
+    defaultImage: string | null = null
   ) =>
   ) =>
     useAsyncData(
     useAsyncData(
       'img' + (id ?? defaultImage ?? 0) + '_' + uuid4(),
       'img' + (id ?? defaultImage ?? 0) + '_' + uuid4(),
-      () => imageManager.get(id.value, defaultImage, size),
+      () => imageManager.get(id.value, size, defaultImage),
       { lazy: true, server: false }, // Always fetch images client-side
       { lazy: true, server: false }, // Always fetch images client-side
     )
     )
 
 

+ 72 - 12
services/data/imageManager.ts

@@ -1,6 +1,8 @@
 import ApiRequestService from './apiRequestService'
 import ApiRequestService from './apiRequestService'
 import FileUtils from '~/services/utils/fileUtils'
 import FileUtils from '~/services/utils/fileUtils'
 import { FILE_TYPE, FILE_VISIBILITY, IMAGE_SIZE } from '~/types/enum/enums'
 import { FILE_TYPE, FILE_VISIBILITY, IMAGE_SIZE } from '~/types/enum/enums'
+import type { AssociativeArray } from '~/types/data'
+import UrlUtils from '~/services/utils/urlUtils'
 
 
 /**
 /**
  * Permet le requêtage, l'upload et la manipulation des images via l'API Opentalent
  * Permet le requêtage, l'upload et la manipulation des images via l'API Opentalent
@@ -14,20 +16,19 @@ class ImageManager {
   }
   }
 
 
   /**
   /**
-   * Retourne l'image correspondante sous forme d'un blob encodé au format base64,
-   * ou l'url d'une image par défaut si l'image est introuvable ou si l'id passé en paramètre est null
+   * Retourne l'image correspondante sous forme soit d'une URL, soit d'un blob encodé au format base64.
    *
    *
-   * Attention, les dimensions (hauteur / largeur) ne s'appliqueront pas à l'image par défaut, il est nécessaire de
-   * les redéfinir dans le composant lui-même.
+   * Retourne l'url de l'image par défaut si l'image est introuvable
+   * ou si l'id passé en paramètre est null.
    *
    *
    * @param id  The id of the image; if null, the url to the default image is returned
    * @param id  The id of the image; if null, the url to the default image is returned
-   * @param defaultImage The path of an image in the 'public' folder, default: '/images/default/picture.jpeg'
    * @param size
    * @param size
+   * @param defaultImage The path of an image in the 'public' folder, default: '/images/default/picture.jpeg'
    */
    */
   public async get(
   public async get(
-    id: number | null,
+    id: number | string | null,
+    size: IMAGE_SIZE = IMAGE_SIZE.MD,
     defaultImage: string | null = null,
     defaultImage: string | null = null,
-    size: IMAGE_SIZE = IMAGE_SIZE.MD
   ): Promise<string> {
   ): Promise<string> {
     const defaultUrl = defaultImage ?? ImageManager.defaultImage
     const defaultUrl = defaultImage ?? ImageManager.defaultImage
 
 
@@ -35,21 +36,80 @@ class ImageManager {
       return defaultUrl
       return defaultUrl
     }
     }
 
 
-    const imageUrl = `api/image/download/${id}/${size}`
+    const matches = id.toString().match(/\/api\/files\/(\d+)(?:\/\w+)?/)
+    if (matches) {
+      // Lors de l'enregistrement d'une entité, les ids des objets liés sont
+      // temporairement convertis en IRI. Avec la réactivité, ceci peut
+      // générer une erreur temporaire avec les liens des images, d'où ce patch.
+      id = parseInt(matches[1])
+    }
+
+    if (!(typeof id === 'number' && Number.isInteger(id))) {
+      throw new Error('Error: image ' + id + ' is invalid')
+    }
+
+    try {
+      return size === IMAGE_SIZE.RAW ?
+        this.getRaw(id) :
+        this.getProcessed(id, size)
+
+    } catch (error) {
+      console.error(error)
+      return defaultUrl
+    }
+  }
+
+  /**
+   * Retourne une image dimensionnée et cropped depuis le cache Liip.
+   *
+   * @param id  The id of the image; if null, the url to the default image is returned
+   * @param size
+   */
+  private async getProcessed(
+    id: number | null,
+    size: IMAGE_SIZE = IMAGE_SIZE.MD
+  ): Promise<string> {
+
+    let imageUrl = `api/image/download/${id}/${size}`
 
 
     // Une image doit toujours avoir le time en options pour éviter les problèmes de cache
     // Une image doit toujours avoir le time en options pour éviter les problèmes de cache
-    const query = [this.getCacheKey()]
+    const query: AssociativeArray = {0: this.getCacheKey()}
 
 
     const response = await this.apiRequestService.get(imageUrl, query);
     const response = await this.apiRequestService.get(imageUrl, query);
 
 
     const cachedImageUrl = response.toString()
     const cachedImageUrl = response.toString()
 
 
     if (!cachedImageUrl) {
     if (!cachedImageUrl) {
-      console.error('Error: image ' + id + ' not found');
-      return defaultUrl;
+      throw new Error('Error: image ' + id + ' not found');
+    }
+
+    return UrlUtils.addQuery(cachedImageUrl, query)
+  }
+
+  /**
+   * Retourne l'image non traitée. Utilisé entre autres pour le
+   * cropper du UiInputImage.
+   *
+   * @param id
+   * @private
+   */
+  private async getRaw(id: number | null): Promise<string> {
+
+    const imageUrl = `api/file/download/${id}`
+
+    // Une image doit toujours avoir le time en options pour éviter les problèmes de cache
+    const query = [this.getCacheKey()]
+
+    const blobPart = await this.apiRequestService.get(imageUrl, query);
+    if (!blobPart) {
+      throw new Error('Error: image ' + id + ' not found');
+    }
+
+    if (!(blobPart instanceof Blob) || blobPart.size === 0) {
+      throw new Error('Error: image ' + id + ' is invalid');
     }
     }
 
 
-    return cachedImageUrl
+    return await this.toBase64(blobPart)
   }
   }
 
 
   public async upload(
   public async upload(

+ 1 - 0
types/enum/enums.ts

@@ -116,4 +116,5 @@ export const enum IMAGE_SIZE {
   SM = 'sm',
   SM = 'sm',
   MD = 'md',
   MD = 'md',
   LG = 'lg',
   LG = 'lg',
+  RAW = 'raw'
 }
 }