integreat_cms/static/src/js/analytics/hix-widget.ts
import { getContent } from "../forms/tinymce-init";
import { getCsrfToken } from "../utils/csrf-token";
let initialContent: string = null;
let initialHixScore: number = null;
const calculateBackgroundColor = (score: number, setOutdated: boolean): string => {
const hixThresholdGood = 15;
const hixThresholdOk = 7;
if (setOutdated) {
return "rgb(16, 111, 254, 0.3)";
}
if (score >= hixThresholdGood) {
return "rgb(74, 222, 128)";
}
if (score > hixThresholdOk) {
return "rgb(250, 204, 21)";
}
return "rgb(239, 68, 68)";
};
/**
* Display the HIX score in a bar graph
*/
const updateHixBar = (score: number, setOutdated: boolean) => {
const hixScore: HTMLElement = document.getElementById("hix-value");
hixScore.textContent = `HIX ${score}`;
const hixMaxScore = 20;
const backgroundColor = calculateBackgroundColor(score, setOutdated);
const hixBarFill: HTMLElement = document.getElementById("hix-bar-fill");
const style = `width:${(score / hixMaxScore) * 100}%;background-color:${backgroundColor};`;
hixBarFill.setAttribute("style", style);
};
/**
* Display a label depending on the current HIX state
*/
const updateHixStateLabel = (state: "updated" | "outdated" | "no-content" | "error") => {
document.querySelectorAll("[data-hix-state]").forEach((element) => {
if (element.getAttribute("data-hix-state") === state) {
element.classList.remove("hidden");
} else {
element.classList.add("hidden");
}
});
// Hide the canvas if an error occurred
if (state === "error" || state === "no-content") {
document.getElementById("hix-container").classList.add("hidden");
} else if (state === "updated") {
document.getElementById("hix-container").classList.remove("hidden");
}
// Hide the button if there is no content or the value is up-to-date
const updateButton = document.getElementById("btn-update-hix-value");
if (state === "updated" || state === "no-content") {
updateButton.classList.add("hidden");
} else {
updateButton.classList.remove("hidden");
}
// Hide HIX feedback when the HIX state is not up-to-date
if (state !== "updated") {
document.getElementById("hix-feedback").classList.add("hidden");
} else {
document.getElementById("hix-feedback").classList.remove("hidden");
}
// Hide the loading spinner
document.getElementById("hix-loading")?.classList.add("hidden");
};
/**
* Display the HIX feedback details
*/
const updateHixFeedback = (hixFeedback: string) => {
let feedbackCount = 0;
let feedbackJson: any;
const feedbackContainer = document.getElementById("hix-feedback");
try {
feedbackJson = JSON.parse(hixFeedback);
} catch (error) {
feedbackContainer.classList.add("hidden");
console.error("Invalid HIX feedback", error);
return;
}
const feedbackSections = document.querySelectorAll("[hix-feedback-category]");
feedbackSections.forEach((feedbackSection) => {
const categoryName = feedbackSection.getAttribute("hix-feedback-category");
const feedbackEntry = feedbackJson.find((item: { category: string }) => item.category === categoryName);
let feedbackResult = feedbackEntry ? feedbackEntry.result : [];
// Cast array into number if HIX feedback is in the old format
if (feedbackResult instanceof Array) {
feedbackResult = feedbackResult.length;
}
if (feedbackResult > 0) {
const categoryCount = feedbackSection.querySelector("span");
categoryCount.textContent = feedbackResult;
feedbackSection.classList.remove("hidden");
feedbackCount += 1;
} else {
feedbackSection.classList.add("hidden");
}
});
if (feedbackCount === 0) {
feedbackContainer.classList.add("hidden");
} else {
feedbackContainer.classList.remove("hidden");
}
};
/**
* Request current HIX data
* @returns HIX score and HIX feedback
*/
const getHixData = async (): Promise<[number?, string?]> => {
const updateButton = document.getElementById("btn-update-hix-value");
const sentContent = getContent().trim();
const response = await fetch(updateButton.dataset.url, {
method: "POST",
headers: {
"X-CSRFToken": getCsrfToken(),
},
body: JSON.stringify({
text: sentContent,
}),
});
const json = await response.json();
if (json.error) {
updateHixStateLabel("error");
return [null, null];
}
if (json.score === null) {
updateHixStateLabel("no-content");
return [null, null];
}
updateHixStateLabel("updated");
initialContent = sentContent;
return [json.score, JSON.stringify(json.feedback)];
};
window.addEventListener("load", async () => {
// If the page has no diagram, do nothing
if (!document.getElementById("hix-bar")) {
return;
}
const toggleMTCheckboxes = (disabled: boolean) => {
const checkboxes: NodeListOf<HTMLInputElement> = document.querySelectorAll(
"#machine-translation-form input[type='checkbox']"
);
checkboxes.forEach((checkbox) => {
const label = document.querySelector(`label[for='${checkbox.id}']`);
if (disabled) {
label.classList.add("pointer-events-none");
checkbox.classList.add("fake-disable");
} else {
label.classList.remove("pointer-events-none");
checkbox.classList.remove("fake-disable");
}
});
};
// Show or hide the checkboxes for automatic translation depending on the HIX score
const updateMTAvailability = (hixValue: number) => {
const mtForm = document.getElementById("machine-translation-form");
const hixScoreWarning = document.getElementById("hix-score-warning");
const minimumHix = parseFloat(mtForm?.dataset.minimumHix);
if (hixValue && hixValue < minimumHix) {
hixScoreWarning?.classList.remove("hidden");
toggleMTCheckboxes(true);
} else {
hixScoreWarning?.classList.add("hidden");
toggleMTCheckboxes(false);
}
};
const initHixWidget = async () => {
initialContent = getContent().trim();
if (!initialContent) {
updateHixStateLabel("no-content");
return;
}
const hixContainer = document.getElementById("hix-container");
let hixScore = parseFloat(hixContainer.dataset.initialHixScore);
let hixFeedback = hixContainer.dataset.initialHixFeedback;
if (!hixScore) {
const response = await getHixData();
hixScore = response[0];
hixFeedback = response[1];
}
if (hixScore != null) {
initialHixScore = hixScore;
updateHixStateLabel("updated");
updateHixBar(initialHixScore, false);
updateHixFeedback(hixFeedback);
updateMTAvailability(initialHixScore);
} else {
updateHixStateLabel("no-content");
}
};
// Set listener, that checks, if tinyMCE content has changed to update the
// HIX status
document.querySelectorAll("[data-content-changed]").forEach((element) => {
// make sure initHixWidget is called only after tinyMCE is initialized
element.addEventListener("tinyMCEInitialized", initHixWidget);
element.addEventListener("contentChanged", () => {
const content = getContent().trim();
const labelState = (() => {
if (!content) {
return "no-content";
}
if (content !== initialContent) {
return "outdated";
}
return null;
})();
if (labelState !== null) {
updateHixBar(initialHixScore, labelState === "outdated");
updateHixStateLabel(labelState);
}
});
});
// Set listener for update button
document.getElementById("btn-update-hix-value").addEventListener("click", async (event) => {
document.getElementById("hix-loading")?.classList.remove("hidden");
event.preventDefault();
const response = await getHixData();
const hixScore = response[0];
const hixFeedback = response[1];
if (hixScore !== null) {
initialHixScore = hixScore;
updateHixBar(initialHixScore, false);
updateHixFeedback(hixFeedback);
updateMTAvailability(initialHixScore);
}
});
const toggleHixIgnore = async () => {
const hixIgnore = document.getElementById("id_hix_ignore") as HTMLInputElement;
const hixBlock = document.getElementById("hix-block");
const mtForm = document.getElementById("machine-translation-form");
if (hixIgnore.checked) {
hixBlock.classList.add("hidden");
toggleMTCheckboxes(true);
mtForm?.classList.add("hidden");
} else {
toggleMTCheckboxes(false);
mtForm?.classList.remove("hidden");
hixBlock.classList.remove("hidden");
}
};
// Set listener to show/hide HIX widget when hix_ignore checkbox is clicked
document.getElementById("id_hix_ignore")?.addEventListener("change", toggleHixIgnore);
toggleHixIgnore();
});