import { Injectable } from '@angular/core'; import { ImageDeleteRequest, ImageDeleteResponse, ImageListRequest, ImageListResponse, ImageUpdateRequest, ImageUpdateResponse, ImageUploadResponse } from 'picsur-shared/dist/dto/api/image-manage.dto'; import { ImageMetaResponse, ImageRequestParams } from 'picsur-shared/dist/dto/api/image.dto'; import { ImageLinks } from 'picsur-shared/dist/dto/image-links.class'; import { FileType2Ext } from 'picsur-shared/dist/dto/mimes.dto'; import { EImage } from 'picsur-shared/dist/entities/image.entity'; import { AsyncFailable } from 'picsur-shared/dist/types'; import { Fail, FT, HasFailed, HasSuccess, Open } from 'picsur-shared/dist/types/failable'; import { ImagesUploadRequest } from 'src/app/models/dto/images-upload-request.dto'; import { ImageUploadRequest } from '../../models/dto/image-upload-request.dto'; import { ApiService } from './api.service'; import { InfoService } from './info.service'; import { UserService } from './user.service'; @Injectable({ providedIn: 'root', }) export class ImageService { constructor( private readonly api: ApiService, private readonly infoService: InfoService, private readonly userService: UserService, ) {} public async UploadImage(image: File): AsyncFailable { const result = await this.api.postForm( ImageUploadResponse, '/api/image/upload', new ImageUploadRequest(image), ).result; return Open(result, 'id'); } public async UploadImages(images: File[]): AsyncFailable { console.log('Uploading images', images); // Split into chunks of 20 const groups = this.chunks(images, 20); const result = await this.api.postForm( ImageUploadResponse, '/api/image/upload/bulk', new ImagesUploadRequest(images), ); return []; } public async GetImageMeta(image: string): AsyncFailable { return await this.api.get(ImageMetaResponse, `/i/meta/${image}`).result; } public async ListAllImages( count: number, page: number, userID?: string, ): AsyncFailable { return await this.api.post( ImageListRequest, ImageListResponse, '/api/image/list', { count, page, user_id: userID, }, ).result; } public async ListMyImages( count: number, page: number, ): AsyncFailable { const userID = await this.userService.snapshot?.id; if (userID === undefined) { return Fail(FT.Authentication, 'User not logged in'); } return await this.ListAllImages(count, page, userID); } public async UpdateImage( id: string, settings: Partial>, ): AsyncFailable { return await this.api.post( ImageUpdateRequest, ImageUpdateResponse, '/api/image/update', { id, ...settings, }, ).result; } public async DeleteImages( images: string[], ): AsyncFailable { return await this.api.post( ImageDeleteRequest, ImageDeleteResponse, '/api/image/delete', { ids: images, }, ).result; } public async DeleteImage(image: string): AsyncFailable { const result = await this.DeleteImages([image]); if (HasFailed(result)) return result; if (result.images.length !== 1) { return Fail( FT.Unknown, `Image ${image} was not deleted, probably lacking permissions`, ); } return result.images[0]; } // Non api calls // Use for native images public GetImageURL( image: string, filetype: string | null, allowOverride = false, ): string { const baseURL = this.infoService.getHostname(allowOverride); const extension = FileType2Ext(filetype ?? ''); return `${baseURL}/i/${image}${ HasSuccess(extension) ? '.' + extension : '' }`; } // Use for user facing urls public CreateImageLinks(imageURL: string, name?: string): ImageLinks { return { source: imageURL, markdown: `![image](${imageURL})`, html: `${name ?? 'image'}`, rst: `.. image:: ${imageURL}`, bbcode: `[img]${imageURL}[/img]`, }; } public GetImageURLCustomized( image: string, filetype: string | null, options: ImageRequestParams, ): string { const baseURL = this.GetImageURL(image, filetype, true); const betterOptions = ImageRequestParams.zodSchema.safeParse(options); if (!betterOptions.success) return baseURL; let queryParams: string[] = []; if (options.height !== undefined) queryParams.push(`height=${options.height}`); if (options.width !== undefined) queryParams.push(`width=${options.width}`); if (options.rotate !== undefined) queryParams.push(`rotate=${options.rotate}`); if (options.flipx !== undefined) queryParams.push(`flipx=${options.flipx}`); if (options.flipy !== undefined) queryParams.push(`flipy=${options.flipy}`); if (options.shrinkonly !== undefined) queryParams.push(`shrinkonly=${options.shrinkonly}`); if (options.greyscale !== undefined) queryParams.push(`greyscale=${options.greyscale}`); if (options.noalpha !== undefined) queryParams.push(`noalpha=${options.noalpha}`); if (options.negative !== undefined) queryParams.push(`negative=${options.negative}`); if (options.quality !== undefined) queryParams.push(`quality=${options.quality}`); if (queryParams.length === 0) return baseURL; return baseURL + '?' + queryParams.join('&'); } public CreateImageLinksFromID( imageID: string, mime: string | null, name?: string, ): ImageLinks { return this.CreateImageLinks(this.GetImageURL(imageID, mime, true), name); } private chunks(arr: T[], size: number): T[][] { let result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, size + i)); } return result; } }