diff options
Diffstat (limited to 'cockpit/js')
| -rw-r--r-- | cockpit/js/cockpit.js | 10 | ||||
| -rw-r--r-- | cockpit/js/config.js | 42 | ||||
| -rw-r--r-- | cockpit/js/database.js | 48 | ||||
| -rw-r--r-- | cockpit/js/graduation-common.js | 133 | ||||
| -rw-r--r-- | cockpit/js/graduation-mock.js | 6 | ||||
| -rw-r--r-- | cockpit/js/graduation-prod.js | 2 | ||||
| -rw-r--r-- | cockpit/js/mock.js | 77 | ||||
| -rw-r--r-- | cockpit/js/selection.js | 167 | ||||
| -rw-r--r-- | cockpit/js/student-common.js | 63 | ||||
| -rw-r--r-- | cockpit/js/student-mock.js | 3 | ||||
| -rw-r--r-- | cockpit/js/student-prod.js | 2 |
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 |
