import type ApiRequestService from './apiRequestService' import FileUtils from '~/services/utils/fileUtils' 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 */ class ImageManager { private apiRequestService: ApiRequestService public static readonly defaultImage = '/images/default/picture.jpeg' public constructor(apiRequestService: ApiRequestService) { this.apiRequestService = apiRequestService } /** * Retourne l'image correspondante sous forme soit d'une URL, soit d'un blob encodé au format base64. * * 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 size * @param defaultImage The path of an image in the 'public' folder, default: '/images/default/picture.jpeg' */ public get( id: number | string | null, size: IMAGE_SIZE = IMAGE_SIZE.MD, defaultImage: string | null = null, ): Promise { const defaultUrl = defaultImage ?? ImageManager.defaultImage if (id === null) { return Promise.resolve(defaultUrl) } 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 TypeError('Error: image ' + id + ' is invalid') } try { return size === IMAGE_SIZE.RAW ? this.getRaw(id) : this.getProcessed(id, size) } catch (error) { console.error(error) return Promise.resolve(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 { const imageUrl = `api/image/download/${id}/${size}` // Une image doit toujours avoir le time en options pour éviter les problèmes de cache const query: AssociativeArray = { 0: this.getCacheKey() } const response = await this.apiRequestService.get(imageUrl, query) const cachedImageUrl = response.toString() if (!cachedImageUrl) { 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 { 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 await this.toBase64(blobPart) } public async upload( filename: string, content: string, visibility: string = FILE_VISIBILITY.NOBODY, config: string | null = null, ) { content = content.replace(/^data:image\/[\w/]+;base64,/, '') const data = JSON.stringify({ filename, content, type: FILE_TYPE.UPLOADED, visibility, config, }) return await this.apiRequestService.post('api/upload', data) } /** * Convert the API response into base64 * @param data * @protected */ protected async toBase64(data: BlobPart) { const blob = FileUtils.newBlob(data) return (await FileUtils.blobToBase64(blob)) ?? '' } /** * On passe cette clé en paramètres des requêtes pour éviter les problèmes de cache * * @protected */ protected getCacheKey() { return new Date().getTime().toString() } } export default ImageManager