This commit is contained in:
Turtle committed 2026-01-11 10:26:39 +01:00
1 parent 565c1b9911
commit 28490ce8b3
36 files changed
+587 -179

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
<!doctype html>
<html>
<body>
<script>
location.href = "./login.html";
</script>
</body>
</html>
Binary file not shown.
Binary file not shown.
+11
View File
@@ -0,0 +1,11 @@
const debug = false;
const couchdb_username = "admin";
const couchdb_password = "admin";
const couchdb_name = "sassy";
const debug_username = "dumble-door"; //set a default user when not logged in
//http://127.0.0.1:5984/_utils/#_config/couchdb@127.0.0.1
+63
View File
@@ -0,0 +1,63 @@
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="../lib/basic.css">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=0.8">
</head>
<body>
<overlay onclose="alert('hello');" id="overlay1">test</overlay>
<div class="header_bar_1">
<div>
test1
</div>
<div>
test2
</div>
<div>
<button class="profile"></button>
</div>
</div>
<div class="header_bar_1_placeholder"></div>
<content>
test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>
test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>
test<br>test<br>test<br>test<br>test<br>test<br>test<br>test
<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>
test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>
test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>
test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>
test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>test<br>
</content>
<script src="../auth.js"></script>
<script src="../lib/basic.js"></script>
<script src="../help/data_xample.js"></script>
<script>
showOverlay("overlay1");
const couchDB = new CouchDB();
async function main() {
await couchDB.set("xample", {
"my_data": 1,
"more_data": "bla"
});
const data = await couchDB.get("xample");
console.log(JSON.stringify(data));
//Gibt { "my_data": 1, "more_data": "bla" } wieder
console.log(JSON.stringify(await couchDB.get("*")));
//Gibt ["users","course:bucket","course:weiche_ware","course:algebra","course:geometrie","course:statistik","xample"] wieder
}
main();
</script>
</body>
</html>
+3
View File
@@ -0,0 +1,3 @@
async function createTemp() {
console.warn("createTemp is deprecated");
}
+91
View File
@@ -0,0 +1,91 @@
##How to import
<link rel="stylesheet" href="./lib/basic.css">
<script src="./auth.js"></script>
<script src="./lib/basic.js"></script>
##Overlays
```
//Erstelle ein element mit <overlay></overlay> in html und vergebe eine id. Um das overlay anzuzeigen, rufe showOverlay mit der id auf
function showOverlay(string id) -> void;
//Schließe das overlay mit der id
function hideOverlay(string id) -> void;
//Rufe das overlay auf
function getOverlay(string id) -> div;
```
##CouchDB
```
//Klasse zum Interagieren mit CouchDB
//Erstelle eine neue CouchDB-Klasse mit new CouchDB();
class CouchDB {
//Überprüfe, ob CouchDB online ist. (Wird sowieso automatisch gemacht, wenn get, set oder createDB verwendet wird)
async checkOnlineStatus() -> bool;
//Erstellt die Datenbank. (Wird auch automatisch gemacht, wenn man get oder set verwendet)
async createDB() -> void;
//Bekomme das Objekt, was in der Datenbank unter der id gespeichert wurde (Gibt null zurück, wenn nicht vorhanden)
async get(string id) -> Map|null;
//Setze ein Objekt, was in der Datenbank gespeichert wird (Bedenke: Du musst ein Objekt angeben, alles andere (string, array) wird nicht genommen)
async set(string id, object content, boolean replace = true) -> Map;
//Lösche ein Objekt, was in der Datenbank gespeichert ist
async delete(string id) -> Map;
}
```
Hinweise:
- Du brauchst eigentlich nur get oder set (weil alles andere automatisch gemacht wird)
- Wenn CouchDB offline oder online geht, während das html file läuft, musst du manuell checkOnlineStatus() aufrufen, um den Status zu aktualisieren
-> Oder einfach die Seite neuladen :/
- Vergiss nicht auth.js einzubinden!!! sonst 💥
**Beispiel:**
```
async function main() {
const couchDB = new CouchDB();
await couchDB.set("login", {
"username": "bla",
"password": "bla"
});
const data = await couchDB.get("login");
console.log(JSON.stringify(data));
//Gibt { "username": "bla", "password": "bla" } wieder
}
main();
```
##User
```
//Gebe dir den Nutzer wieder
async function getUserData(CouchDB couchDB) -> Map?;
//Setze den Nutzer, welcher unter der Variable userData verfügbar sein wird
async function setUserData(CouchDB couchDB) -> bool;
//Aktualisiert die Nutzerdaten in der Datenbank
async function updateUserData(CouchDB couchDB, object userData) -> Map;
```
##Other
```
//Spielt einen sound ab
//(Funktioniert nur, wenn vorher eine Nutzerinteraktion erfolgt ist (Button-Click etc.))
async function playSound(string src) -> void;
```
##Nützliche CSS-Klassen
- .profile = Runde Profilbox
- overlay = Overlay
- .header_bar_1 = Headerbar
- .header_bar_1_placeholder = Platzhalter für Headerbar
- .add_button = Runder "Hinzufügen"-Knopf
Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

