/** * @typedef {'Y' | 'N'} ExactFigure * @typedef {ExactFigure | 'indeterminate'} Figure * @typedef {Record} LanguageFigureMapping */ /** * @param {string} lang * @return {HTMLButtonElement} */ function createButton(lang) { const btn = document.createElement('button'); btn.type = 'button'; btn.dataset.lang = lang; btn.innerText = lang; return btn; } /** * @param {string} lang * @param {string|undefined} namePrefix * @return {HTMLInputElement} */ function createInput(lang, namePrefix = undefined) { const ipt = document.createElement('input'); ipt.type = 'hidden'; ipt.name = `${namePrefix}[${lang}]`; ipt.dataset.lang = lang; return ipt; } /** * @param {Figure} figure * @param {ExactFigure|undefined} parentFigure * @return {string} */ function resolveFigureClassName(figure, parentFigure = undefined) { switch (figure) { case 'Y': return 'btn-success'; case 'N': return 'btn-danger'; case 'indeterminate': if (!parentFigure) { throw new Error('Parent figure is undefined'); } return `${resolveFigureClassName(parentFigure)}-indeterminate`; } } export class WpjTranslationsFigure { /** @type {ExactFigure} */ #parentFigure; /** @type {LanguageFigureMapping} */ #initialState; /** @type {string} */ inputNamePrefix; /** @type {HTMLButtonElement[]} */ buttons = []; /** @type {Map} */ inputs = new Map(); /** * @param {string[]} languages * @param {string} inputNamePrefix * @param {ExactFigure} parentFigure */ constructor(languages, inputNamePrefix, parentFigure = 'Y') { this.#initialState = Object.fromEntries(languages.map(l => [l, 'indeterminate'])); this.inputNamePrefix = inputNamePrefix; this.#parentFigure = parentFigure; } /** * @param {HTMLElement} target */ render(target) { for (const lang of this.languages) { const btn = createButton(lang); const ipt = createInput(lang, this.inputNamePrefix); ipt.value = this.#initialState[lang] ?? 'indeterminate'; btn.insertAdjacentElement('beforeend', ipt); btn.addEventListener('click', this.handleValueChange.bind(this)); this.buttons.push(btn); this.inputs.set(lang, ipt); target.insertAdjacentElement('beforeend', btn); } this.applyStyles(); } /** @param {MouseEvent} ev */ handleValueChange(ev) { const lang = ev.currentTarget.dataset.lang; const ipt = this.inputs.get(lang); switch (ipt.value) { case 'Y': ipt.value = 'N'; break; case 'N': ipt.value = 'indeterminate'; break; case 'indeterminate': ipt.value = 'Y'; break; } ipt.dispatchEvent(new Event('change')); window.somethingChanged = true; this.applyStyles(); } applyStyles() { for (const btn of this.buttons) { const lang = btn.dataset.lang; const input = this.inputs.get(lang); if (!input) { return; } btn.className = 'btn ' + resolveFigureClassName(input.value, this.parentFigure); } } /** @param {ExactFigure} figure */ set parentFigure(figure) { this.#parentFigure = figure; this.applyStyles(); } get parentFigure() { return this.#parentFigure; } get languages() { return Object.keys(this.#initialState); } /** @param {string} language */ valueOf(language) { return this.inputs.get(language)?.value; } get values() { return Object.fromEntries([...this.inputs.entries()].map(([k, v]) => [k, v.value])); } /** * @param {LanguageFigureMapping} state */ setState(state) { for (const lang in state) { if (!(lang in this.#initialState)) { continue; } this.#initialState[lang] = state[lang]; } } toggleParentFigure() { return this.parentFigure = this.parentFigure === 'Y' ? 'N' :'Y'; } } $(() => { wpj.TranslationsFigure = WpjTranslationsFigure; });