Skip to content
Snippets Groups Projects
degree-planner.component.ts 9.2 KiB
Newer Older
Isaac Evavold's avatar
Isaac Evavold committed
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';
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
import { MatDialog } from '@angular/material';
import { Store } from '@ngrx/store';
import { MediaMatcher } from '@angular/cdk/layout';
Isaac Evavold's avatar
Isaac Evavold committed
import { MatSnackBar } from '@angular/material';
Isaac Evavold's avatar
Isaac Evavold committed
import { GlobalState } from '@app/core/state';
import { DegreePlan } from '@app/core/models/degree-plan';
import { Year } from '@app/core/models/year';
Isaac Evavold's avatar
Isaac Evavold committed
import * as selectors from '@app/degree-planner/store/selectors';
import * as utils from '@app/degree-planner/shared/utils';
Scott Berg's avatar
Scott Berg committed
import {
  SwitchPlan,
  MakePlanPrimary,
Isaac Evavold's avatar
Isaac Evavold committed
  CreatePlan,
  ChangePlanName,
Isaac Evavold's avatar
Isaac Evavold committed
  DeletePlan,
  ChangeGradeVisibility,
Scott Berg's avatar
Scott Berg committed
} from '@app/degree-planner/store/actions/plan.actions';
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,
  AddAcademicYear,
} 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;
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
@Component({
  selector: 'cse-degree-planner',
  templateUrl: './degree-planner.component.html',
  styleUrls: ['./degree-planner.component.scss'],
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
})
export class DegreePlannerComponent implements OnInit {
  public termsByAcademicYear: Object;
  public mobileView: MediaQueryList;
  public coursesData$: any;
  public showGrades$: Observable<boolean>;
Isaac Evavold's avatar
Isaac Evavold committed
  public degreePlan$: Observable<DegreePlan | undefined>;
  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>;
  public version: string;
Scott Berg's avatar
Scott Berg committed

  constructor(
    private store: Store<GlobalState>,
    private constants: ConstantsService,
    private termCodeFactory: TermCodeFactory,
    public mediaMatcher: MediaMatcher,
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
    public dialog: MatDialog,
Isaac Evavold's avatar
Isaac Evavold committed
    private snackBar: MatSnackBar,
    private termCodeService: TermCodeFactory,
    this.mobileView = mediaMatcher.matchMedia('(max-width: 959px)');
    this.version = constants.getVersion();
Isaac Evavold's avatar
Isaac Evavold committed
    this.degreePlan$ = this.store.pipe(
      select(selectors.selectVisibleDegreePlan),
Isaac Evavold's avatar
Isaac Evavold committed
      filter(isntUndefined),
    );
Isaac Evavold's avatar
Isaac Evavold committed

    this.showGrades$ = this.store.pipe(select(selectors.selectGradeVisibility));

    this.hasDismissedIEWarning$ = this.store.pipe(
      select(selectors.getUserPreference, 'degreePlannerHasDismissedIEWarning'),
      map(hasDismissedIEWarning => hasDismissedIEWarning === true),
Isaac Evavold's avatar
Isaac Evavold committed
    this.allDegreePlans$ = this.store.pipe(
      select(selectors.selectAllDegreePlans),
    );

    // Get observable for the search open state
    this.isCourseSearchOpen$ = this.store.pipe(
      select(selectors.isCourseSearchOpen),
Isaac Evavold's avatar
Isaac Evavold committed
    );
Scott Berg's avatar
Scott Berg committed

    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;
      }),
    );

Isaac Evavold's avatar
Isaac Evavold committed
    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))),
Isaac Evavold's avatar
Isaac Evavold committed
    );

    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') {
Isaac Evavold's avatar
Isaac Evavold committed
      this.store.dispatch(new SwitchPlan({ newVisibleRoadmapId: event.value }));
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
  public onCreatePlanClick() {
Isaac Evavold's avatar
Isaac Evavold committed
    this.dialog
      .open(PromptDialogComponent, {
        data: {
          initialValue: name,
          title: 'Add degree plan',
          confirmText: 'Create plan',
          inputName: 'Plan name',
Isaac Evavold's avatar
Isaac Evavold committed
      .afterClosed()
      .subscribe((result: { confirmed: boolean; value: string }) => {
        const { confirmed, value } = result;
        if (confirmed) {
          const action = new CreatePlan({ name: value, primary: false });
Isaac Evavold's avatar
Isaac Evavold committed
          this.store.dispatch(action);
        }
      });
  public onAddAcademicYear() {
    this.store.dispatch(new AddAcademicYear());
Isaac Evavold's avatar
Isaac Evavold committed
  public onRenamePlanClick(currentPlan: DegreePlan) {
    this.dialog
      .open(PromptDialogComponent, {
        data: {
          initialValue: currentPlan.name,
          title: 'Rename plan',
          confirmText: 'Save',
          inputName: 'Plan name',
Isaac Evavold's avatar
Isaac Evavold committed
      .afterClosed()
      .subscribe((result: { confirmed: boolean; value: string }) => {
        const { confirmed, value } = result;
        if (confirmed) {
Isaac Evavold's avatar
Isaac Evavold committed
          const { roadmapId } = currentPlan;
          const action = new ChangePlanName({ roadmapId, newName: value });
Isaac Evavold's avatar
Isaac Evavold committed
          this.store.dispatch(action);
        }
      });
  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.`;
Isaac Evavold's avatar
Isaac Evavold committed
    this.dialog
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
      .open(ConfirmDialogComponent, {
        data: {
          title: 'Are you sure?',
          confirmText: 'Change plan',
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
        },
      })
Isaac Evavold's avatar
Isaac Evavold committed
      .afterClosed()
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
      .subscribe((result: { confirmed: true } | undefined) => {
        if (result !== undefined && result.confirmed === true) {
Isaac Evavold's avatar
Isaac Evavold committed
          const action = new MakePlanPrimary();
          this.store.dispatch(action);
        }
      });
Isaac Evavold's avatar
Isaac Evavold committed
  public onDeletePlanClick(currentPlan: DegreePlan) {
    if (currentPlan.primary) {
      this.snackBar.open('The primary degree plan cannot be deleted');
      return;
    }
    this.dialog
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
      .open(ConfirmDialogComponent, {
        data: {
          title: 'Are you sure?',
          confirmText: 'Delete',
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
          text:
            'This will delete this plan and course information related to this plan.',
        },
      })
Isaac Evavold's avatar
Isaac Evavold committed
      .afterClosed()
      .pipe(withLatestFrom(this.store))
      .subscribe(([result, state]) => {
        if (typeof result === 'object' && result.confirmed === true) {
Isaac Evavold's avatar
Isaac Evavold committed
          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);
          }
        }
      }
    }
Scott Berg's avatar
Scott Berg committed

  public changeGradeVisibility(event: MatSlideToggleChange) {
    this.store.dispatch(new ChangeGradeVisibility(event.checked));
  }

Scott Berg's avatar
Scott Berg committed
  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);
  }
jvanboxtel@wisc.edu's avatar
jvanboxtel@wisc.edu committed
}
Isaac Evavold's avatar
Isaac Evavold committed

const isntUndefined = <T>(anything: T | undefined): anything is T => {
  return anything !== undefined;
};