Newer
Older
import {
filter,
map,
withLatestFrom,
distinctUntilChanged,
} from 'rxjs/operators';
import { OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { select } from '@ngrx/store';
import { Component } from '@angular/core';
import { MatSelectChange, MatSlideToggleChange } from '@angular/material';
import { Store } from '@ngrx/store';
import { MediaMatcher } from '@angular/cdk/layout';
import { DegreePlan } from '@app/core/models/degree-plan';
import { Year } from '@app/core/models/year';
import * as selectors from '@app/degree-planner/store/selectors';
import * as utils from '@app/degree-planner/shared/utils';
import { PromptDialogComponent } from '@app/shared/dialogs/prompt-dialog/prompt-dialog.component';
import { ConfirmDialogComponent } from '@app/shared/dialogs/confirm-dialog/confirm-dialog.component';
import {
CloseCourseSearch,
CloseSidenav,
OpenSidenav,
ExpandAcademicYear,
CollapseAcademicYear,
} from './store/actions/ui.actions';
import { YearCode } from '@app/degree-planner/shared/term-codes/yearcode';
import { ConstantsService } from './services/constants.service';
import { TermCodeFactory } from './services/termcode.factory';
import { IE11WarningDialogComponent } from './dialogs/ie11-warning-dialog/ie11-warning-dialog.component';
import { UpdateUserPreferences } from './store/actions/userPreferences.actions';
// From: https://stackoverflow.com/a/21825207
const isIE11 =
!!(window as any).MSInputMethodContext && !!(document as any).documentMode;
selector: 'cse-degree-planner',
templateUrl: './degree-planner.component.html',
styleUrls: ['./degree-planner.component.scss'],
export class DegreePlannerComponent implements OnInit {
public termsByAcademicYear: Object;
public mobileView: MediaQueryList;
public coursesData$: any;
public showGrades$: Observable<boolean>;
public allDegreePlans$: Observable<ReadonlyArray<DegreePlan>>;
public termsByYear$: Observable<ReadonlyArray<Year>>;
public yearCodes$: Observable<ReadonlyArray<YearCode>>;
public isCourseSearchOpen$: Observable<boolean>;
public isLoadingPlan$: Observable<boolean>;
public isSidenavOpen$: Observable<boolean>;
public hasDismissedIEWarning$: Observable<boolean>;
constructor(
private store: Store<GlobalState>,
private constants: ConstantsService,
private termCodeFactory: TermCodeFactory,
public mediaMatcher: MediaMatcher,
private termCodeService: TermCodeFactory,
this.mobileView = mediaMatcher.matchMedia('(max-width: 959px)');
this.version = constants.getVersion();
public ngOnInit() {
this.degreePlan$ = this.store.pipe(
select(selectors.selectVisibleDegreePlan),
this.showGrades$ = this.store.pipe(select(selectors.selectGradeVisibility));
this.hasDismissedIEWarning$ = this.store.pipe(
select(selectors.getUserPreference, 'degreePlannerHasDismissedIEWarning'),
map(hasDismissedIEWarning => hasDismissedIEWarning === true),
this.allDegreePlans$ = this.store.pipe(
select(selectors.selectAllDegreePlans),
);
// Get observable for the search open state
this.isCourseSearchOpen$ = this.store.pipe(
select(selectors.isCourseSearchOpen),
this.isLoadingPlan$ = this.store.pipe(select(selectors.isLoadingPlan));
this.isSidenavOpen$ = this.store.pipe(select(selectors.isSidenavOpen)).pipe(
map(isSidenavOpen => {
if (isSidenavOpen === 'defer') {
return !this.mobileView.matches;
}
return isSidenavOpen;
}),
);
this.yearCodes$ = this.store.pipe(
select(selectors.selectAllVisibleYears),
map(years => Object.keys(years)),
distinctUntilChanged(utils.compareStringArrays),
map(ycs => ycs.map(yc => this.termCodeFactory.fromRawYearCode(yc))),
this.hasDismissedIEWarning$.subscribe(hasDismissedIEWarning => {
if (isIE11 && hasDismissedIEWarning !== true) {
this.dialog
.open(IE11WarningDialogComponent, {
closeOnNavigation: true,
})
.afterClosed()
.subscribe(() => {
this.store.dispatch(
new UpdateUserPreferences({
degreePlannerHasDismissedIEWarning: true,
}),
);
});
}
});
public openSidenav() {
this.store.dispatch(new OpenSidenav());
}
public closeSidenav() {
this.store.dispatch(new CloseSidenav());
}
public handleDegreePlanChange(event: MatSelectChange): void {
if (typeof event.value === 'number') {
this.store.dispatch(new SwitchPlan({ newVisibleRoadmapId: event.value }));
.open(PromptDialogComponent, {
data: {
initialValue: name,
title: 'Add degree plan',
confirmText: 'Create plan',
.subscribe((result: { confirmed: boolean; value: string }) => {
const { confirmed, value } = result;
if (confirmed) {
const action = new CreatePlan({ name: value, primary: false });
public onAddAcademicYear() {
public onRenamePlanClick(currentPlan: DegreePlan) {
this.dialog
.open(PromptDialogComponent, {
data: {
initialValue: currentPlan.name,
title: 'Rename plan',
confirmText: 'Save',
inputName: 'Plan name',
.subscribe((result: { confirmed: boolean; value: string }) => {
const { confirmed, value } = result;
if (confirmed) {
const action = new ChangePlanName({ roadmapId, newName: value });
public onMakePrimayClick(currentPlan: DegreePlan) {
const text =
`This will change your primary plan and replace the current ` +
`courses in your cart with the courses in this plan's ` +
`${this.termCodeService.first().description} term.`;
title: 'Are you sure?',
confirmText: 'Change plan',
.subscribe((result: { confirmed: true } | undefined) => {
if (result !== undefined && result.confirmed === true) {
const action = new MakePlanPrimary();
this.store.dispatch(action);
}
});
public onDeletePlanClick(currentPlan: DegreePlan) {
if (currentPlan.primary) {
this.snackBar.open('The primary degree plan cannot be deleted');
return;
}
this.dialog
title: 'Are you sure?',
confirmText: 'Delete',
text:
'This will delete this plan and course information related to this plan.',
},
})
.afterClosed()
.pipe(withLatestFrom(this.store))
.subscribe(([result, state]) => {
if (typeof result === 'object' && result.confirmed === true) {
const { roadmapId } = currentPlan;
const deleteAction = new DeletePlan({ roadmapId });
this.store.dispatch(deleteAction);
const primaryPlan = state.degreePlanner.allDegreePlans.find(plan => {
return plan.primary;
}) as DegreePlan;
const newVisibleRoadmapId = primaryPlan.roadmapId;
const switchPlanAction = new SwitchPlan({ newVisibleRoadmapId });
this.store.dispatch(switchPlanAction);
}
});
public getTermDropZone() {
const termCodes = ['favoriteCourse-dropZone'];
for (const yearCode in this.termsByAcademicYear) {
if (this.termsByAcademicYear[yearCode]) {
const year = this.termsByAcademicYear[yearCode];
for (const termKey in year.terms) {
if (year.terms[termKey]) {
const term = year.terms[termKey];
termCodes.push('term-' + term.termCode);
}
}
}
}
return termCodes;
}
public changeGradeVisibility(event: MatSlideToggleChange) {
this.store.dispatch(new ChangeGradeVisibility(event.checked));
}
public closeCourseSearch() {
this.store.dispatch(new CloseCourseSearch());
}
public trackYearCodes(_index: number, yearCode: YearCode) {
return yearCode.toString();
}
public toggleAllYears(expand: boolean) {
const event = expand
? new ExpandAcademicYear()
: new CollapseAcademicYear();
this.store.dispatch(event);
}
const isntUndefined = <T>(anything: T | undefined): anything is T => {
return anything !== undefined;
};