src/app/courses/step-view-courses/courses-step-view.component.ts
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { CoursesService } from '../courses.service';
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
import { MatDialog } from '@angular/material/dialog';
import { MatMenuTrigger } from '@angular/material/menu';
import { Subject, combineLatest } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { UserService } from '../../shared/user.service';
import { SubmissionsService } from '../../submissions/submissions.service';
import { ResourcesService } from '../../resources/resources.service';
import { DialogsSubmissionsComponent } from '../../shared/dialogs/dialogs-submissions.component';
import { StateService } from '../../shared/state.service';
import { ChatService } from '../../shared/chat.service';
@Component({
templateUrl: './courses-step-view.component.html',
styleUrls: [ './courses-step-view.scss' ]
})
export class CoursesStepViewComponent implements OnInit, OnDestroy {
onDestroy$ = new Subject<void>();
stepNum = 0;
stepDetail: any = { stepTitle: '', description: '', resources: [] };
courseId: string;
maxStep = 1;
resourceUrl = '';
examStart = 1;
examText: 'continue' | 'retake' | 'take' = 'take';
attempts = 0;
isUserEnrolled = false;
resource: any;
progress: any;
examPassed = false;
parent = false;
canManage = false;
countActivity = true;
isGridView = true;
showChat = false;
isOpenai = false;
@ViewChild(MatMenuTrigger) previewButton: MatMenuTrigger;
constructor(
private chatService: ChatService,
private coursesService: CoursesService,
private dialog: MatDialog,
private resourcesService: ResourcesService,
private router: Router,
private route: ActivatedRoute,
private stateService: StateService,
private submissionsService: SubmissionsService,
private userService: UserService,
) {}
ngOnInit() {
combineLatest(
this.coursesService.courseUpdated$,
this.resourcesService.resourcesListener(this.parent),
this.stateService.getCouchState('exams', 'local')
).pipe(takeUntil(this.onDestroy$))
.subscribe(([ { course, progress = [] }, resources, exams ]: [ { course: any, progress: any }, any[], any[] ]) => {
this.initCourse(course, progress, resources.map((resource: any) => resource.doc), exams);
if (this.countActivity) {
this.coursesService.courseActivity('visit', course, this.stepNum);
this.countActivity = false;
}
this.canManage = this.userService.get().isUserAdmin ||
course.creator !== undefined &&
(`${this.userService.get().name}@${this.userService.get().planetCode}` === course.creator);
});
this.getSubmission();
this.route.paramMap.pipe(takeUntil(this.onDestroy$)).subscribe((params: ParamMap) => {
this.parent = this.route.snapshot.data.parent;
this.stepNum = +params.get('stepNum'); // Leading + forces string to number
this.courseId = params.get('id');
this.attempts = 0;
this.coursesService.requestCourse({ courseId: this.courseId, parent: this.parent });
});
this.resourcesService.requestResourcesUpdate(this.parent);
this.chatService.listAIProviders().subscribe((providers) => {
this.isOpenai = providers.some(provider => provider.model === 'openai');
});
}
getSubmission() {
this.submissionsService.submissionUpdated$.pipe(takeUntil(this.onDestroy$))
.subscribe(({ submission, attempts, bestAttempt = { grade: 0 } }) => {
this.examStart = (this.submissionsService.nextQuestion(submission, submission.answers.length - 1, 'passed') + 1) || 1;
this.examText = submission.answers.length > 0 ? 'continue' : attempts === 0 ? 'take' : 'retake';
this.attempts = attempts;
const examPercent = (bestAttempt.grade / this.stepDetail.exam.totalMarks) * 100;
this.examPassed = examPercent >= this.stepDetail.exam.passingPercentage;
if (!this.parent && this.progress.passed !== this.examPassed) {
this.coursesService.updateProgress({
courseId: this.courseId, stepNum: this.stepNum, passed: this.examPassed
});
}
});
}
ngOnDestroy() {
this.onDestroy$.next();
this.onDestroy$.complete();
}
initCourse(course, progress, resources, exams) {
// To be readable by non-technical people stepNum param will start at 1
this.stepDetail = course.steps[this.stepNum - 1];
this.initResources(resources);
// Fix for multiple progress docs created. If there are more than one for a step, then we need to call updateProgress to fix.
const stepProgressDocs = progress.filter(p => p.stepNum === this.stepNum);
this.progress = stepProgressDocs.find(p => p.passed) || stepProgressDocs[0] || { passed: false };
this.isUserEnrolled = !this.parent && this.checkMyCourses(course._id);
if (this.isUserEnrolled && (this.progress.stepNum === undefined || stepProgressDocs.length > 1)) {
this.coursesService.updateProgress({
courseId: course._id, stepNum: this.stepNum, passed: this.stepDetail.exam === undefined || this.progress.passed
});
}
this.maxStep = course.steps.length;
if (this.stepDetail.exam) {
this.submissionsService.openSubmission({
parentId: this.stepDetail.exam._id + '@' + course._id,
parent: exams.find(exam => exam._id === this.stepDetail.exam._id) || this.stepDetail.exam,
user: this.userService.get(),
type: 'exam' });
}
}
initResources(resources) {
this.stepDetail.resources.sort(this.coursesService.stepResourceSort);
this.stepDetail.resources = this.filterResources(this.stepDetail, resources);
this.resource = this.resource === undefined && this.stepDetail.resources ? this.stepDetail.resources[0] : this.resource;
}
// direction = -1 for previous, 1 for next
changeStep(direction) {
this.router.navigate([ '../' + (this.stepNum + direction) ], { relativeTo: this.route });
this.resource = undefined;
this.countActivity = true;
}
backToCourseDetail() {
this.router.navigate([ '../../' ], { relativeTo: this.route });
}
setResourceUrl(resourceUrl: string) {
this.resourceUrl = resourceUrl;
}
checkMyCourses(courseId: string) {
return this.userService.shelf.courseIds.includes(courseId);
}
onResourceChange(value) {
this.resource = value;
}
goToExam(type = 'exam', preview = false) {
this.router.navigate(
[
'exam',
{
id: this.courseId,
stepNum: this.stepNum,
questionNum: type === 'survey' ? 1 : this.examStart,
type,
preview,
examId: type === 'survey' ? this.stepDetail.survey._id : this.stepDetail.exam._id,
}
],
{ relativeTo: this.route }
);
}
filterResources(step, resources) {
const resourceIds = resources.map((res: any) => res._id);
return step.resources ?
step.resources.filter((resource) => resourceIds.indexOf(resource._id) !== -1 && resource._attachments) :
[];
}
openReviewDialog() {
this.dialog.open(DialogsSubmissionsComponent, {
minWidth: '500px',
maxHeight: '90vh',
data: { parentId: `${this.stepDetail.exam._id}@${this.courseId}` }
});
}
menuTriggerButtonClick(): void {
const stepType = this.coursesService.stepHasExamSurveyBoth(this.stepDetail);
if (stepType === 'both' || stepType === undefined) {
return;
}
this.previewButton.closeMenu();
this.goToExam(stepType, true);
}
get localizedStepInfo(): string {
return $localize`The following information is a course step from the "${this.stepDetail?.stepTitle}" course with a description "${this.stepDetail?.description}". Be sure to assist the learner in the best way you can. `;
}
}