Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | 1x 2719x 2719x 2719x 1x 1x 1991x 1991x 1991x 3978x 3978x 3978x 3978x 1991x 1991x 3978x 3978x 2719x 1259x 1991x 12x 1x 902x 14x 14x 14x | /** * Copyright (c) Siemens 2016 - 2025 * SPDX-License-Identifier: MIT */ import { DestroyRef, inject, Injectable } from '@angular/core'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { SiThemeService } from '@siemens/element-ng/theme'; interface RegisteredIcon { content: SafeHtml | undefined; // Count how often an icon was registered to only remove it if it is no longer in use. referenceCount: number; } const parseDataSvgIcon = (icon: string, domSanitizer: DomSanitizer): SafeHtml => { const parsed = /^data:image\/svg\+xml;utf8,(.*)$/.exec(icon); Iif (!parsed) { console.error('Failed to parse icon', icon); return ''; } return domSanitizer.bypassSecurityTrustHtml(parsed[1]); }; const registeredIcons = new Map<string, RegisteredIcon>(); /** * Adds the provided icons. * It requires an Angular InjectionContent. * The Icons are available until the component is destroyed. * Call this function only in the component which actually uses the icon. * Importing all icons on the global level is discouraged. * * When using a string instead of the object to use an icon, * use the kebab-case version of the icon name. * * @example * ```ts * import { elementIcon } from '@simpl/element-icons/ionic'; * import { addIcons } from '@siemens/element-ng/icon' * * @Component({`<si-icon [icon]="icons.elementIcon"`}) * class MyComponent { * icons = addIcons({ elementIcon }) * } * ``` */ export const addIcons = <T extends string>(icons: Record<T, string>): Record<T, string> => { const iconMap = {} as Record<T, string>; const domSanitizer = inject(DomSanitizer); for (const [key, rawContent] of Object.entries<string>(icons)) { const registeredIcon = registeredIcons.get(key) ?? { content: parseDataSvgIcon(rawContent, domSanitizer), referenceCount: 0 }; registeredIcon.referenceCount++; registeredIcons.set(key, registeredIcon); iconMap[key as T] = key; } // Delete registered Icons after Component is destroyed to optimize memory usage. // WeakMap must not be used, as the Icon can only be removed on component destruction. // When using a WeakMap it would also get destroyed if it is not referenced, but the component may use it later again. inject(DestroyRef).onDestroy(() => { for (const key of Object.keys(icons)) { const registeredIcon = registeredIcons.get(key); if (registeredIcon!.referenceCount === 1) { registeredIcons.delete(key); } else { registeredIcon!.referenceCount--; } } }); return iconMap; }; const getIcon = (key: string): SafeHtml | undefined => registeredIcons.get(key)?.content; @Injectable({ providedIn: 'root' }) export class IconService { private themeService = inject(SiThemeService); getIcon(name: string): SafeHtml | undefined { const camelCaseName = this.kebabToCamelCase(name); return this.themeService.themeIcons()[camelCaseName] ?? getIcon(camelCaseName); } private kebabToCamelCase(str: string): string { return str?.replace(/-./g, match => match.charAt(1).toUpperCase()); } } |