This commit is contained in:
Turtle committed 2025-12-31 12:13:23 +01:00
1 parent bdeaece35a
commit 16bc5ea38e
11 files changed
+445 -338

No files matched your search

+26
View File
@@ -0,0 +1,26 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="./auth.js"></script>
<script src="./lib/basic.js"></script>
<script src="./help/data_xample.js"></script>
</head>
<body>
<script>
(async () => {
try {
await createTemp();
location.href = "./login.html";
} catch(err) {
alert("Ein Fehler ist aufgetreten: " + err);
}
})();
</script>
</body>
</html>
+28 -19
View File
@@ -3,12 +3,11 @@ async function createTemp() {
await couchDB.set("users", { await couchDB.set("users", {
"turtle": { "turtle": {
"password": "1234", "password": "1234",
"level": 1, "level": 0,
"xp": 0,
"avatar": null, "avatar": null,
"abilities": [ "abilities": [],
], "enabled_abilities": [],
"enabled_abilities": [
],
"role": "student", "role": "student",
"courses": [ "courses": [
"weiche_ware", "weiche_ware",
@@ -17,8 +16,9 @@ async function createTemp() {
}, },
"dumble-door": { "dumble-door": {
"password": "bla", "password": "bla",
"level": 4, "level": 10,
"avatar": "https://static.turtle-coding-gbr.de/profile-pic.png", "xp": 0,
"avatar": "https://static.turtle-coding-gbr.de/gmci/profile-pic.png",
"abilities": [ "abilities": [
], ],
"enabled_abilities": [ "enabled_abilities": [
@@ -30,9 +30,18 @@ async function createTemp() {
}, },
"narrator": { "narrator": {
"password": "stanley", "password": "stanley",
"level": 4, "level": 10,
"avatar": "https://static.turtle-coding-gbr.de/st", "xp": 0,
"avatar": "https://static.turtle-coding-gbr.de/gmci/st",
"abilities": [ "abilities": [
"dark_mode",
"accent_color",
"pink_mode",
"profile_pic",
"simple_border",
"gold_border",
"epic_border",
"title_of_honor"
], ],
"enabled_abilities": [ "enabled_abilities": [
], ],
@@ -72,17 +81,17 @@ async function createTemp() {
{ {
"date": "2025-12-27", "date": "2025-12-27",
"questions": [ "questions": [
{ "title": "1", "type": "text" }, { "title": "Wie war die Vorlesung?", "type": "text" },
{ "title": "2", "type": "scala" } { "title": "Wie war der Professor?", "type": "scala" }
], ],
"result": [ "result": [
["Gut, aber ...", 5], ["Gut, weil ...", 5],
["Schlecht", 1] ["Schlecht, weil ...", 1]
] ]
} }
], ],
"start_date": "2025-12-23", "start_date": "2025-12-23",
"expire_date": "2025-12-27" "expire_date": "2026-02-20"
}); });
await couchDB.set("course:weiche_ware", { await couchDB.set("course:weiche_ware", {
"title": "Weiche Ware", "title": "Weiche Ware",
@@ -99,8 +108,8 @@ async function createTemp() {
} }
], ],
"result": [ "result": [
["Gut, aber ...", 5], ["Gut, weil ...", 5],
["Schlecht", 1] ["Schlecht, weil ...", 1]
], ],
"result_history": [ "result_history": [
{ {
@@ -110,12 +119,12 @@ async function createTemp() {
{ "title": "2", "type": "scala" } { "title": "2", "type": "scala" }
], ],
"result": [ "result": [
["Gut, aber ...", 5], ["Gut, weil ...", 5],
["Schlecht", 1] ["Schlecht, weil ...", 1]
] ]
} }
], ],
"start_date": "2025-12-23", "start_date": "2025-12-23",
"expire_date": "2025-12-27" "expire_date": "2025-02-20"
}); });
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

+64 -8
View File
@@ -374,14 +374,15 @@ var userData = null;
const userDataListener = []; const userDataListener = [];
async function setUserData(couchDB) { async function setUserData(couchDB) {
const res = await getUserData(couchDB); const user = await getUserData(couchDB);
if (res == null) { if (user == null) {
location.href = "../login.html"; location.href = "../login.html";
return false; return false;
} }
userData = res; userData = user;
updateAllProfiles(res); updateAllProfiles(user);
_constructProfile(res); _constructProfile(user);
await checkUserCourses(couchDB, user);
while (userDataListener.length > 0) { while (userDataListener.length > 0) {
const call = userDataListener[userDataListener.length - 1]; const call = userDataListener[userDataListener.length - 1];
@@ -407,18 +408,51 @@ async function updateUserData(couchDB, user = userData) {
function updateAllProfiles(user) { function updateAllProfiles(user) {
const hasProfilAbility = user.enabled_abilities.includes("profile_pic"); const features = user.enabled_abilities ??= [];
const hasProfilAbility = features.includes("profile_pic");
const profiles = document.getElementsByClassName("profile"); const profiles = document.getElementsByClassName("profile");
for (var i of profiles) { for (var i of profiles) {
if (hasProfilAbility) { if (hasProfilAbility && user.avatar) {
i.style.backgroundImage = `url("${user.avatar}")`; i.style.backgroundImage = `url("${user.avatar}")`;
} else { } else {
i.style.removeProperty("backgroundImage"); 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; 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) { function _onProfileClick(event) {
var buttonRect = this.getBoundingClientRect(); var buttonRect = this.getBoundingClientRect();
@@ -543,3 +577,25 @@ function getUserDisplayName(user) {
const title_of_honor = hasHonor ? "(Sir) " : ""; const title_of_honor = hasHonor ? "(Sir) " : "";
return `${title_of_honor}${ !user.title ? "" : user.title + " " }${ form1(user.name) }`; 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;
}
+135 -141
View File
@@ -1,160 +1,154 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html>
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Login</title> <title>Login</title>
<link rel="stylesheet" href="./lib/basic.css" /> <link rel="stylesheet" href="./lib/basic.css" />
<script src="./auth.js" defer></script> <script src="./auth.js"></script>
<script src="./lib/basic.js" defer></script> <script src="./lib/basic.js"></script>
<style> <style>
* { * {
box-sizing: border-box; box-sizing: border-box;
margin: 0; margin: 0;
padding: 0; padding: 0;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
} }
body { body {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
height: 100vh; height: 100vh;
background-color: #f6f8fa; background-color: #f6f8fa;
} }
.login-card { .login-card {
background-color: #fff; background-color: #fff;
padding: 2.5rem 2rem; padding: 2.5rem 2rem;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
width: 100%; width: 100%;
max-width: 400px; max-width: 400px;
} }
.login-card h1 { .login-card h1 {
text-align: center; text-align: center;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
font-size: 1.5rem; font-size: 1.5rem;
} }
label { label {
display: block; display: block;
margin-bottom: 0.3rem; margin-bottom: 0.3rem;
font-weight: 600; font-weight: 600;
font-size: 0.9rem; font-size: 0.9rem;
} }
input { input {
width: 100%; width: 100%;
padding: 0.5rem 0.75rem; padding: 0.5rem 0.75rem;
margin-bottom: 1rem; margin-bottom: 1rem;
border: 1px solid #d1d5da; border: 1px solid #d1d5da;
border-radius: 6px; border-radius: 6px;
font-size: 1rem; font-size: 1rem;
} }
button { button {
width: 100%; width: 100%;
padding: 0.6rem; padding: 0.6rem;
background-color: #2ea44f; background-color: var(--accent_color_fg);
color: #fff; color: #fff;
border: none; border: none;
border-radius: 6px; border-radius: 6px;
font-size: 1rem; font-size: 1rem;
font-weight: 600; font-weight: 600;
cursor: pointer; cursor: pointer;
} }
button:hover { button:hover {
background-color: #279f43; background-color: var(--accent_color_fg_o);
} }
.signup-text { .signup-text {
text-align: center; text-align: center;
margin-top: 1rem; margin-top: 1rem;
font-size: 0.9rem; font-size: 0.9rem;
} }
.signup-text a { .signup-text a {
color: #0366d6; color: #0366d6;
text-decoration: none; text-decoration: none;
} }
.signup-text a:hover { .signup-text a:hover {
text-decoration: underline; text-decoration: underline;
} }
</style> </style>
</head> </head>
<body> <body>
<div class="login-card"> <div class="login-card">
<h1>Sign in</h1> <h1>Sign in</h1>
<label for="username">Username</label> <label for="username">Username</label>
<input <input
type="text" type="text"
id="username" id="username"
name="username" name="username"
autocomplete="username" autocomplete="username"
autocapitalize="off" autocapitalize="off"
autocorrect="off" autocorrect="off"
required required
autofocus autofocus
/> />
<label for="password">Password</label> <label for="password">Password</label>
<input <input
type="password" type="password"
id="password" id="password"
name="password" name="password"
autocomplete="current-password" autocomplete="current-password"
required required
/> />
<button type="button" onclick="login()">Sign in</button> <button type="button" onclick="login()">Sign in</button>
<div class="signup-text"> <div class="signup-text">Don't have an account yet? <a href="signup.html">Sign up</a></div>
Don't have an account yet? <a href="signup.html">Sign up</a> </div>
</div>
</div>
<script> <script>
const couchDB = new CouchDB(); const couchDB = new CouchDB();
async function getData() { async function getData() {
try { try {
const res = await couchDB.get("users"); const res = await couchDB.get("users");
return res || {}; return res ?? {};
} catch (e) { } catch (e) {
console.error("Failed to get users:", e); console.error("Failed to get users:", e);
return {}; return {};
} }
} }
async function login() { async function login() {
const data = await getData(); const data = await getData();
const username = document const username = document.getElementById("username").value.trim().toLowerCase();
.getElementById("username") const password = document.getElementById("password").value;
.value.trim()
.toLowerCase();
const password = document.getElementById("password").value;
const user = data[username]; const user = data[username];
if (user == undefined) { if (user == undefined) {
return alert("User doesn't exist!"); return alert("User doesn't exist!");
} }
if (user.password != password) { if (user.password != password) {
return alert("Incorrect password!"); return alert("Incorrect password!");
} }
if (user.role === "student") { if (user.role === "student") {
location.href = location.href = "./student/uebersicht.html?user=" + username;
"./student/uebersicht.html?user=" + username; return;
return; } else {
} else { location.href = "./prof/uebersicht.html?user=" + username;
location.href = "./prof/uebersicht.html?user=" + username; return;
return; }
} }
} </script>
</script> </body>
</body>
</html> </html>
+7 -2
View File
@@ -108,9 +108,14 @@ const _skillCb = {
}, },
"unset_epic_border": () => unsetProfileBorder(), "unset_epic_border": () => unsetProfileBorder(),
"title_of_honor": function() {}, "title_of_honor": function() {},
"profile_pic": () => updateAllProfiles(userData) "profile_pic": () => updateEverything()
}; };
function updateEverything() {
updateAllProfiles(userData);
refreshOverlays();
}
function chooseAccentColor() { function chooseAccentColor() {
var elm = getId("skill_chooseAccentColor"); var elm = getId("skill_chooseAccentColor");
if (!elm) { if (!elm) {
@@ -142,7 +147,7 @@ function setAccentColor(elm) {
hideOverlay("skill_chooseAccentColor"); hideOverlay("skill_chooseAccentColor");
_skillCb.accent_color(); _skillCb.accent_color();
refreshOverlays(); updateEverything()
} }
+1 -1
View File
@@ -140,7 +140,7 @@ async function activateSkill(elm, data) {
try { eval(data.call_unset); } catch (err) { console.error(err); } try { eval(data.call_unset); } catch (err) { console.error(err); }
} }
} }
refreshOverlays(); updateEverything();
} }
async function _buySkill(data) { async function _buySkill(data) {
const elms = document.getElementsByClassName("skill_" + data.id); const elms = document.getElementsByClassName("skill_" + data.id);
+160 -164
View File
@@ -1,189 +1,185 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html>
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sign Up</title> <title>Sign Up</title>
<link rel="stylesheet" href="./lib/basic.css" /> <link rel="stylesheet" href="./lib/basic.css" />
<script src="./auth.js" defer></script> <script src="./auth.js"></script>
<script src="./lib/basic.js" defer></script> <script src="./lib/basic.js"></script>
<style> <style>
* { * {
box-sizing: border-box; box-sizing: border-box;
margin: 0; margin: 0;
padding: 0; padding: 0;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
} }
body { body {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
min-height: 100vh; min-height: 100vh;
background-color: #f6f8fa; background-color: #f6f8fa;
padding: 2rem 0; padding: 2rem 0;
} }
.signup-card { .signup-card {
background-color: #fff; background-color: #fff;
padding: 2.5rem 2rem; padding: 2.5rem 2rem;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
width: 100%; width: 100%;
max-width: 400px; max-width: 400px;
} }
.signup-card h1 { .signup-card h1 {
text-align: center; text-align: center;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
font-size: 1.5rem; font-size: 1.5rem;
} }
label { label {
display: block; display: block;
margin-bottom: 0.3rem; margin-bottom: 0.3rem;
font-weight: 600; font-weight: 600;
font-size: 0.9rem; font-size: 0.9rem;
} }
input, input,
select { select {
width: 100%; width: 100%;
padding: 0.5rem 0.75rem; padding: 0.5rem 0.75rem;
margin-bottom: 1rem; margin-bottom: 1rem;
border: 1px solid #d1d5da; border: 1px solid #d1d5da;
border-radius: 6px; border-radius: 6px;
font-size: 1rem; font-size: 1rem;
} }
button { button {
width: 100%; width: 100%;
padding: 0.6rem; padding: 0.6rem;
background-color: #2ea44f; background-color: var(--accent_color_fg);
color: #fff; color: #fff;
border: none; border: none;
border-radius: 6px; border-radius: 6px;
font-size: 1rem; font-size: 1rem;
font-weight: 600; font-weight: 600;
cursor: pointer; cursor: pointer;
} }
button:hover { button:hover {
background-color: #279f43; background-color: var(--accent_color_fg_o);
} }
.login-text { .login-text {
text-align: center; text-align: center;
margin-top: 1rem; margin-top: 1rem;
font-size: 0.9rem; font-size: 0.9rem;
} }
.login-text a { .login-text a {
color: #0366d6; color: #0366d6;
text-decoration: none; text-decoration: none;
} }
.login-text a:hover { .login-text a:hover {
text-decoration: underline; text-decoration: underline;
} }
</style> </style>
</head> </head>
<body> <body>
<div class="signup-card"> <div class="signup-card">
<h1>Create Account</h1> <h1>Create Account</h1>
<label for="username">Username</label> <label for="username">Username</label>
<input <input
type="text" type="text"
id="username" id="username"
name="username" name="username"
autocomplete="username" autocomplete="username"
autocapitalize="off" autocapitalize="off"
autocorrect="off" autocorrect="off"
required required
autofocus autofocus
/> />
<label for="password">Password</label> <label for="password">Password</label>
<input <input
type="password" type="password"
id="password" id="password"
name="password" name="password"
autocomplete="new-password" autocomplete="new-password"
required required
/> />
<label for="role">Role</label> <label for="role">Role</label>
<select id="role" name="role" required> <select id="role" name="role" required>
<option value="student">Student</option> <option value="student">Student</option>
<option value="professor">Professor</option> <option value="professor">Professor</option>
</select> </select>
<button type="button" onclick="signup()">Sign up</button> <button type="button" onclick="signup()">Sign up</button>
<div class="login-text"> <div class="login-text">Already have an account? <a href="login.html">Sign in</a></div>
Already have an account? <a href="login.html">Sign in</a> </div>
</div>
</div>
<script> <script>
const couchDB = new CouchDB(); const couchDB = new CouchDB();
async function getData() { async function getData() {
try { try {
const res = await couchDB.get("users"); const res = await couchDB.get("users");
return res || {}; return res || {};
} catch (e) { } catch (e) {
console.error("Failed to get users:", e); console.error("Failed to get users:", e);
return {}; return {};
} }
} }
async function signup() { async function signup() {
const username = document const username = document.getElementById("username").value.trim().toLowerCase();
.getElementById("username") const password = document.getElementById("password").value;
.value.trim() const role = document.getElementById("role").value;
.toLowerCase();
const password = document.getElementById("password").value;
const role = document.getElementById("role").value;
if (!username) { if (!username) {
return alert("Please enter a username!"); return alert("Please enter a username!");
} }
if (!password) { if (!password) {
return alert("Please enter a password!"); return alert("Please enter a password!");
} }
const data = await getData(); const data = await getData();
// Check if user already exists // Check if user already exists
if (data[username]) { if (data[username]) {
return alert("Username already exists!"); return alert("Username already exists!");
} }
// Create new user object // Create new user object
data[username] = { data[username] = {
password: password, password: password,
level: 1, level: 1,
avatar: null, xp: 0,
abilities: [], avatar: null,
enabled_abilities: [], abilities: [],
role: role, enabled_abilities: [],
courses: [], role: role,
}; courses: [],
};
try { try {
// Save to database // Save to database
await couchDB.set("users", data); await couchDB.set("users", data);
alert("Account created successfully!"); alert("Account created successfully!");
// Redirect to login page // Redirect to login page
location.href = "./login.html"; location.href = "./login.html";
} catch (e) { } catch (e) {
console.error("Failed to create account:", e); console.error("Failed to create account:", e);
alert("Failed to create account. Please try again."); alert("Failed to create account. Please try again.");
} }
} }
</script> </script>
</body> </body>
</html> </html>
+24 -3
View File
@@ -103,7 +103,7 @@
background-color: #007bff; background-color: #007bff;
color: white; color: white;
border: none; border: none;
border-radius: 5px; border-radius: var(--border-radius);
} }
.weiter-btn:hover { .weiter-btn:hover {
background-color: #0056b3; background-color: #0056b3;
@@ -130,6 +130,7 @@
<div class="main-container" id="container2" style="display: none;"> <div class="main-container" id="container2" style="display: none;">
<div class="end-dialog"> <div class="end-dialog">
<h2>Vielen Dank für deine Teilnahme!</h2><br> <h2>Vielen Dank für deine Teilnahme!</h2><br>
<h3 id="level_up" style="display: none;">Level up!<br><br><br></h3>
<button class="weiter-btn" onclick="onEndEvent(this)"> <button class="weiter-btn" onclick="onEndEvent(this)">
Zurück zur Hauptseite Zurück zur Hauptseite
</button> </button>
@@ -263,7 +264,7 @@ function updateContent(old_page, page) {
if (page >= courseData.questions.length) { if (page >= courseData.questions.length) {
getId("container1").style.display = "none"; getId("container1").style.display = "none";
getId("container2").style.display = "unset"; getId("container2").style.display = "unset";
updateCourseAnswer(finalResult); onFinishEva();
return; return;
} }
@@ -272,7 +273,15 @@ function updateContent(old_page, page) {
getId("content").querySelector(".input").focus(); getId("content").querySelector(".input").focus();
} }
async function onEndEvent(elm) { async function onFinishEva() {
await playLevelSound();
await updateCourseAnswer(finalResult);
if (await levelUpXP(couchDB)) {
const lvlElm = getId("level_up");
if (lvlElm) lvlElm.style.display = "unset";
}
}
async function onEndEvent() {
location.href = "./uebersicht.html?user=" + userData.name; location.href = "./uebersicht.html?user=" + userData.name;
} }
@@ -283,6 +292,18 @@ async function onEndEvent(elm) {
return; 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 = []; finalResult = [];
for (var i in courseData.questions) finalResult.push(null); for (var i in courseData.questions) finalResult.push(null);