aboutsummaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
authorFlorian Fischer <florian.fischer@muhq.space>2026-08-03 14:31:25 +0200
committerFlorian Fischer <florian.fischer@muhq.space>2026-08-03 14:31:25 +0200
commit6839831af54cd9302119bcc3bffafe10475958d4 (patch)
tree12a1c1fbd864e9579cb9c3a09d4bf4686a280b73 /js
parentfccb70cbd876785f6833dfe6efc10ec9e445556d (diff)
downloadbycs-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.js0
-rw-r--r--js/database.js160
-rw-r--r--js/graduation-common.js396
-rw-r--r--js/graduation-mock.js30
-rw-r--r--js/graduation-prod.js15
-rw-r--r--js/group.js57
-rw-r--r--js/mock.js104
-rw-r--r--js/selection.js170
-rw-r--r--js/student-common.js115
-rw-r--r--js/student-mock.js9
-rw-r--r--js/student-prod.js2
-rw-r--r--js/utils-common.js20
-rw-r--r--js/utils-mock.js41
-rw-r--r--js/utils-prod.js36
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`;
-}