Files
fsae41.de/public/index.js
BolkeDerBaer 038910e9f0 Add service worker for push notifications, create calendar layout, and implement WLAN QR code page
- Implemented a service worker (sw.js) to handle push notifications with dynamic options and notification click events.
- Created a calendar layout in test.html with a grid system for displaying events across days and times.
- Developed a visually engaging WLAN QR code page (wlan.html) with animated backgrounds, particle effects, and tips for connecting to the network.
2026-02-22 00:50:22 +01:00

339 lines
11 KiB
JavaScript

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()]}<br><span>${eventDate.getDate()}</span>`;
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));
})();