Files
kupshop/admin/static/js/wpj.translationsFigure.js
2025-08-02 16:30:27 +02:00

171 lines
4.2 KiB
JavaScript

/**
* @typedef {'Y' | 'N'} ExactFigure
* @typedef {ExactFigure | 'indeterminate'} Figure
* @typedef {Record<string, Figure>} 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<string, HTMLInputElement>} */
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;
});