Compare commits
69 Commits
translatio
...
cfe305e698
Author | SHA1 | Date | |
---|---|---|---|
cfe305e698 | |||
1606c7dcf1 | |||
d309ea2b46 | |||
b1372a5fbb | |||
4bc098bc09 | |||
34c2d24891 | |||
74d1ba96b3 | |||
47040057b1 | |||
2984ae4e6d | |||
db273b641d | |||
8f6a28a33e | |||
4fbc47ad8c | |||
f405c325f7 | |||
da52a1dfdf | |||
a13a818485 | |||
d5a467aa55 | |||
17e1415136 | |||
a4a6fc4bb9 | |||
0eff71732e | |||
560f578b26 | |||
9cde2502e2 | |||
68b7149133 | |||
7ae4debfeb | |||
e17cfb384c | |||
e83245e25b | |||
041e391692 | |||
6a0b6d3f72 | |||
2470005892 | |||
b4c90af4f3 | |||
9f716e68a5 | |||
884840eb6e | |||
e743608539 | |||
5142bb66ca | |||
16d7c8f3dc | |||
3ab46cbb72 | |||
276c4d6977 | |||
53cac9f3d2 | |||
de91ad9367 | |||
4f9c7d82f8 | |||
64b492a758 | |||
9241250138 | |||
bbf30c4350 | |||
a6b88b4bb7 | |||
ee0ba6acf8 | |||
7e473955d9 | |||
809c73a767 | |||
7b336f2817 | |||
3808aafa88 | |||
8e0adf54a2 | |||
76a6480528 | |||
89d9d46a81 | |||
6e15ea9006 | |||
7ec5de190d | |||
63d3a44e4b | |||
6f7cf6e873 | |||
bc7f32b57d | |||
6897db4bbf | |||
eab39d0d79 | |||
128ef795d8 | |||
b7f2a3ed67 | |||
3831b81ec2 | |||
e2632c3934 | |||
ac2715f5f4 | |||
527c5b180f | |||
04027e26f1 | |||
a86e1b3935 | |||
603e7738ec | |||
45d78a37e9 | |||
183c41b387 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,2 +1,4 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
public/bundle.js*
|
public/bundle.js*
|
||||||
|
/src/main.css
|
||||||
|
/src/main.css.map
|
||||||
|
@@ -1,7 +1,15 @@
|
|||||||
# RattenheimRechner
|
# RattenheimRechner
|
||||||
|
|
||||||
|
**[rechner.vdrd.de](https://rechner.vdrd.de)**
|
||||||
|
|
||||||
Ein Rechner für die minimale Größe eines Rattenkäfigs nach Standard des [VdRD e.V.](https://vdrd.de)
|
Ein Rechner für die minimale Größe eines Rattenkäfigs nach Standard des [VdRD e.V.](https://vdrd.de)
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run start
|
||||||
|
```
|
||||||
|
|
||||||
## CI
|
## CI
|
||||||
|
|
||||||
CI runs via woodpecker. The following secrets should be set
|
CI runs via woodpecker. The following secrets should be set
|
||||||
|
@@ -2,9 +2,12 @@ module.exports = {
|
|||||||
defaultNamespace: 'translation',
|
defaultNamespace: 'translation',
|
||||||
lexers: {
|
lexers: {
|
||||||
js: ['JsxLexer'], // we're writing jsx inside .js files
|
js: ['JsxLexer'], // we're writing jsx inside .js files
|
||||||
|
html: ['HTMLLexer'],
|
||||||
default: ['JavascriptLexer'],
|
default: ['JavascriptLexer'],
|
||||||
},
|
},
|
||||||
locales: ['en', 'de'],
|
locales: ['en', 'de'],
|
||||||
output: 'public/lang/$LOCALE.json',
|
output: 'public/i18n/$LOCALE.json',
|
||||||
input: [ 'src/*.js', 'public/*.html', ],
|
input: [ 'src/*.js', 'public/*.html', ],
|
||||||
|
keepRemoved: [/dynamic.*/], //prefixing a key with dynamic. will make it not disappear upon calling i18next
|
||||||
|
|
||||||
}
|
}
|
4926
package-lock.json
generated
4926
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
15
package.json
15
package.json
@@ -6,7 +6,9 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "webpack-dev-server --config webpack.config.js",
|
"start": "webpack-dev-server --config webpack.config.js",
|
||||||
"ex-trans": "i18next -c i18next-parser.config.js",
|
"ex-trans": "i18next -c i18next-parser.config.js",
|
||||||
"build": "webpack --config webpack.config.js"
|
"build": "webpack --config webpack.config.js",
|
||||||
|
"build-bulma": "sass --load-path=node_modules src/main.scss src/main.css",
|
||||||
|
"start-bulma": "npm run build-bulma -- --watch"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -19,13 +21,22 @@
|
|||||||
"author": "Julian-Samuel Gebühr",
|
"author": "Julian-Samuel Gebühr",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"css-loader": "^7.1.2",
|
||||||
|
"i18next-parser": "^9.3.0",
|
||||||
|
"sass": "^1.86.3",
|
||||||
|
"sass-loader": "^16.0.5",
|
||||||
|
"style-loader": "^4.0.0",
|
||||||
"webpack": "^5.93.0",
|
"webpack": "^5.93.0",
|
||||||
"webpack-cli": "^5.1.4",
|
"webpack-cli": "^5.1.4",
|
||||||
"webpack-dev-server": "^5.0.4"
|
"webpack-dev-server": "^5.0.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||||
|
"@telemetrydeck/sdk": "^2.0.4",
|
||||||
|
"bulma": "^1.0.3",
|
||||||
"i18next": "^23.12.2",
|
"i18next": "^23.12.2",
|
||||||
"i18next-browser-languagedetector": "^8.0.0",
|
"i18next-browser-languagedetector": "^8.0.0",
|
||||||
"i18next-http-backend": "^2.5.2"
|
"i18next-http-backend": "^2.5.2",
|
||||||
|
"sass-migrator": "^2.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -62,6 +62,7 @@ ul {
|
|||||||
label {
|
label {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
|
font-size: x-large;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slidecontainer {
|
.slidecontainer {
|
||||||
@@ -144,7 +145,7 @@ label {
|
|||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 800px) {
|
@media screen and (max-width: 800px) {
|
||||||
flex: 0 1 calc(25% - 0.5em);
|
flex: 0 1 calc(25% - 0.5em);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,15 +200,31 @@ input.measurement {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navigation-sticky {
|
.top-navigation {
|
||||||
|
margin: 0;
|
||||||
|
border: none;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.homebutton {
|
||||||
|
background-color: var(--secondary-light-one);
|
||||||
|
color: var(--primary-light-one);
|
||||||
|
border-bottom-right-radius: 8px;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 16px;
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-switcher {
|
||||||
background-color: var(--secondary-light-one);
|
background-color: var(--secondary-light-one);
|
||||||
color: var(--primary-light-one);
|
color: var(--primary-light-one);
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
margin: 0;
|
border-bottom-left-radius: 8px;
|
||||||
border-bottom-right-radius: 8px;
|
|
||||||
border: none;
|
border: none;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
width: 20%;
|
width: 15%;
|
||||||
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -231,4 +248,12 @@ input.measurement {
|
|||||||
|
|
||||||
.tooltip:hover .tooltiptext {
|
.tooltip:hover .tooltiptext {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
select, option {
|
||||||
|
background-color: var(--primary-light-one);
|
||||||
|
border-radius: 0.2rem;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-one);
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
BIN
public/assets/img/logo_ausgeschrieben.png
Normal file
BIN
public/assets/img/logo_ausgeschrieben.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 66 KiB |
BIN
public/assets/img/logo_vdrd.png
Normal file
BIN
public/assets/img/logo_vdrd.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
@@ -1,85 +0,0 @@
|
|||||||
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.3;
|
|
||||||
|
|
||||||
const FAILED_BASE_AREA = "base_area";
|
|
||||||
const FAILED_OVERALL_AREA = "overall_area";
|
|
||||||
const FAILED_FALL_HEIGHT = "fall_height";
|
|
||||||
const FAILED_NUM_RATS = "num_rats";
|
|
||||||
const FAILED_MINIMUM_LENGTH_LONG_SIDE = "length_long_side";
|
|
||||||
const FAILED_MINIMUM_LENGTH_SHORT_SIDE= "length_short_side";
|
|
||||||
const FAILED_FLOOR_HEIGHT = "floor_height"
|
|
||||||
|
|
||||||
const FAIL_CRITERIA = {
|
|
||||||
[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 * 100).toFixed(0)}cm betragen.`,
|
|
||||||
[FAILED_FLOOR_HEIGHT]: `Der Mindestabstand zwischen Ebenen muss ${(MINIMUM_FLOOR_HEIGHT * 100).toFixed(0)}cm betragen.`,
|
|
||||||
[FAILED_NUM_RATS]: "Es müssen mindestens 3 Ratten zusammenleben, Paarhaltung ist nicht artgerecht.",
|
|
||||||
[FAILED_MINIMUM_LENGTH_LONG_SIDE]: `Die lange Seite des Käfig muss mindestens ${(MINIMUM_LENGTH_LONG_SIDE * 100).toFixed(0)}cm lang sein um Rennen zu ermöglichen.`,
|
|
||||||
[FAILED_MINIMUM_LENGTH_SHORT_SIDE]: `Die kurze Seite des Käfig muss mindestens ${(MINIMUM_LENGTH_SHORT_SIDE * 100).toFixed(0)}cm lang sein.`,
|
|
||||||
};
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function 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) * AREA_PER_ADDITIONAL_RAT;
|
|
||||||
}
|
|
||||||
|
|
||||||
function cageCheck(dimensions, numRats, numFullFloors) {
|
|
||||||
let failedCriteria = {};
|
|
||||||
|
|
||||||
if (numRats < 2 || numRats > 15) {
|
|
||||||
failedCriteria[FAILED_NUM_RATS] = FAIL_CRITERIA[FAILED_NUM_RATS];
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseArea = dimensions.depth * dimensions.width;
|
|
||||||
if (baseArea < MINIMUM_BASE_AREA) {
|
|
||||||
failedCriteria[FAILED_BASE_AREA] = FAIL_CRITERIA[FAILED_BASE_AREA];
|
|
||||||
}
|
|
||||||
|
|
||||||
const areaNeeded = overallAreaNeeded(numRats);
|
|
||||||
if (baseArea * numFullFloors < areaNeeded) {
|
|
||||||
failedCriteria[FAILED_OVERALL_AREA] = FAIL_CRITERIA[FAILED_OVERALL_AREA];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dimensions.height / numFullFloors > MAXIMUM_FALL_HEIGHT) {
|
|
||||||
failedCriteria[FAILED_FALL_HEIGHT] = FAIL_CRITERIA[FAILED_FALL_HEIGHT];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dimensions.width < MINIMUM_LENGTH_LONG_SIDE && dimensions.depth < MINIMUM_LENGTH_LONG_SIDE) {
|
|
||||||
failedCriteria[FAILED_MINIMUM_LENGTH_LONG_SIDE] = 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] = FAIL_CRITERIA[FAILED_MINIMUM_LENGTH_SHORT_SIDE];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dimensions.height / numFullFloors < MINIMUM_FLOOR_HEIGHT) {
|
|
||||||
failedCriteria[FAILED_FLOOR_HEIGHT] = FAIL_CRITERIA[FAILED_FLOOR_HEIGHT];
|
|
||||||
}
|
|
||||||
|
|
||||||
return failedCriteria;
|
|
||||||
}
|
|
62
public/i18n/de.json
Normal file
62
public/i18n/de.json
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"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",
|
||||||
|
"cage-measurements": "Käfigmaße",
|
||||||
|
"width-cm": "Breite (cm)",
|
||||||
|
"depth-cm": "Tiefe (cm)",
|
||||||
|
"height-cm": "Höhe (cm)",
|
||||||
|
"full-floors": "Vollebenen",
|
||||||
|
"result": "Ergebnis",
|
||||||
|
"number-of-rats": "Anzahl an Ratten",
|
||||||
|
"give-feedback": "Feedback geben",
|
||||||
|
"change-language": "Sprache ändern",
|
||||||
|
"information-on-rat-husbandry": "Information",
|
||||||
|
"basic-rat-info": "Basiswissen Ratten",
|
||||||
|
"cage": "Käfig",
|
||||||
|
"food": "Ernährung",
|
||||||
|
"adopting-rats": "Ratten adoptieren",
|
||||||
|
"about": "Über uns",
|
||||||
|
"the-vdrd": "VdRD e.V.",
|
||||||
|
"imprint": "Impressum",
|
||||||
|
"source-code": "Quellcode",
|
||||||
|
"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.",
|
||||||
|
"failed-floor-height": "Der Mindestabstand zwischen Ebenen muss {{ minimum_floor_height }}cm betragen.",
|
||||||
|
"failed-num-rats": "Es müssen mindestens 3 Ratten zusammenleben, Paarhaltung ist nicht artgerecht.",
|
||||||
|
"failed-minimum-length-long-side": "Die lange Seite des Käfig muss mindestens {{ minimum_length_long_side }}cm lang sein um Rennen zu ermöglichen.",
|
||||||
|
"failed-minimum-length-short-side": "Die kurze Seite des Käfig muss mindestens {{ minimum_length_short_side }}cm lang sein.",
|
||||||
|
"base-area": "Die Mindestgrundfläche des Käfigs muss {{ MINIMUM_BASE_AREA }}m² betragen.",
|
||||||
|
"fall-height": "Die mögliche Fallhöhe darf nicht mehr als {{ maximum_fall_height }}cm betragen.",
|
||||||
|
"floor-height": "Der Mindestabstand zwischen Ebenen muss {{ minimum_floor_height }}cm betragen.",
|
||||||
|
"minimum-length-long-side": "Die lange Seite des Käfig muss mindestens {{ minimum_length_long_side }}cm lang sein um Rennen zu ermöglichen.",
|
||||||
|
"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.",
|
||||||
|
"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!"
|
||||||
|
}
|
61
public/i18n/en.json
Normal file
61
public/i18n/en.json
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"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",
|
||||||
|
"cage-measurements": "Cage measurements",
|
||||||
|
"width-cm": "Width (cm)",
|
||||||
|
"depth-cm": "Depth (cm)",
|
||||||
|
"height-cm": "Height (cm)",
|
||||||
|
"full-floors": "Full floors",
|
||||||
|
"result": "Result",
|
||||||
|
"number-of-rats": "Number of Rats",
|
||||||
|
"give-feedback": "Give Feedback",
|
||||||
|
"change-language": "Change language",
|
||||||
|
"information-on-rat-husbandry": "Information",
|
||||||
|
"basic-rat-info": "Basic Rat Information",
|
||||||
|
"cage": "Cage",
|
||||||
|
"food": "Food",
|
||||||
|
"adopting-rats": "Adopting Rats",
|
||||||
|
"about": "About",
|
||||||
|
"the-vdrd": "VdRD r.V.",
|
||||||
|
"imprint": "Imprint",
|
||||||
|
"source-code": "Source Code",
|
||||||
|
"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.",
|
||||||
|
"failed-floor-height": "The height between floors must be above {{ minimum_floor_height }}cm.",
|
||||||
|
"failed-num-rats": "Rats must live in a group of at least three rats, pairs or lone rats are not species-appropriate.",
|
||||||
|
"failed-minimum-length-long-side": "The long side of the cage must be at least {{ minimum_length_long_side }}cm long to enable running.",
|
||||||
|
"failed-minimum-length-short-side": "The short side of the cage must be at least {{ minimum_length_short_side }}cm.",
|
||||||
|
"base-area": "The base area of the cage must not be below {{ MINIMUM_BASE_AREA }}m².",
|
||||||
|
"fall-height": "The possible fall height between floors must not be above {{ maximum_fall_height }}cm.",
|
||||||
|
"floor-height": "The height between floors must be above {{ minimum_floor_height }}cm.",
|
||||||
|
"minimum-length-long-side": "The long side of the cage must be at least {{ minimum_length_long_side }}cm long to enable running.",
|
||||||
|
"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.",
|
||||||
|
"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?"
|
||||||
|
}
|
@@ -2,117 +2,313 @@
|
|||||||
<html lang="en" xmlns="http://www.w3.org/1999/html" xmlns="http://www.w3.org/1999/html">
|
<html lang="en" xmlns="http://www.w3.org/1999/html" xmlns="http://www.w3.org/1999/html">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Käfigrechner</title>
|
<title data-i18n="title">VdRD Käfigrechner</title>
|
||||||
<link rel="stylesheet" href="assets/css/style.css">
|
<meta content="width=device-width, initial-scale=1" name="viewport"/>
|
||||||
<script src="assets/js/calculator.js"></script>
|
<meta name="description"
|
||||||
|
content="Rechner für die Größe eines Rattenkäfigs basierend auf den Empfehlungen des VdRD e.V.">
|
||||||
|
<meta name="keywords" content="Farbratten, Käfig, Ratten, Rechner, Rat, Calculator, Cage">
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="assets/favicon/apple-touch-icon.png">
|
<link rel="apple-touch-icon" sizes="180x180" href="assets/favicon/apple-touch-icon.png">
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="assets/favicon/favicon-32x32.png">
|
<link rel="icon" type="image/png" sizes="32x32" href="assets/favicon/favicon-32x32.png">
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="assets/favicon/favicon-16x16.png">
|
<link rel="icon" type="image/png" sizes="16x16" href="assets/favicon/favicon-16x16.png">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div class="navigation-sticky">
|
<div class="navbar">
|
||||||
<a href="https://notfellchen.org">
|
<div class="navbar-brand">
|
||||||
<b data-i18n-key="back-to-home">zurück zur Homepage</b>
|
<div class="navbar-item">
|
||||||
</a>
|
<img src="assets/img/logo_vdrd.png" alt="VdRD Log">
|
||||||
|
<h1 data-i18n="app-name" class="title is-3">VdRD Käfigrechner für Ratten</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<img src="img/translation-icon@2x.png" class="translation-icon" / >
|
|
||||||
<select data-i18n-switcher class="locale-switcher">
|
|
||||||
<option value="en">English</option>
|
|
||||||
<option value="de">Deutsch</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<h1 data-i18n-key="app-name">Käfigrechner</h1>
|
<div class="tabs is-centered is-boxed is-toggle" id="primary-tabs">
|
||||||
<div class="container-form">
|
<ul>
|
||||||
<div class="cards">
|
<li class="is-active" data-tab-id="1">
|
||||||
<div class="card" id="card-SavicSuiteRoyaleXL">
|
<a>
|
||||||
<label for="SavicSuiteRoyaleXL">
|
<span class="icon is-small">
|
||||||
<input type="checkbox" id="SavicSuiteRoyaleXL"/>
|
<i class="fas fa-hashtag" aria-hidden="true"></i>
|
||||||
<div class="card-photo">
|
</span>
|
||||||
<img src="assets/img/savic-xl.jpeg">
|
<p data-i18n="how-many-rats-does-this-cage-fit">How many rats does this cage fit?</p>
|
||||||
</div>
|
</a>
|
||||||
<div class="info-container">
|
</li>
|
||||||
<h4><b>Savic Suite Royale XL</b></h4>
|
<li data-tab-id="2">
|
||||||
</div>
|
<a>
|
||||||
</label>
|
<span class="icon is-small">
|
||||||
</div>
|
<i class="fas fa-question" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
<p data-i18n="what-cage-should-i-get">What cage should I get?</p>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li data-tab-id="3">
|
||||||
|
<a>
|
||||||
|
<span class="icon is-small">
|
||||||
|
<i class="fas fa-check" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
<p data-i18n="check-existing-cage">Check existing cage</p>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card" id="card-SavicSuiteRoyale95Double">
|
<div id="tab-content">
|
||||||
<label for="SavicSuiteRoyale95Double">
|
<!--- Check how many rats fit --->
|
||||||
<input type="checkbox" id="SavicSuiteRoyale95Double"/>
|
<div class="is-active" data-content-id="1">
|
||||||
<div class="card-photo">
|
<div class="card">
|
||||||
<div>
|
<div class="card-content">
|
||||||
<img src="assets/img/savic-95-double.jpg">
|
<h2 class="title is-4"><label data-i18n="cage-measurements"
|
||||||
</div>
|
for="form-cage-measurements">Käfigmaße</label></h2>
|
||||||
<div class="info-container">
|
<form>
|
||||||
<h4><b>Savic Suite Royale 95 Double</b></h4>
|
<div class="field">
|
||||||
</div>
|
<label for="num-rats-width" class="label" data-i18n="width-cm">Breite (cm)</label>
|
||||||
|
<p class="control has-icons-left">
|
||||||
|
<input class="input" type="number" id="num-rats-width" placeholder="100 cm">
|
||||||
|
<span class="icon is-small is-left">
|
||||||
|
<i class="fas fa-ruler-horizontal"></i>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card" id="card-TiakiKleintierkäfigEtagere">
|
<div class="field">
|
||||||
<label for="TiakiKleintierkäfigEtagere">
|
<label for="num-rats-depth" class="label" data-i18n="depth-cm">Tiefe (cm)</label>
|
||||||
<input type="checkbox" id="TiakiKleintierkäfigEtagere"/>
|
<p class="control has-icons-left">
|
||||||
<div class="card-photo">
|
<input class="input" type="number" id="num-rats-depth" placeholder="50 cm">
|
||||||
<img src="assets/img/tiaki.jpeg">
|
<span class="icon is-small is-left">
|
||||||
|
<i class="fas fa-ruler"></i>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-container">
|
|
||||||
<h4><b>TIAKI Kleintierkäfig Étagère</b></h4>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card" id="card-ManualMeasurements">
|
<div class="field">
|
||||||
<label for="form-cage-measurements">Käfigmaße</label>
|
<label for="num-rats-height" class="label" data-i18n="height-cm">Höhe (cm)</label>
|
||||||
<form id="form-cage-measurements" class="form-measurements">
|
<p class="control has-icons-left">
|
||||||
<div class="input-measurement">
|
<input class="input" type="number" id="num-rats-height" placeholder="120 cm">
|
||||||
<label for="width" data-i18n-key="width-cm">Breite (cm)</label>
|
<span class="icon is-small is-left">
|
||||||
<input class="measurement" type="number" id="width">
|
<i class="fas fa-ruler-vertical"></i>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-measurement">
|
|
||||||
<label for="depth" data-i18n-key="depth-cm">Tiefe (cm)</label>
|
<div class="field">
|
||||||
<input class="measurement" type="number" id="depth">
|
<label for="num-rats-numFullFloors" class="label" data-i18n="full-floors">
|
||||||
</div>
|
Vollebenen
|
||||||
<div class="input-measurement">
|
</label>
|
||||||
<label for="height" data-i18n-key="height-cm">Höhe (cm)</label>
|
|
||||||
<input class="measurement" type="number" id="height">
|
<input class="control" type="number" id="num-rats-numFullFloors" value="3" step="1">
|
||||||
|
<p class="help">Als Vollebenen zählen alle Ebenen die größer als 0.5m² sind, inklusive des
|
||||||
|
Käfigbodens.</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="card-footer is-fullwidth">
|
||||||
|
<div class="card result-card" id="num-rats-result-card">
|
||||||
<div class="container-inputs">
|
<div class="card-header">
|
||||||
<div class="input-element">
|
<h2 class="card-header-title is-3 is-centered" data-i18n="result">Ergebnis</h2>
|
||||||
<label for="numFullFloors" data-i18n-key="full-floors">Vollebenen</label>
|
</div>
|
||||||
<div class="tooltip">
|
<div class="card-content">
|
||||||
<svg class="text-grey-dark" width="18" height="18" viewBox="0 0 18 18" fill="none" stroke="currentColor"><path d="M9.00026 12.6C9.00026 12.6 9.00026 12.1224 9.00026 11.5333V8.86666C9.00026 8.57211 8.76148 8.33333 8.46693 8.33333H7.93359" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M8.73346 5.26666C8.58619 5.26666 8.4668 5.38605 8.4668 5.53333C8.4668 5.68061 8.58619 5.8 8.73346 5.8C8.88074 5.8 9.00013 5.68061 9.00013 5.53333C9.00013 5.38605 8.88074 5.26666 8.73346 5.26666V5.26666" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M9 17C13.4183 17 17 13.4183 17 9C17 4.58172 13.4183 1 9 1C4.58172 1 1 4.58172 1 9C1 13.4183 4.58172 17 9 17Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>
|
<div id="num-rats-resultsDiv"></div>
|
||||||
<span class="tooltiptext" data-i18n-key="full-floors-tooltip">Als Vollebenen zählen alle Ebenen die größer als 0.5m² sind, inklusive des Käfigbodens.</span>
|
|
||||||
</div>
|
|
||||||
<div class="ncontainer">
|
|
||||||
<div class="input-group">
|
|
||||||
<button id="decreaseFloorNum">-</button>
|
|
||||||
<input type="text" id="numFullFloors" value="3" readonly>
|
|
||||||
<button id="increaseFloorNum">+</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="input-element">
|
<!--- Cage calc --->
|
||||||
<div class="slidecontainer">
|
<div class="" data-content-id="2">
|
||||||
<label for="numRats" id="labelNumRats" data-i18n-key="number-of-rats">Anzahl an Ratten ?</label>
|
<div class="card">
|
||||||
<input type="range" min="3" max="15" value="4" class="slider" id="numRats">
|
<div class="card-content">
|
||||||
|
<h2 class="title is-4">
|
||||||
|
<label data-i18n="number-of-rats" class="label" for="cage-calc">Anzahl der Ratten</label>
|
||||||
|
</h2>
|
||||||
|
<form id="cage-calc">
|
||||||
|
<div class="column">
|
||||||
|
<label id="cageCalcLabelNumRats" for="cageCalcNumRats" class="label"></label>
|
||||||
|
<input type="range" min="3" max="15" value="4" class="slider" id="cageCalcNumRats">
|
||||||
|
<img class="inline-icon" src="assets/img/logo_transparent.png" alt="Kleine Ratte">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<div class="card-content">
|
||||||
|
<div id="cageCalcResultsDiv"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!--- Check cage --->
|
||||||
|
<div class="" data-content-id="3">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content">
|
||||||
|
<h2 class="title is-4"><label class="label" data-i18n="cage-measurements"
|
||||||
|
for="form-cage-measurements">Käfigmaße</label></h2>
|
||||||
|
<form id="form-cage-measurements" class="form-measurements">
|
||||||
|
|
||||||
<div class="container output-element" id="resultsDiv">
|
<div class="field">
|
||||||
|
<label for="width" class="label" data-i18n="width-cm">Breite (cm)</label>
|
||||||
|
<p class="control has-icons-left">
|
||||||
|
<input class="input" type="number" id="width" placeholder="100 cm">
|
||||||
|
<span class="icon is-small is-left">
|
||||||
|
<i class="fas fa-ruler-horizontal"></i>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label for="depth" class="label" data-i18n="depth-cm">Tiefe (cm)</label>
|
||||||
|
<p class="control has-icons-left">
|
||||||
|
<input class="input" type="number" id="depth" placeholder="50 cm">
|
||||||
|
<span class="icon is-small is-left">
|
||||||
|
<i class="fas fa-ruler"></i>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label for="height" class="label" data-i18n="height-cm">Höhe (cm)</label>
|
||||||
|
<p class="control has-icons-left">
|
||||||
|
<input class="input" type="number" id="height" placeholder="120 cm">
|
||||||
|
<span class="icon is-small is-left">
|
||||||
|
<i class="fas fa-ruler-vertical"></i>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column">
|
||||||
|
<div class="field">
|
||||||
|
<label for="numFullFloors" class="label"
|
||||||
|
data-i18n="full-floors">Vollebenen</label>
|
||||||
|
|
||||||
|
<input class="control" type="number" id="numFullFloors" value="3" step="1">
|
||||||
|
<p class="help">Als Vollebenen zählen alle Ebenen die größer als 0.5m² sind, inklusive
|
||||||
|
des Käfigbodens.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column">
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="numRats" id="labelNumRats" data-i18n="number-of-rats">Anzahl
|
||||||
|
an
|
||||||
|
Ratten</label>
|
||||||
|
<input type="range" min="3" max="15" value="4" class="control slider" id="numRats">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<div class="card-content">
|
||||||
|
<div id="resultsDiv"></div>
|
||||||
|
</div>
|
||||||
|
</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">
|
||||||
|
<div class="block">
|
||||||
|
<img class="footer-logo" src="assets/img/logo_ausgeschrieben.png"
|
||||||
|
alt="Logo Verein der Rattenliebhaber und -halter Deutschland e.v.">
|
||||||
|
</div>
|
||||||
|
<div class="block">
|
||||||
|
<div class="language-switcher">
|
||||||
|
<label for="locale-switcher" class="footer-title title label" data-i18n="change-language">Sprache
|
||||||
|
ändern</label>
|
||||||
|
<select data-i18n-switcher class="select" id="locale-switcher">
|
||||||
|
<option value="en">English</option>
|
||||||
|
<option value="de">Deutsch</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<div class="block">
|
||||||
|
<h3 class="footer-title title" data-i18n="information-on-rat-husbandry">Informationen zur
|
||||||
|
Rattenhaltung</h3>
|
||||||
|
<ul class="footer-links">
|
||||||
|
<li class="footer-link"><a href="https://vdrd.de/vor-der-anschaffung/" data-i18n="basic-rat-info">Grundwissen
|
||||||
|
Rattenhaltung</a></li>
|
||||||
|
<li class="footer-link"><a href="https://vdrd.de/das-rattenheim/" data-i18n="cage">Käfig</a></li>
|
||||||
|
<li class="footer-link"><a href="https://vdrd.de/ernaehrung/" data-i18n="food">Ernährung</a></li>
|
||||||
|
<li class="footer-link"><a href="https://vdrd.de/anzahl-geschlecht-und-bezugsquelle/"
|
||||||
|
data-i18n="adopting-rats">Ratten adoptieren</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<div class="block">
|
||||||
|
<h3 class="footer-title title" data-i18n="about">Über uns</h3>
|
||||||
|
<ul class="footer-links">
|
||||||
|
<li class="footer-link"><a href="https://vdrd.de/" data-i18n="the-vdrd">Der VdRD e.V.</a></li>
|
||||||
|
<li class="footer-link"><a href="https://vdrd.de/impressum" data-i18n="imprint">Impressum</a></li>
|
||||||
|
<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">
|
||||||
|
Feedback geben
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div id="modal-feedback" class="modal">
|
||||||
|
|
||||||
|
<div class="modal-background"></div>
|
||||||
|
<div class="modal-card">
|
||||||
|
<header class="modal-card-head">
|
||||||
|
<h2 data-i18n="feedback" class="modal-card-title">Feedback</h2>
|
||||||
|
<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>
|
||||||
|
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>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="./bundle.js"></script>
|
<script src="./bundle.js"></script>
|
||||||
|
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"back-to-home": "Zurück zur Homepage",
|
|
||||||
"app-name": "Käfigrechner",
|
|
||||||
"number-of-rats": "Anzahl an Ratten",
|
|
||||||
"full-floors": "Vollebenen",
|
|
||||||
"width-cm": "Breite (cm)",
|
|
||||||
"depth-cm": "Tiefe (cm)",
|
|
||||||
"height-cm": "Höhe (cm)",
|
|
||||||
"full-floors-tooltip": "Als Vollebenen zählen alle Ebenen die größer als 0.5m² sind, inklusive des Käfigbodens."
|
|
||||||
}
|
|
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"back-to-home": "Back to home",
|
|
||||||
"app-name": "Cage Calculator",
|
|
||||||
"number-of-rats": "Number of Rats",
|
|
||||||
"full-floors": "Full floors",
|
|
||||||
"width-cm": "Width (cm)",
|
|
||||||
"depth-cm": "Depth (cm)",
|
|
||||||
"height-cm": "Height (cm)",
|
|
||||||
"full-floors-tooltip": "A full floor is each floor with a area greater than 0.5m², including the bottom of the cage."
|
|
||||||
}
|
|
47
src/feedback.js
Normal file
47
src/feedback.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import {send} from './telemetry';
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
// Functions to open and close a modal
|
||||||
|
function openModal($el) {
|
||||||
|
$el.classList.add('is-active');
|
||||||
|
send("Modal.open", {
|
||||||
|
modal: $el.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeModal($el) {
|
||||||
|
$el.classList.remove('is-active');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeAllModals() {
|
||||||
|
(document.querySelectorAll('.modal') || []).forEach(($modal) => {
|
||||||
|
closeModal($modal);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a click event on buttons to open a specific modal
|
||||||
|
(document.querySelectorAll('.js-modal-trigger') || []).forEach(($trigger) => {
|
||||||
|
const modal = $trigger.dataset.target;
|
||||||
|
const $target = document.getElementById(modal);
|
||||||
|
|
||||||
|
$trigger.addEventListener('click', () => {
|
||||||
|
openModal($target);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add a click event on various child elements to close the parent modal
|
||||||
|
(document.querySelectorAll('.modal-background, .modal-close, .delete') || []).forEach(($close) => {
|
||||||
|
const $target = $close.closest('.modal');
|
||||||
|
|
||||||
|
$close.addEventListener('click', () => {
|
||||||
|
closeModal($target);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add a keyboard event to close all modals
|
||||||
|
document.addEventListener('keydown', (event) => {
|
||||||
|
if (event.key === "Escape") {
|
||||||
|
closeAllModals();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
556
src/index.js
556
src/index.js
@@ -1,181 +1,53 @@
|
|||||||
const inputDecreaseFloorNum = document.getElementById("decreaseFloorNum");
|
|
||||||
inputDecreaseFloorNum.onclick = decreaseFloorNum;
|
|
||||||
|
|
||||||
const inputIncreaseFloorNum = document.getElementById("increaseFloorNum");
|
|
||||||
inputIncreaseFloorNum.onclick = increaseFloorNum;
|
|
||||||
|
|
||||||
|
|
||||||
const inputWidth = document.getElementById("width");
|
|
||||||
inputWidth.onchange = updateViaManualMeasurements;
|
|
||||||
const inputDepth = document.getElementById("depth");
|
|
||||||
inputDepth.onchange = updateViaManualMeasurements;
|
|
||||||
const inputHeight = document.getElementById("height");
|
|
||||||
inputHeight.onchange = updateViaManualMeasurements;
|
|
||||||
|
|
||||||
|
|
||||||
const selectSavicSuiteRoyaleXL = document.getElementById("SavicSuiteRoyaleXL");
|
|
||||||
const selectSavicSuiteRoyale95Double = document.getElementById("SavicSuiteRoyale95Double");
|
|
||||||
const selectTiakiKleintierkäfigEtagere = document.getElementById("TiakiKleintierkäfigEtagere");
|
|
||||||
|
|
||||||
const cardSavicSuiteRoyaleXL = document.getElementById("card-SavicSuiteRoyaleXL");
|
|
||||||
const cardSavicSuiteRoyale95Double = document.getElementById("card-SavicSuiteRoyale95Double");
|
|
||||||
const cardTiakiKleintierkäfigEtagere = document.getElementById("card-TiakiKleintierkäfigEtagere");
|
|
||||||
|
|
||||||
|
|
||||||
function markActiveCage(cageName) {
|
|
||||||
|
|
||||||
cardSavicSuiteRoyaleXL.classList.remove("card-active");
|
|
||||||
cardSavicSuiteRoyale95Double.classList.remove("card-active");
|
|
||||||
cardTiakiKleintierkäfigEtagere.classList.remove("card-active");
|
|
||||||
if (cageName != "") {
|
|
||||||
const activeCage = document.getElementById("card-" + cageName);
|
|
||||||
activeCage.classList.add("card-active")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateCage(event) {
|
|
||||||
selectSavicSuiteRoyaleXL.checked = false;
|
|
||||||
selectSavicSuiteRoyale95Double.checked = false;
|
|
||||||
selectTiakiKleintierkäfigEtagere.checked = false;
|
|
||||||
const selectedCage = event.currentTarget
|
|
||||||
selectedCage.checked = true;
|
|
||||||
const cageName = selectedCage.id;
|
|
||||||
|
|
||||||
console.log(cageName);
|
|
||||||
var dim = getCageDimensions(cageName);
|
|
||||||
inputWidth.value = dim.width;
|
|
||||||
inputDepth.value = dim.depth;
|
|
||||||
inputHeight.value = dim.height;
|
|
||||||
|
|
||||||
markActiveCage(cageName);
|
|
||||||
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
selectSavicSuiteRoyaleXL.onchange = updateCage;
|
|
||||||
selectSavicSuiteRoyale95Double.onchange = updateCage;
|
|
||||||
selectTiakiKleintierkäfigEtagere.onchange = updateCage;
|
|
||||||
|
|
||||||
|
|
||||||
var labelNumRats = document.getElementById("labelNumRats");
|
|
||||||
|
|
||||||
var ratSlider = document.getElementById("numRats");
|
|
||||||
|
|
||||||
labelNumRats.innerHTML = `Anzahl an Ratten: ` + ratSlider.value;
|
|
||||||
|
|
||||||
ratSlider.oninput = function () {
|
|
||||||
labelNumRats.innerHTML = `Anzahl an Ratten: ` + this.value;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Full floor functions
|
|
||||||
var fullFloorNum = document.getElementById("numFullFloors");
|
|
||||||
|
|
||||||
function getCageDimensions(cageName) {
|
|
||||||
console.log(cageName);
|
|
||||||
if (cageName == "SavicSuiteRoyaleXL") {
|
|
||||||
return new Dimensions(115, 67.5, 153);
|
|
||||||
}
|
|
||||||
if (cageName == "SavicSuiteRoyale95Double") {
|
|
||||||
return new Dimensions(95, 63, 120);
|
|
||||||
}
|
|
||||||
if (cageName == "TiakiKleintierkäfigEtagere") {
|
|
||||||
return new Dimensions(93.5, 63, 141.2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getResultFromChecks(checks) {
|
|
||||||
console.log(checks.length)
|
|
||||||
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 = "✅ Der Käfig erfüllt alle Kriterien!"
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateViaManualMeasurements() {
|
|
||||||
markActiveCage("ManualMeasurements");
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
function update() {
|
|
||||||
const width = inputWidth.value
|
|
||||||
const depth = inputDepth.value
|
|
||||||
const height = inputHeight.value
|
|
||||||
const dimensions = new Dimensions(width / 100, depth / 100, height / 100);
|
|
||||||
const failed_checks = cageCheck(dimensions, ratSlider.value, fullFloorNum.value);
|
|
||||||
console.log(failed_checks);
|
|
||||||
let resultsDiv = document.getElementById("resultsDiv");
|
|
||||||
resultsDiv.innerHTML = `<strong>Ergebnis</strong>`;
|
|
||||||
|
|
||||||
const result = getResultFromChecks(failed_checks);
|
|
||||||
|
|
||||||
resultsDiv.appendChild(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function decreaseFloorNum() {
|
|
||||||
var input = document.getElementById('numFullFloors');
|
|
||||||
var value = parseInt(input.value);
|
|
||||||
if (value > 0) {
|
|
||||||
input.value = value - 1;
|
|
||||||
}
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
function increaseFloorNum() {
|
|
||||||
var input = document.getElementById('numFullFloors');
|
|
||||||
var value = parseInt(input.value);
|
|
||||||
input.value = value + 1;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
update();
|
|
||||||
|
|
||||||
|
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import LanguageDetector from "i18next-browser-languagedetector";
|
import LanguageDetector from "i18next-browser-languagedetector";
|
||||||
import HttpApi from "i18next-http-backend";
|
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.js';
|
||||||
|
import './main.scss';
|
||||||
|
import {send} from './telemetry';
|
||||||
|
import './sus.js';
|
||||||
|
|
||||||
|
/////////////////
|
||||||
|
// TRANSLATION //
|
||||||
|
/////////////////
|
||||||
|
|
||||||
async function initI18next() {
|
async function initI18next() {
|
||||||
// We use() the backend and await it to load
|
|
||||||
// the translations from the network
|
|
||||||
await i18next
|
await i18next
|
||||||
.use(HttpApi)
|
.use(HttpApi)
|
||||||
.use(LanguageDetector)
|
.use(LanguageDetector)
|
||||||
.init({
|
.init({
|
||||||
lng: "en",
|
|
||||||
supportedLngs: ["en", "de"],
|
supportedLngs: ["en", "de"],
|
||||||
nonExplicitSupportedLngs: true,
|
nonExplicitSupportedLngs: true,
|
||||||
fallbackLng: "en",
|
fallbackLng: "de",
|
||||||
debug: true,
|
debug: true,
|
||||||
backend: {
|
backend: {
|
||||||
loadPath: "/lang/{{lng}}.json",
|
loadPath: "/i18n/{{lng}}.json",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function translatePageElements() {
|
function translatePageElements() {
|
||||||
|
// Translate content inside a tag
|
||||||
const translatableElements = document.querySelectorAll(
|
const translatableElements = document.querySelectorAll(
|
||||||
"[data-i18n-key]",
|
"[data-i18n]",
|
||||||
);
|
);
|
||||||
translatableElements.forEach((el) => {
|
translatableElements.forEach((el) => {
|
||||||
const key = el.getAttribute("data-i18n-key");
|
const key = el.getAttribute("data-i18n");
|
||||||
el.innerHTML = i18next.t(key);
|
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) {
|
function bindLocaleSwitcher(initialValue) {
|
||||||
const switcher = document.querySelector(
|
const switcher = document.querySelector(
|
||||||
@@ -185,7 +57,8 @@ function bindLocaleSwitcher(initialValue) {
|
|||||||
switcher.onchange = (e) => {
|
switcher.onchange = (e) => {
|
||||||
i18next
|
i18next
|
||||||
.changeLanguage(e.target.value)
|
.changeLanguage(e.target.value)
|
||||||
.then(translatePageElements);
|
.then(translatePageElements)
|
||||||
|
.then(updateCageCheck);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,4 +66,377 @@ function bindLocaleSwitcher(initialValue) {
|
|||||||
await initI18next();
|
await initI18next();
|
||||||
translatePageElements();
|
translatePageElements();
|
||||||
bindLocaleSwitcher(i18next.resolvedLanguage);
|
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 numRats = ratSlider.value;
|
||||||
|
const numFullFloors = fullFloorNum.value;
|
||||||
|
|
||||||
|
|
||||||
|
const failed_checks = validator.cageCheck(dimensions, numRats,);
|
||||||
|
let resultsDiv = document.getElementById("resultsDiv");
|
||||||
|
|
||||||
|
const result = getResultFromChecks(failed_checks);
|
||||||
|
|
||||||
|
resultsDiv.innerHTML = "";
|
||||||
|
resultsDiv.appendChild(result);
|
||||||
|
|
||||||
|
// Send telemetry
|
||||||
|
send("Update.CageCheck", {
|
||||||
|
width: width,
|
||||||
|
depth: depth,
|
||||||
|
height: height,
|
||||||
|
numRats: numRats,
|
||||||
|
numFullFloors: numFullFloors
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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(numRats);
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Send telemetry
|
||||||
|
send("Update.CageCalc", {
|
||||||
|
numRats: numRats,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateNumRatsCalculator() {
|
||||||
|
|
||||||
|
const width = numRatsCalculatorInputWidth.value;
|
||||||
|
const depth = numRatsCalculatorInputDepth.value;
|
||||||
|
const height = numRatsCalculatorInputHeight.value;
|
||||||
|
const numFullFloors = numRatsNumFullFloors.value;
|
||||||
|
const dimensions = new Dimensions(width / 100, depth / 100, height / 100);
|
||||||
|
const validator = new Validator();
|
||||||
|
const failed_checks = validator.failCageNumberIndependent(dimensions, numFullFloors);
|
||||||
|
|
||||||
|
let overallArea = validator.getOverallArea(dimensions, );
|
||||||
|
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);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Send telemetry
|
||||||
|
send("Update.NumRatsCalc", {
|
||||||
|
width: width,
|
||||||
|
depth: depth,
|
||||||
|
height: height,
|
||||||
|
numFullFloors: numFullFloors
|
||||||
|
});
|
||||||
|
}
|
||||||
|
173
src/main.scss
Normal file
173
src/main.scss
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
// Set your brand colors
|
||||||
|
$purple: #8a4d76;
|
||||||
|
$pink: #7c95fa;
|
||||||
|
$brown: #757763;
|
||||||
|
$beige-light: #d0d1cd;
|
||||||
|
$beige-lighter: #eff0eb;
|
||||||
|
|
||||||
|
// Path to Bulma's sass folder
|
||||||
|
@use "bulma/sass" with (
|
||||||
|
$family-primary: '"Nunito", sans-serif',
|
||||||
|
$control-border-width: 2px,
|
||||||
|
$input-shadow: none
|
||||||
|
);
|
||||||
|
|
||||||
|
#tab-content > div {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tab-content > div.is-active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-card {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inline-icon {
|
||||||
|
height: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-logo {
|
||||||
|
height: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import the Google Font
|
||||||
|
@import url("https://fonts.googleapis.com/css?family=Nunito:400,700");
|
||||||
|
|
||||||
|
.tooltip {
|
||||||
|
position: relative;
|
||||||
|
cursor: help;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: attr(data-tooltip);
|
||||||
|
position: absolute;
|
||||||
|
bottom: 100%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background-color: rgba(0, 0, 0, 0.75);
|
||||||
|
color: white;
|
||||||
|
padding: 0.5em;
|
||||||
|
border-radius: 4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity 0.2s ease-in-out;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover::after {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TOOLTIP
|
||||||
|
Reused from Notfellchen
|
||||||
|
*/
|
||||||
|
|
||||||
|
.tooltip {
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip:hover .tooltiptext {
|
||||||
|
display: flex;
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip .tooltiptext {
|
||||||
|
border-radius: 4px;
|
||||||
|
bottom: calc(100% + 0.6em + 2px);
|
||||||
|
box-shadow: 0px 2px 4px #07172258;
|
||||||
|
background-color: var(--primary-dark-one);
|
||||||
|
color: var(--secondary-light-one);
|
||||||
|
font-size: 0.68rem;
|
||||||
|
justify-content: center;
|
||||||
|
line-height: 1.35em;
|
||||||
|
padding: 0.5em 0.7em;
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
width: 7rem;
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.3s ease-in;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip .tooltiptext::before {
|
||||||
|
border-width: 0.6em 0.8em 0;
|
||||||
|
border-color: transparent;
|
||||||
|
border-top-color: var(--primary-dark-one);
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
border-style: solid;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Makes the tooltip fly from above */
|
||||||
|
.tooltip.top .tooltiptext {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip.top:hover .tooltiptext {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make adjustments for bottom */
|
||||||
|
.tooltip.bottom .tooltiptext {
|
||||||
|
top: calc(100% + 0.6em + 2px);
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip.bottom:hover .tooltiptext {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip.bottom .tooltiptext::before {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
/* 100% of the height of .tooltip */
|
||||||
|
bottom: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip:not(.top) .tooltiptext {
|
||||||
|
bottom: auto;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.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
107
src/sus.js
Normal 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");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
57
src/telemetry.js
Normal file
57
src/telemetry.js
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import TelemetryDeck from '@telemetrydeck/sdk';
|
||||||
|
|
||||||
|
|
||||||
|
///////////////
|
||||||
|
// TELEMETRY //
|
||||||
|
///////////////
|
||||||
|
|
||||||
|
// Telemetry Deck only collects fully anonymized data!
|
||||||
|
|
||||||
|
const getCookieValueOrNull = (name) => {
|
||||||
|
const cookie = document.cookie
|
||||||
|
.split(";")
|
||||||
|
.map(c => c.trim())
|
||||||
|
.find(c => c.startsWith(name + "="));
|
||||||
|
return cookie ? cookie.split("=")[1] : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getOrCreateUUID() {
|
||||||
|
let cookie_val = getCookieValueOrNull("id");
|
||||||
|
if (
|
||||||
|
cookie_val
|
||||||
|
) {
|
||||||
|
return cookie_val;
|
||||||
|
} else {
|
||||||
|
let uuid =crypto.randomUUID();
|
||||||
|
const days = 365;
|
||||||
|
const expires = new Date(Date.now() + days * 864e5).toUTCString();
|
||||||
|
document.cookie = `id=${uuid}; expires=${expires}`;
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send Test Signals when running locally
|
||||||
|
function init() {
|
||||||
|
const appId = "E453AAB8-B1AD-4F3E-87DF-97FC3A0400B9";
|
||||||
|
if (location.hostname === "localhost" || location.hostname === "127.0.0.1") {
|
||||||
|
const td = new TelemetryDeck({
|
||||||
|
appID: appId,
|
||||||
|
clientUser: getOrCreateUUID(),
|
||||||
|
testMode: true
|
||||||
|
});
|
||||||
|
return td;
|
||||||
|
} else {
|
||||||
|
const td = new TelemetryDeck({
|
||||||
|
appID: appId,
|
||||||
|
clientUser: getOrCreateUUID()
|
||||||
|
});
|
||||||
|
return td;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let td = init();
|
||||||
|
|
||||||
|
|
||||||
|
export function send(type, payload) {
|
||||||
|
td.signal(type, payload);
|
||||||
|
}
|
@@ -22,4 +22,16 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
static: "public",
|
static: "public",
|
||||||
},
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.s[ac]ss$/i,
|
||||||
|
use: [
|
||||||
|
'style-loader', // Injects styles into DOM
|
||||||
|
'css-loader', // Resolves CSS imports
|
||||||
|
'sass-loader' // Compiles Sass to CSS
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
};
|
};
|
Reference in New Issue
Block a user