From 7baa96d94f44e1ce0f3316945edb47e917b6ba52 Mon Sep 17 00:00:00 2001 From: Derick M <58572875+TurtIeSocks@users.noreply.github.com> Date: Fri, 30 Aug 2024 11:47:17 -0400 Subject: [PATCH 1/2] feat: add constructor options obj --- README.md | 17 ++++- src/index.ts | 8 ++- src/types.ts | 43 ++++++++----- src/uicons.test.ts | 2 +- src/uicons.ts | 153 +++++++++++++++++++++++---------------------- 5 files changed, 128 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index 6eee8e0..9be102f 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # uicons.js -uicons.js is a JavaScript class based on the [UICONS](https://github.com/UIcons/UIcons) specification. It can be used with any file extensions, works in the browser and on the server. - -View the [example](./example/) code to see implementation details for both audio and image files. See [tests](./src/uicons.test.ts) for additional examples. +uicons.js is a JavaScript class based on the [UICONS](https://github.com/UIcons/UIcons) specification. It provides a simple way to utilize the UICONS standard in your projects and reduces the need for tedious boilerplate code. ### [Demo Page](https://turtiesocks.github.io/uicons.js/) @@ -16,8 +14,18 @@ yarn add uicons.js pnpm add uicons.js ``` +## Features + +- File extension agnostic +- Provides helpful IntelliSense in your IDE based on latest protos +- Works in the browser and server +- Supports both remote and local initialization of the index.json file + ## Usage +- View the [example](./example/) code to see implementation details for both audio and image files +- See [tests](./src/uicons.test.ts) for additional examples. + ```typescript import { UICONS } from 'uicons.js' @@ -76,4 +84,7 @@ pnpm run install # Build and Run Example pnpm run start + +# Run Tests +pnpm run test ``` diff --git a/src/index.ts b/src/index.ts index 13c099c..2c86a60 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,8 @@ -export * from './types.js' +export type { + TimeOfDay, + TrainerCounts, + RewardTypeKeys, + UiconsIndex, + LureIDs, +} from './types.js' export { UICONS } from './uicons.js' diff --git a/src/types.ts b/src/types.ts index 446a75d..1977e8f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,6 +6,16 @@ export type TimeOfDay = 'day' | 'night' export type TrainerCounts = StringOrNumber<0 | 1 | 2 | 3 | 4 | 5 | 6> +export type LureIDs = StringOrNumber< + | { + [K in KeysEndingWith< + typeof Rpc.Item, + 'ITEM_TROY_DISK' + >]: (typeof Rpc.Item)[K] + }[KeysEndingWith] + | 0 +> + export interface UiconsIndex { device?: T gym?: T @@ -24,7 +34,22 @@ export interface UiconsIndex { weather?: T } -export type ExtensionMap = { +export interface Options { + /** + * The path to the directory containing the icons. + */ + path: string + /** + * The index.json data from the uicons repository. + */ + data?: T + /** + * The label to use when logging messages. + */ + label?: string +} + +export type ExtensionMap = { [K in keyof T]: T[K] extends string[] | readonly string[] ? string : T[K] extends object @@ -46,23 +71,11 @@ export type Paths = T extends object }[keyof T] : '' -export type EnumVal< - T extends Record, - filter extends string = '' -> = StringOrNumber +export type EnumVal> = + StringOrNumber export type StringOrNumber = `${T}` | T type KeysEndingWith = { [K in keyof T]: K extends `${S}${infer _Suffix}` ? K : never }[keyof T] - -export type LureIDs = StringOrNumber< - | { - [K in KeysEndingWith< - typeof Rpc.Item, - 'ITEM_TROY_DISK' - >]: (typeof Rpc.Item)[K] - }[KeysEndingWith] - | 0 -> diff --git a/src/uicons.test.ts b/src/uicons.test.ts index 3f96d05..3d8dfbd 100644 --- a/src/uicons.test.ts +++ b/src/uicons.test.ts @@ -17,7 +17,7 @@ describe('webp format', () => { }) }) -const audio = new UICONS(BASE_AUDIO_URL) +const audio = new UICONS({ path: BASE_AUDIO_URL }) describe('wav format', () => { test('should load locally', async () => { diff --git a/src/uicons.ts b/src/uicons.ts index 5fbe046..0eebc9d 100644 --- a/src/uicons.ts +++ b/src/uicons.ts @@ -6,9 +6,9 @@ import type { ExtensionMap, Paths, EnumVal, - StringOrNumber, TrainerCounts, LureIDs, + Options, } from './types.js' /** @@ -17,13 +17,24 @@ import type { * Can be used with any image or audio extensions, as long as they follow the UICONS guidelines * @see https://github.com/UIcons/UIcons * @example - * // Without generic and with the optional label argument - * const uicons = new UICONS('https://example.com/uicons', 'cagemons') + * import { UICONS } from 'uicons.js' + * + * // Basic usage + * const uicons = new UICONS('https://example.com/uicons') + * // With all constructor options + * const uicons = new UICONS({ + * // base URL of the UICONS repository + * path: 'https://example.com/uicons', + * // label for debug purposes + * label: 'cagemons', + * // your own index.json data if you want to load in the constructor + * data: { pokemon: [...], device: [...] }, + * }) * // With stronger typing from index file - * const uicons = new UICONS('https://example.com/uicons', 'cagemons') + * const uicons = new UICONS({ path: 'https://example.com/uicons' }) * // Async initialization fetches the index.json file for you * await uicons.remoteInit() - * // Sync initialization if you already have the index.json + * // Sync initialization if you already have the index.json and want to load it manually * uicons.init(indexJson) */ export class UICONS { @@ -45,54 +56,53 @@ export class UICONS { #type: Set #weather: Set + /** + * @param options The options object for the UICONS instance + */ + constructor(options: Options) + /** + * @param path The base URL of the UICONS repository + */ + constructor(path: string) /** * @param path The base URL of the UICONS repository - * @param label Optional label for debugging purposes + * @param label The optional label for the UICONS instance + * @deprecated Use the new constructor with an options object */ - constructor(path: string, label?: string) { + constructor(path: string, label?: string) + constructor(optionsOrPath: string | Options, oldLabel?: string) { + const { path, label, data }: Options = + typeof optionsOrPath === 'string' + ? { path: optionsOrPath, label: oldLabel } + : optionsOrPath + this.#path = path.endsWith('/') ? path.slice(0, -1) : path - this.#label = label || this.#path - - this.#device = new Set() - this.#gym = new Set() - this.#invasion = new Set() - this.#misc = new Set() - this.#nest = new Set() - this.#pokemon = new Set() - this.#pokestop = new Set() - this.#raid = { egg: new Set() } - this.#reward = { - unset: new Set(), - experience: new Set(), - item: new Set(), - stardust: new Set(), - candy: new Set(), - avatar_clothing: new Set(), - quest: new Set(), - pokemon_encounter: new Set(), - pokecoin: new Set(), - xl_candy: new Set(), - level_cap: new Set(), - sticker: new Set(), - mega_resource: new Set(), - incident: new Set(), - player_attribute: new Set(), - event_badge: new Set(), + this.#label = label ?? this.#path + if (label) { + this.#warn( + 'The label parameter is deprecated, use the options object instead' + ) + } + if (data) this.init(data) + } + + #warn(...args: unknown[]) { + if ( + typeof process !== 'undefined' && + process.env.NODE_ENV === 'development' + ) { + console.warn(`[UICONS ${this.#label}]`, ...args) } - this.#spawnpoint = new Set() - this.#team = new Set() - this.#type = new Set() - this.#weather = new Set() } - static #buildExtensions(json: UiconsIndex): ExtensionMap { + #buildExtensions(json: Index): ExtensionMap { return Object.fromEntries( Object.entries(json) .map(([category, values]) => { if (Array.isArray(values) && values.length > 0) { return [category, values[0].split('.').pop()] } else if (typeof values === 'object') { - return [category, UICONS.#buildExtensions(values)] + return [category, this.#buildExtensions(values)] } return [category, ''] }) @@ -131,21 +141,18 @@ export class UICONS { this.#nest = new Set(data.nest || []) this.#pokemon = new Set(data.pokemon || []) this.#pokestop = new Set(data.pokestop || []) - this.#raid.egg = new Set(data.raid?.egg || []) - Object.assign( - this.#reward, - Object.fromEntries( - Object.entries(data.reward || {}) - .filter(([, v]) => Array.isArray(v)) - .map(([k, v]) => [k, new Set(v)]) - ) + this.#raid = { egg: new Set(data.raid?.egg || []) } + this.#reward = Object.fromEntries( + Object.entries(data.reward || {}) + .filter(([, v]) => Array.isArray(v)) + .map(([k, v]) => [k, new Set(v)]) ) this.#spawnpoint = new Set(data.spawnpoint || []) this.#team = new Set(data.team || []) this.#type = new Set(data.type || []) this.#weather = new Set(data.weather || []) - this.#extensionMap = UICONS.#buildExtensions(data) + this.#extensionMap = this.#buildExtensions(data) return this } @@ -492,38 +499,34 @@ export class UICONS { amount?: string | number ): string reward( - questRewardType?: U, + questRewardType: U = 'unset' as U, rewardId = 0, amount = 0 ): string { this.#isReady() - const safeRewardType = questRewardType || 'unset' - const baseUrl = `${this.#path}/reward/${safeRewardType}` - - if (this.#reward[safeRewardType]) { - const amountSafe = typeof amount === 'number' ? amount : +amount - const amountSuffixes = - Number.isInteger(amountSafe) && amountSafe > 1 - ? [`_a${amount}`, ''] - : [''] - const safeId = +rewardId || amountSafe || 0 - for (let a = 0; a < amountSuffixes.length; a += 1) { - const result = `${safeId}${amountSuffixes[a]}.${ - this.#extensionMap.reward[safeRewardType] - }` - if (this.#reward[safeRewardType].has(result)) { - return `${baseUrl}/${result}` - } - } - } else { - console.warn( - 'UICONS', - this.#label.toUpperCase(), - `Missing category: ${safeRewardType}` - ) + const baseUrl = `${this.#path}/reward/${questRewardType}` + const rewardSet = this.#reward[questRewardType] + if (!rewardSet) { + this.#warn('Invalid quest reward type,', questRewardType) return this.misc(0) } - return `${baseUrl}/0.${this.#extensionMap.reward[safeRewardType]}` + + const amountSafe = typeof amount === 'number' ? amount : +amount + const amountSuffixes = + Number.isInteger(amountSafe) && amountSafe > 1 + ? [`_a${amount}`, ''] + : [''] + const safeId = +rewardId || amountSafe || 0 + + for (let a = 0; a < amountSuffixes.length; a += 1) { + const result = `${safeId}${amountSuffixes[a]}.${ + this.#extensionMap.reward[questRewardType] + }` + if (rewardSet.has(result)) { + return `${baseUrl}/${result}` + } + } + return `${baseUrl}/0.${this.#extensionMap.reward[questRewardType]}` } /** From 243d57a6fe7045c150f0239624f21fb83414fd01 Mon Sep 17 00:00:00 2001 From: Derick M <58572875+TurtIeSocks@users.noreply.github.com> Date: Fri, 30 Aug 2024 11:52:59 -0400 Subject: [PATCH 2/2] fix: deprecated label check --- src/uicons.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uicons.ts b/src/uicons.ts index 0eebc9d..f26472f 100644 --- a/src/uicons.ts +++ b/src/uicons.ts @@ -78,7 +78,7 @@ export class UICONS { this.#path = path.endsWith('/') ? path.slice(0, -1) : path this.#label = label ?? this.#path - if (label) { + if (oldLabel) { this.#warn( 'The label parameter is deprecated, use the options object instead' )