From bf8d17f97a4ec3d293e99b3cd608bc0c62d7bdba Mon Sep 17 00:00:00 2001 From: Vjacheslav Trushkin Date: Thu, 13 Jan 2022 22:19:49 +0200 Subject: [PATCH] Add loadIcon function --- packages/core/src/api/functions.ts | 8 ++ packages/core/src/api/icons.ts | 40 +++++- packages/core/tests/api/loading-test.ts | 163 +++++++++++++++++++++++- packages/ember/src/iconify-icon.ts | 4 +- packages/iconify/src/iconify.ts | 3 +- packages/react/src/iconify.ts | 4 +- packages/svelte/src/Icon.svelte | 2 + packages/svelte/src/functions.ts | 4 +- packages/svelte/src/iconify.ts | 2 +- packages/vue/src/iconify.ts | 4 +- packages/vue2/src/iconify.ts | 4 +- 11 files changed, 222 insertions(+), 16 deletions(-) diff --git a/packages/core/src/api/functions.ts b/packages/core/src/api/functions.ts index 61be7a3..0761430 100644 --- a/packages/core/src/api/functions.ts +++ b/packages/core/src/api/functions.ts @@ -14,6 +14,7 @@ import type { IconifyAPICustomQueryParams, } from './modules'; import type { MergeParams, IconifyAPIMergeQueryParams } from './params'; +import type { IconifyIcon } from '@iconify/types'; /** * Iconify API functions @@ -27,6 +28,13 @@ export interface IconifyAPIFunctions { callback?: IconifyIconLoaderCallback ) => IconifyIconLoaderAbort; + /** + * Load one icon, using Promise syntax + */ + loadIcon: ( + icon: IconifyIconName | string + ) => Promise>; + /** * Add API provider */ diff --git a/packages/core/src/api/icons.ts b/packages/core/src/api/icons.ts index ad23264..91c59dc 100644 --- a/packages/core/src/api/icons.ts +++ b/packages/core/src/api/icons.ts @@ -1,13 +1,14 @@ -import type { IconifyJSON } from '@iconify/types'; -import type { +import type { IconifyIcon, IconifyJSON } from '@iconify/types'; +import { IconifyIconName, IconifyIconSource, + stringToIcon, } from '@iconify/utils/lib/icon/name'; import type { SortedIcons } from '../icon/sort'; import { sortIcons } from '../icon/sort'; import { storeCallback, updateCallbacks } from './callbacks'; import { getAPIModule } from './modules'; -import { getStorage, addIconSet } from '../storage/storage'; +import { getStorage, addIconSet, getIconFromStorage } from '../storage/storage'; import { listToIcons } from '../icon/list'; import { allowSimpleNames } from '../storage/functions'; import { sendAPIQuery } from './query'; @@ -338,3 +339,36 @@ export const loadIcons: IconifyLoadIcons = ( ? storeCallback(callback, sortedIcons, sources) : emptyCallback; }; + +/** + * Cache for loadIcon promises + */ +type LoadIconResult = Promise>; +const iconsQueue: Record = Object.create(null); + +export const loadIcon = (icon: IconifyIconName | string): LoadIconResult => { + if (typeof icon === 'string' && iconsQueue[icon]) { + return iconsQueue[icon]; + } + + const result: LoadIconResult = new Promise((fulfill, reject) => { + const iconObj = typeof icon === 'string' ? stringToIcon(icon) : icon; + loadIcons([iconObj || icon], (loaded) => { + if (loaded.length && iconObj) { + const storage = getStorage(iconObj.provider, iconObj.prefix); + const data = getIconFromStorage(storage, iconObj.name); + if (data) { + fulfill(data); + return; + } + } + + reject(icon); + }); + }); + + if (typeof icon === 'string') { + iconsQueue[icon] = result; + } + return result; +}; diff --git a/packages/core/tests/api/loading-test.ts b/packages/core/tests/api/loading-test.ts index 59b4305..1a71a0b 100644 --- a/packages/core/tests/api/loading-test.ts +++ b/packages/core/tests/api/loading-test.ts @@ -5,7 +5,7 @@ import type { IconifyAPIQueryParams, } from '../../lib/api/modules'; import { setAPIModule } from '../../lib/api/modules'; -import { loadIcons, isPending } from '../../lib/api/icons'; +import { loadIcons, loadIcon, isPending } from '../../lib/api/icons'; describe('Testing API loadIcons', () => { let prefixCounter = 0; @@ -154,6 +154,167 @@ describe('Testing API loadIcons', () => { asyncCounter++; }); + it('Loading one icon with Promise', async () => { + const provider = nextPrefix(); + const prefix = nextPrefix(); + + // Set config + addAPIProvider(provider, { + resources: ['https://api1.local', 'https://api2.local'], + }); + + // Icon loader + const prepareQuery = ( + provider: string, + prefix: string, + icons: string[] + ): IconifyAPIIconsQueryParams[] => { + const item: IconifyAPIIconsQueryParams = { + type: 'icons', + provider, + prefix, + icons, + }; + + // Test input and return as one item + const expected: IconifyAPIIconsQueryParams = { + type: 'icons', + provider, + prefix, + icons: ['icon1'], + }; + expect(item).toEqual(expected); + + return [item]; + }; + + const sendQuery = ( + host: string, + params: IconifyAPIQueryParams, + item: PendingQueryItem + ): void => { + expect(params.type).toBe('icons'); + + // Test input + expect(host).toBe('https://api1.local'); + const expected: IconifyAPIQueryParams = { + type: 'icons', + provider, + prefix, + icons: ['icon1'], + }; + expect(params).toEqual(expected); + + // Send data + item.done({ + prefix, + icons: { + icon1: { + body: '', + }, + }, + }); + }; + + setAPIModule(provider, { + prepare: prepareQuery, + send: sendQuery, + }); + + // Load icon + await loadIcon(provider + ':' + prefix + ':icon1'); + + // Test isPending + expect(isPending({ provider, prefix, name: 'icon1' })).toBe(false); + }); + + it('Loading one icon twice with Promise', (done) => { + const provider = nextPrefix(); + const prefix = nextPrefix(); + + // Set config + addAPIProvider(provider, { + resources: ['https://api1.local', 'https://api2.local'], + }); + + // Icon loader + const prepareQuery = ( + provider: string, + prefix: string, + icons: string[] + ): IconifyAPIIconsQueryParams[] => { + const item: IconifyAPIIconsQueryParams = { + type: 'icons', + provider, + prefix, + icons, + }; + + // Test input and return as one item + const expected: IconifyAPIIconsQueryParams = { + type: 'icons', + provider, + prefix, + icons: ['icon1'], + }; + expect(item).toEqual(expected); + + return [item]; + }; + + const sendQuery = ( + host: string, + params: IconifyAPIQueryParams, + item: PendingQueryItem + ): void => { + expect(params.type).toBe('icons'); + + // Test input + expect(host).toBe('https://api1.local'); + const expected: IconifyAPIQueryParams = { + type: 'icons', + provider, + prefix, + icons: ['icon1'], + }; + expect(params).toEqual(expected); + + // Send data + item.done({ + prefix, + icons: { + icon1: { + body: '', + }, + }, + }); + }; + + setAPIModule(provider, { + prepare: prepareQuery, + send: sendQuery, + }); + + // Load icon, twice + const p1 = loadIcon(provider + ':' + prefix + ':icon1'); + const p2 = loadIcon(provider + ':' + prefix + ':icon1'); + + // Promise instances should be the same because parameter is a string that is cached + expect(p1).toEqual(p2); + + // Test isPending + expect(isPending({ provider, prefix, name: 'icon1' })).toBe(true); + + // Wait for Promise + p1.then((data) => { + expect(data.body).toEqual(''); + done(); + }).catch((err) => { + console.error(err); + done('Failed to load icon'); + }); + }); + it('Split results', (done) => { const provider = nextPrefix(); const prefix = nextPrefix(); diff --git a/packages/ember/src/iconify-icon.ts b/packages/ember/src/iconify-icon.ts index 4c73461..aecf07c 100644 --- a/packages/ember/src/iconify-icon.ts +++ b/packages/ember/src/iconify-icon.ts @@ -51,7 +51,7 @@ import type { IconifyIconLoaderCallback, IconifyIconLoaderAbort, } from '@iconify/core/lib/api/icons'; -import { loadIcons } from '@iconify/core/lib/api/icons'; +import { loadIcons, loadIcon } from '@iconify/core/lib/api/icons'; import { sendAPIQuery } from '@iconify/core/lib/api/query'; import { mergeParams } from '@iconify/core/lib/api/params'; @@ -230,7 +230,7 @@ const _api: IconifyAPIInternalFunctions = { export { _api }; // IconifyAPIFunctions -export { addAPIProvider, loadIcons }; +export { addAPIProvider, loadIcons, loadIcon }; // IconifyStorageFunctions export { iconExists, getIcon, listIcons, addIcon, addCollection, shareStorage }; diff --git a/packages/iconify/src/iconify.ts b/packages/iconify/src/iconify.ts index ed58307..ee76aae 100644 --- a/packages/iconify/src/iconify.ts +++ b/packages/iconify/src/iconify.ts @@ -64,7 +64,7 @@ import type { IconifyIconLoaderCallback, IconifyIconLoaderAbort, } from '@iconify/core/lib/api/icons'; -import { loadIcons } from '@iconify/core/lib/api/icons'; +import { loadIcons, loadIcon } from '@iconify/core/lib/api/icons'; import { sendAPIQuery } from '@iconify/core/lib/api/query'; import { mergeParams } from '@iconify/core/lib/api/params'; @@ -223,6 +223,7 @@ const Iconify: IconifyGlobal = { // IconifyAPIFunctions addAPIProvider, loadIcons, + loadIcon, // IconifyStorageFunctions iconExists, diff --git a/packages/react/src/iconify.ts b/packages/react/src/iconify.ts index 272308c..21f3cc4 100644 --- a/packages/react/src/iconify.ts +++ b/packages/react/src/iconify.ts @@ -55,7 +55,7 @@ import type { IconifyIconLoaderCallback, IconifyIconLoaderAbort, } from '@iconify/core/lib/api/icons'; -import { loadIcons } from '@iconify/core/lib/api/icons'; +import { loadIcons, loadIcon } from '@iconify/core/lib/api/icons'; import { sendAPIQuery } from '@iconify/core/lib/api/query'; import { mergeParams } from '@iconify/core/lib/api/params'; @@ -461,7 +461,7 @@ const _api: IconifyAPIInternalFunctions = { export { _api }; // IconifyAPIFunctions -export { addAPIProvider, loadIcons }; +export { addAPIProvider, loadIcons, loadIcon }; // IconifyStorageFunctions export { iconExists, getIcon, listIcons, addIcon, addCollection, shareStorage }; diff --git a/packages/svelte/src/Icon.svelte b/packages/svelte/src/Icon.svelte index 5193c29..ddcbfc7 100644 --- a/packages/svelte/src/Icon.svelte +++ b/packages/svelte/src/Icon.svelte @@ -15,6 +15,7 @@ import { replaceIDs, buildIcon, loadIcons, + loadIcon, addAPIProvider, _api } from './functions'; @@ -32,6 +33,7 @@ export { replaceIDs, buildIcon, loadIcons, + loadIcon, addAPIProvider, _api } diff --git a/packages/svelte/src/functions.ts b/packages/svelte/src/functions.ts index c5a9d5b..140e048 100644 --- a/packages/svelte/src/functions.ts +++ b/packages/svelte/src/functions.ts @@ -54,7 +54,7 @@ import type { IconifyIconLoaderCallback, IconifyIconLoaderAbort, } from '@iconify/core/lib/api/icons'; -import { loadIcons } from '@iconify/core/lib/api/icons'; +import { loadIcons, loadIcon } from '@iconify/core/lib/api/icons'; import { sendAPIQuery } from '@iconify/core/lib/api/query'; import { mergeParams } from '@iconify/core/lib/api/params'; @@ -357,7 +357,7 @@ const _api: IconifyAPIInternalFunctions = { export { _api }; // IconifyAPIFunctions -export { addAPIProvider, loadIcons }; +export { addAPIProvider, loadIcons, loadIcon }; // IconifyStorageFunctions export { iconExists, getIcon, listIcons, addIcon, addCollection, shareStorage }; diff --git a/packages/svelte/src/iconify.ts b/packages/svelte/src/iconify.ts index a8110e9..c4b2c9e 100644 --- a/packages/svelte/src/iconify.ts +++ b/packages/svelte/src/iconify.ts @@ -62,4 +62,4 @@ export { export { calculateSize, replaceIDs, buildIcon } from './functions'; -export { loadIcons, addAPIProvider, _api } from './functions'; +export { loadIcons, loadIcon, addAPIProvider, _api } from './functions'; diff --git a/packages/vue/src/iconify.ts b/packages/vue/src/iconify.ts index 2e30647..a09438e 100644 --- a/packages/vue/src/iconify.ts +++ b/packages/vue/src/iconify.ts @@ -64,7 +64,7 @@ import type { IconifyIconLoaderCallback, IconifyIconLoaderAbort, } from '@iconify/core/lib/api/icons'; -import { loadIcons } from '@iconify/core/lib/api/icons'; +import { loadIcons, loadIcon } from '@iconify/core/lib/api/icons'; import { sendAPIQuery } from '@iconify/core/lib/api/query'; import { mergeParams } from '@iconify/core/lib/api/params'; @@ -397,7 +397,7 @@ const _api: IconifyAPIInternalFunctions = { export { _api }; // IconifyAPIFunctions -export { addAPIProvider, loadIcons }; +export { addAPIProvider, loadIcons, loadIcon }; // IconifyStorageFunctions export { iconExists, getIcon, listIcons, addIcon, addCollection, shareStorage }; diff --git a/packages/vue2/src/iconify.ts b/packages/vue2/src/iconify.ts index 46be0c3..8361e51 100644 --- a/packages/vue2/src/iconify.ts +++ b/packages/vue2/src/iconify.ts @@ -57,7 +57,7 @@ import type { IconifyIconLoaderCallback, IconifyIconLoaderAbort, } from '@iconify/core/lib/api/icons'; -import { loadIcons } from '@iconify/core/lib/api/icons'; +import { loadIcons, loadIcon } from '@iconify/core/lib/api/icons'; import { sendAPIQuery } from '@iconify/core/lib/api/query'; import { mergeParams } from '@iconify/core/lib/api/params'; @@ -399,7 +399,7 @@ const _api: IconifyAPIInternalFunctions = { export { _api }; // IconifyAPIFunctions -export { addAPIProvider, loadIcons }; +export { addAPIProvider, loadIcons, loadIcon }; // IconifyStorageFunctions export { iconExists, getIcon, listIcons, addIcon, addCollection, shareStorage };