Compare commits

...

21 Commits

Author SHA1 Message Date
666cc732bf feat: Style main result
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-04-18 09:27:26 +02:00
ecc3d418fc fix: remove placeholder when result is calculated 2025-04-18 09:27:12 +02:00
154f550775 fix: Cage checker
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-04-18 08:37:12 +02:00
cb71f61a91 feat: Fill yet empty result field 2025-04-18 08:27:11 +02:00
168e4acf6a fix: Round down number of rats if not int 2025-04-18 08:14:38 +02:00
cfe305e698 feat: Adjust title based on language
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-04-18 01:00:41 +02:00
1606c7dcf1 feat: add floating button to give feedback 2025-04-18 00:39:24 +02:00
d309ea2b46 fix: make sure classes are not overwritten
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-04-18 00:19:18 +02:00
b1372a5fbb feat: Restyle feedback explanation 2025-04-18 00:11:30 +02:00
4bc098bc09 feat(trans): Ann english translations of questions 2025-04-17 23:57:04 +02:00
34c2d24891 feat: Adjust the questions to cage calculator 2025-04-17 23:36:59 +02:00
74d1ba96b3 fix(trans): finally fixed 2025-04-17 23:30:11 +02:00
47040057b1 fix(trans): use dynamic content 2025-04-17 23:07:43 +02:00
2984ae4e6d fix(trans): keep dynamic content 2025-04-17 23:07:33 +02:00
db273b641d fix(trans): adding stuff 2025-04-17 23:01:55 +02:00
8f6a28a33e fix(trans): i hate it 2025-04-17 22:48:28 +02:00
4fbc47ad8c feat(trans): Use de as standard 2025-04-17 22:48:11 +02:00
f405c325f7 fix(trans): Autogen 2025-04-17 22:47:56 +02:00
da52a1dfdf feat: Add sus questions 2025-04-17 22:39:08 +02:00
a13a818485 feat: Add various translations 2025-04-17 21:45:37 +02:00
d5a467aa55 feat: Add sus (translations missing) 2025-04-17 18:24:37 +02:00
8 changed files with 242 additions and 31 deletions

View File

@@ -8,4 +8,6 @@ module.exports = {
locales: ['en', 'de'],
output: 'public/i18n/$LOCALE.json',
input: [ 'src/*.js', 'public/*.html', ],
keepRemoved: [/dynamic.*/], //prefixing a key with dynamic. will make it not disappear upon calling i18next
}

View File

