aboutsummaryrefslogtreecommitdiff
path: root/cockpit/js
diff options
context:
space:
mode:
authorFlorian Fischer <florian.fischer@muhq.space>2026-06-13 13:04:07 +0200
committerFlorian Fischer <florian.fischer@muhq.space>2026-06-13 13:04:07 +0200
commit4882c5ca3a65075e03595588ef3780a36bc74b15 (patch)
tree07aa2a276cd87a8e192147440411c1f51d8aa624 /cockpit/js
parent9fbd21ff6bf97b9ce77cb78f420abd951b8aaf27 (diff)
downloadbycs-sol-4882c5ca3a65075e03595588ef3780a36bc74b15.tar.gz
bycs-sol-4882c5ca3a65075e03595588ef3780a36bc74b15.zip
move generic js into root
Diffstat (limited to 'cockpit/js')
-rw-r--r--cockpit/js/cockpit.js10
-rw-r--r--cockpit/js/config.js42
-rw-r--r--cockpit/js/database.js48
-rw-r--r--cockpit/js/graduation-common.js133
-rw-r--r--cockpit/js/graduation-mock.js6
-rw-r--r--cockpit/js/graduation-prod.js2
-rw-r--r--cockpit/js/mock.js77
-rw-r--r--cockpit/js/selection.js167
-rw-r--r--cockpit/js/student-common.js63
-rw-r--r--cockpit/js/student-mock.js3
-rw-r--r--cockpit/js/student-prod.js2
11 files changed, 5 insertions, 548 deletions
diff --git a/cockpit/js/cockpit.js b/cockpit/js/cockpit.js
index 2babcea..c45682f 100644
--- a/cockpit/js/cockpit.js
+++ b/cockpit/js/cockpit.js
@@ -1,17 +1,17 @@
import {
fetchSubjectsMock as fetchSubjects,
fetchProgressMock as fetchProgress,
-} from "./mock.js";
+} from "../../js/mock.js";
import {
GraduationChange,
fetchGraduation,
submitGraduationChange
-} from "./graduation-ENV.js";
+} from "../../js/graduation-ENV.js";
-import { Student, fetchStudent, fetchStudents } from "./student-ENV.js";
-import { isTeacher as getIsTeacher } from "../../utils-ENV.js";
-import { initSelection } from "./selection.js";
+import { Student, fetchStudent, fetchStudents } from "../../js/student-ENV.js";
+import { isTeacher as getIsTeacher } from "../../js/utils-ENV.js";
+import { initSelection } from "../../js/selection.js";
let isTeacher;
let firstname;
diff --git a/cockpit/js/config.js b/cockpit/js/config.js
deleted file mode 100644
index 2beb91b..0000000
--- a/cockpit/js/config.js
+++ /dev/null
@@ -1,42 +0,0 @@
-const GRADUATION_DB_ID = 262744;
-const GRADUATION_ACTIVITY_ID = 92663494;
-const GRADUATION_DB_PARAMS_MAP = {
- id: "f_1133275",
- name: "f_1130848",
- group: "f_1130869",
- change: "f_1133254",
- reason: "f_1130860",
- date: (date) => {
- return {
- f_1130863_d: date.getDate(),
- f_1130863_m: date.getMonth(),
- f_1130863_y: date.getFullYear(),
- f_1130863_z: 1,
- };
- },
- coach: "f_1130866",
-}
-
-const courseId = 2466361;
-function getCourseId() {
- return courseId;
-}
-
-function getCourseParticipantsUrl() {
- return `https://lernplattform.bycs.de/user/index.php?id=${courseId}`;
-}
-
-async function getUserIconUrl(userId) {
- console.assert(userId, "getUserIconUrl needs a userId");
- const userRes = await fetch(`https://lernplattform.bycs.de/user/profile.php?id=${userId}`)
- const userDoc = new DOMParser().parseFromString(await userRes.text(), 'text/html')
- const userpicture = userDoc.querySelector(".userpicture");
- return userpicture ? userpicture.src : "";
-}
-
-export {
- GRADUATION_DB_ID, GRADUATION_ACTIVITY_ID, GRADUATION_DB_PARAMS_MAP,
- getCourseId,
- getUserIconUrl,
- getCourseParticipantsUrl
-};
diff --git a/cockpit/js/database.js b/cockpit/js/database.js
deleted file mode 100644
index 3297a11..0000000
--- a/cockpit/js/database.js
+++ /dev/null
@@ -1,48 +0,0 @@
-async function loadFromDatabase(id, params) {
- let dbUrl = `/mod/data/view.php?d=${id}`;
- dbUrl += "&perpage=1000";
- // TODO(muhq): Why does this not include entries?
- if (params) {
- dbUrl += "&filter=1&advanced=1";
- for (const param in params) {
- dbUrl += `&${param}=${encodeURIComponent(params[param])}`
- }
- }
-
- const dbRes = await fetch(encodeURI(dbUrl));
- const dbDoc = new DOMParser().parseFromString(await dbRes.text(), 'text/html');
- let data = [];
- dbDoc.querySelectorAll('.db-row').forEach(row => {
- let entry = {};
- row.querySelectorAll('.db-cell').forEach(field => {
- const key = field.dataset.key;
- const value = field.innerText.trim();
- entry[key] = value;
- });
- data.push(entry);
- });
- return data;
-}
-
-async function addEntryToDatabaseActivity(id, params) {
- const url = `/mod/data/edit.php?id=${id}`;
- const editRes = await fetch(url);
- const editDoc = new DOMParser().parseFromString(await editRes.text(), 'text/html');
- const form = editDoc.querySelector('div[role="main"] > form');
-
- const formData = new FormData(form);
-
- for (const key in params) {
- const formKey = key.replace("f_", "field_");
- formData.set(formKey, params[key]);
- }
- formData.set("saveandview", "Speichern");
-
- return await fetch("/mod/data/edit.php", {
- method: "POST",
- // Set the FormData instance as the request body
- body: formData,
- });
-}
-
-export { loadFromDatabase, addEntryToDatabaseActivity };
diff --git a/cockpit/js/graduation-common.js b/cockpit/js/graduation-common.js
deleted file mode 100644
index 4716e6d..0000000
--- a/cockpit/js/graduation-common.js
+++ /dev/null
@@ -1,133 +0,0 @@
-import { GRADUATION_DB_ID, GRADUATION_ACTIVITY_ID, GRADUATION_DB_PARAMS_MAP } from "./config.js";
-import { loadFromDatabase, addEntryToDatabaseActivity } from "./database.js"
-
-class GraduationChange {
- constructor(date, id, name, group, change, reason, coach, json) {
- this.date = date;
- this.id = id;
- this.name = name;
- this.group = group;
- this.change = change;
- this.reason = reason;
- this.coach = coach;
- this.json = json;
- }
-
- static DB_FIELDS = ["id", "name", "group", "change", "reason", "json"];
-
- static newGreenPointChange(student, reason, coach) {
- return new GraduationChange(
- new Date(),
- student.id, student.name, student.group,
- "Grün", reason, coach, ""
- );
- }
-
- static newOrangePointChange(student, reason, coach) {
- return new GraduationChange(
- new Date(),
- student.id, student.name, student.group,
- "Orange", reason, coach, ""
- );
- }
-
- static newRedPointChange(student, reason, coach) {
- return new GraduationChange(
- new Date(),
- student.id, student.name, student.group,
- "Rot", reason, coach, ""
- );
- }
-}
-
-class Graduation {
- constructor(student, green, orange, red, level, stickers, changes) {
- this.student = student;
- this.green = green;
- this.orange = orange;
- this.red = red;
- this.level = level;
- this.stickers = stickers;
- this.changes = changes;
-
- student.graduation = this;
- }
-
- static from(student, changes) {
- let green = 0;
- let orange = 0;
- let red = 0;
- let level = "Beginner";
- let stickers = [];
- for (const change of changes) {
- switch (change.change) {
- case "Grün":
- green++;
- break;
- case "Orange":
- orange++;
- break;
- case "Rot":
- red++;
- break;
- case "Gespräch":
- orange = 0;
- break;
- case "Änderung":
- // TODO:
- break;
- default:
- alert(`Unexpected change: ${change}`);
- }
- }
- return new Graduation(student, green, orange, red, level, stickers, changes);
- }
-
- addChange(change) {
- // TODO(muhq): Implement incremental addition
- let changes = this.changes;
- changes.push(change);
- // This updates the student.graduation reference as well
- Graduation.from(this.student, changes);
- }
-}
-
-async function fetchGraduationChanges(student) {
- let params = {};
- // The id is enough
- params[GRADUATION_DB_PARAMS_MAP["id"]] = student.id;
-
- const data = await loadFromDatabase(GRADUATION_DB_ID, params);
-
- let changes = [];
- for (const e of data) {
- const gradChange = new GraduationChange(e.date, e.id, e.name, e.group, e.change, e.reason, e.coach, e.json);
- changes.push(gradChange);
- }
- return changes;
-}
-
-async function fetchGraduation(student) {
- const changes = await fetchGraduationChanges(student);
- return Graduation.from(student, changes);
-}
-
-async function submitGraduationChange(student, change) {
- let params = {};
- student.graduation.addChange(change);
- for (const key of GraduationChange.DB_FIELDS) {
- if (GRADUATION_DB_PARAMS_MAP[key]) {
- params[GRADUATION_DB_PARAMS_MAP[key]] = change[key];
- }
- }
-
- addEntryToDatabaseActivity(GRADUATION_ACTIVITY_ID, params);
-}
-
-export {
- GraduationChange,
- Graduation,
- fetchGraduation,
- fetchGraduationChanges,
- submitGraduationChange
-}; \ No newline at end of file
diff --git a/cockpit/js/graduation-mock.js b/cockpit/js/graduation-mock.js
deleted file mode 100644
index 9f67185..0000000
--- a/cockpit/js/graduation-mock.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import { Graduation, GraduationChange, } from "./graduation-common.js";
-import {
- fetchGraduationMock as fetchGraduation,
- submitGraduationChangeMock as submitGraduationChange
-} from "./mock.js";
-export { GraduationChange, Graduation, fetchGraduation, submitGraduationChange}; \ No newline at end of file
diff --git a/cockpit/js/graduation-prod.js b/cockpit/js/graduation-prod.js
deleted file mode 100644
index de7cbf4..0000000
--- a/cockpit/js/graduation-prod.js
+++ /dev/null
@@ -1,2 +0,0 @@
-import { GraduationChange, Graduation, fetchGraduation, submitGraduationChange } from "./graduation-common.js";
-export { GraduationChange, Graduation, fetchGraduation, submitGraduationChange }; \ No newline at end of file
diff --git a/cockpit/js/mock.js b/cockpit/js/mock.js
deleted file mode 100644
index a189a11..0000000
--- a/cockpit/js/mock.js
+++ /dev/null
@@ -1,77 +0,0 @@
-import { Graduation, GraduationChange } from "./graduation-common.js";
-import { Student } from "./student-common.js";
-
-const students = [
- new Student(1, 'foo bar', 'test'),
- new Student(2, 'maxi mustermensch', 'test'),
- new Student(3, 'b b b', '5a')
-];
-
-function fetchStudentsMock() {
- return new Promise((resolve) => { resolve(students); });
-}
-
-function fetchSubjectsMock() {
- return new Promise((resolve) => {
- resolve(['Mathe', 'Deutsch', 'Englisch']);
- });
-}
-
-function fetchProgressMock(subject) {
- return new Promise((resolve) => {
- if (subject === "Mathe") {
- resolve([40, 10, 3]);
- } else if (subject === "Deutsch") {
- resolve([10, 2, 0]);
- } else if (subject === "Englisch") {
- resolve([50, 25, 10]);
- } else {
- resolve([0, 0, 0]);
- }
- });
-}
-
-const graduationChanges = {};
-for (const s of students) {
- switch (s.name) {
- case "foo bar":
- graduationChanges[s.name] = [
- GraduationChange.newGreenPointChange(s, "", "Florian Fischer"),
- GraduationChange.newOrangePointChange(s, "", "Florian Fischer"),
- ];
- break;
- case "maxi mustermensch":
- graduationChanges[s.name] = [
- GraduationChange.newGreenPointChange(s, "", "Florian Fischer"),
- GraduationChange.newGreenPointChange(s, "", "Florian Fischer"),
- ];
- break;
- case "b b b":
- graduationChanges[s.name] = [
- GraduationChange.newRedPointChange(s, "", "Florian Fischer"),
- ];
- }
-}
-
-function fetchGraduationMock(s) {
- return new Promise((resolve) => {
- const changes = graduationChanges[s.name];
- if (!changes) {
- resolve(null);
- } else {
- resolve(Graduation.from(s, graduationChanges[s.name]));
- }
- });
-}
-
-function submitGraduationChangeMock(s, c) {
- s.graduation.addChange(c);
-}
-
-export {
- fetchStudentsMock,
- fetchSubjectsMock,
- fetchProgressMock,
- fetchGraduationMock,
- submitGraduationChangeMock,
-};
diff --git a/cockpit/js/selection.js b/cockpit/js/selection.js
deleted file mode 100644
index a1da244..0000000
--- a/cockpit/js/selection.js
+++ /dev/null
@@ -1,167 +0,0 @@
-/**
- * Initialises a selection based on existing HTML elements.
- * For HTML elements are required:
- * - A text input with id $idPrefix
- * - A list with id ${idPrefix}-list
- * - An element shown if no results are available with id ${idPrefix}-noresults
- * - A button to clear the input with id ${idPrefix}-clear
- *
- * @param {string} idPrefix - The id prefix of the four HTML elements.
- * @param {async function} fetchCandidates - The async function to retrieve the selectable candidates.
- * @param {function} fmtCandidate - The function to format the displayed HTML for each candidate.
- * @param {function} onSelection - The function to call when a candidate is selected.
- */
-async function initSelection(idPrefix, fetchCandidates, fmtCandidate, onSelection) {
- const sel = document.getElementById(idPrefix);
- const list = document.getElementById(`${idPrefix}-list`);
- const noResults = document.getElementById(`${idPrefix}-noresults`);
- const clearBtn = document.getElementById(`${idPrefix}-clear`);
-
- const candidates = await fetchCandidates();
-
- let items = [];
-
- let currentFocus = -1;
- let visibleItems = [];
- function setActive() {
- visibleItems.forEach(item => item.classList.remove("active"));
- if (currentFocus >= 0 && visibleItems[currentFocus]) {
- visibleItems[currentFocus].classList.add("active");
- }
- }
-
- for (const candidate of candidates) {
- let item = document.createElement("li");
- item.candidate = candidate;
- item.innerHTML = fmtCandidate(candidate);
- item.style.display = 'none';
-
- item.addEventListener('click', function() {
- sel.value = "";
- sel.dispatchEvent(new Event('keyup'));
- onSelection(candidate);
- });
-
- list.appendChild(item);
- items.push(item);
- }
-
- sel.addEventListener('keyup', function(e) {
- if ((["ArrowDown", "ArrowUp", "Enter"]).includes(e.key))
- return;
-
- const filter = sel.value.toLowerCase();
- visibleItems = [];
- for (const item of items) {
- const itemText = item.textContent.trim().toLowerCase();
-
- if (itemText.indexOf(filter) > -1 && filter !== "") {
- item.style.display = "";
- visibleItems.push(item);
-
- // Highlight matched text
- const regex = new RegExp(`(${filter})`, 'gi');
- item.innerHTML = item.textContent.replace(
- regex,
- "<u class='student-selection-list-highlight'>$1</u>"
- );
- } else {
- item.style.display = "none";
- }
- }
-
- // Show the clear button if there is an input
- clearBtn.style.display = sel.value ? "block" : "none";
-
- // Select the first or none if there are no items visible
- currentFocus = visibleItems.length ? 0 : -1;
- setActive(visibleItems);
- noResults.style.display = visibleItems.length || filter === "" ? "none" : "block";
- });
-
- clearBtn.addEventListener('click', function() {
- sel.value = "";
- sel.dispatchEvent(new Event('keyup')); // reset list
- sel.focus();
- });
-
- sel.addEventListener('keydown', function(e) {
- if (e.key === "ArrowDown" || e.key === "ArrowUp") {
- if(e.key === "ArrowDown")
- currentFocus = (currentFocus + 1) % visibleItems.length;
- else
- currentFocus = (currentFocus - 1 + visibleItems.length) % visibleItems.length;
- setActive();
- e.preventDefault();
- } else if (e.key === "Enter") {
- if (currentFocus > -1 && visibleItems[currentFocus]) {
-
- const visibleItem = visibleItems[currentFocus];
- sel.value = "";
- sel.dispatchEvent(new Event('keyup'));
- onSelection(visibleItem.candidate);
- }
- e.preventDefault();
- }
- });
-}
-
-function pointClickHandler(color) {
- const textareaDiv = document.getElementById("student-info-point-reason-container");
- const textarea = document.getElementById("student-info-point-reason");
- const submitBtn = document.getElementById("student-info-point-reason-submit");
-
- // TODO(muhq): Prefill the textarea with common reasons
- switch (color) {
- case "green":
- textarea.style.background = "rgb(from green r g b / 20%)"
- textarea.value = "";
- break;
- case "orange":
- textarea.style.background = "rgb(from orange r g b / 20%)"
- textarea.value = "";
- break;
- case "red":
- textarea.style.background = "rgb(from red r g b / 20%)"
- textarea.value = "";
- break;
- }
- textareaDiv.style.display = "block";
- textarea.focus();
-
- // Can not use string interpolation because of Moodle's firstname filter.
- const coach = firstname + " " + lastname;
-
- let submit = function() {
- let change;
- switch (color) {
- case "green":
- change = GraduationChange.newGreenPointChange(student, textarea.value, coach);
- break;
- case "orange":
- change = GraduationChange.newOrangePointChange(student, textarea.value, coach);
- break;
- case "red":
- change = GraduationChange.newRedPointChange(student, textarea.value, coach);
- break;
- default:
- alert("Invalid color");
- break;
- }
- submitGraduationChange(student, change);
- textareaDiv.style.display = "none";
- updateStudentInfo();
- }
- // Use onEVENT to override any previous handler
- textarea.onkeydown = function(e) {
- if (e.shiftKey && e.key === "Enter") {
- submit();
- }
- if (e.key === "Escape") {
- textareaDiv.style.display = "none";
- }
- };
- submitBtn.onclick = submit;
-}
-
-export { initSelection };
diff --git a/cockpit/js/student-common.js b/cockpit/js/student-common.js
deleted file mode 100644
index 1f4d083..0000000
--- a/cockpit/js/student-common.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import { getUserIconUrl, getCourseParticipantsUrl } from "./config.js";
-/**
- * Class to represent Students.
- */
-class Student {
- constructor(id, name, group) {
- this.id = id;
- this.name = name;
- this.group = group;
- }
-
- async getIconUrl() {
- return await getUserIconUrl(this.id);
- }
-}
-
-async function fetchStudent(idFilter, nameFilter, groupFilter) {
- const students = await fetchStudents(idFilter, nameFilter, groupFilter);
- return students[0];
-}
-
-const idRegex = /id=(\d+)/;
-async function fetchStudents(idFilter, nameFilter, groupFilter) {
- const res = await fetch(getCourseParticipantsUrl());
- const doc = new DOMParser().parseFromString(await res.text(), "text/html");
- const table = doc.querySelector("tbody");
- let students = [];
- table.querySelectorAll("tr").forEach(row => {
- if (row.classList.contains("emptyrow"))
- return;
-
- const roleCell = row.querySelector(".c4");
- // TODO(muhq): Is this locale dependent?
- if (roleCell.innerHTML.includes("Lehrer")) {
- return;
- }
-
- const nameCell = row.querySelector(".c1");
- let name;
- if (nameCell.firstChild.firstChild.tagName == "SPAN")
- name = nameCell.firstChild.firstChild.title;
- else
- name = nameCell.innerText;
-
- const profileUrl = nameCell.querySelector("a.aabtn").href;
- const match = idRegex.exec(profileUrl);
- const id = match[1];
-
- const groupCell = row.querySelector(".c5");
- const groupsText = groupCell.querySelector("a.quickeditlink").textContent;
- const group = groupsText.trim();
-
- if ((!idFilter || id === idFilter) &&
- (!nameFilter || name.includes(nameFilter)) &&
- (!groupFilter || groupFilter === group))
- {
- students.push(new Student(id, name, group));
- }
- });
- return students;
-}
-
-export { Student, fetchStudents, fetchStudent };
diff --git a/cockpit/js/student-mock.js b/cockpit/js/student-mock.js
deleted file mode 100644
index 0ea12ea..0000000
--- a/cockpit/js/student-mock.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import { Student, fetchStudent } from "./student-common.js";
-import { fetchStudentsMock as fetchStudents } from "./mock.js";
-export { Student, fetchStudents, fetchStudent }; \ No newline at end of file
diff --git a/cockpit/js/student-prod.js b/cockpit/js/student-prod.js
deleted file mode 100644
index f9c430c..0000000
--- a/cockpit/js/student-prod.js
+++ /dev/null
@@ -1,2 +0,0 @@
-import { Student, fetchStudent, fetchStudents } from "./student-common.js";
-export { Student, fetchStudents, fetchStudent }; \ No newline at end of file