/** * @default */ let url = 'skin/skin.svg'; /** * @default */ let pad = 5; let svg = null; /** * @typedef {Object} SkinIcon * A UI icon element from the skin file * @property {string} class - CSS class name (must start with 'openlime-') * @property {SVGElement} element - SVG DOM element */ /** * Manages SVG-based user interface elements (skin) for OpenLIME. * * The Skin system provides a centralized way to manage and customize UI elements * through an SVG-based theming system. Each UI component (buttons, menus, toolbars, * dialogs) sources its visual elements from a single SVG file. * * Design Requirements: * - SVG elements must have class names prefixed with 'openlime-' * - Icons should be properly viewboxed for scaling * - SVG should use relative paths for resources * * Technical Features: * - Async SVG loading * - DOM-based SVG manipulation * - Element cloning support * - Automatic viewbox computation * - Padding management * - Transform handling * * * Default Configuration * * - {string} url - Default skin URL ('skin/skin.svg') * - {number} pad - Icon padding in SVG units (5) * * File Structure Requirements: * ```xml * <svg> * <!-- Icons should use openlime- prefix --> * <g class="openlime-home">...</g> * <g class="openlime-zoom">...</g> * <g class="openlime-menu">...</g> * </svg> * ``` * * Common Icon Classes: * - openlime-home: Home/reset view * - openlime-zoom: Zoom controls * - openlime-menu: Menu button * - openlime-close: Close button * - openlime-next: Next/forward * - openlime-prev: Previous/back * * Usage Notes: * - Always use async/await with icon methods * - Icons are cloned to allow multiple instances * - SVG is loaded once and cached * - Padding is applied uniformly * - ViewBox is computed automatically * * * @static */ class Skin { /** * Sets the URL for the skin SVG file * @param {string} url - Path to SVG file containing UI elements * * @example * ```javascript * // Set custom skin location * Skin.setUrl('/assets/custom-skin.svg'); * ``` */ static setUrl(u) { url = u; } /** * Loads and parses the skin SVG file * Creates a DOM-based SVG element for future use * * @throws {Error} If SVG file fails to load * @returns {Promise<void>} * * @example * ```javascript * await Skin.loadSvg(); * // SVG is now loaded and ready for use * ``` */ static async loadSvg() { var response = await fetch(url); if (!response.ok) { throw Error("Failed loading " + url + ": " + response.statusText); return; } let text = await response.text(); let parser = new DOMParser(); svg = parser.parseFromString(text, "image/svg+xml").documentElement; } /** * Retrieves a specific element from the skin by CSS selector * Automatically loads the SVG if not already loaded * * @param {string} selector - CSS selector for the desired element * @returns {Promise<SVGElement>} Cloned SVG element * @throws {Error} Implicitly if element not found * * @example * ```javascript * // Get home icon * const homeIcon = await Skin.getElement('.openlime-home'); * * // Get menu button * const menuBtn = await Skin.getElement('.openlime-menu'); * ``` */ static async getElement(selector) { if (!svg) await Skin.loadSvg(); return svg.querySelector(selector).cloneNode(true); } /** * Appends an SVG icon to a container element * Handles both string selectors and SVG elements * Automatically manages viewBox and transformations * * @param {HTMLElement} container - Target DOM element to append icon to * @param {string|SVGElement} icon - Icon selector or SVG element * @returns {Promise<SVGElement>} Processed and appended SVG element * * Processing steps: * 1. Loads icon (from selector or element) * 2. Creates SVG wrapper if needed * 3. Computes and sets viewBox * 4. Applies padding * 5. Handles transformations * 6. Appends to container * * @example * ```javascript * // Append by selector * const icon1 = await Skin.appendIcon( * document.querySelector('.toolbar'), * '.openlime-zoom' * ); * * // Append existing SVG * const icon2 = await Skin.appendIcon( * container, * existingSvgElement * ); * ``` */ static async appendIcon(container, icon) { let element = null; let box = null; if (typeof icon == 'string') { element = await Skin.getElement(icon); icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); icon.appendChild(element); document.body.appendChild(icon); box = element.getBBox(); let tlist = element.transform.baseVal; if (tlist.numberOfItems == 0) tlist.appendItem(icon.createSVGTransform()); tlist.getItem(0).setTranslate(-box.x, -box.y); } else { document.body.appendChild(icon); box = icon.getBBox(); } icon.setAttribute('viewBox', `${-pad} ${-pad} ${box.width + 2 * pad} ${box.height + 2 * pad}`); icon.setAttribute('preserveAspectRatio', 'xMidYMid meet'); container.appendChild(icon); return icon; } } export { Skin }