127 lines
3.2 KiB
JavaScript
127 lines
3.2 KiB
JavaScript
/**
|
|
Attributes:
|
|
@property {string} name - The name of the icon, takes precedence over the path
|
|
@property {string} path - The path of the icon file
|
|
*/
|
|
export class DotDungeonIcon extends HTMLElement {
|
|
static elementName = `dd-icon`;
|
|
static formAssociated = false;
|
|
|
|
#shadow;
|
|
|
|
static #styles = ``;
|
|
static _cache = new Map();
|
|
#style;
|
|
#container;
|
|
/** @type {null | string} */
|
|
_name;
|
|
/** @type {null | string} */
|
|
_path;
|
|
|
|
constructor() {
|
|
super();
|
|
this.#shadow = this.attachShadow({ mode: `open`, delegatesFocus: true });
|
|
if (DotDungeonIcon.#styles) this.#embedStyles();
|
|
|
|
this.#container = document.createElement(`div`);
|
|
this.#shadow.appendChild(this.#container);
|
|
};
|
|
|
|
_mounted = false;
|
|
async connectedCallback() {
|
|
if (this._mounted) return;
|
|
|
|
this._name = this.getAttribute(`name`);
|
|
this._path = this.getAttribute(`path`);
|
|
|
|
/*
|
|
This converts all of the double-dash prefixed properties on the element to
|
|
CSS variables so that they don't all need to be provided by doing style=""
|
|
*/
|
|
for (const attrVar of this.attributes) {
|
|
if (attrVar.name?.startsWith(`var:`)) {
|
|
const prop = attrVar.name.replace(`var:`, ``);
|
|
this.style.setProperty(`--` + prop, attrVar.value);
|
|
};
|
|
};
|
|
|
|
/*
|
|
Style fetching if we haven't gotten them yet
|
|
*/
|
|
if (!DotDungeonIcon.#styles) {
|
|
fetch(`./systems/dotdungeon/.styles/v3/components/icon.css`)
|
|
.then(r => r.text())
|
|
.then(t => {
|
|
DotDungeonIcon.#styles = t;
|
|
this.#embedStyles();
|
|
});
|
|
};
|
|
|
|
/*
|
|
Try to retrieve the icon if it isn't present, try the path then default to
|
|
the slot content, as then we can have a default per-icon usage
|
|
*/
|
|
let content;
|
|
if (this._name) {
|
|
content = await this.#getIcon(`./systems/dotdungeon/assets/${this._name}.svg`);
|
|
};
|
|
|
|
if (this._path && !content) {
|
|
content = await this.#getIcon(this._path);
|
|
};
|
|
|
|
if (content) {
|
|
this.#container.appendChild(content.cloneNode(true));
|
|
};
|
|
|
|
/*
|
|
This is so that when we get an HMR event from Foundry we can appropriately
|
|
handle it using our logic to update the component and the icon cache.
|
|
*/
|
|
Hooks.on(`dd-hmr:svg`, (iconName, data) => {
|
|
if (this._name === iconName || this._path?.endsWith(data.path)) {
|
|
const svg = this.#parseSVG(data.content);
|
|
DotDungeonIcon._cache.set(iconName, svg);
|
|
this.#container.replaceChildren(svg.cloneNode(true));
|
|
};
|
|
});
|
|
|
|
this._mounted = true;
|
|
};
|
|
|
|
#embedStyles() {
|
|
this.#style = document.createElement(`style`);
|
|
this.#style.innerHTML = DotDungeonIcon.#styles;
|
|
this.#shadow.appendChild(this.#style);
|
|
};
|
|
|
|
async #getIcon(path) {
|
|
// Cache hit!
|
|
if (DotDungeonIcon._cache.has(path)) {
|
|
console.debug(`.dungeon | Icon ${path} cache hit`);
|
|
return DotDungeonIcon._cache.get(path);
|
|
};
|
|
|
|
const r = await fetch(path);
|
|
switch (r.status) {
|
|
case 200:
|
|
case 201:
|
|
break;
|
|
default:
|
|
console.error(`.dungeon | Failed to fetch icon: ${path}`);
|
|
return;
|
|
};
|
|
|
|
console.debug(`.dungeon | Adding icon ${path} to the cache`);
|
|
const svg = this.#parseSVG(await r.text());
|
|
DotDungeonIcon._cache.set(path, svg);
|
|
return svg;
|
|
};
|
|
|
|
/** Takes an SVG string and returns it as a DOM node */
|
|
#parseSVG(content) {
|
|
const temp = document.createElement(`div`);
|
|
temp.innerHTML = content;
|
|
return temp.querySelector(`svg`);
|
|
}
|
|
};
|