g6
No files matched your search
@@ -0,0 +1,8 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
<script>
|
||||
location.href = "./login.html";
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -0,0 +1,3 @@
|
||||
async function createTemp() {
|
||||
console.warn("createTemp is deprecated");
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 9.5 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 167 B |
|
After Width: | Height: | Size: 1.2 KiB |
@@ -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);
|
||||
}
|
||||
@@ -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)`);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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 = " ";
|
||||
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;">
|
||||
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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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 = " < ";
|
||||
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 = " > ";
|
||||
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';
|
||||
});
|
||||
}
|
||||
@@ -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 = " < ";
|
||||
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 = " > ";
|
||||
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 <input class="input" data="scala" type="range" min="1" max="5" autofocus> 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>
|
||||