409 lines
13 KiB
JavaScript
409 lines
13 KiB
JavaScript
import i18next from "i18next";
|
|
import LanguageDetector from "i18next-browser-languagedetector";
|
|
import HttpApi from "i18next-http-backend";
|
|
import '@fortawesome/fontawesome-free/js/fontawesome'
|
|
import '@fortawesome/fontawesome-free/js/solid'
|
|
import '@fortawesome/fontawesome-free/js/regular'
|
|
import '@fortawesome/fontawesome-free/js/brands'
|
|
import './feedback'
|
|
import './main.scss';
|
|
|
|
/////////////////
|
|
// TRANSLATION //
|
|
/////////////////
|
|
|
|
async function initI18next() {
|
|
await i18next
|
|
.use(HttpApi)
|
|
.use(LanguageDetector)
|
|
.init({
|
|
supportedLngs: ["en", "de"],
|
|
nonExplicitSupportedLngs: true,
|
|
fallbackLng: "en",
|
|
debug: true,
|
|
backend: {
|
|
loadPath: "/i18n/{{lng}}.json",
|
|
},
|
|
});
|
|
}
|
|
|
|
function translatePageElements() {
|
|
// Translate content inside a tag
|
|
const translatableElements = document.querySelectorAll(
|
|
"[data-i18n]",
|
|
);
|
|
translatableElements.forEach((el) => {
|
|
const key = el.getAttribute("data-i18n");
|
|
el.innerHTML = i18next.t(key);
|
|
});
|
|
// Translate alt texts
|
|
const translatableAltTexts = document.querySelectorAll(
|
|
"[alt-i18n-key]",
|
|
);
|
|
translatableAltTexts.forEach((el) => {
|
|
const translation_key = el.getAttribute("alt-i18n-key");
|
|
el.setAttribute("alt", i18next.t(translation_key));
|
|
});
|
|
|
|
}
|
|
|
|
function bindLocaleSwitcher(initialValue) {
|
|
const switcher = document.querySelector(
|
|
"[data-i18n-switcher]",
|
|
);
|
|
switcher.value = initialValue;
|
|
switcher.onchange = (e) => {
|
|
i18next
|
|
.changeLanguage(e.target.value)
|
|
.then(translatePageElements)
|
|
.then(updateCageCheck);
|
|
};
|
|
}
|
|
|
|
(async function () {
|
|
await initI18next();
|
|
translatePageElements();
|
|
bindLocaleSwitcher(i18next.resolvedLanguage);
|
|
updateCageCheck();
|
|
})();
|
|
|
|
//////////
|
|
// TABS //
|
|
//////////
|
|
|
|
|
|
function initTabs(tabs, tabContent, activationClass) {
|
|
tabs.forEach((tab) => {
|
|
tab.addEventListener('click', (e) => {
|
|
let selectedTabID = tab.getAttribute('data-tab-id');
|
|
updateActiveTab(tabs, tab);
|
|
updateActiveContent(tabContent, selectedTabID);
|
|
})
|
|
})
|
|
}
|
|
|
|
function updateActiveTab(tabs, selectedTabID) {
|
|
tabs.forEach((tab) => {
|
|
if (tab && tab.classList.contains(ACTIVATION_CLASS)) {
|
|
tab.classList.remove(ACTIVATION_CLASS);
|
|
}
|
|
});
|
|
selectedTabID.classList.add(ACTIVATION_CLASS);
|
|
}
|
|
|
|
function updateActiveContent(tabsContent, selectedTabID) {
|
|
tabsContent.forEach((item) => {
|
|
if (item && item.classList.contains(ACTIVATION_CLASS)) {
|
|
item.classList.remove(ACTIVATION_CLASS);
|
|
}
|
|
let data = item.getAttribute('data-content-id');
|
|
if (data === selectedTabID) {
|
|
item.classList.add(ACTIVATION_CLASS);
|
|
}
|
|
});
|
|
}
|
|
|
|
const primaryTabs = [...document.querySelectorAll('#primary-tabs li')];
|
|
const primaryTabsContent = [...document.querySelectorAll('#tab-content div')];
|
|
const ACTIVATION_CLASS = 'is-active';
|
|
|
|
initTabs(primaryTabs, primaryTabsContent, ACTIVATION_CLASS);
|
|
|
|
|
|
////////////////
|
|
// CALCULATOR //
|
|
////////////////
|
|
|
|
const MINIMUM_BASE_AREA = 0.5;
|
|
const MINIMUM_AREA_THREE_RATS = 1.5;
|
|
const AREA_PER_ADDITIONAL_RAT = 0.25;
|
|
const MAXIMUM_FALL_HEIGHT = 0.5;
|
|
const MINIMUM_LENGTH_LONG_SIDE = 0.8;
|
|
const MINIMUM_LENGTH_SHORT_SIDE = 0.5;
|
|
const MINIMUM_FLOOR_HEIGHT = 0.25;
|
|
|
|
const FAILED_BASE_AREA = "failed_base_area";
|
|
const FAILED_OVERALL_AREA = "failed_overall_area";
|
|
const FAILED_FALL_HEIGHT = "failed_fall_height";
|
|
const FAILED_NUM_RATS = "failed_num_rats";
|
|
const FAILED_MINIMUM_LENGTH_LONG_SIDE = "failed_length_long_side";
|
|
const FAILED_MINIMUM_LENGTH_SHORT_SIDE = "failed_length_short_side";
|
|
const FAILED_FLOOR_HEIGHT = "failed_floor_height"
|
|
|
|
|
|
const CRITERIA_BASE_AREA = "base_area";
|
|
const CRITERIA_FALL_HEIGHT = "fall_height";
|
|
const CRITERIA_MINIMUM_LENGTH_LONG_SIDE = "length_long_side";
|
|
const CRITERIA_MINIMUM_LENGTH_SHORT_SIDE = "length_short_side";
|
|
const CRITERIA_FLOOR_HEIGHT = "floor_height"
|
|
const CRITERIA_OVERALL_AREA = "overall_area"
|
|
|
|
|
|
class Validator {
|
|
constructor() {
|
|
this.FAIL_CRITERIA = {
|
|
[FAILED_BASE_AREA]: i18next.t('failed-base-area', {"MINIMUM_BASE_AREA": MINIMUM_BASE_AREA}),
|
|
[FAILED_OVERALL_AREA]: i18next.t("failed-overall-area"),
|
|
[FAILED_FALL_HEIGHT]: i18next.t("failed-fall-height", {"maximum_fall_height": (MAXIMUM_FALL_HEIGHT * 100).toFixed(0)}),
|
|
[FAILED_FLOOR_HEIGHT]: i18next.t("failed-floor-height", {"minimum_floor_height": (MINIMUM_FLOOR_HEIGHT * 100).toFixed(0)}),
|
|
[FAILED_NUM_RATS]: i18next.t("failed-num-rats"),
|
|
[FAILED_MINIMUM_LENGTH_LONG_SIDE]: i18next.t("failed-minimum-length-long-side", {"minimum_length_long_side": (MINIMUM_LENGTH_LONG_SIDE * 100).toFixed(0)}),
|
|
[FAILED_MINIMUM_LENGTH_SHORT_SIDE]: i18next.t("failed-minimum-length-short-side", {"minimum_length_short_side": (MINIMUM_LENGTH_SHORT_SIDE * 100).toFixed(0)}),
|
|
};
|
|
this.STATIC_CRITERIA = {
|
|
[CRITERIA_BASE_AREA]: i18next.t('base-area', {"MINIMUM_BASE_AREA": MINIMUM_BASE_AREA}),
|
|
[CRITERIA_FALL_HEIGHT]: i18next.t("fall-height", {"maximum_fall_height": (MAXIMUM_FALL_HEIGHT * 100).toFixed(0)}),
|
|
[CRITERIA_FLOOR_HEIGHT]: i18next.t("floor-height", {"minimum_floor_height": (MINIMUM_FLOOR_HEIGHT * 100).toFixed(0)}),
|
|
[CRITERIA_MINIMUM_LENGTH_LONG_SIDE]: i18next.t("minimum-length-long-side", {"minimum_length_long_side": (MINIMUM_LENGTH_LONG_SIDE * 100).toFixed(0)}),
|
|
[CRITERIA_MINIMUM_LENGTH_SHORT_SIDE]: i18next.t("minimum-length-short-side", {"minimum_length_short_side": (MINIMUM_LENGTH_SHORT_SIDE * 100).toFixed(0)}),
|
|
}
|
|
}
|
|
|
|
overallAreaNeeded(numOfRats) {
|
|
if (numOfRats < 3 || numOfRats > 15) {
|
|
throw new Error("This formula works only from 3 to 15 rats");
|
|
}
|
|
return MINIMUM_AREA_THREE_RATS + (numOfRats - 3.0) * AREA_PER_ADDITIONAL_RAT;
|
|
}
|
|
|
|
allowedNumberOfRats(overallArea) {
|
|
/*
|
|
Calculates the number of rats that are allowed for a certain overall area.
|
|
*/
|
|
let result = 3.0 + (overallArea - MINIMUM_AREA_THREE_RATS) / AREA_PER_ADDITIONAL_RAT;
|
|
if (result < 3) {
|
|
throw new Error("Cages must be for three rats or more");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
failCageNumberIndependent(dimensions, numFullFloors) {
|
|
/*
|
|
Function that checks a cage independent of the number of rats.
|
|
*/
|
|
let failedCriteria = {};
|
|
|
|
const baseArea = dimensions.depth * dimensions.width;
|
|
if (baseArea < MINIMUM_BASE_AREA) {
|
|
failedCriteria[FAILED_BASE_AREA] = this.FAIL_CRITERIA[FAILED_BASE_AREA];
|
|
}
|
|
|
|
if (dimensions.height / numFullFloors > MAXIMUM_FALL_HEIGHT) {
|
|
failedCriteria[FAILED_FALL_HEIGHT] = this.FAIL_CRITERIA[FAILED_FALL_HEIGHT];
|
|
}
|
|
|
|
if (dimensions.width < MINIMUM_LENGTH_LONG_SIDE && dimensions.depth < MINIMUM_LENGTH_LONG_SIDE) {
|
|
failedCriteria[FAILED_MINIMUM_LENGTH_LONG_SIDE] = this.FAIL_CRITERIA[FAILED_MINIMUM_LENGTH_LONG_SIDE];
|
|
}
|
|
|
|
if (dimensions.width < MINIMUM_LENGTH_SHORT_SIDE || dimensions.depth < MINIMUM_LENGTH_SHORT_SIDE) {
|
|
failedCriteria[FAILED_MINIMUM_LENGTH_SHORT_SIDE] = this.FAIL_CRITERIA[FAILED_MINIMUM_LENGTH_SHORT_SIDE];
|
|
}
|
|
|
|
if (dimensions.height / numFullFloors < MINIMUM_FLOOR_HEIGHT) {
|
|
failedCriteria[FAILED_FLOOR_HEIGHT] = this.FAIL_CRITERIA[FAILED_FLOOR_HEIGHT];
|
|
}
|
|
|
|
return failedCriteria;
|
|
}
|
|
|
|
getOverallArea(dimensions, numFullFloors) {
|
|
const baseArea = dimensions.depth * dimensions.width;
|
|
return baseArea * numFullFloors
|
|
}
|
|
|
|
cageCheck(dimensions, numRats, numFullFloors) {
|
|
/*
|
|
Function that checks a cage based on overall criteria and the number of rats.
|
|
*/
|
|
let failedCriteria = this.failCageNumberIndependent(dimensions, numFullFloors);
|
|
|
|
if (numRats < 2 || numRats > 15) {
|
|
failedCriteria[FAILED_NUM_RATS] = this.FAIL_CRITERIA[FAILED_NUM_RATS];
|
|
}
|
|
|
|
const overallArea = this.getOverallArea(dimensions, numFullFloors);
|
|
const overallAreaNeeded = this.overallAreaNeeded(numRats);
|
|
if (overallArea < overallAreaNeeded) {
|
|
failedCriteria[FAILED_OVERALL_AREA] = this.FAIL_CRITERIA[FAILED_OVERALL_AREA];
|
|
}
|
|
|
|
return failedCriteria;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
class Dimensions {
|
|
constructor(width, depth, height) {
|
|
this.width = width;
|
|
this.depth = depth;
|
|
this.height = height;
|
|
}
|
|
|
|
toString() {
|
|
return `${this.width}x${this.depth}x${this.height}`;
|
|
}
|
|
|
|
static fromDict(data) {
|
|
const {width, depth, height} = data;
|
|
return new Dimensions(width, depth, height);
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////
|
|
// DOCUMENT INTERACTION //
|
|
//////////////////////////
|
|
|
|
///////
|
|
// 1 //
|
|
///////
|
|
|
|
const numRatsCalculatorInputWidth = document.getElementById("num-rats-width");
|
|
numRatsCalculatorInputWidth.onchange = updateNumRatsCalculator;
|
|
const numRatsCalculatorInputDepth = document.getElementById("num-rats-depth");
|
|
numRatsCalculatorInputDepth.onchange = updateNumRatsCalculator;
|
|
const numRatsCalculatorInputHeight = document.getElementById("num-rats-height");
|
|
numRatsCalculatorInputHeight.onchange = updateNumRatsCalculator;
|
|
|
|
let numRatsNumFullFloors = document.getElementById("num-rats-numFullFloors");
|
|
numRatsNumFullFloors.oninput = function () {
|
|
updateNumRatsCalculator();
|
|
}
|
|
|
|
///////
|
|
// 2 //
|
|
///////
|
|
|
|
|
|
let cageCalcLabelNumRats = document.getElementById("cageCalcLabelNumRats");
|
|
|
|
let cageCalcRatSlider = document.getElementById("cageCalcNumRats");
|
|
|
|
cageCalcRatSlider.oninput = function () {
|
|
updateCageCalc();
|
|
}
|
|
|
|
|
|
///////
|
|
// 3 //
|
|
///////
|
|
|
|
const inputWidth = document.getElementById("width");
|
|
inputWidth.onchange = updateCageCheck;
|
|
const inputDepth = document.getElementById("depth");
|
|
inputDepth.onchange = updateCageCheck;
|
|
const inputHeight = document.getElementById("height");
|
|
inputHeight.onchange = updateCageCheck;
|
|
|
|
let fullFloorNum = document.getElementById("numFullFloors");
|
|
fullFloorNum.oninput = function () {
|
|
updateCageCheck();
|
|
}
|
|
|
|
|
|
let labelNumRats = document.getElementById("labelNumRats");
|
|
|
|
let ratSlider = document.getElementById("numRats");
|
|
|
|
ratSlider.oninput = function () {
|
|
updateCageCheck();
|
|
}
|
|
|
|
|
|
function getResultFromChecks(checks) {
|
|
if (Object.keys(checks).length > 0) {
|
|
const ul = document.createElement('ul');
|
|
for (const key in checks) {
|
|
const li = document.createElement('li');
|
|
li.textContent = `❌ ` + checks[key];
|
|
ul.appendChild(li);
|
|
}
|
|
return ul;
|
|
} else {
|
|
const p = document.createElement('p');
|
|
p.innerHTML = "✅ " + i18next.t("cage-complies-with-all-criteria")
|
|
return p;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
function formatCriteria(criteria) {
|
|
const ul = document.createElement('ul');
|
|
for (const key in criteria) {
|
|
const li = document.createElement('li');
|
|
li.textContent = `☑️ ` + criteria[key];
|
|
ul.appendChild(li);
|
|
}
|
|
return ul;
|
|
|
|
}
|
|
|
|
function updateCageCheck() {
|
|
labelNumRats.innerHTML = i18next.t("cage-for-x-rats", {"num_rats": ratSlider.value});
|
|
|
|
const width = inputWidth.value
|
|
const depth = inputDepth.value
|
|
const height = inputHeight.value
|
|
const dimensions = new Dimensions(width / 100, depth / 100, height / 100);
|
|
const validator = new Validator();
|
|
const failed_checks = validator.cageCheck(dimensions, ratSlider.value, fullFloorNum.value);
|
|
let resultsDiv = document.getElementById("resultsDiv");
|
|
|
|
const result = getResultFromChecks(failed_checks);
|
|
|
|
resultsDiv.innerHTML = "";
|
|
resultsDiv.appendChild(result);
|
|
}
|
|
|
|
function updateCageCalc() {
|
|
let numRats = cageCalcRatSlider.value
|
|
cageCalcLabelNumRats.innerHTML = i18next.t("cage-for-x-rats", {"num_rats": numRats});
|
|
const validator = new Validator();
|
|
let criteria = validator.STATIC_CRITERIA;
|
|
let minimumOverallArea = validator.overallAreaNeeded(cageCalcRatSlider.value);
|
|
criteria[CRITERIA_OVERALL_AREA] = i18next.t('overall-area', {"numRats": numRats, "minimumOverallArea": minimumOverallArea});
|
|
|
|
let resultsDiv = document.getElementById("cageCalcResultsDiv");
|
|
|
|
const result = formatCriteria(criteria);
|
|
|
|
resultsDiv.innerHTML = "";
|
|
resultsDiv.appendChild(result);
|
|
|
|
}
|
|
|
|
function updateNumRatsCalculator() {
|
|
|
|
const width = numRatsCalculatorInputWidth.value
|
|
const depth = numRatsCalculatorInputDepth.value
|
|
const height = numRatsCalculatorInputHeight.value
|
|
const dimensions = new Dimensions(width / 100, depth / 100, height / 100);
|
|
const validator = new Validator();
|
|
const failed_checks = validator.failCageNumberIndependent(dimensions, fullFloorNum.value);
|
|
|
|
let overallArea = validator.getOverallArea(dimensions, numRatsNumFullFloors.value);
|
|
let allowedNumRats;
|
|
try {
|
|
allowedNumRats = validator.allowedNumberOfRats(overallArea);
|
|
} catch (e) {
|
|
console.log(e);
|
|
failed_checks[FAILED_BASE_AREA] = validator.FAIL_CRITERIA[FAILED_OVERALL_AREA];
|
|
allowedNumRats = 0;
|
|
}
|
|
|
|
|
|
let resultsDiv = document.getElementById("num-rats-resultsDiv");
|
|
|
|
const result = getResultFromChecks(failed_checks);
|
|
|
|
resultsDiv.innerHTML = "";
|
|
const p = document.createElement('p');
|
|
p.textContent = i18next.t("cage-for-x-rats", {"num_rats": allowedNumRats});
|
|
resultsDiv.appendChild(p);
|
|
resultsDiv.appendChild(result);
|
|
}
|
|
|
|
const validator = new Validator(); |