| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519 |
- import { defineStore } from "pinia";
- import {
- MinFullNameSearchLength,
- bbClassRe,
- contextClassRe,
- dayMap,
- englishClassRe,
- firstPhaseClassRe,
- toTime,
- } from "../helpers";
- import {
- TaughtClass,
- ClassSelectorColumn,
- Ordering,
- FilterRule,
- ModuleLanguages,
- ClassesVersion,
- SemesterConfiguration,
- } from "../types";
- import { useClassVersionStore } from "./ClassVersion";
- import { useModulesStore } from "./modules";
- import { usePlanningStore } from "./planning";
- import { useStateStore } from "./state";
- import { useStudenthubStore } from "./studenthub";
- // eslint-disable-next-line
- type SortingFunction = (o1: any, o2: any) => number;
- export const useClassesStore = defineStore("classes", {
- state: () => {
- const stateStore = useStateStore();
- const cvStore = useClassVersionStore();
- return {
- data: {} as Record<string, ClassesVersion>,
- filterRules: [
- {
- column: ClassSelectorColumn.Degree,
- filterData: { degree: `${stateStore.lastSelectedDegreeProgram}` },
- pk: Math.floor(Math.random() * 1e20),
- enabled: true,
- },
- {
- column: ClassSelectorColumn.Module,
- filterData: { term: "" },
- pk: Math.floor(Math.random() * 1e20),
- enabled: true,
- },
- ] as FilterRule[],
- sortBy: {} as Record<string, SortingFunction>,
- cvStore,
- };
- },
- getters: {
- allClasses(): TaughtClass[] {
- const cd = this.currentData;
- if (cd == null) return [];
- return [...cd.taughtClasses, ...cd.blockClasses];
- },
- classesFilteredByCompletion(): TaughtClass[] {
- const stateStore = useStateStore();
- if (!stateStore.hideCompletedClasses) return this.allClasses;
- const studenthubStore = useStudenthubStore();
- return this.allClasses.filter((c) => {
- const m = c.module;
- if (m?.module_id == null) return true;
- return !studenthubStore.hasCompletedModule(m.module_id);
- });
- },
- getAllSortedClasses(): TaughtClass[] {
- const keys = Object.keys(this.sortBy);
- const numberOfProperties = keys.length;
- let modules = [...this.classesFilteredByCompletion];
- if (numberOfProperties > 0) {
- modules = modules.sort((obj1, obj2) => {
- let i = 0;
- let result = 0;
- while (result === 0 && i < numberOfProperties) {
- result = this.sortBy[keys[i]](obj1, obj2);
- i++;
- }
- return result;
- });
- }
- return modules;
- },
- filteredModules(): TaughtClass[] {
- let classes = this.getAllSortedClasses;
- const transformedRules = {} as Record<ClassSelectorColumn, FilterRule[]>;
- this.filterRules.forEach((rule) => {
- if (!rule.enabled) return;
- if (rule.column in transformedRules)
- transformedRules[rule.column].push(rule);
- else transformedRules[rule.column] = [rule];
- });
- Object.keys(transformedRules).forEach((column) => {
- const foundClasses = [] as TaughtClass[];
- let didFilter = false;
- if (column == ClassSelectorColumn.Time) {
- transformedRules[column].forEach((rule) =>
- foundClasses.push(...filterTimeColumn(classes, rule)),
- );
- classes = [...new Set(foundClasses)];
- return;
- }
- if (column == ClassSelectorColumn.Degree) {
- let shouldFilter = true;
- transformedRules[column].forEach((rule) => {
- const degree = rule.filterData?.degree ?? "";
- if (degree.length == 0) {
- shouldFilter = false;
- return;
- }
- didFilter = true;
- foundClasses.push(
- ...classes.filter((c) => c.degree_prg == rule.filterData.degree),
- );
- });
- if (shouldFilter && didFilter) classes = [...new Set(foundClasses)];
- return;
- }
- switch (column) {
- case ClassSelectorColumn.Module:
- transformedRules[column].forEach((rule) => {
- const term = rule.filterData?.term?.toLowerCase() ?? "";
- const checkName = term.length >= MinFullNameSearchLength;
- if (term.length == 0) return;
- didFilter = true;
- foundClasses.push(
- ...classes.filter(
- (c) =>
- c.name.toLowerCase().includes(term) ||
- (checkName &&
- c.module?.name.toLocaleLowerCase().includes(term)),
- ),
- );
- });
- break;
- case ClassSelectorColumn.Room:
- transformedRules[column].forEach((rule) => {
- const term = rule.filterData?.term?.toLowerCase() ?? "";
- if (term.length == 0) return;
- didFilter = true;
- const clsss = classes.filter((c) => {
- if (c.rooms.length == 0) return false;
- return c.rooms.join(" ").toLowerCase().includes(term);
- });
- foundClasses.push(...clsss);
- });
- break;
- case ClassSelectorColumn.Class:
- transformedRules[column].forEach((rule) => {
- const term = rule.filterData?.term?.toLowerCase() ?? "";
- if (term.length == 0) return;
- didFilter = true;
- foundClasses.push(
- ...classes.filter((c) => c.class.toLowerCase().includes(term)),
- );
- });
- break;
- case ClassSelectorColumn.Lecturer:
- transformedRules[column].forEach((rule) => {
- const term = rule.filterData?.term?.toLowerCase() ?? "";
- if (term.length == 0) return;
- didFilter = true;
- foundClasses.push(
- ...classes.filter((c) =>
- c.teachers.join().toLowerCase().includes(term),
- ),
- );
- });
- break;
- case ClassSelectorColumn.TeachingType:
- transformedRules[column].forEach((rule) => {
- if (rule.filterData.teachingType?.length == 0) return;
- didFilter = true;
- foundClasses.push(
- ...classes.filter(
- (c) => c.teaching_type == rule.filterData.teachingType,
- ),
- );
- });
- break;
- default:
- console.warn(`Filter type ${column} is not supported!`);
- return;
- }
- if (didFilter) classes = [...new Set(foundClasses)];
- });
- return classes;
- },
- degreePrograms(): string[] {
- const cd = this.currentData;
- if (cd == null) return [];
- return cd.degreePrograms;
- },
- currentData(): ClassesVersion | null {
- const version = this.cvStore.semVer;
- if (version == null) return null;
- if (!(version in this.data)) {
- console.error(`The version ${version} has not yet been loaded!`);
- return null;
- }
- return this.data[version];
- },
- },
- actions: {
- fetchData(version: string | null = null): Promise<null> {
- if (version == null) {
- version = this.cvStore.semVer;
- if (version == null) {
- // console.error("The current semester/version was unexpectedly null!");
- return new Promise((res, rej) => rej);
- }
- }
- // Check if the data has already been loaded
- if (version in this.data)
- return this.data[version].ready ?? new Promise((res, rej) => rej);
- const semVer = version;
- this.data[semVer] = {
- version: semVer,
- ready: null,
- loaded: false,
- teachers: [],
- rooms: [],
- classes: [],
- days: [],
- taughtClasses: [],
- blockClasses: [],
- degreePrograms: [],
- config: { blockclass_file: "block.pdf" },
- };
- const semVerFolder = this.cvStore.semVerFolderFromSemVer(semVer);
- const semesterFolder = this.cvStore.semesterFolderFromSemVer(semVer);
- this.data[semVer].ready = new Promise((resolve, reject) => {
- const fe = fetch(`${semVerFolder}/classes.json`);
- const bc = fetch(`${semesterFolder}/blockclasses.json`);
- const sc = fetch(`${semesterFolder}/config.json`);
- const modulesStore = useModulesStore();
- const addModuleGetters = (cls: TaughtClass) => {
- Object.defineProperty(cls, "module", {
- get: function () {
- return (
- this._module ||
- (this._module = modulesStore.fromModuleShort(cls.name))
- );
- },
- });
- cls.isBB = cls.class.match(bbClassRe) !== null;
- cls.isFirstPhase = cls.class.match(firstPhaseClassRe) !== null;
- cls.isEnglish = cls.class.match(englishClassRe) !== null;
- cls.isContext = cls.class.match(contextClassRe) !== null;
- cls.languageString = cls.isEnglish
- ? ModuleLanguages.EN
- : ModuleLanguages.DE;
- cls.executionTime =
- cls.weekday !== null
- ? `${dayMap[cls.weekday]}, ${toTime(cls.from)}-${toTime(cls.to)}`
- : "Blockmodul";
- };
- /**
- * Process the block modules
- */
- const bmReady = new Promise((resolveBM) => {
- Promise.all([bc, modulesStore.ready])
- .then((response) => response[0].json())
- .then((data: TaughtClass[]) => {
- data.forEach(addModuleGetters);
- this.data[semVer].blockClasses = data;
- resolveBM(null);
- })
- .catch((error) => {
- console.error(error);
- this.data[semVer].blockClasses = [];
- resolveBM(null); // We will resolve and not reject, as it's okay to not have a blockmodules.json on the server
- });
- });
- /**
- * Process the semester config
- */
- const scReady = new Promise((resolveSC) => {
- Promise.all([sc, modulesStore.ready])
- .then((response) => response[0].json())
- .then((data: SemesterConfiguration) => {
- this.data[semVer].config = data;
- resolveSC(null);
- })
- .catch((error) => {
- console.error(error);
- this.data[semVer].config = { blockclass_file: "block.pdf" };
- resolveSC(null); // We will resolve and not reject, as it's okay to not have a blockmodules.json on the server
- });
- });
- /**
- * Process the standard classes
- */
- Promise.all([fe, bmReady, scReady, modulesStore.ready])
- .then((response) => (response[0] as Response).json())
- .then((data: TaughtClass[]) => {
- data.forEach(addModuleGetters);
- this.data[semVer].taughtClasses = data;
- this.updateUniqueDegreePrograms(semVer);
- this.data[semVer].loaded = true;
- const planningStore = usePlanningStore();
- planningStore.loadFromStorage();
- resolve(null);
- })
- .catch((error) => {
- console.error(error);
- this.data[semVer].taughtClasses = [];
- reject(null);
- });
- });
- return this.data[version].ready ?? new Promise((res, rej) => rej);
- },
- updateUniqueDegreePrograms(version: string) {
- this.data[version].degreePrograms = [];
- this.allClasses.forEach((m) => {
- if (
- m.degree_prg.length > 0 &&
- !this.data[version].degreePrograms.includes(m.degree_prg)
- )
- this.data[version].degreePrograms.push(m.degree_prg);
- });
- this.data[version].degreePrograms.sort((a, b) =>
- a.toLowerCase().localeCompare(b.toLowerCase()),
- );
- },
- getById(module_id: string): TaughtClass | null {
- const matched = this.allClasses.filter((c) => c.id == module_id);
- if (matched.length == 0) return null;
- return matched[0];
- },
- updateOrdering(col: ClassSelectorColumn, dir: Ordering) {
- const colName = col.valueOf();
- if (dir == Ordering.None && col in this.sortBy) {
- delete this.sortBy[col];
- return;
- }
- switch (col) {
- case ClassSelectorColumn.Time:
- this.sortBy[col] = (a: TaughtClass, b: TaughtClass) => {
- const ta = 86400 * (a.weekday ?? 0) + a.from;
- const tb = 86400 * (b.weekday ?? 0) + b.from;
- return (ta - tb) * dir;
- };
- break;
- case ClassSelectorColumn.Lecturer:
- this.sortBy[col] = (a: TaughtClass, b: TaughtClass) => {
- const ta = a.teachers.join("").toLowerCase();
- const tb = b.teachers.join("").toLowerCase();
- return (ta < tb ? -1 : ta > tb ? 1 : 0) * dir;
- };
- break;
- case ClassSelectorColumn.TeachingType:
- this.sortBy[col] = (a: TaughtClass, b: TaughtClass) => {
- const ta = a.teaching_type;
- const tb = b.teaching_type;
- return (ta < tb ? -1 : ta > tb ? 1 : 0) * dir;
- };
- break;
- case ClassSelectorColumn.Room:
- this.sortBy[col] = (a: TaughtClass, b: TaughtClass) => {
- const ta = a.rooms.join("").toLowerCase();
- const tb = b.rooms.join("").toLowerCase();
- return (ta < tb ? -1 : ta > tb ? 1 : 0) * dir;
- };
- break;
- case ClassSelectorColumn.MSP:
- this.sortBy[col] = (a: TaughtClass, b: TaughtClass) => {
- const aMSP = a.module?.hasMSP === true;
- const bMSP = b.module?.hasMSP === true;
- return (aMSP == bMSP ? 0 : aMSP ? -1 : 1) * dir;
- };
- break;
- case ClassSelectorColumn.Module:
- case ClassSelectorColumn.Class:
- this.sortBy[col] = (a, b) =>
- (a[colName] < b[colName] ? -1 : a[colName] > b[colName] ? 1 : 0) *
- dir;
- break;
- default:
- console.error("Unhandled sorting!");
- break;
- }
- },
- insertFilter(afterFilter: FilterRule | null) {
- const emptyFilter = {
- column: ClassSelectorColumn.Module,
- filterData: { term: "" },
- pk: Math.floor(Math.random() * 1e20),
- enabled: true,
- } as FilterRule;
- if (!afterFilter) {
- this.filterRules.push(emptyFilter);
- return;
- }
- const idx = this.filterRules.findIndex((r) => r.pk == afterFilter.pk);
- if (idx == -1) {
- this.filterRules.push(emptyFilter);
- return;
- }
- this.filterRules.splice(idx + 1, 0, emptyFilter);
- },
- removeFilter(filterRule: FilterRule) {
- const idx = this.filterRules.findIndex((r) => r.pk == filterRule.pk);
- if (idx == -1) {
- console.error("Failed to find the filter rule in the current set!");
- return;
- }
- this.filterRules.splice(idx, 1);
- },
- classesForModule(short: string): TaughtClass[] {
- const cd = this.currentData;
- if (cd == null) return [];
- return cd.taughtClasses.filter((c) => c.name == short);
- },
- },
- });
- function filterTimeColumn(
- modules: TaughtClass[],
- filterset: FilterRule,
- ): TaughtClass[] {
- const timeData = filterset.filterData as Record<string, number>;
- const start = timeData.startTime;
- const end = timeData.endTime;
- if (start == undefined && end == undefined) return modules;
- let startingDay = -1;
- if (start != undefined) {
- if (start < 0) {
- const pStart = -1 * start;
- modules = modules.filter((m) => m.from >= pStart);
- } else {
- startingDay = Math.floor(start / 86400);
- const startingTime = start - startingDay * 86400;
- modules = modules.filter(
- (m) =>
- ((m.weekday ?? 0) == startingDay && m.from >= startingTime) ||
- (m.weekday ?? 0) > startingDay,
- );
- }
- }
- if (end != undefined) {
- let aEnd = end;
- if (aEnd < 0) {
- if (startingDay >= 0) {
- const endingTime = aEnd * -1 - startingDay * 86400;
- modules = modules.filter(
- (m) =>
- (m.weekday ?? 0) == startingDay &&
- (endingTime == 0 || m.to <= endingTime),
- );
- } else {
- aEnd *= -1;
- modules = modules.filter((m) => m.to <= aEnd);
- }
- } else {
- const endingDay = Math.floor(aEnd / 86400);
- const endingTime = aEnd - endingDay * 86400;
- modules = modules.filter(
- (m) =>
- (m.weekday ?? 0) < endingDay ||
- ((m.weekday ?? 0) == endingDay &&
- (endingTime == 0 || m.to <= endingTime)),
- );
- }
- }
- return modules;
- }
|