@@ -1,5 +1,6 @@
{
"app-name": "Käfigrechner",
"title": "VdRD Käfigrechner für Ratten",
"app-name": "VdRD Käfigrechner für Ratten",
"how-many-rats-does-this-cage-fit": "Wie viele Ratten passen in den Käfig?",
"what-cage-should-i-get": "Käfiggröße rechnen",
"check-existing-cage": "Käfig prüfen",
@@ -9,7 +10,11 @@
"height-cm": "Höhe (cm)",
"full-floors": "Vollebenen",
"result": "Ergebnis",
"input-data-num-rats": "Bitte gib erst die Maße des Käfigs ein, dann siehst du hier die Anzahl der Ratten die hineinpassen.",
"number-of-rats": "Anzahl an Ratten",
"please-input-cage-calc": "Stell ein für wie viel Ratten der Käfig sein soll, danach siehst du hier das Ergebnis.",
"please-input-cage-check": "Bitte gib erst die Maße des Käfigs und die Anzahl an Ratten ein. Danach siehst du hier, ob der Käfig passt.",
"give-feedback": "Feedback geben",
"change-language": "Sprache ändern",
"information-on-rat-husbandry": "Information",
"basic-rat-info": "Basiswissen Ratten",
@@ -20,8 +25,10 @@
"the-vdrd": "VdRD e.V.",
"imprint": "Impressum",
"source-code": "Quellcode",
"give-feedback": "Feedback geben",
"feedback": "Feedback",
"sus-title": "Bewerte den Rechner",
"explanation-feedback": "Vielen Dank, dass du uns Feedback gibst! Dein Feedback hilft uns den Rechner zu verbessern. Wir sammeln dabei keinerlei persönliche Daten. Du tust uns einfach nur einen großen Gefallen!",
"submit": "Absenden",
"failed-base-area": "Die Mindestgrundfläche des Käfigs muss {{ MINIMUM_BASE_AREA }}m² (also z.B. 100x50cm) betragen.",
"failed-overall-area": "Die Gesamtfläche im Käfig ist zu klein.",
"failed-fall-height": "Die mögliche Fallhöhe darf nicht mehr als {{ maximum_fall_height }}cm betragen.",
@@ -36,5 +43,23 @@
"minimum-length-short-side": "Die kurze Seite des Käfig muss mindestens {{ minimum_length_short_side }}cm lang sein.",
"cage-complies-with-all-criteria": "Der Käfig erfüllt alle Kriterien!",
"cage-for-x-rats": "Käfig für {{ num_rats }} Ratten",
"overall-area": "Die Gesamtfläche für {{ numRats }} Ratten muss mindestens {{ minimumOverallArea }}m² betragen."
"overall-area": "Die Gesamtfläche für {{ numRats }} Ratten muss mindestens {{ minimumOverallArea }}m² betragen.",
"strongly-disagree": "Stimme gar nicht zu",
"strongly-agree": "Stimme voll zu",
"submit-success": "Erfolgreich abgesendet!",
"submit-error": "Fehler beim Absenden!",
"network-error": "Fehler bei der Datenübertragung!",
"dynamic.sus-question-easy-to-use": "Ich finde der Käfigrechner ist einfach zu nutzen.",
"dynamic.sus-question-unnecessarily-complex": "Ich finde den Käfigrechner unnötig komplex.",
"dynamic.sus-question-need-support-of-technical-person": "Ich glaube ich brauche die Hilfe eine Technik-Person umd den Rechner zu nutzen.",
"dynamic.sus-question-well-integrated": "Ich finde, dass die verschiedenen Funktionen des Rechners gut integriert sind.",
"dynamic.sus-question-inconsistency": "Ich finde, dass es im Rechner zu viele Inkonsistenzen gibt.",
"dynamic.sus-question-learn-quickly": "Ich vermute, dass die meisten Leute schnell lernen den Rechner zu benutzen",
"dynamic.sus-question-cumbersome": "Ich finde den Rechner umständlich zu nutzen.",
"dynamic.sus-question-confident": "Ich habe mich bei der Nutzung des Rechners sehr sicher gefühlt.",
"dynamic.sus-question-trust": "Ich vertraue den Ergebnissen des Rechners.",
"dynamic.question-whats-missing": "Welche Funktion fehlt dir?",
"dynamic.question-pain-points": "Was muss aus deiner Sicht am Rechner geändert werden?",
"dynamic.question-other-feedback": "Was willst du uns sonst mitteilen?",
"dynamic.submit-success": "Erfolgreich abgesendet!"
}

View File

@@ -1,5 +1,6 @@
{
"app-name": "Cage Calculator",
"title": "VdRD Rat Cage Calculator",
"app-name": "VdRD Rat Cage Calculator",
"how-many-rats-does-this-cage-fit": "How many rats does this cage fit?",
"what-cage-should-i-get": "Calculate cage size",
"check-existing-cage": "Check cage",
@@ -9,7 +10,11 @@
"height-cm": "Height (cm)",
"full-floors": "Full floors",
"result": "Result",
"input-data-num-rats": "Please input them measurements of the cage. Then you will see the number of rats allowed.",
"number-of-rats": "Number of Rats",
"please-input-cage-calc": "First put in how many rats should fit the cage. After that come back here.",
"please-input-cage-check": "First put in how many rats should fit the cage and the cages measurements. After that come back here to see if they fit.",
"give-feedback": "Give Feedback",
"change-language": "Change language",
"information-on-rat-husbandry": "Information",
"basic-rat-info": "Basic Rat Information",
@@ -20,8 +25,10 @@
"the-vdrd": "VdRD r.V.",
"imprint": "Imprint",
"source-code": "Source Code",
"give-feedback": "Give Feedback",
"feedback": "Feedback",
"sus-title": "Rate the calculator",
"explanation-feedback": "Thank you for giving us feedback! We will use your feedback to improve the calculator.",
"submit": "Submit",
"failed-base-area": "The base area of the cage must not be below {{ MINIMUM_BASE_AREA }}m².",
"failed-overall-area": "The overall area in the cage is to small.",
"failed-fall-height": "The possible fall height between floors must not be above {{ maximum_fall_height }}cm.",
@@ -36,5 +43,22 @@
"minimum-length-short-side": "The short side of the cage must be at least {{ minimum_length_short_side }}cm.",
"cage-complies-with-all-criteria": "This cage complies with all criteria!",
"cage-for-x-rats": "Cage for {{ num_rats }} rats",
"overall-area": "The overall area in the cage must be above {{ minimumOverallArea }}m² for {{ numRats }} rats."
"overall-area": "The overall area in the cage must be above {{ minimumOverallArea }}m² for {{ numRats }} rats.",
"strongly-disagree": "Strongly Disagree",
"strongly-agree": "Strongly Agree",
"submit-success": "Successfully submitted!",
"submit-error": "Error when submitting the form!",
"network-error": "Network error when submitting the form!",
"dynamic.sus-question-easy-to-use": "I thought the calculator is easy to use.",
"dynamic.sus-question-unnecessarily-complex": "I find the calculator unnecessarily complex.",
"dynamic.sus-question-need-support-of-technical-person": "I think that I need the support of a technical person to be able to use the calculator.",
"dynamic.sus-question-well-integrated": "I found various functions in the calculator were well integrated",
"dynamic.sus-question-inconsistency": "I thought there was too much inconsistency in the calculator",
"dynamic.sus-question-learn-quickly": "I would imagine that most people would learn to use the calculator very quickly",
"dynamic.sus-question-cumbersome": "I found the calculator very cumbersome to use",
"dynamic.sus-question-confident": "I felt very confident using the calculator",
"dynamic.sus-question-trust": "I trust the results of the calculator.",
"dynamic.question-whats-missing": "What functionality do you feel is missing?",
"dynamic.question-pain-points": "What do you thing needs to be changed?",
"dynamic.question-other-feedback": "What else do you want to tell us?"
}

