diff --git a/src/components/GraphModule.vue b/src/components/GraphModule.vue index d63143d3..9f7673ee 100644 --- a/src/components/GraphModule.vue +++ b/src/components/GraphModule.vue @@ -123,14 +123,18 @@ export default defineComponent({ }, computed: { recommendedModules(): Module[] { + const moduleMap = store.getters.moduleMap as Map; + const plannedSet = store.getters.allPlannedModuleIdsSet as Set; return this.module.recommendedModuleIds - .map(id => (store.getters.modules as Module[]).find(m => m.id === id)) - .filter((m): m is Module => !!m && !(store.getters.allPlannedModuleIds as string[]).includes(m.id)) + .map(id => moduleMap.get(id)) + .filter((m): m is Module => !!m && !plannedSet.has(m.id)) }, dependentModules(): Module[] { + const moduleMap = store.getters.moduleMap as Map; + const plannedSet = store.getters.allPlannedModuleIdsSet as Set; return this.module.dependentModuleIds - .map(id => (store.getters.modules as Module[]).find(m => m.id === id)) - .filter((m): m is Module => !!m && !(store.getters.allPlannedModuleIds as string[]).includes(m.id)) + .map(id => moduleMap.get(id)) + .filter((m): m is Module => !!m && !plannedSet.has(m.id)) }, hasRecommended(): boolean { return this.recommendedModules.length > 0 diff --git a/src/components/GraphModuleHightlight.vue b/src/components/GraphModuleHightlight.vue index 9a3d1f53..a37074a9 100644 --- a/src/components/GraphModuleHightlight.vue +++ b/src/components/GraphModuleHightlight.vue @@ -48,7 +48,7 @@ export default { }, computed: { isPlanned(): boolean { - return (store.getters.allPlannedModuleIds as string[]).includes(this.id); + return (store.getters.allPlannedModuleIdsSet as Set).has(this.id); }, }, methods: { diff --git a/src/components/ModuleSearch.vue b/src/components/ModuleSearch.vue index 297d26ff..7e7f77fe 100644 --- a/src/components/ModuleSearch.vue +++ b/src/components/ModuleSearch.vue @@ -63,7 +63,7 @@

Kategorie

g.modules).map(m => m.id); - const modulesNotInGroups = store.getters.modules.filter(m => !modulesInGroups.includes(m.id)); + const modulesInGroupIds = new Set(groups.flatMap(g => g.modules).map(m => m.id)); + const modulesNotInGroups = store.getters.modules.filter(m => !modulesInGroupIds.has(m.id)); let filteredGroups: GroupedModule[] = groups.concat({ id: 'none', name: 'Ohne', @@ -211,48 +216,42 @@ export default defineComponent({ filteredGroups = filteredGroups.filter(v => this.filter.categories.includes(v.id)) } - if (this.filter.ects.length > 0) { - filteredGroups = filteredGroups.map(g => { - return { - ...g, - modules: g.modules.filter(m => this.filter.ects.includes(m.ects)) - } - }); - } - - if (this.filter.semester.length > 0) { - filteredGroups = filteredGroups.map(g => { - return { - ...g, - modules: g.modules.filter(m => this.filter.semester.includes(m.term as string)) - } - }); - } + const ectsFilter = this.filter.ects; + const semesterFilter = this.filter.semester; + const queryFilter = this.filter.query.toLowerCase(); + const needsModuleFilter = ectsFilter.length > 0 || semesterFilter.length > 0 || queryFilter.length > 0; - if (this.filter.query.length > 0) { - filteredGroups = filteredGroups.map(g => { - return { - ...g, - modules: g.modules.filter(m => m.name.toLowerCase().includes(this.filter.query.toLowerCase())) - } - }); + if (needsModuleFilter) { + filteredGroups = filteredGroups.map(g => ({ + ...g, + modules: g.modules.filter(m => + (ectsFilter.length === 0 || ectsFilter.includes(m.ects)) && + (semesterFilter.length === 0 || semesterFilter.includes(m.term as string)) && + (queryFilter.length === 0 || m.name.toLowerCase().includes(queryFilter)) + ) + })); } return filteredGroups; - } - }, - watch: { - groupedModules: { - handler(newValue) { - const modules = newValue.flatMap(g => { - return g.modules - }) - - this.isOneModuleAvailable = modules.length !== 0; - }, - deep: true, - immediate: true - } + }, + isOneModuleAvailable(): boolean { + return this.groupedModules.some(g => g.modules.length > 0); + }, + categoryFilterData() { + return store.getters.enrichedCategories.map(c => ({ + id: c.id, + value: c.name, + color: getColorClassForCategoryId(c.id) + })); + }, + ectsFilterData() { + return [...new Set(store.getters.modules.map(m => m.ects))] + .sort((a, b) => a - b) + .map(value => ({ id: value, value: value.toString() })) as { id: number, value: string }[]; + }, + semesterFilterData() { + return SEMESTER_FILTER_DATA; + }, }, methods: { moduleIsDisabled(module: Module): boolean { @@ -262,7 +261,7 @@ export default defineComponent({ (this.showNextPossibleSemester && !module.nextPossibleSemester))); }, moduleIsInPlan(module: Module): boolean { - return store.getters.allPlannedModuleIds.includes(module.id); + return (store.getters.allPlannedModuleIdsSet as Set).has(module.id); }, moduleHasWrongTerm(module: Module): boolean { return ValidationHelper.isModuleInWrongTerm(module, this.termForWhichToSearch); @@ -280,42 +279,6 @@ export default defineComponent({ semester: [] as string[], }; }, - categoryFilterData() { - return store.getters.enrichedCategories.map(c => { - return { - id: c.id, - value: c.name, - color: getColorClassForCategoryId(c.id) - }; - }); - }, - ectsFilterData() { - return store.getters.modules.map(m => { - return m.ects - }).filter((value: number, index: number, self: number[]) => { - return self.indexOf(value) === index; - }).sort((a: number, b: number) => a - b).map((value: number) => { - return { - id: value, - value: value.toString() - }; - }) as { id: number, value: string }[]; - }, - semesterFilterData() { - return [ - { - id: 'FS', - value: 'Frühling' - }, - { - id: 'HS', - value: 'Herbst' - }, - { - id: 'both', - value: 'Beide' - }]; - }, } }); diff --git a/src/components/ModuleSearchListItem.vue b/src/components/ModuleSearchListItem.vue index 91aa7770..2e529e18 100644 --- a/src/components/ModuleSearchListItem.vue +++ b/src/components/ModuleSearchListItem.vue @@ -61,7 +61,7 @@ export default defineComponent({ }, methods: { moduleIsInPlan(module: Module): boolean { - return store.getters.allPlannedModuleIds.includes(module.id); + return (store.getters.allPlannedModuleIdsSet as Set).has(module.id); }, moduleIsDisabled(module: Module): boolean { return this.moduleIsInPlan(module) || (this.disableInvalidModules && ( diff --git a/src/composables/useGraphView.ts b/src/composables/useGraphView.ts index 3b5157c8..15fb4498 100644 --- a/src/composables/useGraphView.ts +++ b/src/composables/useGraphView.ts @@ -51,7 +51,8 @@ export function useGraphView() { } async function computeLayout() { - const plannedModules = modules.value.filter((m) => allPlannedModuleIds.value.includes(m.id)); + const plannedIdSet = store.getters.allPlannedModuleIdsSet as Set; + const plannedModules = modules.value.filter((m) => plannedIdSet.has(m.id)); const rawNodes = generateModuleNodes(plannedModules); const rawEdges = generateModuleEdges( plannedModules, diff --git a/src/helpers/store.ts b/src/helpers/store.ts index 3c87ff42..c268b363 100644 --- a/src/helpers/store.ts +++ b/src/helpers/store.ts @@ -26,14 +26,21 @@ export const store = createStore({ semesters: state => state.semesters, categories: state => state.categories, accreditedModules: state => state.accreditedModules, - modulesByIds: state => moduleIds => - moduleIds.map((id) => state.modules.find((module) => module.id === id)).filter(f => f), - totalPlannedEcts: () => getPlannedEcts(), - totalEarnedEcts: () => getEarnedEcts(), + moduleMap: state => { + const map = new Map(); + state.modules.forEach(m => map.set(m.id, m)); + return map; + }, allPlannedModuleIds: state => state.semesters .flatMap(semester => semester.moduleIds) .concat(state.accreditedModules.map(m => m.moduleId)) - .filter(id => id), + .filter((id): id is string => !!id), + allPlannedModuleIdsSet: (_state, getters) => { + return new Set(getters.allPlannedModuleIds); + }, + totalPlannedEcts: (_state, getters) => getPlannedEcts(undefined, getters.enrichedSemesters, getters.startSemester), + totalEarnedEcts: (_state, getters) => + getEarnedEcts(undefined, getters.enrichedSemesters, getters.startSemester, getters.accreditedModules), startSemester: state => state.startSemester, studienordnung: state => state.studienordnung, validationEnabled: state => state.validationEnabled, @@ -41,43 +48,55 @@ export const store = createStore({ state.modules.map(m => m.validationInfo).filter(f => f?.severity === 'hard').length, hardValidationProblemsByType: state => type => state.modules.map(m => m.validationInfo).filter(f => f?.severity === 'hard' && f?.type === type), - enrichedCategories: (state, getters) => - state.categories.map(category => ({ + enrichedSemesters: (state, getters) => { + const map = getters.moduleMap as Map; + return state.semesters.map(semester => ({ + ...semester, + modules: semester.moduleIds.map(id => map.get(id)).filter((m): m is Module => !!m), + })); + }, + enrichedCategories: (state, getters) => { + const map = getters.moduleMap as Map; + const enrichedSemesters = getters.enrichedSemesters; + const startSemester = getters.startSemester; + const accreditedModules = getters.accreditedModules; + return state.categories.map(category => ({ ...category, - earnedEcts: getEarnedEcts(category), - plannedEcts: getPlannedEcts(category), + earnedEcts: getEarnedEcts(category, enrichedSemesters, startSemester, accreditedModules), + plannedEcts: getPlannedEcts(category, enrichedSemesters, startSemester), colorClass: getColorClassForCategoryId(category.id), - modules: getters.modulesByIds(category.moduleIds), - })), + modules: category.moduleIds.map(id => map.get(id)).filter((m): m is Module => !!m), + })); + }, enrichedFocuses: (state, getters) => { - const plannedModuleIds = getters.allPlannedModuleIds; + const plannedSet = getters.allPlannedModuleIdsSet as Set; + const map = getters.moduleMap as Map; const numberOfModulesRequiredToGetFocus = 8; return state.focuses.map(focus => { - const allModulesForFocus = getters.modulesByIds(focus.moduleIds); + const allModulesForFocus = focus.moduleIds.map(id => map.get(id)).filter((m): m is Module => !!m); return { ...focus, numberOfMissingModules: Math.max( 0, numberOfModulesRequiredToGetFocus - focus.moduleIds.filter(moduleId => - plannedModuleIds.includes(moduleId)).length + plannedSet.has(moduleId)).length ), // to only show actually available modules, we filter out predecessors of already planned ones - availableModules: getters.modulesByIds( - focus.moduleIds.filter(moduleId => - !plannedModuleIds.includes(moduleId) && - !plannedModuleIds.includes(allModulesForFocus.find(module => module.id === moduleId).successorModuleId) - ) - ), + availableModules: focus.moduleIds + .filter(moduleId => { + const successorId = map.get(moduleId)?.successorModuleId; + return ( + !plannedSet.has(moduleId) && + !(successorId && plannedSet.has(successorId)) + ); + }) + .map(id => map.get(id)) + .filter((m): m is Module => !!m), modules: allModulesForFocus, }; }); }, - enrichedSemesters: (state, getters) => - state.semesters.map(semester => ({ - ...semester, - modules: getters.modulesByIds(semester.moduleIds), - })), }, mutations: { setModules(state, modules: Module[]) { @@ -204,34 +223,43 @@ export const store = createStore({ }, } }); -function getEarnedEcts(category?: Category): number { - if (store.getters.startSemester === undefined) { +function getEarnedEcts( + category: Category | undefined, + enrichedSemesters: Semester[], + startSemester: SemesterInfo | undefined, + accreditedModules: AccreditedModule[], +): number { + if (startSemester === undefined) { return 0; } - const indexOfLastCompletedSemester = SemesterInfo.now().difference(store.getters.startSemester); + const indexOfLastCompletedSemester = SemesterInfo.now().difference(startSemester); if (indexOfLastCompletedSemester < 0) { return 0; } - const ectsInSemesters = store.getters.enrichedSemesters + const ectsInSemesters = enrichedSemesters .slice(0, indexOfLastCompletedSemester) .flatMap((semester) => semester.modules) .filter((module) => !category || category.moduleIds.includes(module.id)) .reduce((previousTotal, module) => previousTotal + module.ects, 0); - const accreditedEcts = store.getters.accreditedModules + const accreditedEcts = accreditedModules .filter(module => !category || module.categoryIds.includes(category.id)) .reduce((previousTotal, module) => previousTotal + module.ects, 0); return ectsInSemesters + accreditedEcts; } -function getPlannedEcts(category?: Category): number { - if (store.getters.startSemester === undefined) { +function getPlannedEcts( + category: Category | undefined, + enrichedSemesters: Semester[], + startSemester: SemesterInfo | undefined, +): number { + if (startSemester === undefined) { return 0; } - let semestersToConsider = store.getters.enrichedSemesters; - const indexOfLastCompletedSemester = SemesterInfo.now().difference(store.getters.startSemester); + let semestersToConsider = enrichedSemesters; + const indexOfLastCompletedSemester = SemesterInfo.now().difference(startSemester); if (indexOfLastCompletedSemester >= 0) { semestersToConsider = semestersToConsider.slice(indexOfLastCompletedSemester) diff --git a/src/views/Home.vue b/src/views/Home.vue index 57690960..8803a3dd 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -195,9 +195,9 @@ export default defineComponent({ watch: { $route: { handler() { - setTimeout(() => { + this.$nextTick(() => { this.getPlanDataFromUrl(); - }, 0); + }); }, }, },