From 0efae0ac7dc6305d4dc1aad94d2680ed1a25ced2 Mon Sep 17 00:00:00 2001 From: Alina Zaieva Date: Mon, 27 Mar 2023 15:51:27 +0200 Subject: [PATCH 1/2] Implement getFileTypeIconAsUrl --- .../src/getFileTypeIconAsUrl.test.ts | 98 +++++++++++++++++++ .../src/getFileTypeIconAsUrl.ts | 32 ++++++ packages/react-file-type-icons/src/index.ts | 2 + 3 files changed, 132 insertions(+) create mode 100644 packages/react-file-type-icons/src/getFileTypeIconAsUrl.test.ts create mode 100644 packages/react-file-type-icons/src/getFileTypeIconAsUrl.ts diff --git a/packages/react-file-type-icons/src/getFileTypeIconAsUrl.test.ts b/packages/react-file-type-icons/src/getFileTypeIconAsUrl.test.ts new file mode 100644 index 0000000000000..e67f8c4e4fa9a --- /dev/null +++ b/packages/react-file-type-icons/src/getFileTypeIconAsUrl.test.ts @@ -0,0 +1,98 @@ +import { ICON_SIZES, DEFAULT_BASE_URL } from './initializeFileTypeIcons'; +import { DEFAULT_ICON_SIZE } from './getFileTypeIconProps'; +import type { FileTypeIconSize } from './getFileTypeIconProps'; +import { getFileTypeIconAsUrl } from './getFileTypeIconAsUrl'; + +// Currently this test file only covers the default device pixel ratio, i.e 1 +const getExpectedUrl = (iconSize: FileTypeIconSize, suffix: string, expectedExt: string) => { + return `${DEFAULT_BASE_URL}${iconSize}/${expectedExt}.${suffix}`; +}; + +// Test suite 1 +describe('returns expected urls', () => { + it('returns the correct url for all valid icon sizes with default as svg', () => { + ICON_SIZES.forEach((iconSize: number) => { + const res = getFileTypeIconAsUrl({ + size: iconSize as FileTypeIconSize, + extension: 'doc', + }); + expect(res).toEqual(getExpectedUrl(iconSize as FileTypeIconSize, 'svg', 'docx')); + }); + + ICON_SIZES.forEach((iconSize: number) => { + const res = getFileTypeIconAsUrl({ + size: iconSize as FileTypeIconSize, + extension: 'accdb', + }); + expect(res).toEqual(getExpectedUrl(iconSize as FileTypeIconSize, 'svg', 'accdb')); + }); + }); + + it('returns the correct url for all valid icon sizes with type as png', () => { + ICON_SIZES.forEach((iconSize: number) => { + const res = getFileTypeIconAsUrl({ + size: iconSize as FileTypeIconSize, + extension: 'doc', + imageFileType: 'png', + }); + expect(res).toEqual(getExpectedUrl(iconSize as FileTypeIconSize, 'png', 'docx')); + }); + + ICON_SIZES.forEach((iconSize: number) => { + const res = getFileTypeIconAsUrl({ + size: iconSize as FileTypeIconSize, + extension: 'accdb', + imageFileType: 'png', + }); + expect(res).toEqual(getExpectedUrl(iconSize as FileTypeIconSize, 'png', 'accdb')); + }); + }); +}); + +// Test suite 2 +describe('Returns genericfile for invalid inputs', () => { + it('returns genericfile for invalid extension with default type as svg', () => { + ICON_SIZES.forEach((iconSize: number) => { + const res = getFileTypeIconAsUrl({ + size: iconSize as FileTypeIconSize, + extension: 'blah', + }); + expect(res).toEqual(getExpectedUrl(iconSize as FileTypeIconSize, 'svg', 'genericfile')); + }); + }); + + it('returns genericfile with type as png', () => { + ICON_SIZES.forEach((iconSize: number) => { + const res = getFileTypeIconAsUrl({ + size: iconSize as FileTypeIconSize, + extension: 'NotAValidExtension', + imageFileType: 'png', + }); + expect(res).toEqual(getExpectedUrl(iconSize as FileTypeIconSize, 'png', 'genericfile')); + }); + }); + + it('returns genericfile with default size for empty size, extension and type', () => { + const res = getFileTypeIconAsUrl({}); + expect(res).toEqual(getExpectedUrl(DEFAULT_ICON_SIZE, 'svg', 'genericfile')); + }); + + it('returns genericfile with default size for empty size, extension and type with type as png', () => { + const res = getFileTypeIconAsUrl({ imageFileType: 'png' }); + expect(res).toEqual(getExpectedUrl(DEFAULT_ICON_SIZE, 'png', 'genericfile')); + }); +}); + +// Test suite 3 +describe('Returns correct url for custom CDN url', () => { + it('returns expected url', () => { + const url = getFileTypeIconAsUrl( + { + size: 96, + extension: 'docx', + }, + 'https://example-base-url/assets/item-types-fluent/', + ); + expect(url).toEqual('https://example-base-url/assets/item-types-fluent/96/docx.svg'); + }); +}); diff --git a/packages/react-file-type-icons/src/getFileTypeIconAsUrl.ts b/packages/react-file-type-icons/src/getFileTypeIconAsUrl.ts new file mode 100644 index 0000000000000..73b7ae28ca07a --- /dev/null +++ b/packages/react-file-type-icons/src/getFileTypeIconAsUrl.ts @@ -0,0 +1,32 @@ +import { DEFAULT_BASE_URL } from './initializeFileTypeIcons'; +import { + getFileTypeIconNameFromExtensionOrType, + getFileTypeIconSuffix, + DEFAULT_ICON_SIZE, +} from './getFileTypeIconProps'; +import type { IFileTypeIconOptions } from './getFileTypeIconProps'; + +/** + * Given the `fileTypeIconOptions`, this function returns the CDN-based URL for `FileTypeIcon`. + * Similar to `getFileTypeIconProps`, this also accepts the same type of object + * but rather than returning the `iconName`, this returns the raw URL. + * @param options + * @param baseUrl - optionally provide a custom CDN base url to fetch icons from + */ +export function getFileTypeIconAsUrl( + options: IFileTypeIconOptions, + baseUrl: string = DEFAULT_BASE_URL, +): string | undefined { + const { extension, size = DEFAULT_ICON_SIZE, type, imageFileType } = options; + const baseIconName = getFileTypeIconNameFromExtensionOrType(extension, type); // eg: docx + const baseSuffix = getFileTypeIconSuffix(size, imageFileType); // eg: 96_3x_svg or 96_png + const suffixArray = baseSuffix.split('_'); // eg: ['96', '3x', 'svg'] + + if (suffixArray.length === 3) { + /** suffix is of type 96_3x_svg - it has a pixel ratio > 1*/ + return `${baseUrl}${size}_${suffixArray[1]}/${baseIconName}.${suffixArray[2]}`; + } else if (suffixArray.length === 2) { + /** suffix is of type 96_svg - it has a pixel ratio of 1*/ + return `${baseUrl}${size}/${baseIconName}.${suffixArray[1]}`; + } +} diff --git a/packages/react-file-type-icons/src/index.ts b/packages/react-file-type-icons/src/index.ts index 4a3691c312cfb..6cf4a8febb97f 100644 --- a/packages/react-file-type-icons/src/index.ts +++ b/packages/react-file-type-icons/src/index.ts @@ -8,6 +8,8 @@ export { FileTypeIconMap } from './FileTypeIconMap'; export { getFileTypeIconAsHTMLString } from './getFileTypeIconAsHTMLString'; +export { getFileTypeIconAsUrl } from './getFileTypeIconAsUrl'; + import './version'; export type { FileTypeIconSize, IFileTypeIconOptions, ImageFileType } from './getFileTypeIconProps'; From 25cd19ad5e4e51dc98542ee910f8e047df5da366 Mon Sep 17 00:00:00 2001 From: Alina Zaieva Date: Mon, 27 Mar 2023 16:07:14 +0200 Subject: [PATCH 2/2] Add change files --- ...le-type-icons-875366fc-ab42-48a7-a74f-7394aab5b49f.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/@fluentui-react-file-type-icons-875366fc-ab42-48a7-a74f-7394aab5b49f.json diff --git a/change/@fluentui-react-file-type-icons-875366fc-ab42-48a7-a74f-7394aab5b49f.json b/change/@fluentui-react-file-type-icons-875366fc-ab42-48a7-a74f-7394aab5b49f.json new file mode 100644 index 0000000000000..16bc38db00958 --- /dev/null +++ b/change/@fluentui-react-file-type-icons-875366fc-ab42-48a7-a74f-7394aab5b49f.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "feat(react-file-type-icons): add getFileTypeIconAsUrl to allow icons usage in React without global initialization", + "packageName": "@fluentui/react-file-type-icons", + "email": "alinazaieva@microsoft.com", + "dependentChangeType": "patch" +}