+303
View File
@@ -0,0 +1,303 @@
:root {
--accent_color_fg: rgba(0, 123, 255, 1);
--accent_color_fg_o: rgba(0, 123, 255, 0.5);
--accent_color_bg: rgba(137, 197, 221, 1);
--accent_color_bg_o: rgba(137, 197, 221, 0.5);
--accent_color_bg_o2: rgba(137, 197, 221, 0.25);
--main_color_fg: white;
--main_color_bg: black;
--border-radius: 10px;
--def-shadow: 10px 20px 20px rgb(128, 128, 128, 0.5);
--reversed_def_shadow: inset 1px 2.5px 2.5px rgb(128, 128, 128, 0.5);
}
* {
box-sizing: border-box;
outline-style: none;
}
body, html {
height: 100%;
margin: 0;
font-family: Arial, Helvetica, sans-serif;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
overscroll-behavior: none;
touch-action: pan-x pan-y;
font-size: 20px;
}
::selection {
background-color: var(--accent_color_fg_o);
color: black;
}
.selectable {
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
}
/* ----------- elements ----------- */
.profile {
width: 58px;
height: 58px;
border-radius: 50%;
background: var(--accent_color_bg);
border: none;
background-size: 58px 58px;
}
input {
font-size: 18px;
background: none;
border: 1px solid black;
border-radius: var(--border-radius);
padding: 4px;
}
select {
border-radius: var(--border-radius);
padding: 5px;
font-size: 18px;
}
button {
border-radius: var(--border-radius);
font-size: 18px;
font-weight: normal;
}
.mc-xp-bar {
width: 100%;
height: 20px;
background-image: url("../imgs/xp-bar-bg.png");
background-size: 100% 20px;
}
.mc-xp-bar > div {
width: 100%;
height: 20px;
background-image: url("../imgs/xp-bar-fg.png");
background-size: 100% 20px;
clip-path: inset(0 100% 0 0);
}
/* ----------- overlay ----------- */
overlay {
display: none;
}
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 100;
}
.overlay > div:first-child {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.2);
backdrop-filter: blur(1px);
}
.overlay > div:last-child {
position: fixed;
top: 50%;
left: 50%;
min-width: 250px;
min-height: 150px;
max-height: 95%;
max-width: 95%;
transform: translate(-50%, -50%);
background: var(--accent_color_bg_o);
backdrop-filter: blur(10px);
border-radius: var(--border-radius);
padding: 15px;
text-align: left;
overflow-x: hidden;
overflow-y: auto;
box-shadow: var(--def-shadow);
}
.overlay > div:last-child > .close {
position: absolute;
top: 8px;
right: 8px;
width: 40px;
height: 40px;
background: var(--accent_color_bg);
border-radius: var(--border-radius);
border: none;
font-size: 20px;
font-weight: 600;
}
.overlay > div:last-child > .close:hover {
box-shadow: var(--reversed_def_shadow);
}
.overlay .overlay_title {
height: 40px;
margin-top: 4px;
margin-right: 50px;
font-weight: bold;
}
/* ----------- headerbar ----------- */
.header_bar_1 {
position: fixed;
top: 0%;
left: 50%;
transform: translateX(-50%);
width: 100%;
background: var(--accent_color_bg_o);
backdrop-filter: blur(10px);
padding: 15px;
box-shadow: var(--def-shadow);
height: 75px;
align-items: center;
display: flex;
flex-direction: row;
z-index: 1;
}
.header_bar_1 > div {
display: table-cell;
}
.header_bar_1 > div:nth-child(1) {
text-align: left;
width: 100%;
}
.header_bar_1 > div:nth-child(2) {
text-align: center;
width: 100%;
}
.header_bar_1 > div:nth-child(3) {
text-align: right;
width: 100%;
}
.header_bar_1_placeholder {
height: 75px;
}
.header_bar_1_undocked {
width: 96%;
top: 2%;
border-radius: var(--border-radius);
animation: header_bar_1_undocked_ani 0.5s ease-in-out;
}
.header_bar_1_docked {
width: 100%;
animation: header_bar_1_docked_ani 0.5s ease-in-out;
}
content {
display: block;
padding: 15px;
}
@keyframes header_bar_1_undocked_ani {
from {
width: 100%;
top: 0;
border-radius: 0;
}
to {
width: 96%;
top: 2%;
border-radius: var(--border-radius);
}
}
@keyframes header_bar_1_docked_ani {
from {
width: 96%;
top: 2%;
border-radius: var(--border-radius);
}
to {
width: 100%;
top: 0;
border-radius: 0;
}
}
/* ----------- custom ----------- */
.add_button {
width: 45px;
height: 45px;
border-radius: 50%;
padding: 0;
font-size: 25px;
background-color: white;
border: 1px solid black;
}
.add_button:hover {
border: 2px solid grey;
background-color: lightgray;
}
.do-not-color-me {
color: black !important;
}
dropdown {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: none;
z-index: 300;
}
dropdown > div:first-child {
width: 100%;
height: 100%;
background: none;
}
dropdown > div > button {
border-radius: 0;
}
dropdown > div:last-child {
position: absolute;
background: var(--accent_color_bg_o);
backdrop-filter: blur(10px);
border-radius: var(--border-radius);
transform: translate(calc(-100% + 50px), 5px);
width: 200px;
display: flex;
flex-direction:column;
box-shadow: var(--def-shadow);
}
dropdown > div:last-child label {
background: none;
font-size: 20px;
border: none;
border-bottom: 1px solid black;
padding-top: 8px;
padding-bottom: 8px;
font-weight: normal;
text-align: center;
color: black;
}
dropdown > div:last-child button {
background: none;
font-size: 20px;
border: none;
border-bottom: 1px solid black;
padding-top: 8px;
padding-bottom: 8px;
font-weight: normal;
text-align: center;
}
dropdown > div:last-child button:hover {
background: var(--accent_color_fg_o);
}
dropdown > div:last-child button:first-child {
border-top-left-radius: var(--border-radius);
border-top-right-radius: var(--border-radius);
}
dropdown > div:last-child button:last-child {
border-bottom: none;
border-bottom-left-radius: var(--border-radius);
border-bottom-right-radius: var(--border-radius);
}
+613
View File
@@ -0,0 +1,613 @@
const getId = (id) => document.getElementById(id);
function getOverlay(id) {
const elms = document.getElementsByName(id);
if (elms.length == 0) return null;
return elms[0];
}
function showOverlay(id) {
const elm = getId(id);
if (!elm) return;
const overlay = document.createElement("div");
const styleString = elm.getAttribute("style");
overlay.classList.add("overlay");
overlay.setAttribute("name", id);
overlay.innerHTML = `
<div onclick="_onOverlayDestroy(this.parentElement);"></div>
<div class="${id}" style="${styleString}">
<button class="close" onclick="_onOverlayDestroy(this.parentElement.parentElement);">x</button>
${elm.innerHTML}
</div>
`;
document.body.appendChild(overlay);
const cb = elm.getAttribute("onopen");
if (typeof cb == "string") {
try {
eval(
`const overlay = document.getElementsByClassName("${id}")[0]; ` +
cb
);
} catch (err) {
console.error(err);
}
}
return overlay;
}
function _onOverlayDestroy(elm) {
const ori_elm = getId(elm.getAttribute("name"));
if (ori_elm) {
const cb = ori_elm.getAttribute("onclose");
if (typeof cb == "string") {
try {
eval(cb);
} catch (err) {
console.error(err);
}
}
}
elm.remove();
}
function hideOverlay(id) {
const elms = document.getElementsByName(id);
for (var i of elms) _onOverlayDestroy(i);
}
function refreshOverlays() {
const elms = document.getElementsByClassName("overlay");
for (var i of elms) {
const id = i.getAttribute("name");
hideOverlay(id);
showOverlay(id);
}
}
//----------------------------------//
function _main_events() {
var header_bar_1 = document.querySelector(".header_bar_1");
if (header_bar_1 == null) header_bar_1 = document.querySelector("#header_bar_1");
if (header_bar_1 != null) {
let isFloating = false;
window.addEventListener("load", function () {
if (window.pageYOffset > 0) {
header_bar_1.style.animationDuration = "0s";
header_bar_1.classList.add("header_bar_1_undocked");
isFloating = true;
}
});
window.addEventListener("scroll", function () {
if (window.pageYOffset > 0 && !isFloating) {
header_bar_1.style.animationDuration = "0.5s";
header_bar_1.classList.remove("header_bar_1_docked");
header_bar_1.classList.add("header_bar_1_undocked");
isFloating = true;
} else if (window.pageYOffset === 0 && isFloating) {
header_bar_1.style.animationDuration = "0.5s";
header_bar_1.classList.remove("header_bar_1_undocked");
header_bar_1.classList.add("header_bar_1_docked");
isFloating = false;
}
});
}
}
window.addEventListener("load", (event) => {
_main_events();
});
/* ----------- couch db ----------- */
//http://127.0.0.1:5984/_utils/#_config/couchdb@127.0.0.1
class CouchDB {
static _default_url = "/couchdb/";
constructor() {
this._does_auth_exists_err();
this.request = new XMLHttpRequest();
this.db_name = couchdb_name;
this.is_online = 2;
}
//private methods
_does_auth_exists() {
return (
typeof couchdb_username == "string" &&
typeof couchdb_password == "string" &&
typeof couchdb_name == "string"
);
}
_does_auth_exists_err() {
if (!this._does_auth_exists())
throw 'Keine Auth vorhanden. Digga, mach mal <script src="./auth.js"></script>';
}
_is_error_missing_database(errReason) {
return (
errReason === "no_db_file" || errReason === "Database does not exist."
);
}
_is_error_missing_data(errReason) {
return errReason === "missing";
}
async _ping() {
try {
const response = await fetch(CouchDB._default_url, {
method: "GET",
});
return response.ok;
} catch (err) {
return false;
}
}
_send_db_request(method, url, body = null, errorHandling = true) {
return new Promise(async (resolve, reject) => {
if (this.is_online == 2) {
await this.checkOnlineStatus();
}
if (this.is_online != 1) {
reject("CouchDB is not online!");
return;
}
const dburl = CouchDB._default_url + this.db_name + "/" + url;
const request = this.request;
this.request.onreadystatechange = () => {
if (request.readyState != 4) return;
try {
if (request.status == 200 || request.status == 201) {
var response = JSON.parse(request.responseText);
resolve(response);
}
//errors
else if (request.status == 404) {
var json = JSON.parse(request.responseText);
if (this._is_error_missing_data(json.reason)) {
resolve(null);
} else if (
errorHandling &&
this._is_error_missing_database(json.reason)
) {
this.createDB()
.then((value) => {
this._send_db_request(method, url, body, false)
.then((value) => {
resolve(value);
})
.catch((err) => {
reject(err);
});
})
.catch((err) => {
reject(err);
});
} else {
reject(
`Database not found: ${request.status} (Reason: ${reason.json})`,
);
}
} else {
reject(
`Unexpected status: ${request.status} (Response: ${
request.responseText
})`,
);
}
} catch (err) {
reject("Unexpected error: " + err);
}
};
this.request.open(method, dburl, true);
this.request.setRequestHeader(
"Authorization",
"Basic " + btoa(couchdb_username + ":" + couchdb_password),
);
this.request.setRequestHeader("Content-type", "application/json");
this.request.send(body);
});
}
async _get_db_data(id) {
return await this._send_db_request("GET", id);
}
//public methods
async checkOnlineStatus() {
const res = await this._ping();
if (res === true) {
this.is_online = 1;
} else {
this.is_online = 0;
}
return res;
}
async createDB() {
await this._send_db_request("PUT", "", null, false);
}
async get(id) {
const res = await this._get_db_data(id);
if (res == null) return null;
delete res["_id"];
delete res["_rev"];
return res;
}
async set(id, content, replace = true) {
try {
if (typeof content != "object")
throw (
"basic.js - CouchDB->set: content is not from type object (type = " +
typeof content +
")"
);
let existingDoc;
try {
existingDoc = await this._get_db_data(id);
} catch (err) {
existingDoc = null;
}
if (existingDoc) {
if (replace) {
content = {
...content,
_id: existingDoc._id,
_rev: existingDoc._rev,
};
} else {
content = {
...existingDoc,
...content,
_rev: existingDoc._rev,
};
}
}
const res = await this._send_db_request(
"PUT",
id,
JSON.stringify(content),
);
return {
type: "success",
message: res,
};
} catch (err) {
console.error(err);
return {
type: "error",
message: err,
};
}
}
async delete(id) {
try {
var existingDoc = null;
try {
existingDoc = await this._get_db_data(id);
} catch (err) {}
if (!existingDoc) return {
type: "error",
message: "Not found!",
};
const res = await this._send_db_request(
"DELETE",
`${id}?rev=${existingDoc._rev}`
);
return {
type: "success",
message: res,
};
} catch (err) {
console.error(err);
return {
type: "error",
message: err,
};
}
}
}
//getuser
function getURLArgsMap() {
let spl = location.href.split("?");
if (spl.length != 2) return {};
let _1 = spl[1].split(",");
const res = {};
for (var i of _1) {
let _2 = i.split("=");
if (_2.length == 2) {
res[_2[0]] = _2[1];
} else {
res[i] = null;
}
}
return res;
}
function form1(string) {
if (!string) return string;
return string.charAt(0).toUpperCase() + string.slice(1);
}
async function getUserData(couchDB) {
let username = getURLArgsMap()["user"];
if (!username) {
if (debug && typeof (debug_username) == "string") {
username = debug_username;
} else {
return null;
}
}
const res = await couchDB.get("users");
if (!res) return null;
const data = res[username];
return !data ? null : {
...data,
"name": username
};
}
var userData = null;
const userDataListener = [];
async function setUserData(couchDB) {
const user = await getUserData(couchDB);
if (user == null) {
location.href = "../login.html";
return false;
}
userData = user;
updateAllProfiles(user);
_constructProfile(user);
await checkUserCourses(couchDB, user);
while (userDataListener.length > 0) {
const call = userDataListener[userDataListener.length - 1];
userDataListener.pop();
try { call(); } catch(err) { console.error(err); }
}
return true;
}
async function updateUserData(couchDB, user = userData) {
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 features = user.enabled_abilities ??= [];
const hasProfilAbility = features.includes("profile_pic");
const profiles = document.getElementsByClassName("profile");
for (var i of profiles) {
if (hasProfilAbility && user.avatar) {
i.style.backgroundImage = `url("${user.avatar}")`;
} else {
if (features.includes("pink_mode")) {
i.style.backgroundImage = `url("https://static.turtle-coding-gbr.de/gmci/rosa_profil.png")`;
} else {
i.style.backgroundImage = `url("https://static.turtle-coding-gbr.de/gmci/standart_profil.png")`;
}
}
i.onclick = _onProfileClick;
}
}
async function checkUserCourses(couchDB, userData) {
for (var cName of userData.courses) {
const course = await couchDB.get("course:" + cName);
if (!course || !course.expire_date) continue;
const expire_date = new Date(course.expire_date);
if (expire_date < new Date()) {
course.result_history ??= [];
course.questions ??= [];
course.result ??= [];
if (course.questions.length == 0 || course.result.length == 0) continue;
course.result_history.push({
"date": course.start_date ?? expire_date,
"questions": course.questions,
"result": course.result
});
course.questions = [];
course.result = [];
await couchDB.set("course:" + cName, course);
console.log(`Course ${cName} expired`);
}
}
}
function _onProfileClick(event) {
var buttonRect = this.getBoundingClientRect();
var dropdown = document.createElement("dropdown");
dropdown.addEventListener("click", dropdown.remove);
var bg = document.createElement("div");
dropdown.appendChild(bg);
var div = document.createElement("div");
const _1 = window.innerHeight - buttonRect.y - 200;
if (_1 < 0) {
div.style.transform = "translate(calc(-100% + 58px), calc(-100% - 30px))";
}
div.style.top = (buttonRect.bottom) + 'px';
div.style.left = (buttonRect.left) + 'px';
div.innerHTML = `
<label><b>${form1(userData.name)}</b></label>
<button onclick='_onProfilClickOnProfil()'>Profil</button>
<button onclick='showSkillTree()'>Skill-Tree</button>
<button onclick='location.href = "../login.html";'>Abmelden</button>
`;
dropdown.appendChild(div);
document.body.appendChild(dropdown);
}
function _onProfilClickOnProfil() {
if (typeof(showProfile) == "function") {
showProfile(userData);
} else {
_showProfile(userData);
}
}
function _constructProfile(user) {
const elm = document.createElement("overlay");
elm.setAttribute("id", "profile_overlay");
document.body.appendChild(elm);
elm.innerHTML = `
<div class="overlay_title">Profil-Übersicht</div>
<span style="display: inline-block; width: 100%; text-align: center;">
<div style="border-radius: 50%; width: 140px; height: 140px; display: inline-block; margin-bottom: 10px; background-color: var(--accent_color_bg); background-size: 140px 140px; background-image: url('');" id="profile-pic" class="profile-pic"></div>
<br>
<u style="font-size: 22px;"></u>
<br><br>
</span>
<span class="selectable">
Level: <label></label><br>
Rolle: <label></label>
</span><br>
<span class="avatar-inp">Avatar: <input style="" value=""><br></span><br>
<button style="font-size: 18px; width: 100%; border-radius: var(--border-radius); font-weight: normal; color: black; padding: 5px; background: rgba(255,255,255,0.5);" onclick="_showProfileSave(this);">Speichern</button>
`;
}
function _showProfile(user) {
let elm = getId("profile_overlay");
const features = user.enabled_abilities ??= [];
const hasProfilAbility = features.includes("profile_pic");
const avatarInp = elm.querySelector(".avatar-inp");
const profilePic = elm.querySelector(".profile-pic");
if (hasProfilAbility) {
profilePic.style.setProperty("background-image", `url('${user.avatar}')`);
elm.querySelector("input").setAttribute("value", user.avatar);
avatarInp.style.display = "unset";
} else {
if (features.includes("pink_mode")) {
profilePic.style.setProperty("background-image", `url("https://static.turtle-coding-gbr.de/gmci/rosa_profil.png")`);
} else {
profilePic.style.setProperty("background-image", `url("https://static.turtle-coding-gbr.de/gmci/standart_profil.png")`);
}
avatarInp.style.display = "none";
}
elm.querySelector("u").innerText = getUserDisplayName(user);
const labels = elm.getElementsByTagName("label");
labels[0].innerText = user.level ?? 0;
labels[1].innerText = form1(user.role ?? "Student");
showOverlay("profile_overlay");
}
async function _showProfileSave(elm) {
if (!userData) return;
let couchDB = new CouchDB();
const res = await couchDB.get("users");
const avatar = elm.parentElement.querySelector("input");
userData.avatar = avatar.value;
const user = {...userData};
delete user["name"];
res[userData.name] = user;
await couchDB.set("users", res);
updateAllProfiles(userData);
hideOverlay("profile_overlay");
}
async function playSound(src) {
const elm = document.createElement("audio");
elm.innerHTML = `
<source src="${src}" type="audio/mpeg"></source>
`;
try {
await elm.play();
} catch(err) {
console.log("Error playing audio:", err);
}
}
function hexToRGBA(hex) {
let r = 0, g = 0, b = 0;
if (hex.length === 7) {
r = parseInt(hex.slice(1, 3), 16);
g = parseInt(hex.slice(3, 5), 16);
b = parseInt(hex.slice(5, 7), 16);
}
return [r, g, b];
}
function getUserDisplayName(user) {
const hasHonor = (user.enabled_abilities ??= []).includes("title_of_honor");
const title_of_honor = hasHonor ? "(Sir) " : "";
return `${title_of_honor}${ !user.title ? "" : user.title + " " }${ form1(user.name) }`;
}
async function levelUpXP(couchDB, user = userData, xp = 1) {
var resLevelUp = false;
userData.xp ??= 0;
userData.level ??= 0;
userData.xp += xp;
while (userData.xp >= userData.level+1) {
userData.xp -= (userData.level + 1);
userData.level++;
resLevelUp = true;
}
if (userData.xp < 0) {
console.error("UserData-XP was below zero!");
userData.xp = 0;
}
await updateUserData(couchDB, user);
return resLevelUp;
}
function setXPBarPercent(percent) {
percent = 100 - percent;
const elms = document.querySelectorAll(".mc-xp-bar");
for (var elm of elms) {
const div = elm.querySelector("div");
div.style.setProperty('clip-path', `inset(0 ${percent}% 0 0)`);
}
}
+154
View File
@@ -0,0 +1,154 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=0.8" />
<title>Login</title>
<link rel="stylesheet" href="./lib/basic.css" />
<script src="./auth.js"></script>
<script src="./lib/basic.js"></script>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
}
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f6f8fa;
}
.login-card {
background-color: #fff;
padding: 2.5rem 2rem;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
width: 100%;
max-width: 400px;
}
.login-card h1 {
text-align: center;
margin-bottom: 1.5rem;
font-size: 1.5rem;
}
label {
display: block;
margin-bottom: 0.3rem;
font-weight: 600;
font-size: 0.9rem;
}
input {
width: 100%;
padding: 0.5rem 0.75rem;
margin-bottom: 1rem;
border: 1px solid #d1d5da;
border-radius: 6px;
font-size: 1rem;
}
button {
width: 100%;
padding: 0.6rem;
background-color: var(--accent_color_fg);
color: #fff;
border: none;
border-radius: 6px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
}
button:hover {
background-color: var(--accent_color_fg_o);
}
.signup-text {
text-align: center;
margin-top: 1rem;
font-size: 0.9rem;
}
.signup-text a {
color: #0366d6;
text-decoration: none;
}
.signup-text a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<div class="login-card">
<h1>Sign in</h1>
<label for="username">Username</label>
<input
type="text"
id="username"
name="username"
autocomplete="username"
autocapitalize="off"
autocorrect="off"
required
autofocus
/>
<label for="password">Password</label>
<input
type="password"
id="password"
name="password"
autocomplete="current-password"
required
/>
<button type="button" onclick="login()">Sign in</button>
<div class="signup-text">Don't have an account yet? <a href="signup.html">Sign up</a></div>
</div>
<script>
const couchDB = new CouchDB();
async function getData() {
try {
const res = await couchDB.get("users");
return res ?? {};
} catch (e) {
console.error("Failed to get users:", e);
return {};
}
}
async function login() {
const data = await getData();
const username = document.getElementById("username").value.trim().toLowerCase();
const password = document.getElementById("password").value;
const user = data[username];
if (user == undefined) {
return alert("User doesn't exist!");
}
if (user.password != password) {
return alert("Incorrect password!");
}
if (user.role === "student") {
location.href = "./student/uebersicht.html?user=" + username;
return;
} else {
location.href = "./prof/uebersicht.html?user=" + username;
return;
}
}
</script>
</body>
</html>
+212
View File
@@ -0,0 +1,212 @@
const getSkillTree = () => ({
id: "root",
text: "Skill-Tree",
children: [
{
text: "Dark-Mode",
id: "dark_mode",
description: "Ändert das Aussehen zu einem dunklen Design!",
collision: ["pink_mode"],
children: [
{
text: "Pink-Mode",
description: "Ändert das Aussehen zu einem pinken Design!",
id: "pink_mode",
collision: ["dark_mode"],
},
{
text: "Accent-Color",
description: "Verändere die Primärfarbe",
id: "accent_color",
call: `chooseAccentColor();`,
call_unset: `unsetAccentColor()`
}
]
},
{
text: "Profilbild",
id: "profile_pic",
description: "Erlange die Fähigkeit, das Profilbild zu ändern",
children: [
{
text: "Einfacher Rahmen",
id: "simple_border",
description: "Ein einfacher Rahmen, der sich um das Profilbild legt",
collision: ["gold_border", "epic_border"],
children: [
{
text: "Gold-Rahmen",
id: "gold_border",
description: "Ein goldener Rahmen, der sich um das Profilbild legt",
collision: ["simple_border", "epic_border"],
children: []
},
{
text: "Epic-Rahmen",
id: "epic_border",
description: "Ein epischer Rahmen, der sich um das Profilbild legt",
collision: ["simple_border", "gold_border"],
children: [
{
text: "Ehren-Titel",
id: "title_of_honor",
description: "Kriege den Ehrentitel \"Sir\" verliehen"
}
]
}
]
}
],
}
]
});
const _skillCb = {
"dark_mode": function() {
document.body.style = "background: black; color: white;";
},
"dark_mode_unset": function() {
document.body.style = "background: unset; color: unset;";
},
"pink_mode": function() {
document.body.style = `background: rgba(242, 199, 252, 1); color: #5a076d;`;
},
"pink_mode_unset": function() {
_skillCb.dark_mode_unset();
},
"accent_color": function() {
const storage = (userData.skillStorage ??= {})["accent_color"];
if (!storage) return;
const rgb = storage.join(",");
setRootStyle("--accent_color_fg", `rgba(${rgb}, 1)`);
setRootStyle("--accent_color_fg_o", `rgba(${rgb}, 0.5)`);
setRootStyle("--accent_color_bg", `rgba(${rgb}, 1)`);
setRootStyle("--accent_color_bg_o", `rgba(${rgb}, 0.5)`);
setRootStyle("--accent_color_bg_o2", `rgba(${rgb}, 0.25)`);
},
"accent_color_unset": function() {
removeRootStyle("--accent_color_fg");
removeRootStyle("--accent_color_fg_o");
removeRootStyle("--accent_color_bg");
removeRootStyle("--accent_color_bg_o");
removeRootStyle("--accent_color_bg_o2");
},
"simple_border": function() {
setProfileBorder("../imgs/simple_border.png", 8);
},
"unset_simple_border": () => unsetProfileBorder(),
"gold_border": function() {
setProfileBorder("../imgs/gold_border.png", 8);
},
"unset_gold_border": () => unsetProfileBorder(),
"epic_border": function() {
setProfileBorder("../imgs/epic_border.png", 8);
},
"unset_epic_border": () => unsetProfileBorder(),
"title_of_honor": function() {},
"profile_pic": () => updateEverything()
};
function updateEverything() {
updateAllProfiles(userData);
refreshOverlays();
}
function chooseAccentColor() {
var elm = getId("skill_chooseAccentColor");
if (!elm) {
elm = document.createElement("overlay");
elm.setAttribute("id", "skill_chooseAccentColor");
elm.innerHTML = `
<div class="overlay_title">Wähle eine Farbe:</div><br>
<input type="color" style="width: 100%; border-radius: var(--border-radius);"><br><br>
<button onclick="setAccentColor(this)" style="width: 100%; border-radius: var(--border-radius); font-size: 18px; padding: 10px; background: rgba(255, 255, 255, 0.5);">Bestätigen</button>
`;
document.body.appendChild(elm);
}
setTimeout(() => showOverlay("skill_chooseAccentColor"), 1);
}
function unsetAccentColor() {
userData.skillStorage ??= {};
userData.skillStorage["accent_color"] = null;
updateUserData(new CouchDB());
}
function setAccentColor(elm) {
const input = elm.parentElement.querySelector("input");
const rgba = hexToRGBA(input.value);
userData.skillStorage ??= {};
userData.skillStorage["accent_color"] = rgba;
updateUserData(new CouchDB());
hideOverlay("skill_chooseAccentColor");
_skillCb.accent_color();
updateEverything()
}
//--------------------------------------------//
function unsetProfileBorder() {
const elms = document.getElementsByClassName("profile");
for (var i of elms) i.innerHTML = '';
}
function setProfileBorder(src, x) {
const elms = document.getElementsByClassName("profile");
const style1 = `
width: 67px;
height: 67px;
border-radius: 50%;
border: none;
background-size: 67px 67px;
position: absolute;
background-image: url('${src}');
transform: translate(-${x}px, calc(-50%));
`;
for (var i of elms) {
i.innerHTML = `<div class="border" style="${style1}"></div>`;
}
const style2 = `
width: 160px;
height: 160px;
border-radius: 50%;
border: none;
background-size: 160px 160px;
position: absolute;
background-image: url('${src}');
transform: translate(-${x*1.2}px, -8px);
`;
const profilPic = getId("profile-pic");
if (profilPic) {
profilPic.innerHTML = `<div class="border" style="${style2}"></div>`;
}
}
function setRootStyle(key, value) {
document.documentElement.style.setProperty(key, value);
}
function removeRootStyle(key) {
document.documentElement.style.removeProperty(key);
}
function exeSkill(id, activate = true) {
const cb = _skillCb[activate ? id: id+"_unset"];
if (typeof(cb) != "function") return;
try { cb(); } catch(err) { console.error(err); }
}
var globalSkills = [];
async function runSkills() {
const skills = userData.enabled_abilities;
globalSkills = skills;
for (var i of skills) exeSkill(i, true);
}
userDataListener.push(runSkills);
+294
View File
@@ -0,0 +1,294 @@
//=> use showSkillTree() -> void;
//=> use playLevelSound() -> void;
//=> use playXPSound() -> void;
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);
}
elm.innerHTML = ``;
const div = document.createElement("div");
div.style = `
display: inline-block;
width: 100%;
height: 100%;
overflow: auto;
margin-top: 20px;
text-align: center;
color: black;
`;
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 = `
<div style="padding: 10px 30px; background: rgba(255, 255, 255, 0.6); border-radius: var(--border-radius); display: inline-block;" class="do-not-color-me">
Skill-Points: <span class="aSkillPoints">${aSkillPoints}</span>
</div>
`;
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; color: black;`;
div.innerHTML = `
<b style="display: inline-block; margin-top: 5px;">${data.text}</b><br><br>
<span>${data.description}</span><br><br>
<span style="display: inline-block; border: 1px solid black; background: rgba(255, 255, 255, 0.3); width: 100%; border-radius: var(--border-radius); padding: 10px;">${isEnabled ?
"<span style='color: green;'>Im Besitz</span>":
"<span style='color: red;'>Noch nicht freigeschaltet</span>"
}</span><br><br>
`;
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 += `
<button onclick='activateSkill(this, ${JSON.stringify(data)})' style="${bttnStyle}">${isEnabled ? "Deaktivieren": "Aktivieren"}</button>
`;
} else {
const isParentEnabled = parent.id == "root" || userData.abilities.includes(parent.id);
const allowBuy = aSkillPoints > 0 && isParentEnabled;
div.innerHTML += `
<button onclick='_buySkill(${JSON.stringify(data)})' style="${allowBuy ? bttnStyle: bttnStyleDisabled}" ${allowBuy ? "": "disabled"}>Kaufen</button>
`;
}
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) {
//disabled
userData.enabled_abilities = eAblities.filter((val) => val != data.id);
for (var elm of elms) {
elm.setAttribute("fill", "rgb(187, 243, 183)");
}
} else {
//enabled
const coll = data.collision ??= [];
userData.enabled_abilities = eAblities.filter((val) => !coll.includes(val));
userData.enabled_abilities.push(data.id);
for (var elm of elms) {
elm.setAttribute("fill", "lightgreen");
}
for (var i of coll) {
if (!(userData["abilities"] ??= []).includes(i)) continue;
exeSkill(i, false);
for (var elm of document.getElementsByClassName("skill_" + i)) {
elm.setAttribute("fill", "rgb(187, 243, 183)");
}
}
}
elm.innerText = !isEnabled ? "Deaktivieren" : "Aktivieren";
getOverlay("skill_tree_details_overlay").innerHTML = "";
await updateUserData(new CouchDB(), userData);
hideOverlay("skill_tree_details_overlay");
if (!isEnabled) {
if (typeof(data.call) == "string") {
try { eval(data.call); } catch (err) { console.error(err); }
}
}
exeSkill(data.id, !isEnabled);
if (isEnabled) {
if (typeof(data.call_unset) == "string") {
try { eval(data.call_unset); } catch (err) { console.error(err); }
}
}
updateEverything();
}
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);
playXPSound();
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) {
data.children ??= [];
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;
}
measure_tree(skillTree);
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;
});
}
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];
}
async function playLevelSound() {
await playSound("../audio/levelup2.mp3");
}
async function playXPSound() {
await playSound("../audio/levelup1.mp3");
}
+297
View File
@@ -0,0 +1,297 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=0.5">
<link rel="stylesheet" href="../lib/basic.css">
<script src="../auth.js"></script>
<script src="../lib/basic.js"></script>
<script src="../other/skills.js"></script>
<script src="../other/skilltree.js"></script>
<title>Auswertung</title>
</head>
<body>
<style>
button.navigation {
font-size: 20px;
padding: 15px 15px;
background-color: var(--accent_color_bg);
border: none;
border-radius: var(--border-radius);
cursor: pointer;
text-align: center;
min-width: min(calc(160px + 40px), 100%);
white-space: nowrap;
}
button.navigation:hover {
background: var(--accent_color_bg_o);
}
/*.header_bar_1 > div:first-child > div {
display: inline;
padding: 10px;
padding-right: 30px;
}
.header_bar_1 > div:first-child > div:hover {
background: rgba(127, 127, 127, 0.4);
border-radius: var(--border-radius);
}*/
.content-table {
height: calc(100% - 75px);
max-height: calc(100% - 75px);
min-height: calc(100% - 75px);
width: 100%;
position: fixed;
top: 75px;
left: 0;
}
.content-table > tbody > tr:first-child > td:first-child {
width: 35%;
vertical-align: top;
padding: 20px;
padding-right: 10px;
height: 100%;
}
.content-table > tbody > tr:first-child > td > div {
display: inline-block;
width: 100%;
border: 3px solid var(--accent_color_bg);
border-radius: var(--border-radius);
padding: 10px;
box-shadow: var(--reversed_def_shadow);
background: var(--accent_color_bg_o2);
min-height: 150px;
}
.content-table > tbody > tr:first-child > td:last-child {
width: 65%;
height: 100px;
vertical-align: top;
padding: 20px;
padding-left: 10px !important;
}
.content-table > tbody > tr:first-child > td:last-child > div {
display: flex;
height: 100%;
max-height: 100%;
padding: 15px;
overflow-y: scroll;
overflow-x: hidden;
}
.content-table > tbody > tr:first-child > td:last-child > div button {
padding: 5px;
font-size: 16px;
font-weight: normal;
margin-top: 5px;
background-color: var(--accent_color_bg_o);
border-radius: var(--border-radius);
}
#umfrage_list {
width: 100%;
}
#umfrage_list > div {
padding: 10px;
border: 2px solid var(--accent_color_bg);
border-radius: var(--border-radius);
width: 100%;
min-height: 100px;
margin-bottom: 10px;
}
</style>
<div class="header_bar_1">
<div>
<div>
<button onclick="location.href = './uebersicht.html?user=' + userData.name;" class="navigation">Meine Kurse</button>
</div>
</div>
<div><b>Auswertungen</b></div>
<div>
<button class="profile"></button>
</div>
</div>
<div class="header_bar_1_placeholder"></div>
<table class="content-table"><tr>
<td><div>
<div id="gesamt" class="selectable"></div>
</div></td>
<td><div>
<div id="umfrage_list"></div>
</div></td>
</tr></table>
<script>
const couchDB = new CouchDB();
setUserData(couchDB).then(init);
var gesamtAuswertung = [0,0,0, []];
async function loadStats() {
const gdiv = getId("gesamt");
const udiv = getId("umfrage_list");
udiv.innerHTML = "";
gesamtAuswertung = [0,0,0, []];
for (var i of userData.courses) {
const course = await couchDB.get("course:" + i);
if (course == null) continue;
const list = course["result_history"] ?? [];
for (var pos in list) {
const i2 = list[pos];
udiv.innerHTML += _createElement1(course, i2, i, pos);
}
udiv.innerHTML += _createElement1(course, {
"date": course.expire_date,
"questions": course.questions ?? [],
"result": course.result ?? []
}, i);
}
const bottomLabel = document.createElement("label");
bottomLabel.style = `display: inline-block; font-size: 5px;`;
bottomLabel.innerHTML = "&nbsp;";
udiv.appendChild(bottomLabel);
let studenten = gesamtAuswertung[3].length == 0 ? 0 : gesamtAuswertung[3].reduce((acc, val) => acc + val, 0) / gesamtAuswertung[3].length;
gdiv.innerHTML = `
<b>Gesamtbewertung:</b><br><br>
Positiv bewertet: ${round(gesamtAuswertung[0])}<br>
Neutral bewertet: ${round(gesamtAuswertung[1])}<br>
Negativ bewertet: ${round(gesamtAuswertung[2])}<br><br>
<i>Studenten im Durchschnitt:</i> ${round(studenten)}
`;
}
function _createElement1(course, data, name, pos = -1) {
const startDate = data.date;
const title = course.title;
var startD = "Unbekannt";
if (startDate) {
startD = new Date(startDate).toLocaleDateString('de-DE', {year: 'numeric', month: 'long', day: 'numeric'});
}
var statCount = [0,0,0];
for (var i in data.result) {
const result = data.result[i];
for (var i2 in result) {
const question = data.questions[i2];
const answer = result[i2];
if (question.type == "text") {
continue;
} else if (question.type == "scala") {
if (answer == 5 || answer == 4) {
statCount[0] ++;
} else if (answer == 3) {
statCount[1] ++;
} else if (answer == 1 || answer == 2) {
statCount[2] ++;
}
} else if (question.type == "emoji") {
if (answer == 3) {
statCount[0] ++;
} else if (answer == 2) {
statCount[1] ++;
} else if (answer == 1) {
statCount[2] ++;
}
}
}
gesamtAuswertung[3].push(result.length);
}
var total = statCount.reduce((acc, val) => acc + val, 0);
var percentages;
if (total > 0) {
percentages = statCount.map(value => (value / total) * 100);
} else {
percentages = [0, 0, 0];
}
var endNote;
var bttn;
if (percentages[0] > percentages[1] && percentages[0] > percentages[2]) {
endNote = "Überwiegend positiv";
gesamtAuswertung[0] ++;
bttn = `<button onclick='getXpPoints(this, 2, "${name}", ${pos})'>2 XP abholen</button>`;
} else if (percentages[2] > percentages[0] && percentages[2] > percentages[1]) {
endNote = "Überwiegend negativ";
gesamtAuswertung[2] ++;
bttn = '';
} else {
endNote = "Gemischt";
gesamtAuswertung[1] ++;
bttn = `<button onclick='getXpPoints(this, 1, "${name}", ${pos})'>1 XP abholen</button>`;
}
if ((data["got_xp"] ??= false) || pos == -1) {
bttn = '';
}
return `<div>
<table style="width: 100%; border-collapse:collapse; " class="selectable">
<tr>
<td style="vertical-align: top; width: 60%; border-bottom: 1px solid black;">
Umfrage vom ${startD}
</td>
<td style="vertical-align: top; text-align: right; width: 40%; border-bottom: 1px solid black;">
&nbsp;&nbsp;Kurs: ${title}
</td>
</tr>
<tr>
<td style="padding-left: 15px; display: inline-block; padding-top: 10px; vertical-align: top;">
Positive: ${round(percentages[0])}%<br>
Negative: ${round(percentages[2])}%<br>
Neutral: ${round(percentages[1])}%<br>
</td>
<td style="padding-top: 10px; vertical-align: top;">
<u>Endnote</u>: ${endNote}<br>
${bttn}
</td>
</tr>
</table>
</div>`;
}
function round(n) {
return Number(n.toFixed(2)).toString();
}
async function init() {
await loadStats();
}
async function getXpPoints(elm, xp, name, pos) {
elm.remove();
const course = await couchDB.get("course:" + name);
const history = course.result_history ??= [];
const data = history[pos];
if (data == null) {
console.error(`data is null at course ${name} history=${pos}`);
return;
}
data["got_xp"] = true;
if (await levelUpXP(couchDB, userData, xp)) {
playLevelSound();
} else {
playXPSound();
}
await couchDB.set("course:" + name, course);
}
</script>
</body>
</html>
+347
View File
@@ -0,0 +1,347 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=0.5">
<link rel="stylesheet" href="../lib/basic.css">
<script src="../auth.js"></script>
<script src="../lib/basic.js"></script>
<script src="../other/skills.js"></script>
<script src="../other/skilltree.js"></script>
<script src="https://cdn.jsdelivr.net/npm/qrcodejs/qrcode.min.js"></script>
<script>
const couchDB = new CouchDB();
setUserData(couchDB);
</script>
<title>Kurs Anzeige</title>
</head>
<body>
<div class="header_bar_1">
<button class="back" onclick="location.href = './uebersicht.html?user=' + userData.name;">
Übersicht
</button>
<div>
<!-- <button class="profile" onclick='showOverlay("miniProfile")' style="position: absolute; top: 7px; right: 15px;">
<overlay class="my_overlay" id="miniProfile">
</overlay>
</button> -->
<button class="profile" style="position: absolute; top: 7px; right: 15px;"></button>
</div>
</div>
<div class="contentLayout">
<div class="qrColumn">
<div id="qrCode"></div>
<span id="accesCode"></span>
</div>
<div class="questionColumn">
<div id="questionCards"></div>
</div>
</div>
<button class="live" id="liveButton" onclick="AnsichtZeigen()">
Live Ansicht
</button>
</body>
<script>
const params = getURLArgsMap();
const courseId = params["course"];
if (!courseId) {
console.error("Keine courseId in der URL");
}
const qrCodeUrl = `${window.location.origin}/student/umfrage.html?course=${courseId}`;
new QRCode(document.getElementById("qrCode"), {
text: qrCodeUrl,
width: 500,
height: 500,
})
const accesCodeElem = document.getElementById("accesCode");
accesCodeElem.innerText = "Code: " + courseId;
async function AnsichtZeigen() {
const liveButton = document.getElementById("liveButton")
liveButton.style.display = "none";
const questionList = document.getElementById("questionCards");
const courseInfo = await couchDB.get("course:" + courseId);
let index = 0;
for(const question of courseInfo.questions) {
const overlayId = `qOver${index}`
const answerContainerId = `answerContainer${index}`;
const card = document.createElement("div");
card.className = "questionCard";
card.innerHTML = `
<div class="questionTitle">${question.title}</div>
<button class="questionResults" onclick='questionOverlay("${overlayId}", "${index}")'>
Auswertung
<overlay class="my_overlay" id="${overlayId}">
<div class="overlayTitle">
${question.title}:
</div>
<div id="${answerContainerId}"></div>
</overlay>
</button>
`;
questionList.appendChild(card);
index++;
}
}
async function questionOverlay(overlayId, index) {
const awnserList = document.getElementById(`answerContainer${index}`)
const courseInfo = await couchDB.get("course:" + courseId)
awnserList.innerHTML = "";
switch(courseInfo.questions[index].type) {
case "text":
let j = 0;
for(awnser of courseInfo.result) {
const resultDisplay = document.createElement("div");
resultDisplay.className = "overlayTextRes"
resultDisplay.innerHTML = `<div>Antwort ${j + 1}: ${awnser[index]} </div>`
awnserList.appendChild(resultDisplay)
j++;
}
break;
case "scala":
var score = 0;
for (let j = 0; j < courseInfo.result.length; j++) {
score += courseInfo.result[j][index];
}
var percent;
if (courseInfo.result.length > 0) {
score = score / courseInfo.result.length;
percent = (score/5)*100;
} else {
score = 0;
percent = 0;
}
console.log(courseInfo.result.length);
const resultDisplay = document.createElement("div");
resultDisplay.className = "overlayScalaRes"
resultDisplay.innerHTML = `
<div class="overlayTextRes"> ${percent}% positive </div>
<div class="scoreBar">
<div class="scoreFill" id="scoreFill${index}" style="width: ${(score/5)*100}%"></div>
</div>`;
awnserList.appendChild(resultDisplay);
const scoreFill = document.getElementById(`scoreFill${index}`)
if(percent < 30) {
scoreFill.style.backgroundColor = "red";
} else if(percent < 70) {
scoreFill.style.backgroundColor = "orange";
} else {
scoreFill.style.backgroundColor = "green";
}
break;
case "emoji":
let scoreE = 0;
for(let j = 0; j < courseInfo.result.length; j++) {scoreE += courseInfo.result[j][index]};
scoreE = scoreE / courseInfo.result.length;
const percentE = (scoreE/5)*100;
const resultDisplayE = document.createElement("div");
resultDisplayE.className = "overlayScalaRes"
resultDisplayE.innerHTML = `
<div class="overlayTextRes"> ${percentE}% positive </div>
<div class="scoreBar">
<div class="scoreFill" id="scoreFill${index}" style="width: ${(scoreE/5)*100}%"></div>
</div>`;
awnserList.appendChild(resultDisplayE);
const scoreFillE = document.getElementById(`scoreFill${index}`)
if(percentE < 30) {
scoreFillE.style.backgroundColor = "red";
} else if(percentE < 70) {
scoreFillE.style.backgroundColor = "orange";
} else {
scoreFillE.style.backgroundColor = "green";
}
break;
}
showOverlay(overlayId);
}
</script>
<style>
.scoreBar {
width: 90vh;
height: 30px;
background-color: antiquewhite;
border-radius: 8px;
overflow: hidden;
margin-top: 10px;
}
.scoreFill {
height: 100%;
width: 0%;
background-color: #008000;
transition: width 0.5s ease;
}
.my_overlay {
width: 100vh;
min-height: 30vh;
}
.overlayTitle {
font-weight: bold;
background: var(--accent_color_bg);
/*border-radius: calc(var(--border-radius) + 20px);*/
border-radius: var(--border-radius) var(--border-radius) 0 0;
}
.overlayTitle {
display: flex;
flex-direction: column;
padding: 10px;
max-width: 90vh;
}
.overlayTextRes {
padding: 10px;
font-display: flex;
flex-direction: column;
min-width: 0;
background: var(--accent_color_bg_o);
/*border-radius: calc(var(--border-radius) + 20px);*/
max-width: 90vh;
}
#answerContainer0 .overlayTextRes:last-child {
border-radius: 0 0 var(--border-radius) var(--border-radius);
}
#answerContainer1 .overlayScalaRes:last-child > .overlayTextRes {
border-radius: 0 0 var(--border-radius) var(--border-radius);
}
.contentLayout {
display: flex;
justify-content: center;
align-items: flex-start;
gap: 80px;
margin-top: 15vh;
padding: 0 5vw;
}
.questionCard {
position: relative;
width: 400px;
min-height: 100px;
padding: 16px;
background: var(--accent_color_bg_o);
border-radius: 12px;
box-shadow: var(--def-shadow);
display: flex;
flex-direction: column;
gap: 10px;
}
.questionTitle {
margin: 0;
padding: 10px;
font-size: 18px;
font-weight: bold;
}
.questionResults {
margin: 0;
font-size: 20px;
padding: 10px;
}
.questionColumn {
display: flex;
flex-direction: column;
gap: 20px;
max-width: 320;
}
button.back {
font-size: 20px;
padding: 15px;
padding-left: 40px;
padding-right: 40px;
background-color: var(--accent_color_bg);
border: none;
border-radius: var(--border-radius);
cursor: pointer;
}
button.back:focus-visible {
outline: 2px var(--accent_color_fg_o);
outline-offset: 2px;
}
button.back:hover {
background: var(--accent_color_bg_o);
border-radius: var(--border-radius);
}
.qrColumn {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 1vh;
}
#accesCode {
font-size: 30px;
font-weight: bold;
padding: 10px;
border-radius: var(--border-radius);
background: var(--accent_color_bg_o);
box-shadow: var(--def-shadow);
}
#qrCode img,
#qrCode canvas {
padding: 14px;
border: 4px solid var(--accent_color_fg_o);
border-radius: var(--border-radius);
background: var(--accent_color_bg_o);
box-sizing: border-box;
box-shadow: var(--def-shadow);
}
button.live {
position: absolute;
padding: 15px;
bottom: 8vh;
right: 8vh;
font-size: 40px;
border: none;
border-radius: var(--border-radius);
cursor: pointer;
background-color: var(--accent_color_bg);
box-shadow: var(--def-shadow);
}
button.live:hover {
background: var(--accent_color_bg_o);
}
</style>
</html>
+616
View File
@@ -0,0 +1,616 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=0.5">
<link rel="stylesheet" href="../lib/basic.css">
<script src="../auth.js"></script>
<script src="../lib/basic.js"></script>
<script src="../other/skills.js"></script>
<script src="../other/skilltree.js"></script>
<script>
const couchDB = new CouchDB();
setUserData(couchDB);
</script>
<title>Übersicht</title>
</head>
<body>
<div class="header_bar_1">
<div style="font-weight: bold;">
Meine Kurse
</div>
<div>
<button class="auswertung" onclick='location.href = "./auswertung.html?user=" + userData.name'>
Auswertungen
</button>
</div>
<div>
<button class="profile" onclick='showOverlay("miniProfile")'>
<overlay id="miniProfile">
hello
</overlay>
</button>
</div>
</div>
<div class="header_bar_1_placeholder"></div>
<content>
<div id="course-list"></div>
<button class="new" id="newButton" onclick="showCourseCreation()">+</button>
<overlay id="createCourse" class="createCourse">
<input placeholder="Kursname" style="width: calc(100% - 50px);"><br><br>
Beschreibung:
<textarea placeholder="Beschreibung"></textarea><br><br>
Feedback-Fragen:
<div class="evaluations">
<div></div>
<button class="add_button" onclick="createCourseNewEvaluation()">+</button>
</div><br>
Start-Termin: <input type="date" style="margin-bottom: 5px;"><br>
Abgabe-Termin: <input type="date"><br><br>
<button onclick="saveCreateCourse()">Kurs erstellen</button>
</overlay>
<overlay id="deletionConfirmation">
<p style="padding: 15px;"> Wollen sie wirklich diesen Kurs löschen?</p>
<div style="display: flex; gap: 15px; justify-content: center;">
<button class="confirmation" onclick="confirmNo()">Abbrechen</button>
<button class="confirmation" onclick="confirmYes()">OK</button>
</div>
</overlay>
</content>
<style>
button.confirmation {
font-size: 20px;
font-weight: bold;
padding: 10px;
border: none;
border-radius: var(--border-radius);
background-color: var(--accent_color_fg);
cursor: pointer;
}
button.confirmation:hover {
background-color: var(--accent_color_fg_o);
}
button.new {
display: block;
margin: 20px auto;
font-size: 30px;
font-weight: bold;
border: none;
width: 60px;
height: 60px;
border-radius: 50%;
background-color: var(--accent_color_bg);
cursor: pointer;
}
button.new:hover {
background-color: var(--accent_color_bg_o);
}
button.auswertung {
font-size: 20px;
padding: 15px;
padding-left: 40px;
padding-right: 40px;
background-color: var(--accent_color_bg);
border: none;
border-radius: var(--border-radius);
cursor: pointer;
}
button.auswertung:focus-visible {
outline: 2px var(--accent_color_fg_o);
outline-offset: 2px;
}
button.auswertung:hover {
background: var(--accent_color_bg_o);
border-radius: var(--border-radius);
}
.course-card {
position: relative;
width: 80%;
min-height: 300px;
padding: 16px;
background: var(--accent_color_bg_o);
border-radius: 12px;
box-shadow: var(--def-shadow);
display: flex;
flex-direction: column;
gap: 10px;
}
.course-title {
margin: 0;
padding: 10px;
font-size: 25px;
font-weight: bold;
}
.course-description {
margin: 0;
font-size: 20px;
padding: 10px;
}
.course-actions {
display: flex;
gap: 10px;
margin-top: auto;
}
.action {
padding: 8px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 20px;
}
.action.start {
position: absolute;
top: 16px;
right: 70px;
font-size: 20px;
border-radius: var(--border-radius);
background-color: var(--accent_color_bg);
}
.action.start:hover {
background: var(--accent_color_bg_o);
border-radius: var(--border-radius);
}
.action.delete {
position: absolute;
top: 16px;
right: 16px;
font-size: 20px;
padding-left: 12px;
padding-right: 12px;
max-height: 40px;
border-radius: var(--border-radius);
background-color: var(--accent_color_bg);
}
.action.delete:hover {
background: var(--accent_color_bg_o);
border-radius: var(--border-radius);
}
.action.edit {
position: absolute;
bottom: 16px;
right: 16px;
font-size: 25px;
padding-left: 12px;
padding-right: 12px;
border-radius: var(--border-radius);
background-color: var(--accent_color_bg);
}
.action.edit:hover {
background: var(--accent_color_bg_o);
border-radius: var(--border-radius);
}
#course-list {
display: flex;
flex-direction: column;
gap: 15px;
align-items: center;
}
.createCourse {
min-width: 70% !important;
}
.createCourse input {
width: 100%;
font-size: 18px;
padding: 4px;
background: none;
border: 1px solid black;
border-radius: var(--border-radius);
}
.createCourse textarea {
width: 100%;
font-size: 18px;
resize: none;
padding: 4px;
background: none;
border: 1px solid black;
height: 100px;
border-radius: var(--border-radius);
}
.createCourse .evaluations {
overflow-y: auto;
text-align: center;
max-height: 300px;
border: 2px solid grey;
padding: 10px;
border-radius: var(--border-radius);
}
.createCourse .evaluations .evaluation {
display: inline-block;
width: 100%;
border-radius: var(--border-radius);
border: 1px solid black;
padding: 10px;
text-align: center;
margin-bottom: 10px;
position: relative;
}
.createCourse .evaluation input {
display: inline-block;
font-size: 20px !important;
width: calc(100% - 80px);
}
.createCourse .evaluation > select {
width: 90%;
font-size: 18px;
margin-top: 15px;
}
.createCourse .evaluation > button {
width: 40px;
height: 40px;
font-size: 23px;
position: absolute;
top: 5px;
right: 5px;
overflow: hidden;
}
.createCourse > button {
width: 100%;
font-size: 18px;
border-radius: var(--border-radius);
padding: 4px;
font-weight: normal;
}
</style>
<script>
(async () => {
await setUserData(couchDB);
await setCourseView();
})();
async function setCourseView() {
const courseList = document.getElementById("course-list");
courseList.innerHTML = "";
for(const courseId of userData.courses) {
const course = await couchDB.get("course:" + courseId);
const card = document.createElement("div");
card.className = "course-card";
card.innerHTML = `
<div class="course-title">${course.title}</div>
<div class="course-description">
${course.description}
</div>
<button class="action start" onclick='location.href = "./kursAnzeige.html?user=${userData.name},course=${courseId}";'>
Umfrage Starten
</button>
<button class="action edit" onclick="showEditCourse('${courseId}')">
✎
</button>
<button class="action delete" onclick="deleteCourse('${courseId}')">
🗑
</button>
`;
courseList.appendChild(card);
}
}
function createCourseNewEvaluation() {
const elm = getOverlay("createCourse");
if (elm == null) return;
const evaluations = elm.querySelector(".evaluations > div");
evaluations.appendChild(createEvaluation({
"title": "", "type": "text"
}));
}
async function saveCreateCourse() {
const elm = getOverlay("createCourse");
const inputs = elm.getElementsByTagName("input");
const description = elm.getElementsByTagName("textarea")[0];
const title = inputs[0].value.toLowerCase().trim();
if (title.length == 0) {
alert("Kursname darf nicht leer sein");
return;
}
const courseId = title.replace(/\s+/g, "_");
const sDateInp = inputs[inputs.length-2];
const eDateInp = inputs[inputs.length-1];
const data = {
"title": title,
"description": description.value,
"owner": userData.name,
"questions": [],
"result": [],
"result_history": [],
"start_date": sDateInp.value || "",
"expire_date": eDateInp.value || ""
};
const evaluations = elm.querySelector(".evaluations > div");
const questions = [];
for (var i of evaluations.getElementsByClassName("evaluation")) {
const inpE = i.querySelector("input");
const selE = i.querySelector("select");
if (inpE.value.length == 0) continue;
questions.push({
"title": inpE.value,
"type": selE.value
});
}
data.questions = questions;
evaluations.remove();
await couchDB.set("course:" + courseId, data);
const users = await couchDB.get("users");
users[userData.name].courses.push(courseId);
await couchDB.set("users", users);
hideOverlay("createCourse");
location.reload();
}
async function showCourseCreation() {
showOverlay("createCourse");
}
let confirmReturn = null;
function showConfirm(onYes) {
confirmReturn = onYes;
showOverlay("deletionConfirmation")
}
function confirmYes() {
hideOverlay("deletionConfirmation")
if (confirmReturn) {
confirmReturn()
}
confirmReturn = null;
}
function confirmNo() {
hideOverlay("deletionConfirmation")
confirmReturn = null;
}
async function deleteCourse(courseId) {
showConfirm(
async() => {
await couchDB.delete("course:" + courseId);
const users = await couchDB.get("users");
users[userData.name].courses = users[userData.name].courses.filter(id => id !== courseId);
await couchDB.set("users", users);
location.reload();
}
)
}
</script>
<!--Implementiere hier dein Zeugs-->
<style>
.edit_course {
min-width: 70% !important;
}
.edit_course input {
width: 100%;
font-size: 18px;
padding: 4px;
background: none;
border: 1px solid black;
border-radius: var(--border-radius);
}
.edit_course textarea {
width: 100%;
font-size: 18px;
resize: none;
padding: 4px;
background: none;
border: 1px solid black;
height: 100px;
border-radius: var(--border-radius);
}
.edit_course .evaluations {
overflow-y: auto;
text-align: center;
max-height: 300px;
border: 2px solid grey;
padding: 10px;
border-radius: var(--border-radius);
}
.edit_course .evaluations .evaluation {
display: inline-block;
width: 100%;
border-radius: var(--border-radius);
border: 1px solid black;
padding: 10px;
text-align: center;
margin-bottom: 10px;
position: relative;
}
.edit_course .evaluation input {
display: inline-block;
font-size: 20px !important;
width: calc(100% - 80px);
}
.edit_course .evaluation > select {
width: 90%;
font-size: 18px;
margin-top: 15px;
}
.edit_course .evaluation > button {
width: 40px;
height: 40px;
font-size: 23px;
position: absolute;
top: 5px;
right: 5px;
overflow: hidden;
}
.edit_course > button {
width: 100%;
font-size: 18px;
border-radius: var(--border-radius);
padding: 4px;
font-weight: normal;
}
</style>
<overlay id="edit_course">
<input placeholder="Kursname" style="width: calc(100% - 50px);"><br><br>
Beschreibung:
<textarea placeholder="Beschreibung"></textarea><br><br>
Feedback-Fragen:
<div class="evaluations">
<div></div>
<button class="add_button" onclick="createNewEvaluation()">+</button>
</div><br>
Start-Termin: <input type="date" style="margin-bottom: 5px;"><br>
Abgabe-Termin: <input type="date"><br><br>
<button onclick="saveEditCourse()">Speichern</button>
</overlay>
<script>
async function showEditCourse(name) { //
const data = await couchDB.get("course:" + name);
/*if (data.owner != userData.name) {
console.error("User is not author");
return;
}*/
showOverlay("edit_course");
const elm = getOverlay("edit_course");
const inputs = elm.getElementsByTagName("input");
const description = elm.getElementsByTagName("textarea")[0];
inputs[0].value = data.title;
inputs[0].setAttribute("oldID", name);
description.value = data.description;
const sDateInp = inputs[inputs.length-2];
const eDateInp = inputs[inputs.length-1];
sDateInp.value = data["start_date"] ?? "";
eDateInp.value = data["expire_date"] ?? "";
const evaluations = elm.querySelector(".evaluations > div");
for (var i of data.questions) {
evaluations.appendChild(createEvaluation(i));
}
}
function createNewEvaluation() {
const elm = getOverlay("edit_course");
if (elm == null) return;
const evaluations = elm.querySelector(".evaluations > div");
evaluations.appendChild(
createEvaluation({
"title": "", "type": "text"
})
);
}
function createEvaluation(question) {
const div = document.createElement("div");
div.classList.add("evaluation");
div.innerHTML = `
<button onclick="this.parentElement.remove();">🗑</button>
<input value="${question.title}">
<select>
<option value="text" ${question.type == "text" ? "selected": ""}>Text</option>
<option value="scala" ${question.type == "scala" ? "selected": ""}>Scala</option>
<!--<option value="emoji" ${question.type == "emoji" ? "selected": ""}>Emoji</option>-->
</select>
`;
return div;
}
async function saveEditCourse() {
const elm = getOverlay("edit_course");
const inputs = elm.getElementsByTagName("input");
const description = elm.getElementsByTagName("textarea")[0];
const oldID = inputs[0].getAttribute("oldID");
const data = await couchDB.get("course:" + oldID);
data["title"] = inputs[0].value;
data["description"] = description.value;
const evaluations = elm.querySelector(".evaluations > div");
const questions = [];
for (var i of evaluations.getElementsByClassName("evaluation")) {
const inpE = i.querySelector("input");
const selE = i.querySelector("select");
if (inpE.value.length == 0) continue;
questions.push({
"title": inpE.value,
"type": selE.value
});
}
data["questions"] = questions;
evaluations.remove();
const sDateInp = inputs[inputs.length-2];
const eDateInp = inputs[inputs.length-1];
if (sDateInp.value.length > 0) {
let dateObject = new Date(sDateInp.value);
let dateString = dateObject.toISOString().split('T')[0];
data["start_date"] = dateString;
} if (eDateInp.value.length > 0) {
let dateObject = new Date(eDateInp.value);
let dateString = dateObject.toISOString().split('T')[0];
data["expire_date"] = dateString;
}
await couchDB.set("course:" + oldID, data);
hideOverlay("edit_course");
setCourseView();
}
</script>
</body>
</html>
+185
View File
@@ -0,0 +1,185 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=0.8" />
<title>Sign Up</title>
<link rel="stylesheet" href="./lib/basic.css" />
<script src="./auth.js"></script>
<script src="./lib/basic.js"></script>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
}
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #f6f8fa;
padding: 2rem 0;
}
.signup-card {
background-color: #fff;
padding: 2.5rem 2rem;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
width: 100%;
max-width: 400px;
}
.signup-card h1 {
text-align: center;
margin-bottom: 1.5rem;
font-size: 1.5rem;
}
label {
display: block;
margin-bottom: 0.3rem;
font-weight: 600;
font-size: 0.9rem;
}
input,
select {
width: 100%;
padding: 0.5rem 0.75rem;
margin-bottom: 1rem;
border: 1px solid #d1d5da;
border-radius: 6px;
font-size: 1rem;
}
button {
width: 100%;
padding: 0.6rem;
background-color: var(--accent_color_fg);
color: #fff;
border: none;
border-radius: 6px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
}
button:hover {
background-color: var(--accent_color_fg_o);
}
.login-text {
text-align: center;
margin-top: 1rem;
font-size: 0.9rem;
}
.login-text a {
color: #0366d6;
text-decoration: none;
}
.login-text a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<div class="signup-card">
<h1>Create Account</h1>
<label for="username">Username</label>
<input
type="text"
id="username"
name="username"
autocomplete="username"
autocapitalize="off"
autocorrect="off"
required
autofocus
/>
<label for="password">Password</label>
<input
type="password"
id="password"
name="password"
autocomplete="new-password"
required
/>
<label for="role">Role</label>
<select id="role" name="role" required>
<option value="student">Student</option>
<option value="professor">Professor</option>
</select>
<button type="button" onclick="signup()">Sign up</button>
<div class="login-text">Already have an account? <a href="login.html">Sign in</a></div>
</div>
<script>
const couchDB = new CouchDB();
async function getData() {
try {
const res = await couchDB.get("users");
return res || {};
} catch (e) {
console.error("Failed to get users:", e);
return {};
}
}
async function signup() {
const username = document.getElementById("username").value.trim().toLowerCase();
const password = document.getElementById("password").value;
const role = document.getElementById("role").value;
if (!username) {
return alert("Please enter a username!");
}
if (!password) {
return alert("Please enter a password!");
}
const data = await getData();
// Check if user already exists
if (data[username]) {
return alert("Username already exists!");
}
// Create new user object
data[username] = {
password: password,
level: 1,
xp: 0,
avatar: null,
abilities: [],
enabled_abilities: [],
role: role,
courses: [],
};
try {
// Save to database
await couchDB.set("users", data);
alert("Account created successfully!");
// Redirect to login page
location.href = "./login.html";
} catch (e) {
console.error("Failed to create account:", e);
alert("Failed to create account. Please try again.");
}
}
</script>
</body>
</html>
+298
View File
@@ -0,0 +1,298 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=0.5">
<link rel="stylesheet" href="../lib/basic.css">
<script src="../auth.js"></script>
<script src="../lib/basic.js"></script>
<script src="../other/skills.js"></script>
<script src="../other/skilltree.js"></script>
<script src="./uebersicht_overlay.js"></script>
<script src="https://cdn.jsdelivr.net/npm/qrcodejs/qrcode.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<title>Kurs Anzeige</title>
</head>
<body>
<div class="header_bar_1">
<div>
<button onclick="location.href = './uebersicht.html?user=' + userData.name;" class="back">Meine
Kurse</button>
</div>
<div>
</div>
<div>
<button class="profile" onclick='showOverlay("miniProfile")'>
<overlay id="miniProfile" />
</button>
</div>
</div>
<div class="header_bar_1_placeholder"></div>
<div class="course-card">
<span style="font-weight: bold;" id="course-name"></span>
<span id="course-owner"></span>
<br>
<span>Bewertungen der Sessions</span>
<div id="course-surveys" style="height: 200px; overflow-y: scroll; scrollbar-width: none; ">
</div>
</div>
<script>
const evaluationResults = {};
const couchDB = new CouchDB();
setUserData(couchDB).then(
() => {
const params = getURLArgsMap();
const courseId = params["course"];
if (courseId) {
loadCourse(courseId);
}
}
)
async function loadCourse(courseId) {
const course = await couchDB.get("course:" + courseId);
if (course == undefined) {
alert("Kurs existiert nicht!");
return;
}
const results = course.result_history;
document.getElementById("course-name").innerHTML = course.title;
document.getElementById("course-owner").innerHTML = "Ersteller: " + course.owner;
const courseSurveys = document.getElementById("course-surveys");
courseSurveys.innerHTML = "";
results.forEach((result, idx) => {
evaluationResults[idx] = result;
const date = new Date(result.date);
const dateStr = `${date.getDate()}.${date.getMonth() + 1}.${date.getFullYear()}`;
courseSurveys.innerHTML += `<div class="session" onclick="evaluationClick(${idx})">${dateStr}</div>`;
});
}
function evaluationClick(idx) {
const result = evaluationResults[idx];
if (!result) return;
showEvaluationResult(result);
}
</script>
<style>
button.back {
font-size: 20px;
padding: 15px;
padding-left: 40px;
padding-right: 40px;
background-color: var(--accent_color_bg);
border: none;
border-radius: var(--border-radius);
cursor: pointer;
}
button.back:focus-visible {
outline: 2px var(--accent_color_fg_o);
outline-offset: 2px;
}
button.back:hover {
background: var(--accent_color_bg_o);
border-radius: var(--border-radius);
}
</style>
<style>
.xp-bar {
width: 100%;
height: 20px;
background-color: #dbdfdb;
border-radius: 10px;
overflow: hidden;
margin: 10px 0;
}
.xp-bar::-moz-progress-bar {
background-color: #31b335;
}
.xp-bar::-webkit-progress-value {
background: #31b335;
}
.main-container {
display: flex;
flex-direction: column;
gap: 20px;
padding: 1.5rem;
align-items: center;
height: calc(100vh - 75px);
}
button.survey {
font-size: 20px;
padding: 15px;
padding-left: 40px;
padding-right: 40px;
background-color: var(--accent_color_bg);
border: none;
border-radius: var(--border-radius);
cursor: pointer;
max-width: 30rem;
width: 100%;
}
button.survey:focus-visible {
outline: 2px var(--accent_color_fg_o);
outline-offset: 2px;
}
button.survey:hover {
background: var(--accent_color_bg_o);
border-radius: var(--border-radius);
}
.courses-container {
width: 100%;
}
.level-container {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.level {
font-family: Minecraftia;
color: #7bd67e;
}
#courses-container {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
max-height: 60vh;
}
#courses-list {
width: 100%;
overflow-y: scroll;
scrollbar-width: none;
height: auto;
}
.course-card {
position: relative;
margin: 1rem;
min-height: 300px;
padding: 16px;
background: var(--accent_color_bg_o);
border-radius: 12px;
box-shadow: var(--def-shadow);
display: flex;
flex-direction: column;
gap: 10px;
}
.course-title {
margin: 0;
padding: 10px;
font-size: 25px;
font-weight: bold;
}
.course-description {
margin: 0;
font-size: 20px;
padding: 10px;
}
.course-actions {
display: flex;
gap: 10px;
margin-top: auto;
}
.action {
padding: 8px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 20px;
}
.action.start {
position: absolute;
top: 16px;
right: 70px;
font-size: 20px;
border-radius: var(--border-radius);
background-color: var(--accent_color_bg);
}
.action.start:hover {
background: var(--accent_color_bg_o);
border-radius: var(--border-radius);
}
.action.delete {
position: absolute;
top: 16px;
right: 16px;
font-size: 20px;
padding-left: 12px;
padding-right: 12px;
max-height: 40px;
border-radius: var(--border-radius);
background-color: var(--accent_color_bg);
}
.action.delete:hover {
background: var(--accent_color_bg_o);
border-radius: var(--border-radius);
}
button.new {
display: block;
margin: 20px auto;
font-size: 30px;
font-weight: bold;
border: none;
width: 60px;
height: 60px;
background-color: var(--accent_color_bg);
cursor: pointer;
}
button.new:hover {
background-color: var(--accent_color_bg_o);
}
.session {
font-size: 20px;
padding: 15px;
margin: 5px;
background-color: var(--accent_color_bg);
border: none;
border-radius: var(--border-radius);
cursor: pointer;
width: 100%;
max-width: 30rem;
}
.session:hover {
background: var(--accent_color_bg_o);
border-radius: var(--border-radius);
}
</style>
</body>
</html>
+325
View File
@@ -0,0 +1,325 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=0.7" />
<link rel="stylesheet" href="../lib/basic.css" />
<script src="../auth.js"></script>
<script src="../lib/basic.js"></script>
<script src="./uebersicht_overlay.js"></script>
<script src="../other/skills.js"></script>
<script src="../other/skilltree.js"></script>
<title>Übersicht</title>
</head>
<body>
<style>
.holder_div {
position: fixed;
bottom: 2%;
left: 2%;
width: 96%;
background-color: var(--accent_color_bg_o);
backdrop-filter: blur(10px);
border-radius: var(--border-radius);
padding: 10px;
box-shadow: var(--def-shadow);
border: 1px solid black;
}
.holder_div > div.action_div {
display: flex;
flex-direction: row;
height: min-content;
gap: 10px;
}
.holder_div > div.action_div button {
font-size: 20px;
padding: 15px;
padding-left: 40px;
padding-right: 40px;
background-color: var(--accent_color_bg);
border: none;
border-radius: var(--border-radius);
cursor: pointer;
max-width: 100%;
width: 100%;
overflow-x: hidden;
}
.holder_div > div.action_div button:focus-visible {
outline: 2px var(--accent_color_fg_o);
outline-offset: 2px;
}
.holder_div > div.action_div button:hover {
background: var(--accent_color_bg_o);
border-radius: var(--border-radius);
}
.level-container {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.level {
font-family: Minecraftia;
color: darkgreen;
margin-bottom: 5px;
}
#courses-list {
margin-top: 30px;
padding: 0 2%;
padding-bottom: 200px;
}
.course-card {
position: relative;
margin-top: 1px;
margin-bottom: 1rem;
min-height: 300px;
padding: 16px;
background: var(--accent_color_bg_o);
border-radius: 12px;
box-shadow: var(--def-shadow);
display: flex;
flex-direction: column;
gap: 10px;
}
.course-card:hover {
background: #b9d5e1;
}
.course-title {
margin: 0;
padding: 10px;
font-size: 25px;
font-weight: bold;
}
.course-description {
margin: 0;
font-size: 20px;
padding: 10px;
}
.course-actions {
display: flex;
gap: 10px;
margin-top: auto;
}
.action {
padding: 8px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 20px;
}
.action.start {
position: absolute;
top: 16px;
right: 70px;
font-size: 20px;
border-radius: var(--border-radius);
background-color: var(--accent_color_bg);
}
.action.start:hover {
background: var(--accent_color_bg_o);
border-radius: var(--border-radius);
}
.action.delete {
position: absolute;
top: 16px;
right: 16px;
font-size: 20px;
padding-left: 12px;
padding-right: 12px;
max-height: 40px;
border-radius: var(--border-radius);
background-color: var(--accent_color_bg);
}
.action.delete:hover {
background: var(--accent_color_bg_o);
border-radius: var(--border-radius);
}
</style>
<div class="header_bar_1">
<div style="font-weight: bold">Meine Kurse</div>
<div></div>
<div>
<button class="profile" onclick="showOverlay('miniProfile')">
<overlay id="miniProfile" />
</button>
</div>
</div>
<div class="header_bar_1_placeholder"></div>
<div id="courses-list"></div>
<div class="holder_div">
<div class="level_div">
<div id="level-display" class="level-container">
<div class="level">Level <span id="level-number">5</span></div>
<div class="mc-xp-bar"><div></div></div>
</div>
</div>
<hr style="background-color: black; color: black;">
<div class="action_div">
<button class="survey" onclick="participateInSurvey()">An Umfrage teilnehmen</button>
<button onclick="newCourse()">Kurs hinzufügen</button>
</div>
</div>
<!-- Overlays -->
<overlay id="survey">
<div style="text-align: center; padding: 15px; margin-right: 2rem">
<input id="survey-code" type="text" placeholder="Umfrage-Code" />
<br />
<button style="margin-top: 1rem" onclick="startSurvey()">Teilnehmen</button>
</div>
</overlay>
<overlay id="add-course">
<div style="text-align: center; padding: 15px; margin-right: 2rem">
<input id="survey-code" type="text" placeholder="Kursname" />
<br />
<button class="" style="margin-top: 1rem" onclick="addCourse()">Hinzufügen</button>
</div>
</overlay>
<script>
const couchDB = new CouchDB();
setUserData(couchDB).then((val) => {
if (!val) return;
setCourseView();
setLevelProgress(userData.xp);
setLevel(userData.level);
});
async function startSurvey() {
const code = getOverlay("survey").getElementsByTagName("INPUT")[0].value.trim();
console.log(code);
var course = await couchDB.get("course:" + code);
if (course != undefined) {
const users = await couchDB.get("users");
users[userData.name].courses ??= [];
if (!users[userData.name].courses.includes(code)) {
users[userData.name].courses.push(code);
}
await couchDB.set("users", users);
location.href = "./umfrage.html?user=" + userData.name + ",course=" + code;
} else {
alert("Umfrage nicht gefunden!");
}
}
async function setCourseView() {
const courseList = document.getElementById("courses-list");
courseList.innerHTML = "";
for (const courseId of userData.courses) {
const course = await couchDB.get("course:" + courseId);
const expire_date = new Date(course.expire_date);
const isExpired = expire_date < new Date();
const card = document.createElement("div");
card.className = "course-card";
card.addEventListener("click", (e) => {
if (e.target.closest("button")) return;
location.href =
"./kursAnzeige.html?user=" + userData.name + ",course=" + courseId;
});
const surveyUrl = (card.innerHTML = `
<div class="course-title">${course.title}</div>
<div class="course-description">
${course.description}
</div>
${isExpired ? '': `
<button class="action start" onclick='location.href = "./umfrage.html?user=${userData.name},course=${courseId.trim()}";'>
Umfrage Starten
</button>`
}
<button class="action delete" onclick="deleteCourse('${courseId}')">
🗑
</button>
`);
courseList.appendChild(card);
}
}
async function deleteCourse(courseId) {
const users = await couchDB.get("users");
users[userData.name].courses = users[userData.name].courses.filter(
(id) => id !== courseId,
);
await couchDB.set("users", users);
location.reload();
}
function participateInSurvey() {
showOverlay("survey");
}
function selectCourse(courseName) {
console.log("Course selected:", courseName);
showOverlay("course");
}
function newCourse() {
showOverlay("add-course");
}
async function addCourse() {
const courseName = getOverlay("add-course")
.getElementsByTagName("INPUT")[0]
.value.trim();
const course = await couchDB.get("course:" + courseName);
if (course == undefined) {
alert("Kurs existiert nicht!");
return;
}
const users = await couchDB.get("users");
users[userData.name].courses.push(courseName);
await couchDB.set("users", users);
setCourseView();
location.reload();
}
function setLevelProgress(xp) {
const max = userData.level + 1;
const percent = xp / max;
setXPBarPercent(percent*100);
}
function setLevel(level) {
document.getElementById("level-number").textContent = level;
}
function addCourseToList(courseName) {
const coursesList = document.getElementById("courses-list");
const courseDiv = document.createElement("div");
courseDiv.className = "course-item";
courseDiv.innerHTML = `
<span>${courseName}</span>
<button onclick="editCourse('${courseName}')">Edit</button>
`;
coursesList.appendChild(courseDiv);
}
</script>
</body>
<style>
@font-face {
font-family: Minecraftia;
src: url(data:font/truetype;base64,AAEAAAANAIAAAwBQRkZUTV/JAIgAAEcgAAAAHEdERUYBAwAkAABG+AAAAChPUy8yZsMzdwAAAVgAAABgY21hcG6etckAAAUIAAABomdhc3D//wADAABG8AAAAAhnbHlmwglSaQAACFgAADdYaGVhZPk9cqMAAADcAAAANmhoZWEIgwHUAAABFAAAACRobXR4OJ0AAAAAAbgAAANObG9jYaVll4IAAAasAAABqm1heHAA3wAqAAABOAAAACBuYW1lJ/FDLgAAP7AAAAUTcG9zdNmblGkAAETEAAACKwABAAAAAQAA+92lvl8PPPUACwQAAAAAAMtPFtMAAAAAy08W0/+A/wAEAAUAAAAACAACAAAAAAAAAAEAAAUA/wAAAASA/4D9gAQAAAEAAAAAAAAAAAAAAAAAAADTAAEAAADUACgACgAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAgKpAZAABQAEAgACAAAA/8ACAAIAAAACAAAzAMwAAAAABAAAAAAAAACgAAAHQAAACgAAAAAAAAAARlNUUgBAACD7AgOA/4AAAAUAAQAAAAH7AAAAAAKAA4AAAAAgAAEBAAAAAAAAAAKOAAACjgAAAQAAAAKAAAADAAAAAwAAAAMAAAADAAAAAYAAAAKAAAACgAAAAoAAAAMAAAABAAAAAwAAAAEAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAEAAAABAAAAAoAAAAMAAAACgAAAAwAAAAOAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAIAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAIAAAADAAAAAgAAAAMAAAADAAAAAYAAAAMAAAADAAAAAwAAAAMAAAADAAAAAoAAAAMAAAADAAAAAQAAAAMAAAACgAAAAYAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAACAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAoAAAAEAAAACgAAAA4AAAAEAAAADAAAAAwAAAAKAAAADAAAAAQAAAAKAAAADAAAAA4AAAAIAAAADAAAAAwAAAAMAAAADgAAAAwAAAAIAAAADgAAAAoAAAAKAAAABgAAABAAAAASAAAABgAAAAgAAAAGAAAACAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAABgAAAAgAAgAIAAAACAAAAAwD/gAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAACgAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAGAAAABgAAAAQAAAAIAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAA4AAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAIAAAADAAAAAwAAAAMAAAADAAAAAYAAAAGAAAABgAAAAYAAAAKAAAACgAAAAoAAAAMAAAACAAAAAwAAAAIAAAACAAAAAwAAAAOAAAADAAAAAAAAAAAAAAMAAAADAAAAHAABAAAAAACcAAMAAQAAABwABACAAAAAHAAQAAMADAB+AP8BeB6eIBQgHiAgICIgJiA6IKwhIvsC//8AAAAgAKEBeB6eIBQgGCAgICIgJiA5IKwhIvsB////4//B/0niJOCv4Kzgq+Cq4KfgleAk368F0QABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQYAAAEAAAAAAAAAAQIAAAACAAAAAAAAAAAAAAAAAAAAAQAAAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGEAhYaIipKXnaKho6Wkpqiqqausrq2vsLK0s7W3tru6vL3LcWNkaMx3oG9q0XVpAIeZAHIAAGZ2AAAAAABrewCnuYBibQAAAABsfM0AgYSWAAAAw8jJxMW4AMDBANDOz9LTAHjGygCDi4KMiY6PkI2UlQCTm5yaAAAAcAAAAHkAAAAAAAAAAAwADAAMAAwAHgA8AGwAmgDMAQwBHgFCAWYBigGiAa4BugHGAegCGAIuAmAClAK4At4DBgMkA1oDhgOaA64D3APwBBwESARuBIwEsgTWBPAFBgUaBTwFVAVoBYAFrgW8BeAGBAYkBkAGbAaOBroGzAbmBw4HMgdsB5AHuAfKB/IIBAgmCDIIRghmCIoIrgjQCPAJDAkwCVAJYgmCCawJvgniCfgKGAo+CmIKggqkCsAK1gr6CxQLTAtsC4wLsgvGC+wMCgwcDE4MdgysDOAM9A0oDToNZA14DbYNxg3SDfoOBg4kDkIOXg54DooOpg7IDtQO7g7+DxwPWA+MD8YP/BAkEE4QdhCkEMgQ8hEaETwRchGWEbgR4BIEEhwSNBJSEmoSjhK4EuYTEhNCE2oTmBPQFAAUJhRMFGwUkhTCFOIVCBUwFVYVfBWiFc4WABYsFmIWihawFtYXAhcWFygXQBdeF4YXohfKF/IYHhhGGHQYkBi4GNQY8BkMGSwZWBl2GaIZ0hn0GgAaEhokGjYaShpoGoYapBq8GtAa6BsKGywbUBtqG44brAAAAAEAAAAAA4ADgAADAAAxESERA4ADgPyAAAIAAAAAAIADgAADAAcAADE1MxUDETMRgICAgIABAAKA/YAAAAQAAAIAAgADgAADAAcACwAPAAARNTMVMzUzFSURMxEzETMRgICA/wCAgIACAICAgICAAQD/AAEA/wAAAAIAAAAAAoADgAADAB8AAAE1IxUDESM1MzUjNTMRMxEzETMRMxUjFTMVIxEjESMRAYCAgICAgICAgICAgICAgIABgICA/oABAICAgAEA/wABAP8AgICA/wABAP8AAAAAAAUAAAAAAoADgAAHAAsADwATABsAACE1ITUhFSMVEzUzFSU1IRUlNTMVPQEzNTMVIRUBAP8AAgCAgID+AAGA/gCAgIABAICAgIABAICAgICAgICAgICAgIAAAAAABwAAAAACgAOAAAMABwALAA8AEwAXABsAADE1MxUhETMRJREzGQE1MxU1ETMRJREzESU1MxWAAYCA/gCAgID+AIABgICAgAEA/wCAAQD/AAEAgICAAQD/AIABAP8AgICAAAAAAAgAAAAAAoADgAADAAcACwAPABsAHwAjACcAADM1IRUzNTMVJREzEQE1MxUBNSM1IzUzNTMRMxEBNTMVMzUzFSU1MxWAAQCAgP2AgAGAgP8AgICAgID+gICAgP8AgICAgICAAQD/AAEAgID/AICAgID/AP8AAgCAgICAgICAAAAAAgAAAgABAAOAAAMABwAAETUzFTURMxGAgAIAgICAAQD/AAAABQAAAAACAAOAAAMABwALAA8AEwAAITUhFSU1MxUlETMZATUzFT0BIRUBAAEA/oCA/wCAgAEAgICAgICAAYD+gAGAgICAgIAABQAAAAACAAOAAAMABwALAA8AEwAAMTUhFT0BMxU1ETMRATUzFSU1IRUBAICA/wCA/oABAICAgICAgAGA/oABgICAgICAAAAABQAAAQACAAKAAAMABwALAA8AEwAAETUzFSE1MxUlNSEVJTUzFSE1MxWAAQCA/oABAP6AgAEAgAEAgICAgICAgICAgICAAAAAAQAAAIACgAMAAAsAACURITUhETMRIRUhEQEA/wABAIABAP8AgAEAgAEA/wCA/wAAAQAA/4AAgAEAAAMAABURMxGAgAGA/oAAAQAAAYACgAIAAAMAABE1IRUCgAGAgIAAAQAAAAAAgAEAAAMAADERMxGAAQD/AAAABQAAAAACgAOAAAMABwALAA8AEwAAMTUzFTURMxkBNTMVNREzGQE1MxWAgICAgICAgAEA/wABAICAgAEA/wABAICAAAAFAAAAAAKAA4AAAwAHAA8AFwAbAAAzNSEVATUzFQERMxEzFSMVIREjNTM1MxEBNSEVgAGA/wCA/oCAgIABgICAgP4AAYCAgAGAgID/AAKA/oCAgAGAgID9gAKAgIAAAAABAAAAAAKAA4AACwAAMTUhESM1MzUzESEVAQCAgIABAIACAICA/QCAAAAAAAYAAAAAAoADgAAHAAsADwATABcAGwAAMREzFSE1MxEBNTMVPQEhFQE1MxUFETMRATUhFYABgID+AIABAP4AgAGAgP4AAYABAICA/wABAICAgICAAQCAgIABAP8AAQCAgAAAAAAHAAAAAAKAA4AAAwAHAAsADwATABcAGwAAMzUhFSU1MxUhETMRATUhFQE1MxUFETMRATUhFYABgP4AgAGAgP6AAQD+AIABgID+AAGAgICAgIABAP8AAQCAgAEAgICAAQD/AAEAgIAAAAMAAAAAAoADgAADAAcAEwAAEzUzFT0BMxUTESERMxUhESM1IRGAgICA/gCAAYCAAQACAICAgICA/YABAAEAgAGAgPyAAAAAAAQAAAAAAoADgAADAAcACwATAAAzNSEVJTUzFSERMxEBESEVIRUhFYABgP4AgAGAgP2AAoD+AAGAgICAgIABgP6AAYABgICAgAAAAAAFAAAAAAKAA4AAAwAHAA8AEwAXAAAzNSEVNREzESERMxUhFSEZATUzFT0BIRWAAYCA/YCAAYD+gIABAICAgAEA/wACAICA/wACAICAgICAAAMAAAAAAoADgAADAAcADwAAIREzGQE1MxU1ESEVIxEhEQEAgIDLine truncated
}
</style>
</html>
+227
View File
@@ -0,0 +1,227 @@
//=> 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 = `
<div class="overlay_title">Ergebnisse</div>
<div class="overlay_content" style="display: inline-block; min-width: 90vw; padding: 5px;"></div>
`;
_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 = `
<span style="text-align: left; display: inline-block; width: 100%;">
${pos+1}. ${question.title}
</span><br>`;
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 = "&nbsp;&lt;&nbsp;";
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 = `
<label style="border-radius: 50%; display: inline-block; width: 15px; height: 15px; background: ${isSel ? "none": "black"}; border: 2px solid black;"></label>
`;
if (!isSel) {
const pos2 = i;
div.addEventListener("click", () => _updateEvaResContent(elm, pos2, data));
_setEvaResHover(div);
}
span2.appendChild(div);
}
const nextPage = document.createElement("label");
nextPage.innerHTML = "&nbsp;&gt;&nbsp;";
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 = `<div style="width: 100%; height: 100%; overflow-y: auto;">
${answers}
</div>`;
span.style.justifyContent = "top";
span.style.setProperty("justify-content", "start");
}
}
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';
});
}
+321
View File
@@ -0,0 +1,321 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=0.8">
<link rel="stylesheet" href="../lib/basic.css">
<script src="../auth.js"></script>
<script src="../lib/basic.js"></script>
<script src="../other/skills.js"></script>
<script src="../other/skilltree.js"></script>
<title>Umfrage</title>
</head>
<body>
<style>
.main-container {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
height: 94%;
width: 94%;
max-width: 700px;
box-shadow: var(--def-shadow);
border-radius: var(--border-radius);
background-color: var(--accent_color_bg_o2);
text-align: center;
padding: 15px;
}
.bttn-close {
position: absolute;
top: 10px;
right: 10px;
width: 50px;
height: 50px;
font-size: 20px;
font-weight: bold;
border-radius: var(--border-radius);
border: 1px solid black;
background: var(--accent_color_bg);
}
.bttn-close:hover {
box-shadow: var(--reversed_def_shadow);
}
#content {
display: flex;
width: 100%;
flex-direction: column;
}
#content > div:first-child {
background-color: rgba(255, 255, 255, 0.7);
border-radius: var(--border-radius);
padding: 30px 10px;
/*min-height: 120px;*/
font-size: 22px;
font-weight: bold;
margin-bottom: 40px;
width: 100%;
}
#content > div:nth-child(2) {
display: inline-block;
min-height: 200px;
}
#content > div:nth-child(2) > textarea {
width: 100%;
resize: none;
border-radius: var(--border-radius);
border: none;
background-color: rgba(255, 255, 255, 0.7);
padding: 20px;
min-height: 200px;
font-size: 16px;
}
#content > div:nth-child(2) > input {
width: 50%;
margin-top: 50px;
}
#content > .weiter-btn {
position: absolute;
text-align: center;
bottom: 80px;
left: 50%;
font-size: 20px;
transform: translateX(-50%);
border-radius: var(--border-radius);
}
#content > div:last-child {
position: absolute;
width: 100%;
text-align: center;
bottom: 20px;
left: 0;
}
.weiter-btn {
display: inline-block;
min-width: 150px;
padding: 10px;
font-size: 18px;
text-align: center;
background-color: #007bff;
color: white;
border: none;
border-radius: var(--border-radius);
}
.weiter-btn:hover {
background-color: #0056b3;
}
.end-dialog {
position: absolute;
top: 50%;
left: 50%;
width: 100%;
transform: translate(-50%, -50%);
text-align: center;
}
</style>
<div class="main-container" id="container1">
<button class="bttn-close" onclick='close_page()'>X</button>
<div style="height: 60px;"></div>
<div id="content"></div>
</div>
<div class="main-container" id="container2" style="display: none;">
<div class="end-dialog">
<h2>Vielen Dank für deine Teilnahme!</h2><br>
<h3 id="level_up" style="display: none;">Level up!<br><br><br></h3>
<h3 id="xp_up" style="display: none;">+1 XP-Punkt<br><br><br></h3>
<button class="weiter-btn" onclick="onEndEvent(this)">
Zurück zur Hauptseite
</button>
</div>
</div>
<script>
const couchDB = new CouchDB();
var courseData = null;
async function setCourse() {
const args = getURLArgsMap();
const coursename = args.course;
if (!coursename) return false;
const kurs = await couchDB.get("course:" + coursename);
if (!kurs) return false;
courseData = kurs;
return true;
}
async function updateCourseAnswer(answer) {
const args = getURLArgsMap();
const coursename = args.course;
if (!coursename) return null;
const kurs = await couchDB.get("course:" + coursename);
if (!kurs) return null;
kurs.result.push(answer);
await couchDB.set("course:" + coursename, kurs);
return kurs;
}
function close_page() {
location.href = "./uebersicht.html?user=" + userData.name;
}
function createNavBar(pos) {
const div = document.createElement("div");
div.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 = "&nbsp;&lt;&nbsp;";
if (pos-1 >= 0) {
prevPage.addEventListener("click", () => updateContent(pos, pos-1));
} else {
prevPage.style.opacity = "0.4";
}
prevPage.style.marginTop = "3px";
div.appendChild(prevPage);
for (var i = 0; i < courseData.questions.length; i++) {
const isSel = i == pos;
const divI = document.createElement("div");
divI.style = `
display: inline-block;
padding-left: 10px;
padding-right: 10px;
text-align: center;
`;
divI.innerHTML = `
<label style="border-radius: 50%; display: inline-block; width: 15px; height: 15px; background: ${isSel ? "none": "black"}; border: 2px solid black;"></label>
`;
if (!isSel) {
const pos2 = i;
divI.addEventListener("click", () => updateContent(pos, pos2));
}
div.appendChild(divI);
}
const nextPage = document.createElement("label");
nextPage.innerHTML = "&nbsp;&gt;&nbsp;";
if (pos+1 < courseData.questions.length) {
nextPage.addEventListener("click", () => updateContent(pos, pos+1));
} else {
nextPage.style.opacity = "0.4";
}
nextPage.style.marginTop = "3px";
div.appendChild(nextPage);
return div;
}
function setUmfrage(position = 0) {
const elm = getId("content");
const data = courseData.questions[position];
var input;
if (data.type == "scala") {
input = `Schlecht&nbsp;<input class="input" data="scala" type="range" min="1" max="5" autofocus>&nbsp;Gut`;
} else {
input = `<textarea class="input" data="text" placeholder="Text hier eingeben..." autofocus></textarea>`;
}
elm.innerHTML = `
<div>${data.title}</div>
<div>${input}</div>
<button class="weiter-btn" onclick="updateContent(${position}, ${position+1})">${
position == courseData.questions.length-1 ? "Umfrage beenden": "Weiter"
}</button>
`;
elm.appendChild(createNavBar(position));
}
var finalResult = null;
function updateContent(old_page, page) {
if (page < 0) page = 0;
const inp = getId("content").querySelector(".input");
const type = inp.getAttribute("data");
if (type == "scala") {
try {
finalResult[old_page] = parseInt(inp.value);
} catch(err) {
console.error(err);
}
} else {
finalResult[old_page] = inp.value;
}
if (page >= courseData.questions.length) {
getId("container1").style.display = "none";
getId("container2").style.display = "unset";
onFinishEva();
return;
}
setUmfrage(page);
getId("content").querySelector(".input").focus();
}
async function onFinishEva() {
await updateCourseAnswer(finalResult);
if (await levelUpXP(couchDB)) {
const e = getId("level_up");
if (e) e.style.display = "unset";
await playLevelSound();
} else {
const e = getId("xp_up");
if (e) e.style.display = "unset";
await playXPSound();
}
}
async function onEndEvent() {
location.href = "./uebersicht.html?user=" + userData.name;
}
(async () => {
await setUserData(couchDB);
if (!(await setCourse())) {
console.error("No course defined");
return;
}
if (!courseData.expire_date) {
console.error("courseData.expire_date is null");
return;
}
const expire_date = new Date(courseData.expire_date);
if (courseData.questions.length == 0 || expire_date < new Date()) {
onEndEvent();
return;
}
finalResult = [];
for (var i in courseData.questions) finalResult.push(null);
setUmfrage();
})();
</script>
</body>
</html>