From 6e483c1f3e0b398f0d1831f4c3cfb276c6376df5 Mon Sep 17 00:00:00 2001
From: Turtle <104490184+CodingTurtle2@users.noreply.github.com>
Date: Sun, 28 Dec 2025 17:30:17 +0100
Subject: [PATCH] g3.0
---
help/data_xample.js | 25 ++-
help/documentation.md | 7 +-
lib/basic.css | 7 +
lib/basic.js | 18 ++-
student/skilltree.js | 294 ++++++++++++++++++++++++++++++++++
student/uebersicht_overlay.js | 225 ++++++++++++++++++++++++++
6 files changed, 568 insertions(+), 8 deletions(-)
create mode 100644 student/skilltree.js
create mode 100644 student/uebersicht_overlay.js
diff --git a/help/data_xample.js b/help/data_xample.js
index a93be73..37daaff 100644
--- a/help/data_xample.js
+++ b/help/data_xample.js
@@ -5,7 +5,12 @@ async function createTemp() {
"password": "1234",
"level": 1,
"avatar": null,
- "abilities": [],
+ "abilities": [
+ "dark_mode"
+ ],
+ "enabled_abilities": [
+ "dark_mode"
+ ],
"role": "student",
"courses": [
"weiche_ware",
@@ -14,9 +19,14 @@ async function createTemp() {
},
"dumble-door": {
"password": "bla",
- "level": 10,
+ "level": 4,
"avatar": "https://static.turtle-coding-gbr.de/profile-pic.png",
- "abilities": [],
+ "abilities": [
+ "dark_mode"
+ ],
+ "enabled_abilities": [
+ "dark_mode"
+ ],
"role": "professor",
"courses": [
"weiche_ware"
@@ -24,9 +34,14 @@ async function createTemp() {
},
"narrator": {
"password": "stanley",
- "level": 100,
+ "level": 4,
"avatar": "https://static.turtle-coding-gbr.de/st",
- "abilities": [],
+ "abilities": [
+ "dark_mode"
+ ],
+ "enabled_abilities": [
+ "dark_mode"
+ ],
"role": "professor",
"courses": [
"bucket"
diff --git a/help/documentation.md b/help/documentation.md
index a600f9c..7ae2c48 100644
--- a/help/documentation.md
+++ b/help/documentation.md
@@ -65,10 +65,13 @@ main();
```
//Gebe dir den Nutzer wieder
-async function getUserData(couchDB) -> Map?;
+async function getUserData(CouchDB couchDB) -> Map?;
//Setze den Nutzer, welcher unter der Variable userData verfügbar sein wird
-async function setUserData(couchDB) -> bool;
+async function setUserData(CouchDB couchDB) -> bool;
+
+//Aktualisiert die Nutzerdaten in der Datenbank
+async function updateUserData(CouchDB couchDB, object userData) -> Map;
```
##Nützliche CSS-Klassen
diff --git a/lib/basic.css b/lib/basic.css
index ad237af..2e6cc50 100644
--- a/lib/basic.css
+++ b/lib/basic.css
@@ -108,6 +108,13 @@ overlay {
font-weight: 600;
}
+.overlay .overlay_title {
+ height: 40px;
+ margin-top: 4px;
+ margin-right: 50px;
+ font-weight: bold;
+}
+
/* ----------- headerbar ----------- */
.header_bar_1 {
diff --git a/lib/basic.js b/lib/basic.js
index 020a4e4..3b5b545 100644
--- a/lib/basic.js
+++ b/lib/basic.js
@@ -43,6 +43,8 @@ function showOverlay(id) {
console.error(err);
}
}
+
+ return overlay;
}
function _onOverlayDestroy(elm) {
const ori_elm = getId(elm.getAttribute("name"));
@@ -367,6 +369,20 @@ async function setUserData(couchDB) {
updateAllProfiles(res);
return true;
}
+async function updateUserData(couchDB, user) {
+ const res = await couchDB.get("users");
+ if (res == null) return {"type":"error", "message": "CouchDB failed"};
+
+ const name = user["name"];
+ if (!name) return {"type":"error", "message": "Incorrect UserData was presented"};
+
+ const u = { ...user };
+ delete u["name"];
+ res[name] = u;
+
+ return await couchDB.set("users", res);
+}
+
function updateAllProfiles(user) {
const profiles = document.getElementsByClassName("profile");
@@ -417,7 +433,7 @@ function _showProfile(user) {
document.body.appendChild(elm);
}
elm.innerHTML = `
-
Profil-Übersicht
+Profil-Übersicht
diff --git a/student/skilltree.js b/student/skilltree.js
new file mode 100644
index 0000000..ff77311
--- /dev/null
+++ b/student/skilltree.js
@@ -0,0 +1,294 @@
+//=> use showSkillTree() -> void;
+
+const getSkillTree = () => ({
+ id: "root",
+ text: "Skill-Tree",
+ children: [
+ {
+ text: "DarkMode",
+ id: "dark_mode",
+ description: "Setzt die Theme auf dunkel!",
+ children: [
+ {
+ text: "Bla",
+ description: "Lorem ipsum et idk! Lorem ipsum et idk! Lorem ipsum et idk! Lorem ipsum et idk! Lorem ipsum et idk! Lorem ipsum et idk! Lorem ipsum et idk! Lorem ipsum et idk!",
+ id: "pink_mode",
+ children: []
+ }
+ ]
+ },
+ {
+ text: "1",
+ id: "1",
+ description: "one",
+ children: [
+ {
+ text: "2",
+ id: "2",
+ description: "two",
+ children: []
+ },
+ {
+ text: "3",
+ id: "3",
+ description: "three",
+ children: []
+ }
+ ]
+ }
+ ]
+});
+
+var aSkillPoints = 0;
+
+function showSkillTree() {
+ var elm = getId("skill_tree_overlay");
+ if (!elm) {
+ elm = document.createElement("overlay");
+ elm.setAttribute("id", "skill_tree_overlay");
+ document.body.appendChild(elm);
+ }
+
+ const div = document.createElement("div");
+ div.style = `
+display: inline-block;
+width: 100%;
+height: 100%;
+overflow: auto;
+margin-top: 20px;
+text-align: center;
+ `;
+
+ var res = generateSkillTree(getSkillTree(), userData.abilities)
+
+ div.appendChild(res[0]);
+ aSkillPoints = userData.level - res[1];
+
+ elm.appendChild(div);
+
+ const div2 = document.createElement("div");
+ div2.style = `
+display: inline-block;
+width: 100%;
+text-align: center;
+margin-top: 20px;
+ `;
+ div2.innerHTML = `
+
+Skill-Points: ${aSkillPoints}
+
+ `;
+
+ elm.appendChild(div2);
+
+ showOverlay("skill_tree_overlay");
+}
+
+function _getRect(parent, data) {
+ var elm = getId("skill_tree_details_overlay");
+ if (!elm) {
+ elm = document.createElement("overlay");
+ elm.setAttribute("id", "skill_tree_details_overlay");
+ document.body.appendChild(elm);
+ }
+
+ const isEnabled = userData.abilities.includes(data.id);
+
+ elm.innerHTML = '';
+ const div = document.createElement("div");
+ div.style = `display: inline-block; width: 100%; min-width: 300px; text-align: center;`;
+ div.innerHTML = `
+${data.text}
+${data.description}
+${isEnabled ?
+ "Im Besitz":
+ "Noch nicht freigeschaltet"
+}
+ `;
+
+ const bttnStyle = `display: inline-block; width: 100%; border-radius: var(--border-radius); padding: 10px; font-size: 18px; font-weight: normal; background: rgba(255, 255, 255, 0.3);`;
+ const bttnStyleDisabled = bttnStyle + `border: 1px solid black; opacity: 0.7;`;
+
+ if (isEnabled) {
+ const eAblities = userData.enabled_abilities ??= [];
+ const isEnabled = eAblities.includes(data.id);
+
+ div.innerHTML += `
+
+ `;
+ } else {
+ const isParentEnabled = parent.id == "root" || userData.abilities.includes(parent.id);
+ const allowBuy = aSkillPoints > 0 && isParentEnabled;
+
+ div.innerHTML += `
+
+ `;
+ }
+
+ elm.appendChild(div);
+
+ showOverlay("skill_tree_details_overlay");
+}
+async function activateSkill(elm, data) {
+ const elms = document.getElementsByClassName("skill_" + data.id);
+ const eAblities = userData.enabled_abilities ??= [];
+ const isEnabled = eAblities.includes(data.id);
+
+ if (isEnabled) {
+ userData.enabled_abilities = eAblities.filter((val) => val != data.id);
+ for (var elm of elms) {
+ elm.setAttribute("fill", "rgb(187, 243, 183)");
+ }
+ } else {
+ userData.enabled_abilities.push(data.id);
+ for (var elm of elms) {
+ elm.setAttribute("fill", "lightgreen");
+ }
+ }
+
+ elm.innerText = !isEnabled ? "Deaktivieren" : "Aktivieren";
+
+ getOverlay("skill_tree_details_overlay").innerHTML = "";
+ await updateUserData(new CouchDB(), userData);
+ hideOverlay("skill_tree_details_overlay");
+}
+async function _buySkill(data) {
+ const elms = document.getElementsByClassName("skill_" + data.id);
+ if (elms.length == 0) return;
+ for (var elm of elms) {
+ elm.setAttribute("fill", "rgb(187, 243, 183)");
+ }
+
+ aSkillPoints--;
+ const aSkillPoints_elms = document.getElementsByClassName("aSkillPoints");
+ for (var i of aSkillPoints_elms) {
+ i.innerText = aSkillPoints;
+ }
+
+ userData.abilities.push(data.id);
+
+ getOverlay("skill_tree_details_overlay").innerHTML = "";
+ await updateUserData(new CouchDB(), userData);
+
+ hideOverlay("skill_tree_details_overlay");
+}
+
+function generateSkillTree(skillTree, abilities) {
+ const NS = "http://www.w3.org/2000/svg";
+ const nWidth = 120;
+ const nHeight = 40;
+ const lvlGap = 100;
+ const borderRadius = getComputedStyle(document.documentElement).getPropertyValue('--border-radius').trim();
+ const siblingGap = 40;
+
+ /* -------- calc layout -------- */
+
+ function measure_tree(data) {
+ if (data.children.length === 0) {
+ data._width = nWidth;
+ } else {
+ data._width =
+ data.children.reduce((sum, c) => sum + measure_tree(c), 0) +
+ siblingGap * (data.children.length - 1);
+ }
+ return data._width;
+ }
+
+ function calc_tree(data, x, y) {
+ data._x = x;
+ data._y = y;
+ data.enabled = data.id == "root" || abilities.includes(data.id);
+
+ let childX = x - data._width / 2;
+ data.children.forEach(child => {
+ const cx = childX + child._width / 2;
+ const cy = y + lvlGap;
+ calc_tree(child, cx, cy);
+ childX += child._width + siblingGap;
+ });
+ }
+
+ measure_tree(skillTree);
+ calc_tree(skillTree, 0, 0);
+
+ /* -------- bounding-stuff -------- */
+
+ let minX = Infinity, maxX = -Infinity, maxY = -Infinity;
+
+ function bounds(data) {
+ minX = Math.min(minX, data._x - nWidth / 2);
+ maxX = Math.max(maxX, data._x + nWidth / 2);
+ maxY = Math.max(maxY, data._y + nHeight);
+ data.children.forEach(bounds);
+ }
+ bounds(skillTree);
+
+ const padding = 20;
+ const width = maxX - minX + padding * 2;
+ const height = maxY + padding * 2;
+
+ /* -------- other -------- */
+
+ const svg = document.createElementNS(NS, "svg");
+ svg.setAttribute("width", width);
+ svg.setAttribute("height", height);
+ svg.setAttribute(
+ "viewBox",
+ `${minX - padding} ${-padding} ${width} ${height}`
+ );
+
+ function drawLine(parent, data) {
+ const line = document.createElementNS(NS, "line");
+ line.setAttribute("x1", parent._x);
+ line.setAttribute("y1", parent._y + nHeight);
+ line.setAttribute("x2", data._x);
+ line.setAttribute("y2", data._y);
+ line.setAttribute("stroke", "#555");
+ svg.appendChild(line);
+ }
+
+ var levelCount = 0;
+
+ function drawNode(parent, data) {
+ data.children.forEach(child => {
+ drawLine(data, child);
+ drawNode(data, child);
+ });
+
+ const elmA = document.createElementNS(NS, "rect");
+ elmA.setAttribute("x", data._x - nWidth / 2);
+ elmA.setAttribute("y", data._y);
+ elmA.setAttribute("width", nWidth);
+ elmA.setAttribute("height", nHeight);
+ elmA.setAttribute("rx", borderRadius);
+ elmA.classList.add("skill_" + data.id);
+ elmA.setAttribute(
+ "fill", data.id == "root" ? "lightblue"
+ : (data.enabled ? (
+ userData.enabled_abilities.includes(data.id) ? "lightgreen": "rgb(187, 243, 183)"
+ ): "lightgray")
+ );
+ elmA.setAttribute("stroke", "#333");
+
+ const elmB = document.createElementNS(NS, "text");
+ elmB.setAttribute("x", data._x);
+ elmB.setAttribute("y", data._y + nHeight / 2 + 5);
+ elmB.setAttribute("text-anchor", "middle");
+ elmB.setAttribute("font-size", "14");
+ elmB.textContent = data.text;
+
+ if (data.id != "root") {
+ if (data.enabled) levelCount++;
+
+ const js = `_getRect(${JSON.stringify(parent)}, ${JSON.stringify(data)})`;
+ elmA.setAttribute("onclick", js);
+ elmB.setAttribute("onclick", js);
+ }
+
+ svg.appendChild(elmA);
+ svg.appendChild(elmB);
+ }
+
+ drawNode(null, skillTree);
+ return [svg, levelCount];
+}
\ No newline at end of file
diff --git a/student/uebersicht_overlay.js b/student/uebersicht_overlay.js
new file mode 100644
index 0000000..6b101f8
--- /dev/null
+++ b/student/uebersicht_overlay.js
@@ -0,0 +1,225 @@
+//=> use showEvaluationResult(object result_data) -> void;
+
+
+function showEvaluationResult(result_history_data) {
+ const couchDB = new CouchDB();
+
+ var elm = getId("evaluation_result");
+ if (elm == null) {
+ elm = document.createElement("overlay");
+ elm.setAttribute("id", "evaluation_result");
+
+ document.body.appendChild(elm);
+ }
+
+ elm.innerHTML = `
+Ergebnisse
+
+ `;
+
+ _updateEvaResContent(showOverlay("evaluation_result"), 0, result_history_data);
+}
+
+function _updateEvaResContent(elm, pos, data) {
+ const root = elm.querySelector(".overlay_content");
+
+ const question = data.questions[pos];
+ const result = data.result.map(arr => arr[pos]);
+
+ root.innerHTML = `
+
+ ${pos+1}. ${question.title}
+
`;
+
+ const span = document.createElement("span");
+ span.style = `
+width: 100%;
+display: inline-block;
+padding: 10px;
+border-radius: var(--border-radius);
+margin-top: 10px;
+margin-bottom: 20px;
+max-height: 60vh;
+min-height: 60vh;
+display: flex;
+flex-direction: column;
+justify-content: center;
+background: rgba(255, 255, 255, 0.6);
+ `;
+ root.appendChild(span);
+
+ const span2 = document.createElement("span");
+ span2.style = `
+width: 100%;
+text-align: center;
+display: flex;
+font-size: 30px;
+font-weight: bold;
+align-items: middle;
+justify-content: center;
+flex-direction: row;
+ `;
+
+ const prevPage = document.createElement("label");
+ prevPage.innerHTML = " < ";
+ if (pos-1 >= 0) {
+ prevPage.addEventListener("click", () => _updateEvaResContent(elm, pos-1, data));
+ _setEvaResHover(prevPage);
+ } else {
+ prevPage.style.opacity = "0.4";
+ }
+ prevPage.style.marginTop = "3px";
+ span2.appendChild(prevPage);
+
+ for (var i = 0; i < data.questions.length; i++) {
+ const isSel = i == pos;
+ const div = document.createElement("div");
+ div.style = `
+display: inline-block;
+padding-left: 10px;
+padding-right: 10px;
+text-align: center;
+ `;
+ div.innerHTML = `
+
+ `;
+
+ if (!isSel) {
+ const pos2 = i;
+ div.addEventListener("click", () => _updateEvaResContent(elm, pos2, data));
+ _setEvaResHover(div);
+ }
+ span2.appendChild(div);
+ }
+
+ const nextPage = document.createElement("label");
+ nextPage.innerHTML = " > ";
+ if (pos+1 < data.questions.length) {
+ nextPage.addEventListener("click", () => _updateEvaResContent(elm, pos+1, data));
+ _setEvaResHover(nextPage);
+ } else {
+ nextPage.style.opacity = "0.4";
+ }
+ nextPage.style.marginTop = "3px";
+ span2.appendChild(nextPage);
+
+ root.appendChild(span2);
+
+ if (question.type == "scala" || question.type == "emoji") {
+ const canvas = document.createElement("canvas");
+ canvas.style = `
+width: 100%;
+height: calc(100% - 6vh);
+border-radius: var(--border-radius);
+ `;
+
+ const ctx = canvas.getContext("2d");
+ _drawEvaResChart(ctx, question, result);
+
+ span.appendChild(canvas);
+ } else {
+ let answers = "";
+ for (var i of result) {
+ const div = document.createElement("div");
+ div.style = `
+padding: 10px;
+background: rgba(255, 255, 255, 1);
+margin-bottom: 10px;
+border-radius: var(--border-radius);
+ `;
+ div.innerText += i;
+ answers += div.outerHTML;
+ }
+ span.innerHTML = `
+ ${answers}
+
`;
+ }
+}
+
+function _drawEvaResChart(ctx, question, result) {
+ const colors = [
+ '216, 22, 22',
+ '255, 178, 0',
+ '255, 236, 0',
+ '215, 255, 0',
+ '65, 255, 0'
+ ];
+
+
+ if (question.type == "scala") {
+ let data = [0,0,0,0,0];
+ for (var i of result) {
+ if (i < 1) i = 1;
+ if (i > 5) i = 5;
+ data[i-1]++;
+ }
+ new Chart(ctx, {
+ type: 'bar',
+ data: {
+ labels: ['Sehr schlecht', 'Schlecht', 'Normal', 'Gut', 'Sehr gut'],
+ datasets: [{
+ label: "Anzahl der Stimmen",
+ data: data,
+ backgroundColor: [
+ 'rgba(' + colors[0] + ', 0.2)',
+ 'rgba(' + colors[1] + ', 0.2)',
+ 'rgba(' + colors[2] + ', 0.2)',
+ 'rgba(' + colors[3] + ', 0.2)',
+ 'rgba(' + colors[4] + ', 0.2)'
+ ],
+ borderColor: [
+ 'rgba(' + colors[0] + ', 1)',
+ 'rgba(' + colors[1] + ', 1)',
+ 'rgba(' + colors[2] + ', 1)',
+ 'rgba(' + colors[3] + ', 1)',
+ 'rgba(' + colors[4] + ', 1)'
+ ],
+ borderWidth: 2
+ }]
+ },
+ options: {
+ scales: { y: { beginAtZero: true } }
+ }
+ });
+ } else if (question.type == "emoji") {
+ let data = [0,0,0];
+ for (var i of result) {
+ if (i < 1) i = 1;
+ if (i > 3) i = 5;
+ data[i-1]++;
+ }
+ new Chart(ctx, {
+ type: 'bar',
+ data: {
+ labels: ['Schlecht', 'Ausgeglichen', 'Gut'],
+ datasets: [{
+ label: "Anzahl der Stimmen",
+ data: data,
+ backgroundColor: [
+ 'rgba(' + colors[0] + ', 0.2)',
+ 'rgba(' + colors[2] + ', 0.2)',
+ 'rgba(' + colors[4] + ', 0.2)'
+ ],
+ borderColor: [
+ 'rgba(' + colors[0] + ', 1)',
+ 'rgba(' + colors[2] + ', 1)',
+ 'rgba(' + colors[4] + ', 1)'
+ ],
+ borderWidth: 2
+ }]
+ },
+ options: {
+ scales: { y: { beginAtZero: true } }
+ }
+ });
+ }
+}
+
+function _setEvaResHover(elm) {
+ elm.addEventListener('mouseover', function() {
+ elm.style.opacity = '0.5';
+ });
+ elm.addEventListener('mouseout', function() {
+ elm.style.opacity = '1';
+ });
+}
\ No newline at end of file