View File

@@ -2,7 +2,7 @@
<html lang="en" xmlns="http://www.w3.org/1999/html" xmlns="http://www.w3.org/1999/html">
<head>
<meta charset="UTF-8">
<title>Käfigrechner</title>
<title data-i18n="title">VdRD Käfigrechner</title>
<meta content="width=device-width, initial-scale=1" name="viewport"/>
<meta name="description"
content="Rechner für die Größe eines Rattenkäfigs basierend auf den Empfehlungen des VdRD e.V.">
@@ -17,7 +17,7 @@
<div class="navbar-brand">
<div class="navbar-item">
<img src="assets/img/logo_vdrd.png" alt="VdRD Log">
<h1 data-i18n="app-name" class="title is-3">Käfigrechner</h1>
<h1 data-i18n="app-name" class="title is-3">VdRD Käfigrechner für Ratten</h1>
</div>
</div>
</div>
@@ -108,10 +108,16 @@
<div class="card-footer is-fullwidth">
<div class="card result-card" id="num-rats-result-card">
<div class="card-header">
<h2 class="card-header-title is-3 is-centered" data-i18n="result">Ergebnis</h2>
<h2 class="card-header-title title is-2 is-centered" data-i18n="result">Ergebnis</h2>
</div>
<div class="card-content">
<div id="num-rats-resultsDiv"></div>
<div class="is-size-5" id="num-rats-resultsDiv">
<p data-i18n="input-data-num-rats">
Bitte gib erst die Maße ein, dann siehst du hier die Anzahl
der Ratten die hineinpassen.
</p>
</div>
</div>
</div>
</div>
@@ -134,10 +140,12 @@
<div class="card-footer is-fullwidth">
<div class="card result-card" id="cage-calc-result-card">
<div class="card-header">
<h2 class="card-header-title is-3 is-centered" data-i18n="result">Ergebnis</h2>
<h2 class="card-header-title title is-2 is-centered" data-i18n="result">Ergebnis</h2>
</div>
<div class="card-content">
<div id="cageCalcResultsDiv"></div>
<div class="is-size-5" id="cageCalcResultsDiv"></div>
<p data-i18n="please-input-cage-calc">Stell ein für wie viel Ratten der Käfig sein soll, danach
siehst du hier das Ergebnis</p>
</div>
</div>
</div>
@@ -209,16 +217,22 @@
<div class="card-footer is-fullwidth">
<div class="card result-card" id="result-card">
<div class="card-header">
<h2 class="card-header-title is-3 is-centered" data-i18n="result">Ergebnis</h2>
<h2 class="card-header-title title is-2 is-centered" data-i18n="result">Ergebnis</h2>
</div>
<div class="card-content">
<div id="resultsDiv"></div>
<div id="resultsDiv">
<p data-i18n="please-input-cage-check">Bitte gib erst die Maße des Käfigs und die Anzahl an
Ratten ein. Danach siehst du hier, ob der Käfig passt.</p>
</div>
</div>
</div>
</div>
</div>
</div>
<button class="button is-primary floating js-modal-trigger" data-target="modal-feedback"
data-i18n="give-feedback"></button>
<div class="footer" aria-label="Footer">
<div class="columns">
<div class="column">
@@ -261,7 +275,8 @@
<li class="footer-link"><a href="https://codeberg.org/moanos/rettenrechner" data-i18n="source-code">Quellcode</a>
</li>
<li class="footer-link">
<a class="js-modal-trigger is-text is-link" data-target="modal-feedback" data-i18n="give-feedback">
<a class="js-modal-trigger is-text is-link" data-target="modal-feedback"
data-i18n="give-feedback">
Feedback geben
</a>
</li>
@@ -271,6 +286,7 @@
</div>
</div>
<div id="modal-feedback" class="modal">
<div class="modal-background"></div>
@@ -280,14 +296,26 @@
<button class="delete" aria-label="close"></button>
</header>
<div class="modal-card-body">
<h1 class="title" data-i18n="sus-title">Bewerte den Rechner</h1>
<p class="is-spaced mb-4" data-i18n="explanation-feedback"></p>
<form id="sus-form">
<!--- Questions here --->
<div class="control" id="sus-control">
<button class="button is-primary is-fullwidth" type="submit" data-i18n="submit">Absenden</button>
</div>
</form>
<div id="response-message" class="notification is-hidden mt-3"></div>
</div>
<div class="modal-card-foot">
<p>
Wir freuen uns sehr, wenn du uns Feedback zu unserem Rattenrechner gibst!
Der einfachste Weg das zu tun ist per E-Mail an <a
href="mailto:webmaster@vdrd.de">webmaster@vdrd.de</a>.
</p>
<p>
Der Code dieser Website ist <a href="https://codeberg.org/moanos/rettenrechner">öffentlich einsehbar.</a>
Gerne kannst du auch direkt dort <a href="https://codeberg.org/moanos/rettenrechner/issues">einen Issue</a> eröffnen!
Der Code dieser Website ist <a href="https://codeberg.org/moanos/rettenrechner">öffentlich
einsehbar.</a>
Gerne kannst du auch direkt dort <a href="https://codeberg.org/moanos/rettenrechner/issues">einen
Issue</a> eröffnen!
</p>
</div>
</div>

