const PB = new PocketBase(); // Modul-funktion function toggleForms() { document.getElementById('loginForm').classList.toggle('hidden'); document.getElementById('signupForm').classList.toggle('hidden'); } function openAuth() { document.getElementById('auth-module').style.display = 'flex'; } function closeAuth() { document.getElementById('auth-module').style.display = 'none'; } function shakeBox() { const box = document.getElementById('auth-box'); box.classList.add('shake'); setTimeout(() => box.classList.remove('shake'), 300); } // Klick außerhalb des auth-box schließt das Modul document.getElementById('auth-module').addEventListener('click', function (e) { if (e.target === this) { closeAuth(); } }); document.getElementById('loginForm').addEventListener('submit', async function (e) { e.preventDefault(); let email = e.target[0].value let passwort = e.target[1].value let authData = null; try { authData = await PB.collection('users').authWithPassword(email, passwort); } catch (error) { shakeBox(); console.error(error); return; } // after the above you can also access the auth data from the authStore console.log(PB.authStore.isValid); console.log(PB.authStore.token); console.log(PB.authStore.record.id); closeAuth(); }); document.getElementById('signupForm').addEventListener('submit', async function (e) { e.preventDefault(); let r = null; let authData = null; const data = { "name": e.target[0].value, "email": e.target[1].value, "password": e.target[2].value, "passwordConfirm": e.target[3].value }; try { r = await PB.collection('users').create(data); } catch (error) { shakeBox(); console.log(error); return } try { authData = await PB.collection('users').authWithPassword(data.email, data.passwort); } catch (error) { shakeBox(); console.error(error); return; } closeAuth(); }); // Clock funktion // TODO: getHtml from Server ?? function formatDate(date) { return `${padZero(date.getDate(), 2)}.${padZero(date.getMonth() + 1, 2)}.${date.getFullYear()} um ${padZero(date.getHours(), 2)}:${padZero(date.getMinutes(), 2)}:${padZero(date.getSeconds(), 2)}`; } function padZero(num, places) { return num.toString().padStart(places, '0'); } function pad(d) { return (d < 10) ? '0' + d.toString() : d.toString(); } async function get_moodle() { let r = await fetch("/moodle/getClasses") let d = await r.json() return d } function getNextOrCurrentLesson(data, now = new Date()) { let nextLesson = null; for (const dateKey in data) { for (const entry of data[dateKey]) { const year = Number(dateKey.slice(0, 4)); const month = Number(dateKey.slice(4, 6)) - 1; const day = Number(dateKey.slice(6, 8)); const startHour = Math.floor(entry.startTime / 100); const startMin = entry.startTime % 100; const endHour = Math.floor(entry.endTime / 100); const endMin = entry.endTime % 100; const start = new Date(year, month, day, startHour, startMin); const end = new Date(year, month, day, endHour, endMin); // 🟢 Stunde läuft gerade if (now >= start && now < end) { return { start, end, entry, status: "running" }; } // 🔵 Stunde kommt noch if (start > now) { if (!nextLesson || start < nextLesson.start) { nextLesson = { start, end, entry, status: "next" }; } } } } return nextLesson; } function render_countdow_v2(two = false) { const now = new Date(); const day = now.getDay(); const time = pad(now.getHours().toString()) + ':' + pad(now.getMinutes().toString()); let target; if (!nextClass) { requestAnimationFrame(render_countdow_v2); return; } if (nextClass.status === "running") { target = nextClass.end; } else if (nextClass.status === "next") { target = nextClass.start; } const distance = Math.abs(target - now); document.getElementById("target-info").innerHTML = `Bis zum ${formatDate(target)} Uhr sind es noch:`; // Display Label if (nextClass.status === "running") { document.getElementById("main-heading").textContent = `"${nextClass.entry.su[0].longname}" in Raum ${nextClass.entry.ro[0].name} findet grade statt`; } else if (nextClass.status === "next") { document.getElementById("main-heading").textContent = `Nächstes Stunde: ${nextClass.entry.su[0].longname} in Raum ${nextClass.entry.ro[0].name}`; } const days = distance / (1000 * 60 * 60 * 24); const hours = (distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60); const minutes = (distance % (1000 * 60 * 60)) / (1000 * 60); const seconds = (distance % (1000 * 60)) / 1000; const milliseconds = distance % 1000; // Calculate total values const totalDays = days; const totalWeeks = Math.floor(totalDays / 7); const totalHours = totalDays * 24; const totalMinust = totalHours * 60; const totalSeconds = totalMinust * 60; const totalYears = (totalDays / 365).toFixed(2); // Calculate total years to 2 decimal places // Display countdown/countup document.getElementById("countdown").innerHTML = `${Math.floor(days)}d ${Math.floor(hours)}h ${Math.floor(minutes)}m ${Math.floor(seconds)}s ${padZero(milliseconds, 3)}ms`; // Display totals with thousand separators document.getElementById("totals").innerHTML = `Tage: ${totalDays.toLocaleString(undefined, { maximumFractionDigits: 2, minimumFractionDigits: 2 })} | Stunden: ${totalHours.toLocaleString(undefined, { maximumFractionDigits: 2, minimumFractionDigits: 2 })} | Minuten: ${totalMinust.toLocaleString(undefined, { maximumFractionDigits: 2, minimumFractionDigits: 2 })} | Sekunden: ${totalSeconds.toLocaleString(undefined, { maximumFractionDigits: 2, minimumFractionDigits: 2 })}`; requestAnimationFrame(render_countdow_v2); } async function init_countdown() { document.getElementById("main-heading").textContent = "Lade Stundenplan..."; nextClass = getNextOrCurrentLesson(await get_moodle()); // Update next class every second setInterval(async () => { nextClass = getNextOrCurrentLesson(await get_moodle()); }, 1000); // Start the render loop render_countdow_v2(); } let nextClass = null; init_countdown(); // fotos hochladen async function addPic() { const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.multiple = false; fileInput.accept = 'image/*'; fileInput.click(); // listen to file input changes and add the selected files to the form data fileInput.addEventListener('change', async function () { const formData = new FormData(); // set regular text field formData.append('alt', "demo" || prompt("Bitte eine Bildbeschreibung eingeben:")); formData.append('gewicht', 1); formData.append('allowed', false); for (let file of fileInput.files) { formData.append('img', file); } const createdRecord = await PB.collection('images').create(formData); alert("Bild erfolgreich hochgeladen!"); }); } // Render event Data function render_event(data) { const eventContainer = document.getElementById('events'); const eventDiv = document.createElement('div'); eventDiv.classList.add('event'); const dateDiv = document.createElement('div'); dateDiv.classList.add('date'); const eventDate = new Date(data.start); const monthNames = ["JAN", "FEB", "MÄR", "APR", "MAI", "JUN", "JUL", "AUG", "SEP", "OKT", "NOV", "DEZ"]; dateDiv.innerHTML = `${monthNames[eventDate.getMonth()]}
${eventDate.getDate()}`; if (eventDate.getDate() == new Date().getDate() && eventDate.getMonth() == new Date().getMonth() && eventDate.getFullYear() == new Date().getFullYear()) { dateDiv.classList.add("today"); const duration = 15 * 1000, animationEnd = Date.now() + duration, defaults = { startVelocity: 30, spread: 360, ticks: 60, zIndex: 0 }; function randomInRange(min, max) { return Math.random() * (max - min) + min; } const interval = setInterval(function () { const timeLeft = animationEnd - Date.now(); if (timeLeft <= 0) { return clearInterval(interval); } const particleCount = 50 * (timeLeft / duration); // since particles fall down, start a bit higher than random confetti( Object.assign({}, defaults, { particleCount, origin: { x: randomInRange(0.1, 0.3), y: Math.random() - 0.2 }, }) ); confetti( Object.assign({}, defaults, { particleCount, origin: { x: randomInRange(0.7, 0.9), y: Math.random() - 0.2 }, }) ); }, 250); } const detailsDiv = document.createElement('div'); detailsDiv.classList.add("info") const typeDiv = document.createElement('div'); typeDiv.classList.add('type'); typeDiv.textContent = data.type; const titleStrong = document.createElement('strong'); titleStrong.textContent = data.title; const info = document.createElement('p'); info.textContent = data.info; const timeSmall = document.createElement('small'); time = eventDate.getHours().toString().padStart(2, '0') + ':' + eventDate.getMinutes().toString().padStart(2, '0'); timeSmall.textContent = `🕒 ${time} Uhr`; if (data.type != "Klausur") { titleStrong.classList.add('rainbow-text'); //typeDiv.classList.add('rainbow-text'); } detailsDiv.appendChild(typeDiv); detailsDiv.appendChild(titleStrong); detailsDiv.appendChild(document.createElement('br')); detailsDiv.appendChild(info); if (data.type == "Klausur") { detailsDiv.appendChild(timeSmall); } eventDiv.appendChild(dateDiv); eventDiv.appendChild(detailsDiv); eventContainer.appendChild(eventDiv); } async function add_event() { const userDate = prompt("Bitte gib ein Datum im Format TT.MM.JJJJ ein (z. B. 05.11.2025):"); const userTime = prompt("Bitte gib eine Uhrzeit im Format HH:MM ein (z. B. 14:30):"); const [day, month, year] = userDate.split('.').map(Number); const [hours, minutes] = userTime.split(':').map(Number); // Date-Objekt erstellen (Monat ist 0-basiert!) const userDateTime = new Date(year, month - 1, day, hours, minutes); // example create data const data = { "date": userDateTime.toISOString(), "title": prompt("Titel?"), "info": prompt("Info?"), "type": prompt("Typ?", "Klausur"), }; const record = await PB.collection('termine').create(data); } (async () => { const records = await PB.collection('termine').getList(1, 5, { sort: '+start', filter: `start >= "${new Date().getFullYear()}-${(new Date().getMonth() + 1).toString().padStart(2, '0')}-${(new Date().getDate()).toString().padStart(2, '0')} 00:00:00Z"` }); records.items.forEach(record => render_event(record)); })();