UNPKG

@oslokommune/punkt-elements

Version:

Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo

141 lines (119 loc) 4.19 kB
import { PktElement } from '@/base-elements/element' import { ElementProps } from '@/types/typeUtils' import { PktIconName } from '@oslokommune/punkt-assets/dist/icons/icon' import { html, PropertyValues } from 'lit' import { customElement, property } from 'lit/decorators.js' import { unsafeSVG } from 'lit/directives/unsafe-svg.js' const defaultPath = 'https://punkt-cdn.oslo.kommune.no/latest/icons/' // Allow global override of icon fetch if (typeof window !== 'undefined') { window.pktFetch = window.pktFetch === undefined ? fetch : window.pktFetch window.pktIconPath = window.pktIconPath || defaultPath } const errorSvg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"></svg>' const dlCache: { [key: string]: Promise<string> } = {} const MAX_RETRIES = 2 const RETRY_DELAY = 1500 const isSessionStorageAvailable = typeof Storage !== 'undefined' && typeof sessionStorage !== 'undefined' const fetchIcon = (url: string): Promise<string> => window .pktFetch!(url) .then((response: Response) => { if (!response.ok) { throw new Error('Missing icon: ' + url) } return response.text() }) const fetchIconWithRetry = async (url: string, retries = MAX_RETRIES): Promise<string> => { try { return await fetchIcon(url) } catch (error) { if (retries > 0) { await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY)) return fetchIconWithRetry(url, retries - 1) } // eslint-disable-next-line no-console console.error('Failed to fetch icon: ' + url) return errorSvg } } const downloadIconOrGetFromCache = async ( name: PktIconName, path: string | undefined, ): Promise<string | null> => { const key = path + name + '.svg' if (isSessionStorageAvailable && sessionStorage.getItem(key)) { return sessionStorage.getItem(key) } // If a fetch is already in-flight for this icon, await the same promise if (key in dlCache) { return dlCache[key] } if (typeof window !== 'undefined' && typeof window.pktFetch === 'function') { dlCache[key] = fetchIconWithRetry(key).then((text) => { if (text !== errorSvg && isSessionStorageAvailable) { sessionStorage.setItem(key, text) } delete dlCache[key] return text }) return dlCache[key] } return errorSvg } type Props = ElementProps<PktIcon, 'path' | 'name'> export class PktIcon extends PktElement<Props> { @property({ type: String, reflect: false }) path: string | undefined = typeof window !== 'undefined' ? window.pktIconPath : defaultPath @property({ type: String, reflect: true }) name: PktIconName = '' @property({ type: SVGElement }) private icon: ReturnType<typeof unsafeSVG> = unsafeSVG(errorSvg) @property({ type: Array, noAccessor: true }) private _updatedProps: string[] = [] connectedCallback(): void { super.connectedCallback() this.classList.add('pkt-icon') } async attributeChangedCallback(name: string, _old: string | null, value: string | null) { super.attributeChangedCallback(name, _old, value) if (name === 'name' || name === 'path') this.getIcon(this.name) } protected async updated(_changedProperties: PropertyValues) { super.updated(_changedProperties) if (_changedProperties.has('name') || _changedProperties.has('path')) { this.getIcon(this.name) } } protected async getIcon(name: PktIconName = '') { if (this._updatedProps.length > 0) { if (!this.path) this.path = typeof window !== 'undefined' ? window.pktIconPath : defaultPath try { this.icon = unsafeSVG( await downloadIconOrGetFromCache(this.name || '', this.path), ) as SVGElement } catch { this.icon = unsafeSVG(errorSvg) } this._updatedProps = [] } else { if (!this._updatedProps.includes(name)) { this._updatedProps.push(name) } } } render() { return html`${this.name && this.icon}` } } declare global { interface HTMLElementTagNameMap { 'pkt-icon': PktIcon } } try { customElement('pkt-icon')(PktIcon) } catch (e) { console.warn('Forsøker å definere <pkt-icon>, men den er allerede definert') }