export const $ = (selector, root = document) => root.querySelector(selector); /** Create an element: h('button', { class: 'btn', text: 'Go', onclick }, ...children). */ export function h(tag, props = {}, ...children) { const el = document.createElement(tag); for (const [key, value] of Object.entries(props)) { if (value == null || value === false) continue; if (key === 'class') el.className = value; else if (key === 'text') el.textContent = value; else if (key === 'dataset') Object.assign(el.dataset, value); else if (key.startsWith('on')) el.addEventListener(key.slice(2).toLowerCase(), value); else el.setAttribute(key, value === true ? '' : value); } el.append(...children.flat().filter((c) => c != null && c !== false)); return el; } // ---------------------------------------------------------------------- icons // Every icon is a grey square for now. When a theme provides images, list them // in its theme.json ("assets": { "gen.gen1": "img/autoclipper.svg", ... }) and // they replace the placeholders, with no change to the UI code. let assetBase = ''; let assets = {}; export function setThemeAssets(themeId, themeAssets) { assetBase = `themes/${themeId}/`; assets = themeAssets ?? {}; } /** key examples: "res.units", "gen.gen1", "upg.click_1" */ export function icon(key) { const path = assets[key]; if (path) return h('img', { class: 'icon', src: assetBase + path, alt: '', loading: 'lazy' }); return h('span', { class: 'icon icon--placeholder', 'aria-hidden': 'true', dataset: { icon: key } }); } /** Re-resolve the icons that live in index.html (the resource bar). */ export function refreshStaticIcons(root = document) { for (const el of root.querySelectorAll('.resourcebar [data-icon]')) el.replaceWith(icon(el.dataset.icon)); }