From 0f2045213f8399897c73f8e69f4fd5e3d5766f8e Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Mon, 13 Nov 2023 14:39:13 -0500 Subject: [PATCH] Add image url utilities --- src/api.ts | 15 +- .../api/__tests__/image-urls-api.test.ts | 114 +++++++++++++++ src/utils/api/image-api.ts | 7 +- src/utils/api/image-urls-api.ts | 138 ++++++++++++++++++ src/utils/index.ts | 2 +- 5 files changed, 268 insertions(+), 8 deletions(-) create mode 100644 src/utils/api/__tests__/image-urls-api.test.ts create mode 100644 src/utils/api/image-urls-api.ts diff --git a/src/api.ts b/src/api.ts index 128187bf4..e882749ed 100644 --- a/src/api.ts +++ b/src/api.ts @@ -12,6 +12,10 @@ import { ImageType } from './generated-client/models/image-type'; import type { ClientInfo, DeviceInfo } from './models'; import type { ImageRequestParameters } from './models/api/image-request-parameters'; import { getAuthorizationHeader } from './utils'; +import { getImageApi } from './utils/api/image-api'; +// NOTE: This import is used for TSDoc +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import type { ImageUrlsApi } from './utils/api/image-urls-api'; import { getSessionApi } from './utils/api/session-api'; import { getUserApi } from './utils/api/user-api'; @@ -78,6 +82,7 @@ export class Api { /** * Get an item image URL. + * @deprecated Use {@link ImageUrlsApi.getItemImageUrlById} instead. * @param itemId The Item ID. * @param imageType An optional Image Type (Primary by default). * @param params Additional request parameters. @@ -88,10 +93,12 @@ export class Api { imageType: ImageType = ImageType.Primary, params: ImageRequestParameters = {} ): string | undefined { - // TODO: We could probably use ImageApiAxiosParamCreator to make this more robust - return globalInstance.create({ - baseURL: this.basePath - }).getUri({ url: `/Items/${itemId}/Images/${imageType}`, params }); + return getImageApi(this) + .getItemImageUrlById( + itemId, + imageType, + params + ); } get authorizationHeader(): string { diff --git a/src/utils/api/__tests__/image-urls-api.test.ts b/src/utils/api/__tests__/image-urls-api.test.ts new file mode 100644 index 000000000..f79bdf88b --- /dev/null +++ b/src/utils/api/__tests__/image-urls-api.test.ts @@ -0,0 +1,114 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Configuration } from '../../../generated-client/configuration'; +import { ImageType } from '../../../generated-client/models/image-type'; +import { ImageUrlsApi } from '../image-urls-api'; + +const IMAGE_API = new ImageUrlsApi(new Configuration({ basePath: 'https://example.com' })); + +/** + * ImageUrlsApi class tests. + * + * @group unit + */ +describe('ImageUrlsApi', () => { + it('`getItemImageUrlById` should return an item image url', () => { + // Id only + expect(IMAGE_API.getItemImageUrlById('TEST')).toBe('https://example.com/Items/TEST/Images/Primary'); + // Id, ImageType, and Params + expect(IMAGE_API.getItemImageUrlById('TEST', ImageType.Backdrop, { fillWidth: 100, fillHeight: 100 })) + .toBe('https://example.com/Items/TEST/Images/Backdrop?fillWidth=100&fillHeight=100'); + }); + + it('`getItemImageUrl` should return an item image url', () => { + // Empty item + expect(IMAGE_API.getItemImageUrl()).toBeUndefined(); + expect(IMAGE_API.getItemImageUrl({})).toBeUndefined(); + // Item with Id only + expect(IMAGE_API.getItemImageUrl({ Id: 'TEST' })).toBe('https://example.com/Items/TEST/Images/Primary'); + // Item with Id and Tag + expect(IMAGE_API.getItemImageUrl({ Id: 'TEST', ImageTags: { [ImageType.Primary]: 'foo' } })) + .toBe('https://example.com/Items/TEST/Images/Primary?tag=foo'); + // Item with Id, ImageType, and Params + expect(IMAGE_API.getItemImageUrl( + { Id: 'TEST' }, + ImageType.Backdrop, + { fillWidth: 100, fillHeight: 100, tag: 'foo' }) + ).toBe('https://example.com/Items/TEST/Images/Backdrop?fillWidth=100&fillHeight=100&tag=foo'); + }); + + it('`getItemBackdropImageUrls` should return an array of image urls', () => { + // No Item + expect(IMAGE_API.getItemBackdropImageUrls()).toHaveLength(0); + // Item with Id only + expect(IMAGE_API.getItemBackdropImageUrls({ Id: 'TEST' })).toHaveLength(0); + // Item with Id and ParentBackdropItemId + expect(IMAGE_API.getItemBackdropImageUrls({ + Id: 'TEST', + ParentBackdropItemId: 'PARENT' + })).toHaveLength(0); + // Item with Id and BackdropImageTags + let backdropUrls = IMAGE_API.getItemBackdropImageUrls({ + Id: 'TEST', + BackdropImageTags: [ 'tag1', 'tag2' ] + }); + expect(backdropUrls).toHaveLength(2); + expect(backdropUrls[0]).toBe('https://example.com/Items/TEST/Images/Backdrop?tag=tag1'); + expect(backdropUrls[1]).toBe('https://example.com/Items/TEST/Images/Backdrop?tag=tag2'); + // TODO: Item with Id, ParentBackdropItemId, and ParentBackdropImageTags + backdropUrls = IMAGE_API.getItemBackdropImageUrls({ + Id: 'TEST', + ParentBackdropItemId: 'PARENT', + ParentBackdropImageTags: [ 'parent1', 'parent2', 'parent3' ] + }); + expect(backdropUrls).toHaveLength(3); + expect(backdropUrls[0]).toBe('https://example.com/Items/PARENT/Images/Backdrop?tag=parent1'); + expect(backdropUrls[1]).toBe('https://example.com/Items/PARENT/Images/Backdrop?tag=parent2'); + expect(backdropUrls[1]).toBe('https://example.com/Items/PARENT/Images/Backdrop?tag=parent2'); + }); + + it('`getSplashscreenImageUrl` should return an image url', () => { + // No Params + expect(IMAGE_API.getSplashscreenImageUrl()).toBe('https://example.com/Branding/Splashscreen'); + // Params + expect(IMAGE_API.getSplashscreenImageUrl({ fillWidth: 100, fillHeight: 100 })) + .toBe('https://example.com/Branding/Splashscreen?fillWidth=100&fillHeight=100'); + }); + + it('`getUserImageUrl` should return a user image url', () => { + // No User + expect(IMAGE_API.getUserImageUrl()).toBeUndefined(); + // User with Id + expect(IMAGE_API.getUserImageUrl({ Id: 'TEST' })).toBe('https://example.com/Users/TEST/Images/Primary'); + // User with Id and Tag + expect(IMAGE_API.getUserImageUrl({ Id: 'TEST', PrimaryImageTag: 'foo' })) + .toBe('https://example.com/Users/TEST/Images/Primary?tag=foo'); + // User with Id and Tag and Params + expect(IMAGE_API.getUserImageUrl({ Id: 'TEST', PrimaryImageTag: 'foo' }, { fillWidth: 100, fillHeight: 100 })) + .toBe('https://example.com/Users/TEST/Images/Primary?fillWidth=100&fillHeight=100&tag=foo'); + }); + + it('should return relative paths when no basePath is configured', () => { + const relativeImageApi = new ImageUrlsApi(); + // getItemImageUrlById + expect(relativeImageApi.getItemImageUrlById('TEST')).toBe('/Items/TEST/Images/Primary'); + // getItemImageUrl + expect(relativeImageApi.getItemImageUrl({ Id: 'TEST' })).toBe('/Items/TEST/Images/Primary'); + // getItemBackdropImageUrls + const backdropUrls = relativeImageApi.getItemBackdropImageUrls({ + Id: 'TEST', + BackdropImageTags: [ 'tag1', 'tag2' ] + }); + expect(backdropUrls).toHaveLength(2); + expect(backdropUrls[0]).toBe('/Items/TEST/Images/Backdrop?tag=tag1'); + expect(backdropUrls[1]).toBe('/Items/TEST/Images/Backdrop?tag=tag2'); + // getSplashscreenImageUrl + expect(relativeImageApi.getSplashscreenImageUrl()).toBe('/Branding/Splashscreen'); + // getUserImageUrl + expect(relativeImageApi.getUserImageUrl({ Id: 'TEST' })).toBe('/Users/TEST/Images/Primary'); + }); +}); diff --git a/src/utils/api/image-api.ts b/src/utils/api/image-api.ts index 1d3da3a9e..7159b0817 100644 --- a/src/utils/api/image-api.ts +++ b/src/utils/api/image-api.ts @@ -5,8 +5,9 @@ */ import type { Api } from '../../api'; -import { ImageApi } from '../../generated-client/api/image-api'; -export function getImageApi(api: Api): ImageApi { - return new ImageApi(api.configuration, undefined, api.axiosInstance); +import { ImageUrlsApi } from './image-urls-api'; + +export function getImageApi(api: Api): ImageUrlsApi { + return new ImageUrlsApi(api.configuration, undefined, api.axiosInstance); } diff --git a/src/utils/api/image-urls-api.ts b/src/utils/api/image-urls-api.ts new file mode 100644 index 000000000..e4b1d60de --- /dev/null +++ b/src/utils/api/image-urls-api.ts @@ -0,0 +1,138 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import type { ImageApiGetSplashscreenRequest } from '../../generated-client/api/image-api'; +import { ImageApi } from '../../generated-client/api/image-api'; +import type { BaseItemDto } from '../../generated-client/models/base-item-dto'; +import { ImageType } from '../../generated-client/models/image-type'; +import type { UserDto } from '../../generated-client/models/user-dto'; +import type { ImageRequestParameters } from '../../models/api/image-request-parameters'; + +/** + * ImageUrlsApi - ImageApi with URL utility methods + * @export + * @class ImageUrlsApi + * @extends {ImageApi} + */ +export class ImageUrlsApi extends ImageApi { + /** + * Get an Item image URL for a given Item ID. + * @param itemId The Item ID. + * @param [imageType=ImageType.Primary] An optional Image Type. + * @param [params={}] Additional request parameters. + * @returns The image URL. + */ + public getItemImageUrlById( + itemId: string, + imageType: ImageType = ImageType.Primary, + params: ImageRequestParameters = {} + ): string { + // TODO: We could probably use ImageApiAxiosParamCreator to make this more robust + return this.axios.getUri({ + baseURL: this.configuration?.basePath, + url: `/Items/${itemId}/Images/${imageType}`, + params + }); + } + + /** + * Get an Item image URL with automatic `tag` param handling. + * @param item The Item. + * @param [imageType] An optional Image Type (Primary by default). + * @param [params={}] Additional request parameters. + * @returns The Item image URL. + */ + public getItemImageUrl(item?: BaseItemDto, imageType: ImageType = ImageType.Primary, params: ImageRequestParameters = {}): string | undefined { + if (!item?.Id) return; + + return this.getItemImageUrlById( + item.Id, + imageType, + { + ...params, + tag: params.tag ?? item.ImageTags?.[imageType] + } + ); + } + + /** + * Get an Item's backdrop image URLs. + * @param item The Item. + * @param [params={}] Additional request parameters. + * @returns An array of backdrop image URLs. + */ + public getItemBackdropImageUrls(item?: BaseItemDto, params: ImageRequestParameters = {}): string[] { + const urls: string[] = []; + const id = item?.Id; + + if (!id) return urls; + + item.BackdropImageTags?.forEach(tag => { + const url = this.getItemImageUrlById( + id, + ImageType.Backdrop, + { + ...params, + tag + } + ); + + urls.push(url); + }); + + if (urls.length > 0) return urls; + + const parentId = item.ParentBackdropItemId; + if (parentId) { + item.ParentBackdropImageTags?.forEach(tag => { + const url = this.getItemImageUrlById( + parentId, + ImageType.Backdrop, + { + ...params, + tag + } + ); + + urls.push(url); + }); + } + + return urls; + } + + /** + * Get the splash screen image URL. + * @param [params={}] Additional request parameters. + * @returns The splash screen image URL. + */ + public getSplashscreenImageUrl(params: ImageApiGetSplashscreenRequest = {}): string { + return this.axios.getUri({ + baseURL: this.configuration?.basePath, + url: '/Branding/Splashscreen', + params + }); + } + + /** + * Get a User's primary image URL. + * @param user The User. + * @param [params={}] Additional request parameters. + * @returns The User's primary image URL. + */ + public getUserImageUrl(user?: UserDto, params: ImageRequestParameters = {}): string | undefined { + if (!user?.Id) return; + + return this.axios.getUri({ + baseURL: this.configuration?.basePath, + url: `/Users/${user.Id}/Images/Primary`, + params: { + ...params, + tag: user.PrimaryImageTag + } + }); + } +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 38591c9ae..7d261dc0f 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -6,6 +6,6 @@ export * from './address-candidates'; export * from './authentication'; -export * from './url'; export * from './browser-profiles'; +export * from './url'; export * from './versioning';