Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions src/components/GraphModule.vue
Original file line number Diff line number Diff line change
Expand Up @@ -123,14 +123,18 @@ export default defineComponent({
},
computed: {
recommendedModules(): Module[] {
const moduleMap = store.getters.moduleMap as Map<string, Module>;
const plannedSet = store.getters.allPlannedModuleIdsSet as Set<string>;
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<string, Module>;
const plannedSet = store.getters.allPlannedModuleIdsSet as Set<string>;
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
Expand Down
2 changes: 1 addition & 1 deletion src/components/GraphModuleHightlight.vue
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export default {
},
computed: {
isPlanned(): boolean {
return (store.getters.allPlannedModuleIds as string[]).includes(this.id);
return (store.getters.allPlannedModuleIdsSet as Set<string>).has(this.id);
},
},
methods: {
Expand Down
125 changes: 44 additions & 81 deletions src/components/ModuleSearch.vue
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
<h3>Kategorie</h3>
<ModuleFilter
v-model:selected="filter.categories"
:data="categoryFilterData()"
:data="categoryFilterData"
data-cy-tag="ModuleFilter-CategoryFilter"
:is-single-select="false"
:is-button-group="false"
Expand All @@ -74,7 +74,7 @@
</h3>
<ModuleFilter
v-model:selected="filter.ects"
:data="ectsFilterData()"
:data="ectsFilterData"
:is-single-select="false"
data-cy-tag="ModuleFilter-EctsFilter"
is-button-group
Expand All @@ -85,7 +85,7 @@
</h3>
<ModuleFilter
v-model:selected="filter.semester"
:data="semesterFilterData()"
:data="semesterFilterData"
data-cy-tag="ModuleFilter-SemesterFilter"
is-single-select
is-button-group
Expand Down Expand Up @@ -126,6 +126,12 @@ import ModuleFilter from "./ModuleFilter.vue";
import ModuleSearchList from "./ModuleSearchList.vue";
import type { GroupedModule } from "../types/GroupedModule";

const SEMESTER_FILTER_DATA = [
{ id: 'FS', value: 'Frühling' },
{ id: 'HS', value: 'Herbst' },
{ id: 'both', value: 'Beide' }
];

export default defineComponent({
name: 'ModuleSearch',
components: {
Expand Down Expand Up @@ -177,7 +183,6 @@ export default defineComponent({
data() {
return {
isSearching: false,
isOneModuleAvailable: true,
filter: {
query: '',
categories: [] as string[],
Expand All @@ -197,8 +202,8 @@ export default defineComponent({
colorClass: getColorClassForCategoryId(c.id),
};
});
const modulesInGroups = groups.flatMap(g => 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',
Expand All @@ -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<number>(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 {
Expand All @@ -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<string>).has(module.id);
},
moduleHasWrongTerm(module: Module): boolean {
return ValidationHelper.isModuleInWrongTerm(module, this.termForWhichToSearch);
Expand All @@ -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'
}];
},
}
});
</script>
2 changes: 1 addition & 1 deletion src/components/ModuleSearchListItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>).has(module.id);
},
moduleIsDisabled(module: Module): boolean {
return this.moduleIsInPlan(module) || (this.disableInvalidModules && (
Expand Down
3 changes: 2 additions & 1 deletion src/composables/useGraphView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>;
const plannedModules = modules.value.filter((m) => plannedIdSet.has(m.id));
const rawNodes = generateModuleNodes(plannedModules);
const rawEdges = generateModuleEdges(
plannedModules,
Expand Down
96 changes: 62 additions & 34 deletions src/helpers/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,58 +26,77 @@ 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<string, Module>();
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<string>(getters.allPlannedModuleIds);
},
Comment thread
jeremystucki marked this conversation as resolved.
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,
numberOfHardValidationProblems: state =>
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<string, Module>;
return state.semesters.map(semester => ({
...semester,
modules: semester.moduleIds.map(id => map.get(id)).filter((m): m is Module => !!m),
}));
Comment thread
jeremystucki marked this conversation as resolved.
},
enrichedCategories: (state, getters) => {
const map = getters.moduleMap as Map<string, Module>;
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<string>;
const map = getters.moduleMap as Map<string, Module>;
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))
);
Comment thread
jeremystucki marked this conversation as resolved.
})
.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[]) {
Expand Down Expand Up @@ -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)
Expand Down
Loading
Loading