View File

@@ -30,7 +30,7 @@ document.addEventListener('DOMContentLoaded', () => {
});
// Add a click event on various child elements to close the parent modal
(document.querySelectorAll('.modal-background, .modal-close, .delete, .button') || []).forEach(($close) => {
(document.querySelectorAll('.modal-background, .modal-close, .delete') || []).forEach(($close) => {
const $target = $close.closest('.modal');
$close.addEventListener('click', () => {

View File

@@ -8,6 +8,7 @@ import '@fortawesome/fontawesome-free/js/brands';
import './feedback.js';
import './main.scss';
import {send} from './telemetry';
import './sus.js';
/////////////////
// TRANSLATION //
@@ -20,7 +21,7 @@ async function initI18next() {
.init({
supportedLngs: ["en", "de"],
nonExplicitSupportedLngs: true,
fallbackLng: "en",
fallbackLng: "de",
debug: true,
backend: {
loadPath: "/i18n/{{lng}}.json",
@@ -345,16 +346,16 @@ function formatCriteria(criteria) {
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 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 numRats = ratSlider.value;
const numFullFloors = fullFloorNum.value;
const failed_checks = validator.cageCheck(dimensions, numRats,);
const failed_checks = validator.cageCheck(dimensions, numRats, numFullFloors);
let resultsDiv = document.getElementById("resultsDiv");
const result = getResultFromChecks(failed_checks);
@@ -408,7 +409,7 @@ function updateNumRatsCalculator() {
const validator = new Validator();
const failed_checks = validator.failCageNumberIndependent(dimensions, numFullFloors);
let overallArea = validator.getOverallArea(dimensions, );
let overallArea = validator.getOverallArea(dimensions, numFullFloors);
let allowedNumRats;
try {
allowedNumRats = validator.allowedNumberOfRats(overallArea);
@@ -425,7 +426,8 @@ function updateNumRatsCalculator() {
resultsDiv.innerHTML = "";
const p = document.createElement('p');
p.textContent = i18next.t("cage-for-x-rats", {"num_rats": allowedNumRats});
p.textContent = i18next.t("cage-for-x-rats", {"num_rats": Math.floor(allowedNumRats)});
p.className = " is-size-5 has-text-weight-semibold"
resultsDiv.appendChild(p);
resultsDiv.appendChild(result);

View File

@@ -148,3 +148,26 @@ Reused from Notfellchen
.tooltip:not(.top) .tooltiptext::before {
top: auto;
}
// SUS Slider
.sus-slider {
width: 100%;
}
.slider-labels {
display: flex;
justify-content: space-between;
font-size: 0.9rem;
margin-top: 0.25rem;
}
// FLOATING BUTTON
.floating {
position: fixed;
border-radius: 0.3rem;
bottom: 4.5rem;
right: 1rem;
}

107
src/sus.js Normal file
View File

@@ -0,0 +1,107 @@
import i18next from "i18next";
const scaleQuestions = [
"sus-question-easy-to-use", // I thought CageCalc is easy to use
"sus-question-unnecessarily-complex", // I find CageCalc unnecessarily complex
"sus-question-need-support-of-technical-person", // I think that I need the support of a technical person to be able to use CageCalc
"sus-question-well-integrated", // I found various functions in CageCalc were well integrated
"sus-question-inconsistency", // I thought there was too much inconsistency in CageCalc
"sus-question-learn-quickly", // I would imagine that most people would learn to use CageCalc very quickly
"sus-question-cumbersome", // I found CageCalc very cumbersome to use
"sus-question-confident", // I felt very confident using CageCalc
"sus-question-trust", // I felt very confident using CageCalc
];
const freetextQuestions = [
"question-whats-missing", // Welche Funktion fehlt dir?
"question-pain-points", // Was muss aus deiner Sicht an XX geändert werden?
"question-other-feedback" // Was willst du uns mitgeben?
]
function prepareQuestionnaire() {
const form = document.getElementById("sus-form");
scaleQuestions.forEach((key, index) => {
const field = document.createElement("div");
field.className = "field";
field.id = key;
field.innerHTML = `
<label class="label" data-i18n="dynamic.${key}"></label>
<p class="control">
<input class="sus-slider" type="range" min="1" max="5" step="1" name=${key} required>
<div class="slider-labels">
<span data-i18n="strongly-disagree">${i18next.t('strongly-disagree')}</span>
<span data-i18n="strongly-agree">${i18next.t('strongly-agree')}</span>
</div>
</p>
`;
form.insertBefore(field, form.querySelector('#sus-control'));
});
freetextQuestions.forEach((key, index) => {
const field = document.createElement("div");
field.classList.add("field");
field.id = key;
field.innerHTML = `
<label class="label" data-i18n="dynamic.${key}"></label>
<p class="control">
<input class="input" type="text" name=${key}/>
</p>
`
form.insertBefore(field, form.querySelector('#sus-control'));
})
return form;
}
window.addEventListener('DOMContentLoaded', () => {
let form = prepareQuestionnaire();
form.addEventListener("submit", async (e) => {
e.preventDefault();
const formData = new FormData(form);
const jsonData = {};
for (let [key, value] of formData.entries()) {
if (key.startsWith("sws-question")) {
jsonData[key] = parseInt(value, 10);
} else {
jsonData[key] = value;
}
}
try {
const response = await fetch("https://storandom.hyteck.de/submit", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(jsonData)
});
const messageDiv = document.getElementById("response-message");
if (response.ok) {
messageDiv.classList.add("is-success");
messageDiv.innerHTML = i18next.t("submit-success");
} else {
messageDiv.classList.add("is-danger");
messageDiv.innerHTML = i18next.t("submit-error");
}
messageDiv.classList.remove("is-hidden");
} catch (error) {
const messageDiv = document.getElementById("response-message");
messageDiv.classList.add("is-danger");
messageDiv.removeAttribute("data-i18n");
messageDiv.setAttribute("data-i18n", "");
messageDiv.innerHTML = i18next.t("network-error");
let errorP = document.createElement("p");
errorP.className = "error";
errorP.innerText = error;
messageDiv.appendChild(errorP);
messageDiv.classList.remove("is-hidden");
}
});
});