diff options
| author | Florian Fischer <florian.fischer@muhq.space> | 2026-08-03 14:31:25 +0200 |
|---|---|---|
| committer | Florian Fischer <florian.fischer@muhq.space> | 2026-08-03 14:31:25 +0200 |
| commit | 6839831af54cd9302119bcc3bffafe10475958d4 (patch) | |
| tree | 12a1c1fbd864e9579cb9c3a09d4bf4686a280b73 /js | |
| parent | fccb70cbd876785f6833dfe6efc10ec9e445556d (diff) | |
| download | bycs-sol-6839831af54cd9302119bcc3bffafe10475958d4.tar.gz bycs-sol-6839831af54cd9302119bcc3bffafe10475958d4.zip | |
refactor project structure
* merge sites into single project root
* fix name of mass-graduation site
* enable typescript support
Diffstat (limited to 'js')
| -rw-r--r-- | js/config.js | 0 | ||||
| -rw-r--r-- | js/database.js | 160 | ||||
| -rw-r--r-- | js/graduation-common.js | 396 | ||||
| -rw-r--r-- | js/graduation-mock.js | 30 | ||||
| -rw-r--r-- | js/graduation-prod.js | 15 | ||||
| -rw-r--r-- | js/group.js | 57 | ||||
| -rw-r--r-- | js/mock.js | 104 | ||||
| -rw-r--r-- | js/selection.js | 170 | ||||
| -rw-r--r-- | js/student-common.js | 115 | ||||
| -rw-r--r-- | js/student-mock.js | 9 | ||||
| -rw-r--r-- | js/student-prod.js | 2 | ||||
| -rw-r--r-- | js/utils-common.js | 20 | ||||
| -rw-r--r-- | js/utils-mock.js | 41 | ||||
| -rw-r--r-- | js/utils-prod.js | 36 |
14 files changed, 0 insertions, 1155 deletions
diff --git a/js/config.js b/js/config.js deleted file mode 100644 index e69de29..0000000 --- a/js/config.js +++ /dev/null diff --git a/js/database.js b/js/database.js deleted file mode 100644 index e929896..0000000 --- a/js/database.js +++ /dev/null @@ -1,160 +0,0 @@ -import { getCourseId } from "./utils-ENV.js"; - -const DB_TEMPLATE_LIST_HEADER = ` -<table class="table table-striped"> - <thead> - <tr> - <th>Json</th> - </tr> - </thead> - <tbody> -`; -const DB_TEMPLATE_LIST_ENTRY = ` -<tr class="db-row"> - <td class="db-cell" data-key="date">##timeadded##</td> - <td class="db-cell" data-key="coach">##user##</td> - <td class="db-cell" data-key="json">[[Json]]</td> -</tr> -`; -const DB_TEMPLATE_LIST_FOOTER = ` - </tbody> -</table> -`; - -let studentDbs = populateStudentDbs(); - -const ID_QUERY_PARAM_REGEX = /[?&]id=(\d+)/ - -async function populateStudentDbs() { - const courseUrl = `https://lernplattform.bycs.de/course/view.php?id=${getCourseId()}`; - const courseRes = await fetch(courseUrl); - const courseDoc = new DOMParser().parseFromString(await courseRes.text(), 'text/html'); - - let dbs = new Map(); - for (const link of courseDoc.querySelectorAll("div.activityname a.aalink")) { - if (link.textContent.trim() === "Submit Datenbank") { - const dbId = link.href.match(ID_QUERY_PARAM_REGEX)[1] - dbs.set("submit", new DB(dbId)); - continue; - } - - const match = link.textContent.match(/(\d+)(Shadow|Public|Submit)/); - if (!match) continue; - const studentId = match[1]; - const dbType = match[2]; - const dbId = link.href.match(ID_QUERY_PARAM_REGEX)[1] - - if (!dbs.has(studentId)) { - dbs.set(studentId, {}); - } - - dbs.get(studentId)[dbType.toLowerCase()] = new DB(dbId); - } - - return dbs; -} - -async function getSubmitDb() { - const dbs = await studentDbs; - return dbs.get("submit"); -} - -class DB { - constructor(id) { - this.id = id; - } - - async fetchEntries(params) { - return loadFromDatabaseActivity(this.id, params) - } - - async saveEntry(json) { - return addEntryToDatabaseActivity(this.id, json); - } - - static async getStudentDbs(student) { - const dbs = await studentDbs; - return dbs.get(student.id); - } -} - -async function loadFromDatabaseActivity(id, params) { - let dbUrl = `${window.location.origin}/mod/data/view.php?id=${id}&mode=list&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; - let value; - // Moodle includes the full date in the title attribute of the included span - if (key === "date") { - value = field.querySelector("span").title; - } else { - value = field.innerText.trim(); - } - entry[key] = value; - }); - data.push(entry); - }); - return data; -} - -async function loadFromStudentDatabase(student, type) { - const dbs = await DB.getStudentDbs(student); - const db = dbs[type]; - return await db.fetchEntries() -} - -async function addEntryToDatabaseActivity(id, json) { - 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 of formData.keys()) { - if (key.startsWith("field_")) { - formData.set(key, json); - // We found our json key - break; - } - } - formData.set("saveandview", "Speichern"); - - return await fetch("/mod/data/edit.php", { - method: "POST", - // Set the FormData instance as the request body - body: formData, - }); -} - -async function addEntryToStudentDatabase(student, type, json) { - let db; - // global submit database - if (type === "submit") { - db = await getSubmitDb(); - // individuell databases - } else { - const dbs = await DB.getStudentDbs(student); - db = dbs[type]; - } - await db.saveEntry(json); -} - -export { - DB, - getSubmitDb, - loadFromDatabaseActivity, loadFromStudentDatabase, - addEntryToDatabaseActivity, addEntryToStudentDatabase, -}; diff --git a/js/graduation-common.js b/js/graduation-common.js deleted file mode 100644 index 0c28465..0000000 --- a/js/graduation-common.js +++ /dev/null @@ -1,396 +0,0 @@ -import { getSubmitDb, loadFromStudentDatabase, addEntryToStudentDatabase } from "./database.js" - -import NeulingPng from "../assets/Neuling.png"; -import EntdeckerPng from "../assets/Entdecker.png"; -import NavigatorPng from "../assets/Navigator.png"; -import PioneerPng from "../assets/Pioneer.png"; - -const LEVEL_NAMES = ["Neuling", "Entdecker", "Navigator", "Pionieer"]; - -const LEVEL_IMAGES = [ - new URL(NeulingPng), - new URL(EntdeckerPng), - new URL(NavigatorPng), - new URL(PioneerPng), -]; - -class GraduationChange { - constructor(date, change, reason, coach, context) { - this.date = date; - this.change = change; - this.reason = reason; - this.coach = coach; - this.context = context; - } - - isPublic() { - return [ - "Grün", "Orange", "Rot", - "ObservationStart", "ObservationEnd", - "LevelChange" - ].includes(this.change); - } - - publicDBFields () { - return { - "change": this.change, - "date": this.date, - }; - } - - dbFields () { - return { - // We include the date in the json because Moodle reports the timeadded - // locale dependent. - "date": this.date, - "change": this.change, - "reason": this.reason, - "context": this.context, - }; - } - - static fromDBEntry(e) { - const {coach, json} = e; - const change = JSON.parse(json); - return new GraduationChange(new Date(change.date), change.change, change.reason, coach, change.context); - } - - static newObservationRequest() { - return new GraduationChange(new Date(), "ObservationRequest", "", "", ""); - } - - static newObservationStart() { - return new GraduationChange(new Date(), "ObservationStart", "", "", ""); - } - - static newObservationEnd(reason, success) { - return new GraduationChange(new Date(), "ObservationEnd", reason, "", JSON.stringify(success)); - } - - static newObservationChange(observation) { - return new GraduationChange(observation.date, "Observation", "", "", JSON.stringify(observation.status)); - } - - static newGreenPointChange(reason, coach) { - return new GraduationChange(new Date(), "Grün", reason, coach, ""); - } - - static newOrangePointChange(reason, coach) { - return new GraduationChange(new Date(), "Orange", reason, coach, ""); - } - - static newRedPointChange(reason, coach) { - return new GraduationChange(new Date(), "Rot", reason, coach, ""); - } -} - -class Graduation { - constructor(student, green, orange, red, level, stickers, observationRequested, observation, pastObservations, changes) { - this.student = student; - this.green = green; - this.orange = orange; - this.red = red; - this.level = level; - this.stickers = stickers; - this.observationRequested = observationRequested; - this.observation = observation; - this.pastObservations = pastObservations; - this.changes = changes; - - student.graduation = this; - } - - static from(student, changes) { - let green = 0; - let orange = 0; - let red = 0; - let level = 1; - let openObservationRequest = null; - let observationPeriod = null; - let pastObservations = []; - let stickers = []; - - // The parsing logic assumes that changes are ordered chronologically - changes.sort((a, b) => a.date - b.date); - - for (const change of changes) { - switch (change.change) { - case "Grün": - green++; - break; - - case "Orange": - orange++; - break; - - case "Rot": - red++; - break; - - case "ObservationRequest": - openObservationRequest = change.date; - break; - - case "ObservationStart": - console.assert(openObservationRequest, "No request for observation start"); - observationPeriod = new ObservationPeriod(change.date); - openObservationRequest = null; - break; - - case "ObservationEnd": - console.assert(observationPeriod, "No active observation period"); - observationPeriod.finishPeriod(change.context.success); - if (change.context.success) { - level++; - } - pastObservations.push(observationPeriod); - observationPeriod = null; - break; - - case "Observation": - console.assert(observationPeriod, "No active observation period"); - const observation = Observation.fromChange(change); - observationPeriod.addObservation(observation); - break; - - case "LevelChange": - level = change.context.level; - break; - - default: - alert(`Unexpected change: ${change}`); - } - } - return new Graduation(student, green, orange, red, level, - stickers, - openObservationRequest, observationPeriod, pastObservations, - 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); - } - - /** - * Request observation - */ - async requestObservation() { - const request = GraduationChange.newObservationRequest(); - this.observationRequested = request.date; - await addEntryToStudentDatabase(this.student, "submit", JSON.stringify(request.publicDBFields())); - } - - /** - * Start an observation - */ - async startObservation() { - if (!this.observationRequested) { - console.assert(this.observationRequested, "No observation requested"); - return; - } - this.observationRequested = null; - this.observation = new ObservationPeriod(new Date()); - return submitGraduationChange(this.student, GraduationChange.newObservationStart()); - } - - /** - * End an observation - */ - async endObservation(reason, success) { - if (!this.observation) { - console.assert(this.observation, "No active observation"); - return; - } - this.observation.end = new Date(); - this.pastObservations.push(this.observation); - this.observation = null; - return submitGraduationChange(this.student, GraduationChange.newObservationEnd(reason, success)); - } - - /** - * Return if the student is in observation. - */ - isObserved() { - return Boolean(this.observation); - } - - /** - * Return the URI of the icon representing the graduation level. - */ - getIcon() { - return LEVEL_IMAGES[this.level - 1]; - } - - /** - * Return the name of the current graduation level. - * @return {String} The name of the current level. - */ - getLevelName() { - return LEVEL_NAMES[this.level - 1]; - } -} - -class ObservationPeriod { - constructor(start) { - this.start = start; - this.observations = []; - } - - finishPeriod(success) { - this.end = new Date(); - this.success = success; - } - - ended() { - return Boolean(this.end); - } - - ready() { - if (this.observations.length < 10) - return false; - - // at least 10 observation on different days - let days = new Map(); - for (const observation of this.observations) { - const d = observation.date; - days.set(`${d.getFullYear()}${d.getMonth()}${d.getDate()}`, true); - } - return days.size >= 10; - } - - getObservationsSums() { - let sums = Array(OBSERVATION_TYPES.length).fill().map(() => ({"yes":0, "no":0})); - for (const observation of this.observations) { - for (let i = 0; i < sums.length; i++) { - if (observation.status[i]) { - if (observation.status[i] === "yes") - sums[i].yes++; - else - sums[i].no++; - } - } - } - return sums; - } - - addObservation(observation) { - this.observations.push(observation); - } -} - -const OBSERVATION_TYPES = [ - "Arbeitet konzentriert an Aufgaben", - "Arbeitet zunehmend selbstständig", - "Führt das Logbuch zuverlässig", - "Bringt Material mit", - "Hält Regeln ein", - "Geht verantwortungsvoll mit Materialien und LernRaum um", - "Nimmt Rückmeldungen an und setzt sie um", -]; - -class Observation { - constructor(date, status) { - console.assert(status.length == OBSERVATION_TYPES.length, "invalid observation status"); - this.date = date; - this.status = status; - } - - static fromChange(change) { - return new Observation(change.date, JSON.parse(change.context)) - } -} - -function changesFromDbEntries(entries) { - let changes = []; - for (const e of entries) { - const gradChange = GraduationChange.fromDBEntry(e); - changes.push(gradChange); - } - return changes; -} - -async function fetchGraduationChangesFromDb(student, type) { - const data = await loadFromStudentDatabase(student, type); - return changesFromDbEntries(data); -} - -const submitEntriesPromise = async function() { - const db = await getSubmitDb(); - const entries = await db.fetchEntries(); - return changesFromDbEntries(entries); -}(); - -async function addSubmittedChanges(student, changes) { - let submitEntries = await submitEntriesPromise; - for (const e of submitEntries.filter( (c) => c.coach === student.name )) { - changes.push(e); - } - return changes; -} - -async function fetchGraduationChanges(student) { - let changes = await fetchGraduationChangesFromDb(student, "shadow"); - return addSubmittedChanges(student, changes); -} - -async function fetchPublicGraduationChanges(student) { - let changes = await fetchGraduationChangesFromDb(student, "public"); - return addSubmittedChanges(student, changes); -} - -async function fetchGraduation(student) { - const changes = await fetchGraduationChanges(student); - return Graduation.from(student, changes); -} - -async function fetchPublicGraduation(student) { - const changes = await fetchPublicGraduationChanges(student); - return Graduation.from(student, changes); -} - -async function submitGraduationChange(student, change) { - student.graduation.addChange(change); - - // Submit changes concurrently - let promises = []; - - if (change.isPublic()) { - promises.push( - // TODO(muhq): strip non public fields from changes - addEntryToStudentDatabase(student, "public", JSON.stringify(change.publicDBFields())) - ); - } - - promises.push( - addEntryToStudentDatabase(student, "shadow", JSON.stringify(change.dbFields())) - ); - - await Promise.all(promises); -} - -async function submitObservation(student, observation) { - console.assert(student.graduation.observation, "no active observation"); - student.graduation.observation.addObservation(observation); - await submitGraduationChange(student, GraduationChange.newObservationChange(observation)); -} - -export { - LEVEL_NAMES, - LEVEL_IMAGES, - GraduationChange, - Graduation, - fetchGraduation, - fetchPublicGraduation, - fetchGraduationChanges, - fetchPublicGraduationChanges, - submitGraduationChange, - submitObservation, - - OBSERVATION_TYPES, - Observation, - ObservationPeriod, -}; diff --git a/js/graduation-mock.js b/js/graduation-mock.js deleted file mode 100644 index 1c84433..0000000 --- a/js/graduation-mock.js +++ /dev/null @@ -1,30 +0,0 @@ -import { - LEVEL_NAMES, - GraduationChange, - Graduation, - - OBSERVATION_TYPES, - Observation, - ObservationPeriod, -} from "./graduation-common.js"; - -import { - fetchGraduationMock as fetchGraduation, - fetchPublicGraduationMock as fetchPublicGraduation, - submitGraduationChangeMock as submitGraduationChange, - submitObservationMock as submitObservation -} from "./mock.js"; - -export { - LEVEL_NAMES, - GraduationChange, - Graduation, - fetchGraduation, - fetchPublicGraduation, - submitGraduationChange, - submitObservation, - - OBSERVATION_TYPES, - Observation, - ObservationPeriod, -}; diff --git a/js/graduation-prod.js b/js/graduation-prod.js deleted file mode 100644 index 0e86956..0000000 --- a/js/graduation-prod.js +++ /dev/null @@ -1,15 +0,0 @@ -export { - LEVEL_NAMES, - GraduationChange, - Graduation, - fetchGraduation, - fetchPublicGraduation, - fetchGraduationChanges, - fetchPublicGraduationChanges, - submitGraduationChange, - submitObservation, - - OBSERVATION_TYPES, - Observation, - ObservationPeriod, -} from "./graduation-common.js"; diff --git a/js/group.js b/js/group.js deleted file mode 100644 index ba5e1a0..0000000 --- a/js/group.js +++ /dev/null @@ -1,57 +0,0 @@ -import { getCourseParticipantsUrl } from "./utils-ENV.js"; -import { fetchStudents } from "./student-ENV.js"; - -/** - * Class to represent a Group of Students. - */ -class Group { - constructor(name, students) { - this.name = name; - if (!students) { - this.students = []; - } - } - - setStudents(students) { - this.students = students; - } - - addStudent(student) { - this.students.push(student); - } - - async getStudents() { - return this.students; - } -} - -let groups = new Map(); - -function buildGroups(students) { - for (const student of students) { - let group = groups.get(student.group); - - if (!group) { - group = new Group(student.group); - groups.set(student.group, group); - } - - group.addStudent(student); - } -} - -/** - * Fetch all Groups and return them as a map. - */ -async function fetchGroups() { - if (groups.size) { - return groups; - } - - // Fetch all students and build the groups from them - const students = await fetchStudents(); - buildGroups(students); - return groups; -} - -export { Group, buildGroups, fetchGroups }; diff --git a/js/mock.js b/js/mock.js deleted file mode 100644 index 91b1525..0000000 --- a/js/mock.js +++ /dev/null @@ -1,104 +0,0 @@ -import { Graduation, GraduationChange } from "./graduation-common.js"; -import { Student } from "./student-common.js"; -import { buildGroups } from "./group.js"; - -const students = [ - new Student(1, 'foo bar', 'test'), - new Student(2, 'maxi mustermensch', 'test'), - new Student(3, 'b b b', '5a') -]; - -// Build the groups from our mock students -buildGroups(students); - -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("Gute Arbeit", "Florian Fischer"), - GraduationChange.newOrangePointChange("", "Florian Fischer"), - ]; - break; - case "maxi mustermensch": - graduationChanges[s.name] = [ - GraduationChange.newGreenPointChange("", "Florian Fischer"), - GraduationChange.newGreenPointChange("", "Florian Fischer"), - new GraduationChange(new Date(), "ObservationStart", "", "Florian Fischer", ""), - ]; - break; - case "b b b": - graduationChanges[s.name] = [ - GraduationChange.newRedPointChange("", "Florian Fischer"), - ]; - } - // Build the student's graduation - Graduation.from(s, graduationChanges[s.name]); -} - -function fetchGraduationMock(s) { - return new Promise((resolve) => { - const changes = graduationChanges[s.name]; - if (!changes) { - resolve(null); - } else { - resolve(Graduation.from(s, changes)); - } - }); -} - -function fetchPublicGraduationMock(s) { - return new Promise((resolve) => { - const changes = graduationChanges[s.name]; - if (!changes) { - resolve(null); - } else { - resolve(Graduation.from(s, changes.filter((change) => change.isPublic()))); - } - }); -} - -function submitGraduationChangeMock(s, c) { - console.assert(s.graduation, "no graduation") - s.graduation.addChange(c); -} - -function submitObservationMock(s, o) { - console.assert(s.graduation, "no graduation") - console.assert(s.graduation.observation, "no observation period") - s.graduation.observation.addObservation(o); -} - -export { - fetchStudentsMock, - fetchSubjectsMock, - fetchProgressMock, - fetchGraduationMock, - fetchPublicGraduationMock, - submitGraduationChangeMock, - submitObservationMock, -}; diff --git a/js/selection.js b/js/selection.js deleted file mode 100644 index eda20ae..0000000 --- a/js/selection.js +++ /dev/null @@ -1,170 +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 loader = document.getElementById(`${idPrefix}-loader`); - - loader.style.display = "unset"; - const candidates = await fetchCandidates(); - loader.style.display = "none"; - - 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/js/student-common.js b/js/student-common.js deleted file mode 100644 index 2dfa35d..0000000 --- a/js/student-common.js +++ /dev/null @@ -1,115 +0,0 @@ -import { getCourseId, getUserIconUrl, getCourseParticipantsUrl } from "./utils-ENV.js"; -import { fetchGraduation } from "./graduation-common.js"; -/** - * Class to represent Students. - */ -class Student { - graduation = null; - - constructor(id, name, group) { - this.id = id; - this.name = name; - this.group = group; - } - - async getIconUrl() { - return await getUserIconUrl(this.id); - } - - getGroup() { - return this.group; - } - - async getGraduation() { - if(!this.graduation) { - // fetchGraduation(this) sets this.graduation - await fetchGraduation(this); - } - return this.graduation; - } - - static async fetchById(id) { - let name = ""; - let group = ""; - - const res = await fetch(`/user/view.php?id=${id}&course=${getCourseId()}`); - const doc = new DOMParser().parseFromString(await res.text(), "text/html"); - - name = doc.querySelector("div.page-header-headings > h2").textContent; - - const cards = doc.querySelectorAll("div.card-body dl"); - for (const card of cards) { - if (card.querySelector("dt").textContent !== "Gruppe") - continue; - group = card.querySelector("dd > a").textContent; - break; - } - - return new Student(id, name, group); - } -} - -// Cache the fetched students. -let students = []; - -const idRegex = /id=(\d+)/; -async function _fetchStudents() { - const res = await fetch(getCourseParticipantsUrl()); - const doc = new DOMParser().parseFromString(await res.text(), "text/html"); - const table = doc.querySelector("tbody"); - let fetchedStudents = []; - 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 group = groupCell.querySelector("a.quickeditlink").textContent; - - fetchedStudents.push(new Student(id, name, group)); - }); - return fetchedStudents; -} - -async function fetchStudents(idFilter, nameFilter, groupFilter) { - if (!students.size) { - const studentsPromise = _fetchStudents() - const storedStudents = localStorage.getItem("students"); - if (storedStudents) { - studentsPromise.then((fetched) => localStorage.setItem("students", JSON.stringify(fetched))); - for (const o of JSON.parse(storedStudents)) { - students.push(new Student(o.id, o.name, o.group)); - } - } else { - students = await studentsPromise; - localStorage.setItem("students", JSON.stringify(students)); - } - } - - return students.filter((s) => (!idFilter || s.id === idFilter) && - (!nameFilter || s.name.includes(nameFilter)) && - (!groupFilter || s.group.includes(groupFilter))); -} - -async function fetchStudent(idFilter, nameFilter, groupFilter) { - const students = await fetchStudents(idFilter, nameFilter, groupFilter); - return students[0]; -} - -export { Student, fetchStudents, fetchStudent }; diff --git a/js/student-mock.js b/js/student-mock.js deleted file mode 100644 index 7c79e12..0000000 --- a/js/student-mock.js +++ /dev/null @@ -1,9 +0,0 @@ -import { Student } from "./student-common.js"; -import { fetchStudentsMock as fetchStudents } from "./mock.js"; - -async function fetchStudent(idFilter, nameFilter, groupFilter) { - const students = await fetchStudents(idFilter, nameFilter, groupFilter); - return students[0]; -} - -export { Student, fetchStudents, fetchStudent }; diff --git a/js/student-prod.js b/js/student-prod.js deleted file mode 100644 index 9c8d7cb..0000000 --- a/js/student-prod.js +++ /dev/null @@ -1,2 +0,0 @@ -import { Student, fetchStudent, fetchStudents } from "./student-common.js"; -export { Student, fetchStudents, fetchStudent }; diff --git a/js/utils-common.js b/js/utils-common.js deleted file mode 100644 index 8e4cfac..0000000 --- a/js/utils-common.js +++ /dev/null @@ -1,20 +0,0 @@ -export const DATE_FMT = new Intl.DateTimeFormat("de-DE", { month: "numeric", day: "numeric", year: "numeric"}); - -export const DATETIME_FMT = new Intl.DateTimeFormat("de-DE", { month: "numeric", day: "numeric", year: "numeric", hour: "numeric", minute: "numeric" }); - -export function dateStringNow() { - return DATE_FMT.format(new Date()); -} - -export function datetimeStringNow() { - return DATETIME_FMT.format(new Date()); -} - -// TODO(muhq): Cache the retrieved URL -export 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 : ""; -} diff --git a/js/utils-mock.js b/js/utils-mock.js deleted file mode 100644 index da2f94b..0000000 --- a/js/utils-mock.js +++ /dev/null @@ -1,41 +0,0 @@ -import { fetchStudent } from "./student-ENV.js"; -export { DATE_FMT, DATETIME_FMT, dateStringNow, datetimeStringNow } from "./utils-common.js"; - -export function isTeacher() { - return !(new URLSearchParams(document.location.search)).has("student"); -} - -export function getFirstAndLastname() { - let firstname = ""; - let lastname = ""; - - const params = new URLSearchParams(document.location.search); - if (params.has("firstname")) - firstname = params.get("firstname") - if (params.has("lastname")) - lastname = params.get("lastname") - - return [firstname, lastname]; -} - -export function getName() { - let name = ""; - - const params = new URLSearchParams(document.location.search); - if (params.has("firstname")) - name = params.get("firstname") - if (params.has("lastname")) - name += params.get("lastname") - - return name; -} - -export function getUserId() { - const name = getName(); - - return fetchStudent(null, name).id; -} - -export function getCourseId() { - return "1337"; -} diff --git a/js/utils-prod.js b/js/utils-prod.js deleted file mode 100644 index ddde8a9..0000000 --- a/js/utils-prod.js +++ /dev/null @@ -1,36 +0,0 @@ -export { DATE_FMT, DATETIME_FMT, dateStringNow, datetimeStringNow, getUserIconUrl } from "./utils-common.js"; - -export function isTeacher() { - return Boolean(document.querySelector('.editmode-switch-form')) - || document.body.classList.contains('editing'); -} - -export function getFirstAndLastname() { - let firstname = ""; - let lastname = ""; - - const autoNameDiv = document.getElementById("auto-name"); - firstname = autoNameDiv.children[0].innerHTML; - lastname = autoNameDiv.children[1].innerHTML; - autoNameDiv.remove(); - - return [firstname, lastname]; -} - -export function getName() { - return document.querySelector("bycs-topbar").shadowRoot.querySelector("div.user-name").textContent; -} - -export function getUserId() { - // M is exported by Moodle - return String(M.cfg.userId); -} - -export function getCourseId() { - // M is exported by Moodle - return String(M.cfg.courseId); -} - -export function getCourseParticipantsUrl() { - return `https://lernplattform.bycs.de/user/index.php?id=${getCourseId()}&perpage=5000`; -} |
