src/app/modules/learn/components/course-consumption/assessment-player/assessment-player.component.ts
OnInit
OnDestroy
ComponentCanDeactivate
selector | app-assessment-player |
styleUrls | ./assessment-player.component.scss |
templateUrl | ./assessment-player.component.html |
constructor(resourceService: ResourceService, activatedRoute: ActivatedRoute, courseConsumptionService: CourseConsumptionService, configService: ConfigService, courseBatchService: CourseBatchService, toasterService: ToasterService, location: Location, playerService: PlayerService, publicPlayerService: PublicPlayerService, userService: UserService, assessmentScoreService: AssessmentScoreService, navigationHelperService: NavigationHelperService, router: Router, contentUtilsServiceService: ContentUtilsServiceService, telemetryService: TelemetryService, layoutService: LayoutService, generaliseLabelService: GeneraliseLabelService, CourseProgressService: CourseProgressService, CsCourseService: CsCourseService, notificationService: NotificationServiceImpl)
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Parameters :
|
assessmentEvents | |
Type : EventEmitter
|
|
window:beforeunload |
window:beforeunload()
|
calculateProgress | ||||||
calculateProgress(isLogAuditEvent?: boolean)
|
||||||
Parameters :
Returns :
void
|
canDeactivate |
canDeactivate()
|
Decorators :
@HostListener('window:beforeunload')
|
Returns :
boolean
|
Public contentProgressEvent | ||||
contentProgressEvent(event)
|
||||
Parameters :
Returns :
void
|
Private firstNonCollectionContent | ||||
firstNonCollectionContent(contents)
|
||||
Parameters :
Returns :
any
|
Public getActiveContent |
getActiveContent()
|
Returns :
any
|
Private getCollectionInfo | ||||||
getCollectionInfo(courseId: string)
|
||||||
Parameters :
Returns :
Observable<any>
|
Private getContentState |
getContentState()
|
Returns :
void
|
getContentStateRequest | ||||||
getContentStateRequest(course: any)
|
||||||
Parameters :
Returns :
{ userId: any; courseId: string; contentIds: any; batchId: string; fields: {}; }
|
getCourseCompletionStatus | ||||||||
getCourseCompletionStatus(showPopup: boolean)
|
||||||||
Parameters :
Returns :
void
|
Public getCurrentContent |
getCurrentContent()
|
Returns :
any
|
getLanguageChangeEvent |
getLanguageChangeEvent()
|
Returns :
void
|
goBack |
goBack()
|
Returns :
void
|
highlightContent |
highlightContent()
|
Returns :
void
|
initLayout |
initLayout()
|
Returns :
void
|
Private initPlayer | ||||||
initPlayer(id: string)
|
||||||
Parameters :
Returns :
void
|
logAuditEvent | ||||||
logAuditEvent(isUnit?: boolean)
|
||||||
Parameters :
Returns :
void
|
logTelemetry | ||||||||||||
logTelemetry(id, content?: literal type, rollup?)
|
||||||||||||
Parameters :
Returns :
void
|
navigateToInitPlayer | |||||||||
navigateToInitPlayer(event: any, id)
|
|||||||||
Parameters :
Returns :
any
|
navigateToPlayerPage | |||||||||
navigateToPlayerPage(collectionUnit: literal type, event?)
|
|||||||||
Parameters :
Returns :
void
|
ngOnDestroy |
ngOnDestroy()
|
Returns :
void
|
ngOnInit |
ngOnInit()
|
Returns :
void
|
onAssessmentEvents | ||||
onAssessmentEvents(event)
|
||||
Parameters :
Returns :
void
|
onCourseCompleteClose |
onCourseCompleteClose()
|
Returns :
void
|
onQuestionScoreReviewEvents | ||||
onQuestionScoreReviewEvents(event)
|
||||
Parameters :
Returns :
void
|
onQuestionScoreSubmitEvents | ||||
onQuestionScoreSubmitEvents(event)
|
||||
Parameters :
Returns :
void
|
onRatingPopupClose |
onRatingPopupClose()
|
Returns :
void
|
onSelfAssessLastAttempt | ||||
onSelfAssessLastAttempt(event)
|
||||
Parameters :
Returns :
void
|
onShareLink |
onShareLink()
|
Returns :
void
|
onTocCardClick |
onTocCardClick()
|
Returns :
void
|
routerEventsChangeHandler |
routerEventsChangeHandler()
|
Returns :
any
|
setActiveContent |
setActiveContent(selectedContent: string, isSingleContent?: boolean)
|
Returns :
void
|
Private setTelemetryContentImpression |
setTelemetryContentImpression()
|
Returns :
void
|
Private setTelemetryCourseImpression |
setTelemetryCourseImpression()
|
Returns :
void
|
setTelemetryShareData | ||||
setTelemetryShareData(param)
|
||||
Parameters :
Returns :
void
|
Private subscribeToContentProgressEvents |
subscribeToContentProgressEvents()
|
Returns :
any
|
Private subscribeToQueryParam |
subscribeToQueryParam()
|
Returns :
void
|
Private validEndEvent | ||||||
validEndEvent(event)
|
||||||
Parameters :
Returns :
number
|
_routerStateContentStatus |
Type : any
|
activeContent |
Type : any
|
assessmentMaxAttempts |
Type : number
|
batchId |
Type : string
|
cardType |
Type : TocCardType
|
Default value : TocCardType.COURSE
|
certificateDescription |
Type : object
|
Default value : {}
|
collectionId |
Type : string
|
consumedContents |
Type : number
|
Default value : 0
|
contentProgressEvents$ |
Default value : new Subject()
|
contentRatingModal |
Default value : false
|
contentStatus |
Type : []
|
Default value : []
|
courseFallbackImg |
Type : string
|
Default value : './../../../../../assets/images/book.png'
|
courseHierarchy |
courseId |
Type : string
|
courseName |
Type : string
|
courseProgress |
Type : number
|
enrolledBatchInfo |
Public generaliseLabelService |
Type : GeneraliseLabelService
|
groupId |
isContentPresent |
Default value : false
|
isCourseCompleted |
Default value : false
|
isCourseCompletionPopupShown |
Default value : false
|
isFullScreenView |
Default value : false
|
isParentCourse |
Default value : false
|
isRouterExtrasAvailable |
Default value : false
|
isStatusChange |
Default value : false
|
isUnitCompleted |
Default value : false
|
lastActiveContentBeforeModuleChange |
layoutConfiguration |
modal |
Decorators :
@ViewChild('modal')
|
navigationObj |
Type : literal type
|
nextModule |
noContentMessage |
Type : string
|
Default value : ''
|
Private objectRollUp |
Type : object
|
Default value : {}
|
parentCourse |
playerConfig |
playerOption |
previousContent |
Type : null
|
Default value : null
|
prevModule |
Public resourceService |
Type : ResourceService
|
shareLink |
Type : string
|
shareLinkModal |
Type : boolean
|
showCourseCompleteMessage |
Default value : false
|
showLastAttemptsModal |
Default value : false
|
showLoader |
Default value : true
|
showMaxAttemptsModal |
Default value : false
|
showPlayer |
Default value : false
|
showQSExitConfirmation |
Default value : false
|
telemetryCdata |
Type : Array<literal type>
|
telemetryContentImpression |
Type : IImpressionEventInput
|
telemetryPlayerPageImpression |
Type : IImpressionEventInput
|
telemetryShareData |
Type : Array<ITelemetryShare>
|
totalContents |
Type : number
|
Default value : 0
|
Public treeModel |
Type : any
|
Private unsubscribe |
Default value : new Subject<void>()
|
import { Location } from '@angular/common';
import { TelemetryService, IAuditEventInput, IImpressionEventInput } from '@sunbird/telemetry';
import { Component, OnInit, OnDestroy, ViewChild, Inject, HostListener, EventEmitter, Output } from '@angular/core';
import { ActivatedRoute, Router, NavigationExtras, NavigationStart } from '@angular/router';
import { TocCardType } from '@project-sunbird/common-consumption';
import { UserService, GeneraliseLabelService, PlayerService } from '@sunbird/core';
import { AssessmentScoreService, CourseBatchService, CourseConsumptionService, CourseProgressService } from '@sunbird/learn';
import { PublicPlayerService, ComponentCanDeactivate } from '@sunbird/public';
import { ConfigService, ResourceService, ToasterService, NavigationHelperService,
ContentUtilsServiceService, ITelemetryShare, LayoutService } from '@sunbird/shared';
import * as _ from 'lodash-es';
import { combineLatest, Observable, Subject } from 'rxjs';
import { first, map, takeUntil, tap } from 'rxjs/operators';
import { CsContentProgressCalculator } from '@project-sunbird/client-services/services/content/utilities/content-progress-calculator';
import TreeModel from 'tree-model';
import { NotificationServiceImpl } from '../../../../notification/services/notification/notification-service-impl';
import { CsCourseService } from '@project-sunbird/client-services/services/course/interface';
import { result } from 'lodash';
const ACCESSEVENT = 'renderer:question:submitscore';
@Component({
selector: 'app-assessment-player',
templateUrl: './assessment-player.component.html',
styleUrls: ['./assessment-player.component.scss']
})
export class AssessmentPlayerComponent implements OnInit, OnDestroy, ComponentCanDeactivate {
constructor(
public resourceService: ResourceService,
private activatedRoute: ActivatedRoute,
private courseConsumptionService: CourseConsumptionService,
private configService: ConfigService,
private courseBatchService: CourseBatchService,
private toasterService: ToasterService,
private location: Location,
private playerService: PlayerService,
private publicPlayerService: PublicPlayerService,
private userService: UserService,
private assessmentScoreService: AssessmentScoreService,
private navigationHelperService: NavigationHelperService,
private router: Router,
private contentUtilsServiceService: ContentUtilsServiceService,
private telemetryService: TelemetryService,
private layoutService: LayoutService,
public generaliseLabelService: GeneraliseLabelService,
private CourseProgressService: CourseProgressService,
@Inject('CS_COURSE_SERVICE') private CsCourseService: CsCourseService,
@Inject('SB_NOTIFICATION_SERVICE') private notificationService: NotificationServiceImpl
) {
this.playerOption = {
showContentRating: true
};
// this.assessmentMaxAttempts = this.configService.appConfig.CourseConsumption.selfAssessMaxLimit;
const _routerExtras = this.router.getCurrentNavigation();
if (_.get(_routerExtras, 'extras.state')) {
this.isRouterExtrasAvailable = true;
this._routerStateContentStatus = _.get(_routerExtras, 'extras.state.contentStatus');
this.contentStatus = _.get(_routerExtras, 'extras.state.contentStatus.content') ?
_.get(_routerExtras, 'extras.state.contentStatus.content') : [];
}
}
@ViewChild('modal') modal;
@Output() assessmentEvents = new EventEmitter<any>();
private unsubscribe = new Subject<void>();
contentProgressEvents$ = new Subject();
batchId: string;
collectionId: string;
courseId: string;
courseHierarchy;
enrolledBatchInfo;
showLoader = true;
noContentMessage = '';
activeContent: any;
isContentPresent = false;
courseFallbackImg = './../../../../../assets/images/book.png';
cardType: TocCardType = TocCardType.COURSE;
contentStatus = [];
playerConfig;
playerOption;
courseName: string;
courseProgress: number;
private objectRollUp = {};
public treeModel: any;
isParentCourse = false;
telemetryContentImpression: IImpressionEventInput;
telemetryPlayerPageImpression: IImpressionEventInput;
telemetryCdata: Array<{}>;
shareLink: string;
telemetryShareData: Array<ITelemetryShare>;
shareLinkModal: boolean;
isUnitCompleted = false;
isFullScreenView = false;
isCourseCompleted = false;
showCourseCompleteMessage = false;
certificateDescription = {};
parentCourse;
prevModule;
nextModule;
totalContents = 0;
consumedContents = 0;
layoutConfiguration;
isCourseCompletionPopupShown = false;
previousContent = null;
groupId;
assessmentMaxAttempts: number;
showMaxAttemptsModal = false;
isRouterExtrasAvailable = false;
_routerStateContentStatus: any;
showLastAttemptsModal = false;
navigationObj: { event: any; id: any; };
showPlayer = false;
showQSExitConfirmation = false;
isStatusChange = false;
lastActiveContentBeforeModuleChange;
contentRatingModal = false;
@HostListener('window:beforeunload')
canDeactivate() {
// returning true will navigate without confirmation
// returning false will show a confirm dialog before navigating away
return _.get(this.activeContent, 'mimeType') === 'application/vnd.sunbird.questionset' && !this.showQSExitConfirmation ? false : true;
}
navigateToPlayerPage(collectionUnit: {}, event?) {
this.previousContent = null;
this.lastActiveContentBeforeModuleChange = this.activeContent;
const navigationExtras: NavigationExtras = {
queryParams: { batchId: this.batchId, courseId: this.courseId, courseName: this.parentCourse.name },
state: { contentStatus: this._routerStateContentStatus }
};
if (event && !_.isEmpty(event.event)) {
navigationExtras.queryParams.selectedContent = event.data.identifier;
} else if (_.get(collectionUnit, 'mimeType') === 'application/vnd.ekstep.content-collection' && _.get(collectionUnit, 'children.length')
&& _.get(this.contentStatus, 'length')) {
const parsedChildren = this.courseConsumptionService.parseChildren(collectionUnit);
const collectionChildren = [];
this.contentStatus.forEach(item => {
if (parsedChildren.find(content => content === item.contentId)) {
collectionChildren.push(item);
}
});
/* istanbul ignore else */
if (collectionChildren.length) {
const selectedContent: any = collectionChildren.find(item => item.status !== 2);
/* istanbul ignore else */
if (selectedContent) {
navigationExtras.queryParams.selectedContent = selectedContent.contentId;
}
}
}
this.router.navigate(['/learn/course/play', _.get(collectionUnit, 'identifier')], navigationExtras);
}
ngOnInit() {
this.layoutConfiguration = this.layoutService.initlayoutConfig();
this.initLayout();
this.subscribeToQueryParam();
this.subscribeToContentProgressEvents().subscribe(data => { });
this.navigationHelperService.contentFullScreenEvent.
pipe(takeUntil(this.unsubscribe)).subscribe(isFullScreen => {
this.isFullScreenView = isFullScreen;
});
this.noContentMessage = _.get(this.resourceService, 'messages.stmsg.m0121');
this.getLanguageChangeEvent();
this.routerEventsChangeHandler().subscribe();
}
initLayout() {
this.layoutConfiguration = this.layoutService.initlayoutConfig();
this.layoutService.switchableLayout().
pipe(takeUntil(this.unsubscribe)).subscribe(layoutConfig => {
if (layoutConfig != null) {
this.layoutConfiguration = layoutConfig.layout;
}
});
}
goBack() {
this.previousContent = null;
this.lastActiveContentBeforeModuleChange = this.activeContent;
const paramas = {};
if (!this.isCourseCompletionPopupShown) {
paramas['showCourseCompleteMessage'] = true;
}
if (_.get(this.activatedRoute, 'snapshot.queryParams.textbook')) {
paramas['textbook'] = _.get(this.activatedRoute, 'snapshot.queryParams.textbook');
}
if (!this.isCourseCompleted) {
this.isStatusChange = true;
}
setTimeout(() => {
if (this.batchId) {
this.router.navigate(['/learn/course', this.courseId, 'batch', this.batchId], {queryParams: paramas});
} else {
this.router.navigate(['/learn/course', this.courseId], { queryParams: paramas });
}
}, 500);
}
getLanguageChangeEvent() {
this.resourceService.languageSelected$.pipe(takeUntil(this.unsubscribe)).subscribe(item => {
this.noContentMessage = _.get(this.resourceService, 'messages.stmsg.m0121');
});
}
private subscribeToQueryParam() {
combineLatest([this.activatedRoute.params, this.activatedRoute.queryParams])
.pipe(takeUntil(this.unsubscribe))
.subscribe(([params, queryParams]) => {
this.consumedContents = 0;
this.totalContents = 0;
this.collectionId = params.collectionId;
this.batchId = queryParams.batchId;
this.courseId = queryParams.courseId;
this.courseName = queryParams.courseName;
this.groupId = _.get(queryParams, 'groupId');
const selectedContent = queryParams.selectedContent;
let isSingleContent = this.collectionId === selectedContent;
this.isParentCourse = this.collectionId === this.courseId;
if (this.batchId) {
this.telemetryCdata = [{ id: this.batchId, type: 'CourseBatch' }];
if (this.groupId) {
this.telemetryCdata.push({
id: this.groupId,
type: 'Group'
});
}
this.getCollectionInfo(this.courseId)
.pipe(takeUntil(this.unsubscribe))
.subscribe((data) => {
const model = new TreeModel();
this.treeModel = model.parse(data.courseHierarchy);
this.parentCourse = data.courseHierarchy;
const module = this.courseConsumptionService.setPreviousAndNextModule(this.parentCourse, this.collectionId);
this.nextModule = _.get(module, 'next');
this.prevModule = _.get(module, 'prev');
this.getCourseCompletionStatus();
this.layoutService.updateSelectedContentType.emit(data.courseHierarchy.contentType);
if (!this.isParentCourse && data.courseHierarchy.children) {
this.courseHierarchy = data.courseHierarchy.children.find(item => item.identifier === this.collectionId);
} else {
this.courseHierarchy = data.courseHierarchy;
}
if (!isSingleContent && _.get(this.courseHierarchy, 'mimeType') !==
this.configService.appConfig.PLAYER_CONFIG.MIME_TYPE.collection) {
isSingleContent = true;
}
this.enrolledBatchInfo = data.enrolledBatchDetails;
this.certificateDescription = this.courseBatchService.getcertificateDescription(this.enrolledBatchInfo);
this.setActiveContent(selectedContent, isSingleContent);
}, error => {
this.toasterService.error(this.resourceService.messages.fmsg.m0051);
this.goBack();
});
} else {
this.telemetryCdata = [{
id: this.groupId,
type: 'Group'
}];
this.publicPlayerService.getCollectionHierarchy(this.collectionId, {})
.pipe(takeUntil(this.unsubscribe))
.subscribe((data) => {
this.courseHierarchy = data.result.content;
this.layoutService.updateSelectedContentType.emit(this.courseHierarchy.contentType);
if (this.courseHierarchy.mimeType !== 'application/vnd.ekstep.content-collection') {
this.activeContent = this.courseHierarchy;
this.initPlayer(_.get(this.activeContent, 'identifier'));
} else {
this.setActiveContent(selectedContent);
}
}, error => {
this.toasterService.error(this.resourceService.messages.fmsg.m0051);
this.goBack();
});
}
this.setTelemetryCourseImpression();
});
}
private getCollectionInfo(courseId: string): Observable<any> {
const inputParams = { params: this.configService.appConfig.CourseConsumption.contentApiQueryParams };
return combineLatest([
this.courseConsumptionService.getCourseHierarchy(courseId, inputParams),
this.courseBatchService.getEnrolledBatchDetails(this.batchId),
]).pipe(map((results: any) => {
return {
courseHierarchy: results[0],
enrolledBatchDetails: results[1],
};
}));
}
getContentStateRequest(course: any) {
return {
userId: this.userService.userid,
courseId: this.courseId,
contentIds: this.courseConsumptionService.parseChildren(course),
batchId: this.batchId,
fields: ['progress', 'score']
};
}
setActiveContent(selectedContent: string, isSingleContent?: boolean) {
this.previousContent = _.cloneDeep(this.activeContent);
if (_.get(this.courseHierarchy, 'children')) {
let flattenDeepContents = this.courseConsumptionService.flattenDeep(this.courseHierarchy.children);
flattenDeepContents = _.filter(flattenDeepContents, (o) => o.mimeType !== 'application/vnd.sunbird.question');
if (selectedContent) {
this.activeContent = flattenDeepContents.find(content => content.identifier === selectedContent);
} else {
this.activeContent = this.firstNonCollectionContent(flattenDeepContents);
}
/* istanbul ignore else */
if (this.activeContent) {
this.isContentPresent = true;
this.initPlayer(_.get(this.activeContent, 'identifier'));
}
} else if (isSingleContent) {
this.activeContent = this.courseHierarchy;
this.initPlayer(_.get(this.activeContent, 'identifier'));
}
this.getContentState();
}
private firstNonCollectionContent(contents) {
return contents.find((content) => content.mimeType !== 'application/vnd.ekstep.content-collection');
}
navigateToInitPlayer(event: any, id) {
this.navigationObj = {
event: event,
id: id
};
if (_.get(event, 'event.isDisabled')) {
return this.toasterService.error(_.get(this.resourceService, 'frmelmnts.lbl.selfAssessMaxAttempt'));
} else if (_.get(event, 'event.isLastAttempt') && !this._routerStateContentStatus) {
this.showLastAttemptsModal = true;
} else if (_.get(this.navigationObj, 'event.data') && this.navigationObj?.event?.data?.identifier !== this.activeContent?.identifier) {
this.onTocCardClick();
}
}
onTocCardClick() {
this.showPlayer = false;
this.previousContent = _.cloneDeep(this.activeContent);
this.activeContent = this.navigationObj.event.data;
this.initPlayer(_.get(this.activeContent, 'identifier'));
this.highlightContent();
this.logTelemetry(this.navigationObj.id, this.navigationObj.event.data);
}
private getContentState() {
if (this.batchId && (_.get(this.activeContent, 'contentType') === 'SelfAssess' || !this.isRouterExtrasAvailable)) {
const req: any = this.getContentStateRequest(this.courseHierarchy);
this.CsCourseService
.getContentState(req, { apiPath: '/content/course/v1' })
.pipe(takeUntil(this.unsubscribe))
.subscribe((_res) => {
const res = this.CourseProgressService.getContentProgressState(req, _res);
const _contentIndex = _.findIndex(this.contentStatus, {contentId: _.get(this.activeContent, 'identifier')});
const _resIndex = _.findIndex(res.content, {contentId: _.get(this.activeContent, 'identifier')});
if (_.get(this.activeContent, 'contentType') === 'SelfAssess' && this.isRouterExtrasAvailable) {
this.contentStatus[_contentIndex]['status'] = _.get(res.content[_resIndex], 'status');
} else {
this.contentStatus = res.content || [];
}
this.highlightContent();
this.calculateProgress();
}, error => {
console.log('Content state read CSL API failed ', error);
});
} else {
this.highlightContent();
this.calculateProgress();
}
}
public getCurrentContent() {
return this.previousContent ? this.previousContent : this.activeContent;
}
public getActiveContent() {
return this.previousContent ? this.previousContent : this.activeContent;
}
public contentProgressEvent(event) {
/* istanbul ignore else */
if (!this.batchId || _.get(this.enrolledBatchInfo, 'status') !== 1) {
return;
}
const telObject = _.get(event, 'detail.telemetryData');
const eid = _.get(telObject, 'eid');
const isMimeTypeH5P = _.get(this.activeContent, 'mimeType') === 'application/vnd.ekstep.h5p-archive';
/* istanbul ignore else */
if (eid === 'END' && !isMimeTypeH5P && !this.validEndEvent(event)) {
return;
}
const request: any = {
userId: this.userService.userid,
contentId: _.cloneDeep(_.get(telObject, 'object.id')) || _.get(this.activeContent, 'identifier'),
courseId: this.courseId,
batchId: this.batchId,
status: (eid === 'END' && (_.get(this.getCurrentContent, 'contentType') !== 'SelfAssess') && this.courseProgress === 100
&& !this.isStatusChange) ? 2 : 1,
progress: (this.courseProgress && !this.isStatusChange) ? this.courseProgress : undefined
};
// Set status to 2 if mime type is application/vnd.ekstep.h5p-archive
if (isMimeTypeH5P) {
request['status'] = 2;
}
/* istanbul ignore else */
if (!eid) {
const contentType = this.activeContent.contentType;
/* istanbul ignore else */
if (contentType === 'SelfAssess' && _.get(event, 'data') === ACCESSEVENT) {
request['status'] = 2;
}
}
/* istanbul ignore else */
if (request.status === 2 && !this.isUnitCompleted) {
this.logAuditEvent();
}
const currentContent = this.getActiveContent();
const summary = _.get(telObject, 'edata.summary');
let totallength;
summary?.forEach((k) => {
if (k['totallength']) {
totallength = k['totallength'];
}
});
if ((_.get(currentContent, 'mimeType') === 'application/pdf') && eid === 'END' && totallength === 1) {
request['status'] = 2;
}
this.courseConsumptionService.updateContentsState(request)
.pipe(takeUntil(this.unsubscribe))
.subscribe(updatedRes => {
if (!this.isRouterExtrasAvailable) {
this.contentStatus = _.cloneDeep(updatedRes.content);
} else {
const _contentIndex = _.findIndex(this.contentStatus, {contentId: request.contentId});
const _resIndex = _.findIndex(updatedRes.content, {contentId: request.contentId});
if (_resIndex !== -1) {
// Update the available status data object
this._routerStateContentStatus['progress'] = _.get(updatedRes, 'progress');
this.contentStatus[_contentIndex]['status'] = _.get(updatedRes.content[_resIndex], 'status');
this._routerStateContentStatus['totalCount'] = _.get(updatedRes, 'totalCount');
this._routerStateContentStatus['completedCount'] = _.get(updatedRes, 'completedCount');
}
}
/* istanbul ignore else */
if (!this.isUnitCompleted) {
this.calculateProgress(true);
}
}, err => console.error('updating content status failed', err));
this.courseConsumptionService.updateContentState.emit();
}
onAssessmentEvents(event) {
/* istanbul ignore else */
if (!this.batchId || _.get(this.enrolledBatchInfo, 'status') !== 1) {
return;
}
this.assessmentScoreService.receiveTelemetryEvents(event);
this.calculateProgress();
this.assessmentEvents.emit(event);
}
onQuestionScoreReviewEvents(event) {
this.assessmentScoreService.handleReviewButtonClickEvent();
}
onQuestionScoreSubmitEvents(event) {
/* istanbul ignore else */
if (event) {
this.assessmentScoreService.handleSubmitButtonClickEvent(true);
this.contentProgressEvent(event);
}
}
/**
* @since #SH-120
* @param {object} event - telemetry end event data
* @description - It will return the progress calculated from client-service(Common Consumption)
*/
private validEndEvent(event) {
const playerSummary: Array<any> = _.get(event, 'detail.telemetryData.edata.summary');
const contentMimeType = _.get(this.previousContent, 'mimeType') ? _.get(this.previousContent, 'mimeType') : _.get(this.activeContent, 'mimeType');
const contentType = _.get(this.previousContent, 'primaryCategory') ? _.get(this.previousContent, 'primaryCategory') : _.get(this.activeContent, 'primaryCategory');
this.courseProgress = CsContentProgressCalculator.calculate(playerSummary, contentMimeType);
console.log(_.find(playerSummary, ['endpageseen', true]));
if (_.toLower(contentType) === 'course assessment') {
this.courseProgress = _.find(playerSummary, ['endpageseen', true]) ||
_.find(playerSummary, ['visitedcontentend', true]) ? this.courseProgress : 0;
}
return this.courseProgress;
}
calculateProgress(isLogAuditEvent?: boolean) {
/* istanbul ignore else */
if (this.batchId && _.get(this.courseHierarchy, 'children')) {
this.consumedContents = 0;
this.totalContents = 0;
this.courseHierarchy.children.forEach(unit => {
if (unit.mimeType === 'application/vnd.ekstep.content-collection') {
let consumedContents = [];
let flattenDeepContents = [];
/* istanbul ignore else */
if (_.get(unit, 'children.length')) {
flattenDeepContents = this.courseConsumptionService.flattenDeep(unit.children).filter(item => item.mimeType !== 'application/vnd.ekstep.content-collection');
/* istanbul ignore else */
if (this.contentStatus && this.contentStatus.length) {
consumedContents = flattenDeepContents.filter(o => {
return this.contentStatus.some(({ contentId, status }) => o.identifier === contentId && status === 2);
});
}
}
unit.consumedContent = consumedContents.length;
unit.contentCount = flattenDeepContents.length;
unit.isUnitConsumed = consumedContents.length === flattenDeepContents.length;
unit.isUnitConsumptionStart = false;
if (consumedContents.length) {
unit.progress = (consumedContents.length / flattenDeepContents.length) * 100;
unit.isUnitConsumptionStart = true;
} else {
unit.progress = 0;
unit.isUnitConsumptionStart = false;
}
} else {
const consumedContent = this.contentStatus.filter(({ contentId, status }) => unit.identifier === contentId && status === 2);
unit.consumedContent = consumedContent.length;
unit.contentCount = 1;
unit.isUnitConsumed = consumedContent.length === 1;
unit.progress = consumedContent.length ? 100 : 0;
unit.isUnitConsumptionStart = Boolean(consumedContent.length);
}
this.consumedContents = this.consumedContents + unit.consumedContent;
this.totalContents = this.totalContents + unit.contentCount;
this.courseHierarchy.progress = 0;
/* istanbul ignore else */
if (this.consumedContents) {
this.courseHierarchy.progress = (this.consumedContents / this.totalContents) * 100;
}
this.isUnitCompleted = this.totalContents === this.consumedContents;
/* istanbul ignore else */
if (isLogAuditEvent && this.isUnitCompleted) {
this.logAuditEvent(true);
}
});
} else {
this.isUnitCompleted = false;
if (this.contentStatus && this.contentStatus.length) {
const contentState = this.contentStatus.filter(({ contentId, status }) =>
_.get(this.courseHierarchy, 'identifier') === contentId && status === 2);
if (contentState.length > 0) {
this.isUnitCompleted = true;
}
}
if (isLogAuditEvent && this.isUnitCompleted) {
this.logAuditEvent(true);
}
}
}
private subscribeToContentProgressEvents() {
return this.contentProgressEvents$.pipe(
map(event => {
this.contentProgressEvent(event);
return {
contentProgressEvent: event
};
}),
takeUntil(this.unsubscribe)
);
}
logTelemetry(id, content?: {}, rollup?) {
if (this.batchId) {
this.telemetryCdata = [{ id: this.batchId, type: 'CourseBatch' }];
}
if (rollup) {
rollup = {l1: this.courseId};
}
const objectRollUp = this.courseConsumptionService.getContentRollUp(this.courseHierarchy, _.get(content, 'identifier'));
const interactData = {
context: {
env: _.get(this.activatedRoute.snapshot.data.telemetry, 'env') || 'content',
cdata: this.telemetryCdata
},
edata: {
id: id,
type: 'click',
pageid: _.get(this.activatedRoute.snapshot.data.telemetry, 'pageid') || 'play-collection',
},
object: {
id: content ? _.get(content, 'identifier') : this.courseId,
type: content ? _.get(content, 'contentType') : 'Course',
ver: content ? `${_.get(content, 'pkgVersion')}` : `1.0`,
rollup: rollup || this.courseConsumptionService.getRollUp(objectRollUp) || {}
}
};
this.telemetryService.interact(interactData);
}
private setTelemetryContentImpression() {
const objectRollUp = this.courseConsumptionService.getContentRollUp(this.courseHierarchy, _.get(this.activeContent, 'identifier'));
const telemetryContentImpression = {
context: {
env: this.activatedRoute.snapshot.data.telemetry.env,
cdata: this.telemetryCdata
},
edata: {
type: this.activatedRoute.snapshot.data.telemetry.type,
pageid: this.activatedRoute.snapshot.data.telemetry.pageid,
uri: this.router.url,
},
object: {
id: this.activeContent.identifier,
type: this.activeContent.contentType || 'content',
ver: this.activeContent.pkgVersion ? this.activeContent.pkgVersion.toString() : '1.0',
rollup: this.courseConsumptionService.getRollUp(objectRollUp) || {}
}
};
this.telemetryService.impression(telemetryContentImpression);
}
private setTelemetryCourseImpression() {
this.telemetryPlayerPageImpression = {
context: {
env: this.activatedRoute.snapshot.data.telemetry.env,
cdata: this.telemetryCdata
},
edata: {
type: this.activatedRoute.snapshot.data.telemetry.type,
pageid: this.activatedRoute.snapshot.data.telemetry.pageid,
uri: this.router.url,
},
object: {
id: this.courseId || _.get(this.courseHierarchy, 'identifier'),
type: _.get(this.courseHierarchy, 'contentType') || 'Course',
ver: `${_.get(this.courseHierarchy, 'pkgVersion')}` || '1.0',
rollup: { l1: this.courseId }
}
};
}
logAuditEvent(isUnit?: boolean) {
const auditEventInput: IAuditEventInput = {
'context': {
'env': this.activatedRoute.snapshot.data.telemetry.env,
'cdata': [
{ id: this.courseId, type: 'CourseId' },
{ id: this.userService.userid, type: 'UserId' },
{ id: this.batchId, type: 'BatchId' },
]
},
'object': {
'id': this.batchId,
'type': this.activatedRoute.snapshot.data.telemetry.object.type,
'ver': this.activatedRoute.snapshot.data.telemetry.object.ver,
'rollup': { l1: this.courseId }
},
'edata': {
props: ['courseId', 'userId', 'batchId'],
state: '',
prevstate: ''
}
};
if (isUnit) {
auditEventInput.context.cdata.push({ id: this.courseHierarchy.identifier, type: 'UnitId' });
auditEventInput.edata.props.push('unitId');
} else {
auditEventInput.context.cdata.push({ id: this.activeContent.identifier, type: 'ContentId' });
auditEventInput.edata.props.push('contentId');
}
this.telemetryService.audit(auditEventInput);
}
ngOnDestroy() {
this.unsubscribe.next();
this.unsubscribe.complete();
}
onShareLink() {
this.shareLink = this.contentUtilsServiceService.getCourseModulePublicShareUrl(this.courseId, this.collectionId);
this.setTelemetryShareData(this.courseHierarchy);
}
onRatingPopupClose() {
this.contentRatingModal = false;
this.getCourseCompletionStatus(true);
}
setTelemetryShareData(param) {
this.telemetryShareData = [{
id: param.identifier,
type: param.contentType,
ver: param.pkgVersion ? param.pkgVersion.toString() : '1.0'
}];
}
onCourseCompleteClose() {
this.showCourseCompleteMessage = false;
}
highlightContent() {
if (this.contentStatus && this.contentStatus.length > 0) {
this.contentStatus.forEach((item) => {
if (_.get(item, 'contentId') === _.get(this.activeContent, 'identifier') && item.status === 0) {
item.status = 1;
}
});
}
}
getCourseCompletionStatus(showPopup: boolean = false) {
/* istanbul ignore else */
if (!this.isCourseCompleted) {
let maxAttemptsExceeded = false;
this.showMaxAttemptsModal = false;
let isLastAttempt = false;
const req: any = this.getContentStateRequest(this.parentCourse);
if (_.get(this.activeContent, 'contentType') === 'SelfAssess' || !this.isRouterExtrasAvailable) {
this.CsCourseService
.getContentState(req, { apiPath: '/content/course/v1' })
.pipe(takeUntil(this.unsubscribe))
.subscribe((_res) => {
const res = this.CourseProgressService.getContentProgressState(req, _res);
/* istanbul ignore next*/
_.forEach(_.get(res, 'content'), (contentState) => {
if (_.get(contentState, 'contentId') === this.activeContent.identifier) {
/* istanbul ignore next*/
if (_.get(contentState, 'score.length') >= _.get(this.activeContent, 'maxAttempts')) { maxAttemptsExceeded = true; }
if (_.get(this.activeContent, 'maxAttempts') - _.get(contentState, 'score.length') === 1) { isLastAttempt = true; }
/* istanbul ignore next*/
if (_.get(this.activeContent, 'contentType') === 'SelfAssess') {
/* istanbul ignore next*/
const _contentIndex = _.findIndex(this.contentStatus, {contentId: _.get(this.activeContent, 'identifier')});
this.contentStatus[_contentIndex]['bestScore'] = _.get(contentState, 'bestScore');
this.contentStatus[_contentIndex]['score'] = _.get(contentState, 'score');
}
}
});
/* istanbul ignore else */
if (maxAttemptsExceeded && !showPopup) {
this.showMaxAttemptsModal = true;
this.showQSExitConfirmation = true;
} else if (isLastAttempt) {
this.toasterService.error(_.get(this.resourceService, 'frmelmnts.lbl.selfAssessLastAttempt'));
} else if (_.get(res, 'content.length')) {
this.isCourseCompleted = (res.totalCount === res.completedCount);
if (!this.isCourseCompleted && !res.totalCount) {
this.isCourseCompleted = res.progress >= 100 ? true : this.isCourseCompleted;
}
this.showCourseCompleteMessage = this.isCourseCompleted && showPopup;
if (this.showCourseCompleteMessage) {
this.notificationService.fetchNotificationList();
}
this.isCourseCompletionPopupShown = this.isCourseCompleted;
}
this.calculateProgress();
}, error => {
console.log('Content state read CSL API failed ', error);
});
} else {
/* istanbul ignore next*/
_.forEach(this.contentStatus, (contentState) => {
/* istanbul ignore next*/
if (_.get(contentState, 'contentId') === _.get(this.activeContent, 'identifier')) {
/* istanbul ignore next*/
if (_.get(contentState, 'score.length') >= _.get(this.activeContent, 'maxAttempts')) { maxAttemptsExceeded = true; }
if (_.get(this.activeContent, 'maxAttempts') - _.get(contentState, 'score.length') === 1) { isLastAttempt = true; }
}
});
/* istanbul ignore else */
if (maxAttemptsExceeded) {
this.showMaxAttemptsModal = true;
this.showQSExitConfirmation = true;
} else if (isLastAttempt) {
this.toasterService.error(_.get(this.resourceService, 'frmelmnts.lbl.selfAssessLastAttempt'));
} else if (this.contentStatus && this.contentStatus.length) {
this.isCourseCompleted = (_.get(this._routerStateContentStatus, 'totalCount') === _.get(this._routerStateContentStatus, 'completedCount'));
this.showCourseCompleteMessage = this.isCourseCompleted && showPopup;
if (this.showCourseCompleteMessage) {
this.notificationService.fetchNotificationList();
}
this.isCourseCompletionPopupShown = this.isCourseCompleted;
}
}
}
}
private initPlayer(id: string) {
let maxAttemptsExceeded = false;
this.showMaxAttemptsModal = false;
let isLastAttempt = false;
/* istanbul ignore if */
if (_.get(this.activeContent, 'contentType') === 'SelfAssess') {
const _contentIndex = _.findIndex(this.contentStatus, {contentId: _.get(this.activeContent, 'identifier')});
/* istanbul ignore if */
if (_contentIndex > 0 && _.get(this.contentStatus[_contentIndex], 'score.length') >= _.get(this.activeContent, 'maxAttempts')) { maxAttemptsExceeded = true; }
if (_contentIndex > 0 && _.get(this.activeContent, 'maxAttempts') - _.get(this.contentStatus[_contentIndex], 'score.length') === 1) { isLastAttempt = true; }
}
/* istanbul ignore if */
if (maxAttemptsExceeded) {
this.showMaxAttemptsModal = true;
this.showQSExitConfirmation = true;
} else {
/* istanbul ignore if */
if (isLastAttempt && !this.showLastAttemptsModal && this._routerStateContentStatus) {
this.toasterService.error(_.get(this.resourceService, 'frmelmnts.lbl.selfAssessLastAttempt'));
}
this.assessmentScoreService.init({
batchDetails: this.enrolledBatchInfo,
courseDetails: this.courseHierarchy,
contentDetails: { identifier: id }
});
const options: any = { courseId: this.collectionId };
/* istanbul ignore else */
if (this.batchId) {
options.batchId = this.batchId;
}
if (this.activeContent.mimeType === this.configService.appConfig.PLAYER_CONFIG.MIME_TYPE.questionset) {
const serveiceRef = this.userService.loggedIn ? this.playerService : this.publicPlayerService;
this.publicPlayerService.getQuestionSetHierarchy(id).pipe(
takeUntil(this.unsubscribe))
.subscribe((response) => {
const objectRollup = this.courseConsumptionService.getContentRollUp(this.courseHierarchy, id);
this.objectRollUp = objectRollup ? this.courseConsumptionService.getRollUp(objectRollup) : {};
if (response && response.context) {
response.context.objectRollup = this.objectRollUp;
}
const contentDetails = {contentId: id, contentData: response.questionSet };
this.playerConfig = serveiceRef.getConfig(contentDetails);
this.publicPlayerService.getQuestionSetRead(id).subscribe((data: any) => {
this.playerConfig['metadata']['instructions'] = _.get(data, 'result.questionset.instructions');
this.showPlayer = true;
}, (error) => {
this.showPlayer = true;
});
const _contentIndex = _.findIndex(this.contentStatus, { contentId: _.get(this.playerConfig, 'context.contentId') });
this.playerConfig['metadata']['maxAttempt'] = _.get(this.activeContent, 'maxAttempts');
const _currentAttempt = _.get(this.contentStatus[_contentIndex], 'score.length') || 0;
this.playerConfig['metadata']['currentAttempt'] = _currentAttempt == undefined ? 0 : _currentAttempt;
this.playerConfig['context']['objectRollup'] = this.objectRollUp;
this.showLoader = false;
}, (err) => {
this.toasterService.error(this.resourceService.messages.stmsg.m0009);
this.showLoader = false;
});
} else {
this.courseConsumptionService.getConfigByContent(id, options)
.pipe(first(), takeUntil(this.unsubscribe))
.subscribe(config => {
this.showPlayer = true;
const objectRollup = this.courseConsumptionService.getContentRollUp(this.courseHierarchy, id);
this.objectRollUp = objectRollup ? this.courseConsumptionService.getRollUp(objectRollup) : {};
if (config && config.context) {
config.context.objectRollup = this.objectRollUp;
}
this.playerConfig = config;
const _contentIndex = _.findIndex(this.contentStatus, { contentId: _.get(config, 'context.contentId') });
this.playerConfig['metadata']['maxAttempt'] = _.get(this.activeContent, 'maxAttempts');
const _currentAttempt = _contentIndex > 0 ? _.get(this.contentStatus[_contentIndex], 'score.length') : 0;
this.playerConfig['metadata']['currentAttempt'] = _currentAttempt == undefined ? 0 : _currentAttempt;
this.showLoader = false;
this.setTelemetryContentImpression();
}, (err) => {
this.showLoader = false;
this.toasterService.error(this.resourceService.messages.stmsg.m0009);
});
}
}
}
onSelfAssessLastAttempt(event) {
if (_.get(event, 'data') === 'renderer:selfassess:lastattempt' || _.get(event, 'edata.isLastAttempt')) {
this.toasterService.error(_.get(this.resourceService, 'frmelmnts.lbl.selfAssessLastAttempt'));
}
if (_.get(event, 'data') === 'renderer:maxLimitExceeded' || _.get(event, 'edata.maxLimitExceeded')) {
this.showMaxAttemptsModal = true;
this.showQSExitConfirmation = true;
}
}
routerEventsChangeHandler() {
return this.router.events
.pipe(
takeUntil(this.unsubscribe),
tap(event => {
if (event instanceof NavigationStart) {
const isH5pContent = [_.get(this.playerConfig, 'metadata.mimeType'), _.get(this.activeContent, 'mimeType')].every(mimeType => mimeType === 'application/vnd.ekstep.h5p-archive');
if (isH5pContent) {
this.contentRatingModal = true;
}
}
})
)
}
}
<app-landing-section [textToDisplay]=""
[layoutConfiguration]="layoutConfiguration" [noTitle]="true"></app-landing-section>
<!-- Header: Back Button Previous Module & Next Module button -->
<div [ngClass]="layoutConfiguration ? 'sb-back-actionbar' : 'sb-bg-color-white back-btn-container cc-player__btn-back relative9'" class="relative position mt-0">
<div class="ui container py-0 px-0 d-flex flex-ai-center">
<div class="p-0 d-flex flex-ai-center w-100">
<!-- Back button -->
<button type="button" [ngClass]="layoutConfiguration ? 'sb-btn-primary sb-btn-round' : 'sb-btn-link sb-btn-link-primary sb-left-icon-btn px-0'" class="sb-btn sb-btn-normal" tabindex="0" (click)="goBack(); logTelemetry('back-track', courseHierarchy)" attr.aria-label="{{resourceService?.frmelmnts?.btn?.back}}">
<i class="icon-svg icon-svg--xxs icon-back mr-4"><svg class="icon icon-svg--primary">
<use xlink:href="assets/images/sprite.svg#arrow-long-left"></use>
</svg></i>
<span>{{resourceService?.frmelmnts?.btn?.back}}</span>
</button>
<div class="w-100 relative9 m-0 p-0 ml-16" [ngClass]="{'header-block': isFullScreenView}">
<div class="textbook p-0">
<!-- Textbook details -->
<div class="textbook__details d-flex flex-ai-center">
<!-- Textbook image -->
<div class="textbook__heading">
<!-- Textbook title -->
<h5 class="textbook__title sb-color-primary font-weight-bold mb-0" tabindex="0">{{courseHierarchy?.name | titlecase}}
</h5>
<!-- Textbook Rating with share icon -->
<div class="textbook__rating mt-4">
<button tabindex="0" (click)="onShareLink();shareLinkModal=true;"
class="sb-btn sb-btn-normal sb-btn-link sb-btn-link-primary sb-left-icon-btn">
<i class="icon-svg icon-svg--xxs icon-share mr-4"><svg class="icon icon-svg--primary">
<use xlink:href="assets/images/sprite.svg#share"></use>
</svg></i>
{{resourceService?.frmelmnts?.lbl?.share}}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div *ngIf="!showLoader" [appTelemetryImpression]="telemetryPlayerPageImpression" [ngClass]="layoutConfiguration ? 'sbt-inside-page-container' : ''" class="text-left relative9">
<!-- CC-Player Video -->
<div class="ui container">
<div class="cc-player sb-g">
<section class="sb-g-col-xs-12 sb-g-col-md-8 sb-g-col-lg-8 sb-g-col-xxxl-12 cc-player__video sb-toc-player-column">
<div class="toc-content text-left">
<div class="content-video">
<section class="content-video__player" id="player-area-height">
<div class="aspectratio" data-ratio="16:9" id="help-video-aspect-ratio" #aspectRatio>
<app-player *ngIf="showPlayer" class="content-player" [playerOption]="playerOption" [playerConfig]="playerConfig"
[contentProgressEvents$]="contentProgressEvents$" (assessmentEvents)="onAssessmentEvents($event)"
(questionScoreSubmitEvents)="onQuestionScoreSubmitEvents($event)"
(questionScoreReviewEvents)="onQuestionScoreReviewEvents($event)"
(ratingPopupClose)="onRatingPopupClose()" (selfAssessLastAttempt)="onSelfAssessLastAttempt($event)"></app-player>
</div>
<app-content-actions [assessmentEvents]="assessmentEvents" *ngIf="isFullScreenView" class="player-fullscreen-action-items" [isFullScreen]="true" [contentData]="activeContent" [objectRollUp]="objectRollUp">
</app-content-actions>
<app-content-actions [assessmentEvents]="assessmentEvents" *ngIf="activeContent" [contentData]="activeContent" [objectRollUp]="objectRollUp">
</app-content-actions>
</section>
</div>
<div class="mt-8">
<app-credits-and-licence *ngIf="activeContent" [contentData]="activeContent"></app-credits-and-licence>
</div>
</div>
</section>
<section class="sb-g-col-xs-12 sb-g-col-md-4 sb-g-col-lg-4 sb-g-col-xxxl-4">
<div class="cc-player__list px-0">
<!-- Module Progress bar -->
<div class="certified-course__progress" *ngIf="totalContents > 0">
<!-- Previous / next module buttons -->
<div class="d-flex flex-ai-center flex-jc-space-between mb-8">
<!-- Previous module button -->
<button class="sb-btn sb-btn-link sb-btn-link-primary sb-btn-normal sb-left-icon-btn" aria-label="previous-module" *ngIf="prevModule"
tabindex="0" (click)="navigateToPlayerPage(prevModule); logTelemetry('previous-module', prevModule, true);">
<i class="icon-svg icon-svg--xxs icon-back mr-4 rotate-90" role="img" aria-label="arrow-up"><svg class="icon icon-svg--primary"><use xlink:href="assets/images/sprite.svg#arrow-up"></use></svg></i>{{generaliseLabelService?.frmelmnts?.lbl?.prevModule | translate}}
</button>
<!-- Previous Module button -->
<!-- Next Module button -->
<button class="sb-btn sb-btn-link sb-btn-link-primary sb-btn-normal sb-right-icon-btn ml-auto" aria-label="next-module" *ngIf="nextModule"
tabindex="0" (click)="navigateToPlayerPage(nextModule); logTelemetry('next-module', nextModule, true)">
{{generaliseLabelService?.frmelmnts?.lbl?.nextModule | translate}}<i class="icon-svg icon-svg--xxs icon-back ml-4 rotate90" role="img" aria-label="arrow-up"><svg class="icon icon-svg--primary"><use xlink:href="assets/images/sprite.svg#arrow-up"></use></svg></i>
</button>
<!-- Next Module button -->
</div>
<div *ngIf="!isUnitCompleted">
<div class="fsmall">{{generaliseLabelService?.frmelmnts?.lbl?.moduleProgress}}</div>
<div class="sb-color-primary fnormal font-weight-bold mt-4">{{consumedContents}}/{{totalContents}} {{resourceService?.frmelmnts?.lbl?.completed}}</div>
<mat-progress-bar mode="determinate" [value]="consumedContents" class="mb-0 mr-0 mt-8 tiny"></mat-progress-bar>
</div>
<div class="module-complete-content d-flex flex-ai-center py-8 px-16 mb-8" *ngIf="isUnitCompleted">
<div class="mr-16 complete-content-image"><img src="./assets/images/badge-complete.svg" alt="complete-content-image"></div>
<label class="font-weight-bold mb-0 sb-color-secondary fnormal">{{generaliseLabelService?.frmelmnts?.lbl?.moduleFinish}}</label>
</div>
</div>
<h5 class="p-16 mb-0 font-weight-bold">{{resourceService?.frmelmnts?.lbl?.contentsLabel}}</h5>
<div class="side-toc-content chapter-box">
<div *ngFor="let item of courseHierarchy?.children; let index = index;">
<p *ngIf="item?.mimeType === 'application/vnd.ekstep.content-collection'" class="title">{{item?.name}}</p>
<div *ngIf="item;else noContent">
<div *ngIf="item?.mimeType !== 'application/vnd.ekstep.content-collection' && !item?.children?.length">
<ng-container *ngIf="item?.contentType">
<div class="child-content-padding">
<!-- toc card for Non Self Assess content -->
<sb-toc-card *ngIf="item?.contentType !== 'SelfAssess'" [content]="item"
(tocCardClick)="navigateToInitPlayer($event, 'toc-card')" [type]="cardType" [contentStatus]="contentStatus">
</sb-toc-card>
<!-- toc card for Self Assess content with Label for Best Score -->
<sb-toc-card *ngIf="item?.contentType === 'SelfAssess'" [content]="item"
(tocCardClick)="navigateToInitPlayer($event, 'toc-card')" [type]="cardType" [contentStatus]="contentStatus"
[maxAttempts]="item?.maxAttempts" [scoreLabel]="'Best Score'" [disabled]="'disabled-toc-card'">
</sb-toc-card>
</div>
</ng-container>
</div>
<div *ngIf="item?.mimeType === 'application/vnd.sunbird.questionset'">
<div class="child-content-padding">
<sb-toc-card [content]="item" (tocCardClick)="navigateToInitPlayer($event, 'toc-card')" [type]="cardType"
[contentStatus]="contentStatus" [maxAttempts]="item?.maxAttempts" [scoreLabel]="'Best Score'" [disabled]="'disabled-toc-card'">
</sb-toc-card>
</div>
</div>
<div *ngIf="item?.children?.length">
<div *ngFor="let child of item?.children" >
<div class="toc-child-item" *ngIf="child?.mimeType !== 'application/vnd.sunbird.question'">
<sb-toc-child-item [activeContent]="activeContent" [childData]="child" (tocCardClick)="navigateToInitPlayer($event, 'child-item')"
[contentStatus]="contentStatus" [type]="cardType">
</sb-toc-child-item>
</div>
</div>
</div>
</div>
<ng-template #noContent>
<div class="heading">{{noContentMessage}}</div>
</ng-template>
</div>
<sb-toc-card [content]="courseHierarchy"
*ngIf="courseHierarchy?.mimeType !== 'application/vnd.ekstep.content-collection'"
(tocCardClick)="navigateToInitPlayer($event)" [type]="cardType" [contentStatus]="contentStatus">
</sb-toc-card>
</div>
</div>
</section>
</div>
</div>
<div class="ui container py-16" *ngIf="showLoader">
<app-loader [data]='loaderMessage'></app-loader>
</div>
</div>
<app-modal-wrapper *ngIf="shareLinkModal" [config]="{disableClose: false, panelClass: 'material-modal'}" (dismiss)="shareLinkModal = false">
<ng-template sbModalContent>
<app-share-link [shareLink]="shareLink" [telemetryShareData]="telemetryShareData">
</app-share-link>
</ng-template>
</app-modal-wrapper>
<app-course-completion *ngIf="showCourseCompleteMessage" [certificateDescription]="certificateDescription" (close)="onCourseCompleteClose()"></app-course-completion>
<!-- Start - Modal for Assessment attempts -->
<app-modal-wrapper *ngIf="showMaxAttemptsModal" [config]="{disableClose: true, size: 'normal'}"
(dismiss)="closeLoginModal()" #modal>
<ng-template sbModalContent>
<div class="sb-modal">
<div class="transition ui dimmer page modals active visible">
<div class="ui modal transition active visible normal">
<div class="sb-modal-content sb-join-modal-content">
<div class="sb-h4 px-0 py-20">
{{resourceService?.frmelmnts?.lbl?.selfAssessMaxAttempt}}
</div>
</div>
<div class="sb-modal-actions">
<a appTelemetryInteract [telemetryInteractObject]="telemetryInteractObject" tabindex="0"
[telemetryInteractEdata]="signInInteractEdata"
(click)="goBack(); showMaxAttemptsModal = !showMaxAttemptsModal"
class="sb-btn sb-btn-normal sb-btn-error ml-auto sb-cls-btn">
{{resourceService?.frmelmnts?.btn?.close}}
</a>
</div>
</div>
</div>
</div>
</ng-template>
</app-modal-wrapper>
<!-- End - Modal for Assessment attempts -->
<!-- Start - Modal for Assessment attempts -->
<app-modal-wrapper *ngIf="showLastAttemptsModal" [config]="{disableClose: true, size: 'small'}" #modal>
<ng-template sbModalContent>
<div class="sb-modal">
<div class="transition ui dimmer page modals active visible">
<div class="ui modal transition active visible small">
<div class="sb-modal-content sb-join-modal-content">
<div class="sb-h4 px-0 py-20">
{{resourceService?.frmelmnts?.lbl?.selfAssessLastAttempt}}
</div>
</div>
<div class="sb-modal-actions">
<button class="sb-btn sb-btn-normal sb-btn-primary" tabindex="0"
(click)="showLastAttemptsModal = false; onTocCardClick();">
{{resourceService.frmelmnts?.btn?.ok}}
</button>
<button class="sb-btn sb-btn-normal sb-btn-outline-primary" tabindex="0"
(click)="showLastAttemptsModal = false;">
{{resourceService.frmelmnts?.btn?.cancel}}
</button>
</div>
</div>
</div>
</div>
</ng-template>
</app-modal-wrapper>
<!-- End - Modal for Assessment attempts -->
<!--Rating popup -->
<app-content-rating *ngIf="contentRatingModal && playerConfig" [contentData]="playerConfig.metadata"
(closeModal)="onRatingPopupClose()"></app-content-rating>
./assessment-player.component.scss
@use "@project-sunbird/sb-styles/assets/mixins/mixins" as *;
@use "components/video" as *;
:host {
.header-shadow {
min-height: calculateRem(80px);
background-color: var(--gray-0);
box-shadow: 0 calculateRem(10px) calculateRem(10px) calculateRem(-10px) rgba(var(--rc-rgba-black), 0.25);
z-index: 99;
position: relative;
}
.chapter-box {
border-bottom: .0625rem solid var(--gray-100);
padding: 0 calculateRem(16px);
&:last-child{
border-bottom: 0px solid var(--gray-100);
}
}
.side-toc-content {
max-height: calc(100vh - 20rem);
overflow-y: auto;
padding-bottom: calculateRem(24px);
}
//sbchapter css
.sbchapter {
border-bottom: calculateRem(1px) solid var(--rc-dddddd);
padding: calculateRem(16px) calculateRem(16px) calculateRem(8px);
h4 {
font-size: var(--font-size-xs);
line-height: calculateRem(18px);
color: var(--gray-800);
margin-bottom: calculateRem(8px);
}
&__item {
background: var(--white);
border: calculateRem(1px) solid var(--rc-dddddd);
box-shadow: 0 calculateRem(3px) calculateRem(4px) 0 rgba(var(--rc-rgba-primary), 0.1);
border: calculateRem(0.5px) solid rgba(var(--rc-rgba-primary-300), 0.5);
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: calculateRem(8px);
padding: calculateRem(8px);
cursor: pointer;
min-height: calculateRem(56px);
h4 {
font-size: var(--font-size-sm);
line-height: calculateRem(20px);
color: var(--primary-400);
margin: 0px;
padding: 0px;
}
.sbchapter__img {
width: calculateRem(40px);
height: calculateRem(40px);
flex: 0 0 calculateRem(40px);
img {
width: 100%;
}
}
&:last-child {
margin-bottom: 0px;
}
&:hover {
background: var(--gray-0);
}
&--active {
background: var(--primary-100);
}
}
}
}
::ng-deep{
.toc-content{
width: 100%;
height: 100%;
app-credits-and-licence{
sb-accordion{
.sbaccordion__panel{
box-shadow: none;
}
}
}
}
}
.header-block {
display: none;
}
.back-btn-container {
z-index: 1;
}
.textbook {
&-container {
box-shadow: 0 0 calculateRem(10px) 0 rgba(var(--black), 0.25);
position: relative;
background-color: var(--gray-0);
}
&__details {
flex: 1;
min-width: 0;
}
&__bookimg {
width: calculateRem(48px);
height: calculateRem(48px);
img {
width: calculateRem(48px);
height: calculateRem(48px);
}
}
&__heading {
min-width: 0;
.textbook__title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
&__addbtn {
white-space: nowrap;
}
}
:host::ng-deep {
.certified-course__progress {
.ui.progress .bar {
height: calculateRem(8px) !important;
background: var(--secondary-color);
}
}
.sbchapter__item__score {
display: none !important;
}
}
.cc-player__list {
background-color: var(--gray-0);
padding: 0 calculateRem(16px);
box-shadow: 0 calculateRem(3px) calculateRem(4px) 0 rgba(var(--rc-rgba-primary), 0.1);
}
.certified-course {
align-self: center;;
}
@include respond-below(sm) {
.certified-course__progress {
margin-top: calculateRem(8px);
}
}
.module-complete-content {
background-color: var(--rc-bfe1cf);
.complete-content-image {
width: calculateRem(64px);
img {
max-width: 100%;
}
}
}