Forked from an inaccessible project.
-
Isaac Evavold authoredIsaac Evavold authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
plan.effects.ts 9.77 KiB
// Libraries
import { Injectable } from '@angular/core';
import { ROOT_EFFECTS_INIT, Actions, Effect, ofType } from '@ngrx/effects';
import { Observable, forkJoin } from 'rxjs';
import { map, flatMap, tap, withLatestFrom, filter } from 'rxjs/operators';
import { GlobalState } from '@app/core/state';
import { Store } from '@ngrx/store';
// Services
import { DegreePlannerApiService } from '@app/degree-planner/services/api.service';
import { getDegreePlannerState } from '@app/degree-planner/selectors';
// Actions
import {
InitialPlanLoadResponse,
ChangeVisiblePlanRequest,
ChangeVisiblePlanResponse,
PlanActionTypes,
ChangeCourseTermResponse,
AddCourseResponse,
RemoveCourseResponse,
RemoveSavedForLaterResponse,
AddSavedForLaterResponse,
} from '@app/degree-planner/actions/plan.actions';
// Models
import { PlannedTerm } from '@app/core/models/planned-term';
@Injectable()
export class DegreePlanEffects {
constructor(
private actions$: Actions,
private api: DegreePlannerApiService,
private store$: Store<GlobalState>,
) {}
@Effect()
init$: Observable<InitialPlanLoadResponse> = this.actions$.pipe(
ofType(ROOT_EFFECTS_INIT),
// Load all plans available to the user.
flatMap(() => this.api.getAllDegreePlans()),
// Pick one of the plans to use as the visible plan.
map(allDegreePlans => {
const visibleRoadmapId = (
allDegreePlans.find(plan => plan.primary) || allDegreePlans[0]
).roadmapId;
return { visibleRoadmapId, allDegreePlans };
}),
// Get term data for the degree plan specified by the roadmap ID and any
// courses that were 'saved for later' by the user.
flatMap(stdin => {
return forkJoin(
this.loadTermsForPlan(stdin),
this.api.getSavedForLaterCourses(),
this.api.getAllSubjects(),
).pipe(
map(([planDetails, savedForLater, subjects]) => {
const savedForLaterCourses = savedForLater.map(course => {
course.subject = subjects[course.subjectCode];
return course;
});
return { ...planDetails, savedForLaterCourses, subjects };
}),
);
}),
// Wrap data in an Action for dispatch
map(stdin => new InitialPlanLoadResponse(stdin)),
);
@Effect()
switch$: Observable<ChangeVisiblePlanResponse> = this.actions$.pipe(
ofType<ChangeVisiblePlanRequest>(PlanActionTypes.ChangeVisiblePlanRequest),
// Get the roadmap ID from the action.
map(action => ({ visibleRoadmapId: action.payload.newVisibleRoadmapId })),
// Get term data for the degree plan specified by the roadmap ID.
flatMap(stdin => this.loadTermsForPlan(stdin)),
// Wrap data in an Action for dispatch
map(stdin => new ChangeVisiblePlanResponse(stdin)),
);
@Effect()
MoveCourseBetweenTerms$ = this.actions$.pipe(
ofType<any>(PlanActionTypes.ChangeCourseTermRequest),
withLatestFrom(this.store$.select(getDegreePlannerState)),
filter(([_, state]) => typeof state.visibleRoadmapId === 'number'),
// Get the roadmap ID from the action.
tap(([action, state]) => {
console.log(action);
console.log(state);
}),
// Get term data for the degree plan specified by the roadmap ID.
flatMap(([action, state]) => {
// TODO error handle the API calls
return this.api
.updateCourseTerm(
state.visibleRoadmapId,
action.payload.id,
action.payload.to,
)
.pipe(
map(response => {
return {
response,
action,
};
}),
);
}),
// // Wrap data in an Action for dispatch
map(({ response, action }) => {
if (response === 1) {
return new ChangeCourseTermResponse({
id: action.payload.id,
from: action.payload.from,
to: action.payload.to,
});
}
return;
}),
);
@Effect()
AddCourse$ = this.actions$.pipe(
ofType<any>(PlanActionTypes.AddCourseRequest),
withLatestFrom(this.store$.select(getDegreePlannerState)),
filter(([_, state]) => typeof state.visibleRoadmapId === 'number'),
// Get the roadmap ID from the action.
tap(([action, state]) => {
console.log(action);
console.log(state);
}),
// Get term data for the degree plan specified by the roadmap ID.
flatMap(([action, state]) => {
// TODO error handle the API calls
const roadmapId = state.visibleRoadmapId;
const { subjectCode, termCode, courseId } = action.payload;
return this.api
.addCourse(roadmapId as number, subjectCode, courseId, termCode)
.pipe(
map(response => {
return {
response,
action,
};
}),
);
}),
// Wrap data in an Action for dispatch
map(({ response, action }) => {
// TODO add error handleing
return new AddCourseResponse({ course: response });
}),
);
@Effect()
RemoveCourse$ = this.actions$.pipe(
ofType<any>(PlanActionTypes.RemoveCourseRequest),
withLatestFrom(this.store$.select(getDegreePlannerState)),
filter(([_, state]) => typeof state.visibleRoadmapId === 'number'),
// Get the roadmap ID from the action.
// tap(([action, state]) => {
// console.log(action);
// console.log(state);
// }),
// Get term data for the degree plan specified by the roadmap ID.
flatMap(([action, state]) => {
// TODO error handle the API calls
return this.api
.removeCourse(state.visibleRoadmapId as number, action.payload.id)
.pipe(
map(response => {
return {
response,
action,
};
}),
);
}),
// Wrap data in an Action for dispatch
map(({ response, action }) => {
if (response === null) {
const { id } = action.payload;
return new RemoveCourseResponse({ id });
}
return;
}),
);
@Effect()
RemoveSavedForLater$ = this.actions$.pipe(
ofType<any>(PlanActionTypes.RemoveSavedForLaterReqeust),
withLatestFrom(this.store$.select(getDegreePlannerState)),
filter(([_, state]) => typeof state.visibleRoadmapId === 'number'),
// tap(([action, state]) => {
// console.log('REMOVE SAVED FOR LATER ----------');
// console.log(action);
// console.log(state);
// console.log('---------------------------------');
// }),
// Get term data for the degree plan specified by the roadmap ID.
flatMap(([action, state]) => {
// TODO error handle the API calls
return this.api
.removeSavedForLater(
action.payload.subjectCode,
action.payload.courseId,
)
.pipe(
map(response => {
return {
response,
action,
};
}),
);
}),
// // Wrap data in an Action for dispatch
map(({ response, action }) => {
if (response === null) {
const { courseId, subjectCode } = action.payload;
return new RemoveSavedForLaterResponse({ courseId, subjectCode });
// TODO Update UI and remove saved response
}
return;
}),
);
@Effect()
SaveForLater$ = this.actions$.pipe(
ofType<any>(PlanActionTypes.AddSavedForLaterReqeust),
withLatestFrom(this.store$.select(getDegreePlannerState)),
filter(([_, state]) => typeof state.visibleRoadmapId === 'number'),
// Get the roadmap ID from the action.
// tap(([action, state]) => {
// console.log('ADD SAVED FOR LATER ----------');
// console.log(action);
// console.log(state);
// console.log('---------------------------------');
// }),
// Get term data for the degree plan specified by the roadmap ID.
flatMap(([action, state]) => {
// TODO error handle the API calls
return this.api
.saveForLater(action.payload.subjectCode, action.payload.courseId)
.pipe(
map(response => {
return {
response,
action,
};
}),
);
}),
// // // Wrap data in an Action for dispatch
map(({ response, action }) => {
if (response === null) {
const { subjectCode, courseId } = action.payload;
return new AddSavedForLaterResponse({ subjectCode, courseId });
}
// return;
return;
}),
);
private loadTermsForPlan<T extends { visibleRoadmapId: number }>(stdin: T) {
return forkJoin(
this.api.getAllNotes(stdin.visibleRoadmapId),
this.api.getAllCourses(stdin.visibleRoadmapId),
this.api.getActiveTerms(),
).pipe(
// Combine courses and notes by term.
map(([notes, courses, currentTerms]) => {
/**
* Using the notes & courses relevant to the current degree plan and
* the active terms, generate a sorted list of all unqiue term codes.
*/
const uniqueTermCodes = [notes, courses, currentTerms]
.map((ts: { termCode: string }[]) => ts.map(t => t.termCode))
.reduce((flat, nested) => flat.concat(nested), [])
.filter((termCode, index, self) => self.indexOf(termCode) === index)
.sort();
/**
* For each unique termCode, build a Term object that includes any
* courses or notes relevant to that termCode.
*/
const visibleTerms: PlannedTerm[] = uniqueTermCodes.map(termCode => {
return {
termCode,
note: notes.find(note => note.termCode === termCode),
courses: courses.filter(course => course.termCode === termCode),
};
});
const activeTermCodes = uniqueTermCodes.filter(termCode => {
return termCode >= currentTerms[0].termCode;
});
return Object.assign({}, stdin, { visibleTerms }, { activeTermCodes });
}),
);
}
}