forked from CIT/Vmeda.Online
		
	MOBILE-3653 scorm: Implement index page
This commit is contained in:
		
							parent
							
								
									aebc359083
								
							
						
					
					
						commit
						a2cf8db2ea
					
				
							
								
								
									
										34
									
								
								src/addons/mod/scorm/components/components.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/addons/mod/scorm/components/components.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { NgModule } from '@angular/core';
 | 
			
		||||
import { AddonModScormIndexComponent } from './index/index';
 | 
			
		||||
import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
import { CoreCourseComponentsModule } from '@features/course/components/components.module';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    declarations: [
 | 
			
		||||
        AddonModScormIndexComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    imports: [
 | 
			
		||||
        CoreSharedModule,
 | 
			
		||||
        CoreCourseComponentsModule,
 | 
			
		||||
    ],
 | 
			
		||||
    providers: [
 | 
			
		||||
    ],
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonModScormIndexComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonModScormComponentsModule {}
 | 
			
		||||
							
								
								
									
										246
									
								
								src/addons/mod/scorm/components/index/addon-mod-scorm-index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										246
									
								
								src/addons/mod/scorm/components/index/addon-mod-scorm-index.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,246 @@
 | 
			
		||||
<!-- Buttons to add to the header. -->
 | 
			
		||||
<core-navbar-buttons slot="end">
 | 
			
		||||
    <core-context-menu>
 | 
			
		||||
        <core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate"
 | 
			
		||||
            [href]="externalUrl" iconAction="fas-external-link-alt">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
 | 
			
		||||
            (action)="expandDescription()" iconAction="fas-arrow-right">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}"
 | 
			
		||||
            iconAction="far-newspaper" (action)="gotoBlog()">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && !hasOffline && isOnline" [priority]="700" [content]="'core.refresh' | translate"
 | 
			
		||||
            (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && hasOffline && isOnline"  [priority]="600" (action)="doRefresh(null, $event, true)"
 | 
			
		||||
            [content]="'core.settings.synchronizenow' | translate" [iconAction]="syncIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)"
 | 
			
		||||
            [iconAction]="prefetchStatusIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="size" [priority]="400" [content]="'core.clearstoreddata' | translate:{$a: size}"
 | 
			
		||||
            iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
    </core-context-menu>
 | 
			
		||||
</core-navbar-buttons>
 | 
			
		||||
 | 
			
		||||
<!-- Content. -->
 | 
			
		||||
<core-loading [hideUntil]="loaded" class="core-loading-center">
 | 
			
		||||
 | 
			
		||||
    <core-course-module-description [description]="description" [component]="component" [componentId]="componentId"
 | 
			
		||||
        contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
 | 
			
		||||
    </core-course-module-description>
 | 
			
		||||
 | 
			
		||||
    <!-- Warning message. -->
 | 
			
		||||
    <ion-card class="core-info-card" *ngIf="scorm && scorm.warningMessage">
 | 
			
		||||
        <ion-item>
 | 
			
		||||
            <ion-icon name="fas-info-circle" slot="start"></ion-icon>
 | 
			
		||||
            <ion-label>{{ scorm.warningMessage }}</ion-label>
 | 
			
		||||
        </ion-item>
 | 
			
		||||
    </ion-card>
 | 
			
		||||
 | 
			
		||||
    <ng-container *ngIf="scorm && loaded && !scorm.warningMessage">
 | 
			
		||||
        <!-- Attempts status. -->
 | 
			
		||||
        <ion-card *ngIf="(scorm.displayattemptstatus || offlineAttempts.length) && !skip">
 | 
			
		||||
            <ion-card-header class="ion-text-wrap">
 | 
			
		||||
                <ion-card-title>{{ 'addon.mod_scorm.attempts' | translate }}</ion-card-title>
 | 
			
		||||
            </ion-card-header>
 | 
			
		||||
            <ion-list class="addon-mod_scorm-attempt-summary">
 | 
			
		||||
                <ng-container *ngIf="scorm.displayattemptstatus">
 | 
			
		||||
                    <ion-item class="ion-text-wrap" *ngIf="scorm.maxattempt! >= 0">
 | 
			
		||||
                        <ion-label>
 | 
			
		||||
                            <h3>{{ 'addon.mod_scorm.noattemptsallowed' | translate }}</h3>
 | 
			
		||||
                        </ion-label>
 | 
			
		||||
                        <p slot="end">
 | 
			
		||||
                            <span *ngIf="scorm.maxattempt == 0">{{ 'core.unlimited' | translate }}</span>
 | 
			
		||||
                            <span *ngIf="scorm.maxattempt! > 0">{{ scorm.maxattempt }}</span>
 | 
			
		||||
                        </p>
 | 
			
		||||
                    </ion-item>
 | 
			
		||||
                    <ion-item class="ion-text-wrap" *ngIf="numAttempts >= 0">
 | 
			
		||||
                        <ion-label>
 | 
			
		||||
                            <h3>{{ 'addon.mod_scorm.noattemptsmade' | translate }}</h3>
 | 
			
		||||
                        </ion-label>
 | 
			
		||||
                        <p slot="end">{{ numAttempts }}</p>
 | 
			
		||||
                    </ion-item>
 | 
			
		||||
                    <ion-item class="ion-text-wrap" *ngFor="let attempt of onlineAttempts">
 | 
			
		||||
                        <ion-label>
 | 
			
		||||
                            <h3>{{ 'addon.mod_scorm.gradeforattempt' | translate }} {{attempt.num}}</h3>
 | 
			
		||||
                        </ion-label>
 | 
			
		||||
                        <p slot="end">
 | 
			
		||||
                            <span *ngIf="attempt.grade != -1">{{ attempt.gradeFormatted }}</span>
 | 
			
		||||
                            <span *ngIf="attempt.grade == -1">{{ 'addon.mod_scorm.cannotcalculategrade' | translate }}</span>
 | 
			
		||||
                        </p>
 | 
			
		||||
                    </ion-item>
 | 
			
		||||
                </ng-container>
 | 
			
		||||
                <ion-item class="ion-text-wrap" *ngFor="let attempt of offlineAttempts">
 | 
			
		||||
                    <ion-label>
 | 
			
		||||
                        <h3>{{ 'addon.mod_scorm.gradeforattempt' | translate }} {{attempt.num}}</h3>
 | 
			
		||||
                        <p *ngIf="!scorm.maxattempt || attempt.num <= scorm.maxattempt">
 | 
			
		||||
                            {{ 'addon.mod_scorm.offlineattemptnote' | translate }}
 | 
			
		||||
                        </p>
 | 
			
		||||
                        <p *ngIf="scorm.maxattempt && attempt.num > scorm.maxattempt">
 | 
			
		||||
                            {{ 'addon.mod_scorm.offlineattemptovermax' | translate }}
 | 
			
		||||
                        </p>
 | 
			
		||||
                    </ion-label>
 | 
			
		||||
                    <p slot="end">
 | 
			
		||||
                        <span *ngIf="attempt.grade != -1">{{ attempt.gradeFormatted }}</span>
 | 
			
		||||
                        <span *ngIf="attempt.grade == -1">{{ 'addon.mod_scorm.cannotcalculategrade' | translate }}</span>
 | 
			
		||||
                    </p>
 | 
			
		||||
                </ion-item>
 | 
			
		||||
                <ion-item class="ion-text-wrap" *ngIf="scorm.displayattemptstatus && gradeMethodReadable">
 | 
			
		||||
                    <ion-label>
 | 
			
		||||
                        <h3>{{ 'addon.mod_scorm.grademethod' | translate }}</h3>
 | 
			
		||||
                    </ion-label>
 | 
			
		||||
                    <p slot="end">{{ gradeMethodReadable }}</p>
 | 
			
		||||
                </ion-item>
 | 
			
		||||
                <ion-item class="ion-text-wrap" *ngIf="scorm.displayattemptstatus && gradeFormatted">
 | 
			
		||||
                    <ion-label>
 | 
			
		||||
                        <h3>{{ 'addon.mod_scorm.gradereported' | translate }}</h3>
 | 
			
		||||
                    </ion-label>
 | 
			
		||||
                    <p slot="end">
 | 
			
		||||
                        <span *ngIf="grade != -1">{{ gradeFormatted }}</span>
 | 
			
		||||
                        <span *ngIf="grade == -1">{{ 'addon.mod_scorm.cannotcalculategrade' | translate }}</span>
 | 
			
		||||
                    </p>
 | 
			
		||||
                </ion-item>
 | 
			
		||||
                <ion-item class="ion-text-wrap" *ngIf="syncTime">
 | 
			
		||||
                    <ion-label>
 | 
			
		||||
                        <h3>{{ 'core.lastsync' | translate }}</h3>
 | 
			
		||||
                        <p>{{ syncTime }}</p>
 | 
			
		||||
                    </ion-label>
 | 
			
		||||
                </ion-item>
 | 
			
		||||
            </ion-list>
 | 
			
		||||
        </ion-card>
 | 
			
		||||
 | 
			
		||||
        <!-- Synchronization warning. -->
 | 
			
		||||
        <ion-card class="core-warning-card" *ngIf="!errorMessage && hasOffline">
 | 
			
		||||
            <ion-item>
 | 
			
		||||
                <ion-icon name="fas-exclamation-triangle" slot="start"></ion-icon>
 | 
			
		||||
                <ion-label>{{ 'core.hasdatatosync' | translate: {$a: moduleName} }}</ion-label>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
        </ion-card>
 | 
			
		||||
 | 
			
		||||
        <!-- TOC. -->
 | 
			
		||||
        <ion-card *ngIf="scorm && organizations && !skip &&
 | 
			
		||||
            ((scorm.displaycoursestructure && organizations.length) || organizations.length > 1)" class="addon-mod_scorm-toc">
 | 
			
		||||
            <ion-card-header class="ion-text-wrap">
 | 
			
		||||
                <ion-card-title>{{ 'addon.mod_scorm.contents' | translate }}</ion-card-title>
 | 
			
		||||
            </ion-card-header>
 | 
			
		||||
            <ion-list>
 | 
			
		||||
                <ion-item class="ion-text-wrap" *ngIf="organizations.length > 1">
 | 
			
		||||
                    <ion-label>{{ 'addon.mod_scorm.organizations' | translate }}</ion-label>
 | 
			
		||||
                    <ion-select [(ngModel)]="currentOrganization.identifier" (ionChange)="loadOrganization()"
 | 
			
		||||
                        interface="action-sheet">
 | 
			
		||||
                        <ion-select-option *ngFor="let org of organizations" [value]="org.identifier">
 | 
			
		||||
                            {{ org.title }}
 | 
			
		||||
                        </ion-select-option>
 | 
			
		||||
                    </ion-select>
 | 
			
		||||
                </ion-item>
 | 
			
		||||
                <ion-item class="ion-text-center" *ngIf="scorm.displaycoursestructure && loadingToc">
 | 
			
		||||
                    <ion-label>
 | 
			
		||||
                        <ion-spinner></ion-spinner>
 | 
			
		||||
                    </ion-label>
 | 
			
		||||
                </ion-item>
 | 
			
		||||
                <ion-item class="ion-text-wrap" *ngIf="scorm.displaycoursestructure && !loadingToc">
 | 
			
		||||
                    <!-- If data shown doesn't belong to last attempt, show a warning. -->
 | 
			
		||||
                    <ion-label>
 | 
			
		||||
                        <p *ngIf="attemptToContinue">
 | 
			
		||||
                            {{ 'addon.mod_scorm.dataattemptshown' | translate:{number: attemptToContinue} }}
 | 
			
		||||
                        </p>
 | 
			
		||||
                        <p>{{ currentOrganization.title }}</p>
 | 
			
		||||
                        <div *ngFor="let sco of toc" class="core-padding-{{sco.level}} addon-mod_scorm-type-{{sco.scormtype}}">
 | 
			
		||||
                            <p *ngIf="sco.isvisible">
 | 
			
		||||
                                <ion-icon *ngIf="sco.icon" [name]="sco.icon.icon" [attr.aria-label]="sco.icon.description"
 | 
			
		||||
                                    slot="start">
 | 
			
		||||
                                </ion-icon>
 | 
			
		||||
                                <a *ngIf="sco.prereq && sco.launch" (click)="open($event, false, sco.id)" tappable="true">
 | 
			
		||||
                                    <core-format-text [text]="sco.title" contextLevel="module" [contextInstanceId]="module.id"
 | 
			
		||||
                                        [courseId]="courseId">
 | 
			
		||||
                                    </core-format-text>
 | 
			
		||||
                                </a>
 | 
			
		||||
                                <span *ngIf="!sco.prereq || !sco.launch">
 | 
			
		||||
                                    <core-format-text [text]="sco.title" contextLevel="module" [contextInstanceId]="module.id"
 | 
			
		||||
                                        [courseId]="courseId">
 | 
			
		||||
                                    </core-format-text>
 | 
			
		||||
                                </span>
 | 
			
		||||
                                <span *ngIf="accessInfo && accessInfo.canviewscores && sco.scoreraw">
 | 
			
		||||
                                    ({{ 'addon.mod_scorm.score' | translate }}: {{sco.scoreraw}})
 | 
			
		||||
                                </span>
 | 
			
		||||
                            </p>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </ion-label>
 | 
			
		||||
                </ion-item>
 | 
			
		||||
            </ion-list>
 | 
			
		||||
        </ion-card>
 | 
			
		||||
 | 
			
		||||
        <!-- Open in browser button. -->
 | 
			
		||||
        <ion-card *ngIf="errorMessage">
 | 
			
		||||
            <ion-item class="ion-text-wrap">
 | 
			
		||||
                <ion-label>
 | 
			
		||||
                    <p class="text-danger">{{ errorMessage | translate }}</p>
 | 
			
		||||
                </ion-label>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
            <ion-button class="ion-margin ion-text-wrap" expand="block" [href]="externalUrl" core-link>
 | 
			
		||||
                {{ 'core.openinbrowser' | translate }}
 | 
			
		||||
                <ion-icon name="fas-external-link-alt" slot="end"></ion-icon>
 | 
			
		||||
            </ion-button>
 | 
			
		||||
        </ion-card>
 | 
			
		||||
 | 
			
		||||
        <!-- Warning that user doesn't have any more attempts. -->
 | 
			
		||||
        <ion-card *ngIf="!errorMessage && attemptsLeft == 0">
 | 
			
		||||
            <ion-item class="ion-text-wrap">
 | 
			
		||||
                <ion-label>
 | 
			
		||||
                    <p class="text-danger">{{ 'addon.mod_scorm.exceededmaxattempts' | translate }}</p>
 | 
			
		||||
                </ion-label>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
        </ion-card>
 | 
			
		||||
 | 
			
		||||
        <!-- Open SCORM in app form -->
 | 
			
		||||
        <ion-card *ngIf="!errorMessage && scorm && (!scorm.lastattemptlock || attemptsLeft > 0)">
 | 
			
		||||
            <ion-list>
 | 
			
		||||
                <ng-container *ngIf="!downloading && !skip">
 | 
			
		||||
                    <!-- Create new attempt -->
 | 
			
		||||
                    <ion-item class="ion-text-wrap"
 | 
			
		||||
                        *ngIf="!scorm.forcenewattempt && numAttempts > 0 && !incomplete && attemptsLeft > 0">
 | 
			
		||||
                        <ion-label>{{ 'addon.mod_scorm.newattempt' | translate }}</ion-label>
 | 
			
		||||
                        <ion-checkbox slot="end" name="newAttempt" [(ngModel)]="startNewAttempt">
 | 
			
		||||
                        </ion-checkbox>
 | 
			
		||||
                    </ion-item>
 | 
			
		||||
 | 
			
		||||
                    <ion-item class="ion-text-wrap" *ngIf="statusMessage">
 | 
			
		||||
                        <ion-label>
 | 
			
		||||
                            <p>{{ statusMessage | translate }}</p>
 | 
			
		||||
                        </ion-label>
 | 
			
		||||
                    </ion-item>
 | 
			
		||||
 | 
			
		||||
                    <!-- Open mode (Preview or Normal) -->
 | 
			
		||||
                    <ion-grid>
 | 
			
		||||
                        <ion-row class="ion-align-items-center">
 | 
			
		||||
                            <ion-col *ngIf="!scorm.hidebrowse">
 | 
			
		||||
                                <ion-button expand="block" fill="outline" (click)="open($event, true)" class="ion-text-wrap">
 | 
			
		||||
                                    {{ 'addon.mod_scorm.browse' | translate }}
 | 
			
		||||
                                    <ion-icon name="fas-search" slot="end"></ion-icon>
 | 
			
		||||
                                </ion-button>
 | 
			
		||||
                            </ion-col>
 | 
			
		||||
                            <ion-col>
 | 
			
		||||
                                <ion-button expand="block" (click)="open($event)" class="ion-text-wrap">
 | 
			
		||||
                                    {{ 'addon.mod_scorm.enter' | translate }}
 | 
			
		||||
                                    <ion-icon name="fas-arrow-right" slot="end"></ion-icon>
 | 
			
		||||
                                </ion-button>
 | 
			
		||||
                            </ion-col>
 | 
			
		||||
                        </ion-row>
 | 
			
		||||
                    </ion-grid>
 | 
			
		||||
                </ng-container>
 | 
			
		||||
 | 
			
		||||
                <!-- Download progress. -->
 | 
			
		||||
                <ion-item class="ion-text-center" *ngIf="downloading">
 | 
			
		||||
                    <ion-label>
 | 
			
		||||
                        <ion-spinner></ion-spinner>
 | 
			
		||||
                        <h2 *ngIf="progressMessage">{{ progressMessage | translate }}</h2>
 | 
			
		||||
                        <core-progress-bar *ngIf="showPercentage" [progress]="percentage"></core-progress-bar>
 | 
			
		||||
                    </ion-label>
 | 
			
		||||
                </ion-item>
 | 
			
		||||
            </ion-list>
 | 
			
		||||
        </ion-card>
 | 
			
		||||
    </ng-container>
 | 
			
		||||
</core-loading>
 | 
			
		||||
							
								
								
									
										19
									
								
								src/addons/mod/scorm/components/index/index.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/addons/mod/scorm/components/index/index.scss
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
@import "~theme/globals";
 | 
			
		||||
 | 
			
		||||
:host {
 | 
			
		||||
    .addon-mod_scorm-attempt-summary ion-item > p {
 | 
			
		||||
        font-size: 14px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .addon-mod_scorm-toc {
 | 
			
		||||
        // Hide all non sco icons using css to maintain padding.
 | 
			
		||||
        ion-icon {
 | 
			
		||||
            opacity: 0;
 | 
			
		||||
            @include margin(5px, 8px, null, null);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .addon-mod_scorm-type-sco ion-icon {
 | 
			
		||||
            opacity: 1
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										605
									
								
								src/addons/mod/scorm/components/index/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										605
									
								
								src/addons/mod/scorm/components/index/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,605 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { CoreConstants } from '@/core/constants';
 | 
			
		||||
import { Component, OnInit, Optional } from '@angular/core';
 | 
			
		||||
import { CoreCourseModuleMainActivityComponent } from '@features/course/classes/main-activity-component';
 | 
			
		||||
import { CoreCourseContentsPage } from '@features/course/pages/contents/contents';
 | 
			
		||||
import { CoreCourse } from '@features/course/services/course';
 | 
			
		||||
import { IonContent } from '@ionic/angular';
 | 
			
		||||
import { CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { Translate } from '@singletons';
 | 
			
		||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
 | 
			
		||||
import { AddonModScormPrefetchHandler } from '../../services/handlers/prefetch';
 | 
			
		||||
import {
 | 
			
		||||
    AddonModScorm,
 | 
			
		||||
    AddonModScormAttemptCountResult,
 | 
			
		||||
    AddonModScormGetScormAccessInformationWSResponse,
 | 
			
		||||
    AddonModScormAttemptGrade,
 | 
			
		||||
    AddonModScormOrganization,
 | 
			
		||||
    AddonModScormProvider,
 | 
			
		||||
    AddonModScormScorm,
 | 
			
		||||
} from '../../services/scorm';
 | 
			
		||||
import { AddonModScormHelper, AddonModScormTOCScoWithIcon } from '../../services/scorm-helper';
 | 
			
		||||
import {
 | 
			
		||||
    AddonModScormAutoSyncEventData,
 | 
			
		||||
    AddonModScormSync,
 | 
			
		||||
    AddonModScormSyncProvider,
 | 
			
		||||
    AddonModScormSyncResult,
 | 
			
		||||
} from '../../services/scorm-sync';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component that displays a SCORM entry page.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'addon-mod-scorm-index',
 | 
			
		||||
    templateUrl: 'addon-mod-scorm-index.html',
 | 
			
		||||
    styleUrls: ['index.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
    component = AddonModScormProvider.COMPONENT;
 | 
			
		||||
    moduleName = 'scorm';
 | 
			
		||||
 | 
			
		||||
    scorm?: AddonModScormScorm; // The SCORM object.
 | 
			
		||||
    currentOrganization: Partial<AddonModScormOrganization> = {}; // Selected organization.
 | 
			
		||||
    startNewAttempt = false;
 | 
			
		||||
    errorMessage?: string; // Error message.
 | 
			
		||||
    syncTime?: string; // Last sync time.
 | 
			
		||||
    hasOffline = false; // Whether the SCORM has offline data.
 | 
			
		||||
    attemptToContinue?: number; // The attempt to continue or review.
 | 
			
		||||
    statusMessage?: string; // Message about the status.
 | 
			
		||||
    downloading = false; // Whether the SCORM is being downloaded.
 | 
			
		||||
    percentage?: string; // Download/unzip percentage.
 | 
			
		||||
    showPercentage = false; // Whether to show the percentage.
 | 
			
		||||
    progressMessage?: string; // Message about download/unzip.
 | 
			
		||||
    organizations?: AddonModScormOrganization[]; // List of organizations.
 | 
			
		||||
    loadingToc = false; // Whether the TOC is being loaded.
 | 
			
		||||
    toc?: AddonModScormTOCScoWithIcon[]; // Table of contents (structure).
 | 
			
		||||
    accessInfo?: AddonModScormGetScormAccessInformationWSResponse; // Access information.
 | 
			
		||||
    skip?: boolean; // Launch immediately.
 | 
			
		||||
    incomplete = false; // Whether last attempt is incomplete.
 | 
			
		||||
    numAttempts = -1; // Number of attempts.
 | 
			
		||||
    grade?: number; // Grade.
 | 
			
		||||
    gradeFormatted?: string; // Grade formatted.
 | 
			
		||||
    gradeMethodReadable?: string; // Grade method in a readable format.
 | 
			
		||||
    attemptsLeft = -1; // Number of attempts left.
 | 
			
		||||
    onlineAttempts: AttemptGrade[] = []; // Grades for online attempts.
 | 
			
		||||
    offlineAttempts: AttemptGrade[] = []; // Grades for offline attempts.
 | 
			
		||||
 | 
			
		||||
    protected fetchContentDefaultError = 'addon.mod_scorm.errorgetscorm'; // Default error to show when loading contents.
 | 
			
		||||
    protected syncEventName = AddonModScormSyncProvider.AUTO_SYNCED;
 | 
			
		||||
    protected attempts?: AddonModScormAttemptCountResult; // Data about online and offline attempts.
 | 
			
		||||
    protected lastAttempt?: number; // Last attempt.
 | 
			
		||||
    protected lastIsOffline = false; // Whether the last attempt is offline.
 | 
			
		||||
    protected hasPlayed = false; // Whether the user has opened the player page.
 | 
			
		||||
    protected dataSentObserver?: CoreEventObserver; // To detect data sent to server.
 | 
			
		||||
    protected dataSent = false; // Whether some data was sent to server while playing the SCORM.
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        protected content?: IonContent,
 | 
			
		||||
        @Optional() courseContentsPage?: CoreCourseContentsPage,
 | 
			
		||||
    ) {
 | 
			
		||||
        super('AddonModScormIndexComponent', content, courseContentsPage);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Component being initialized.
 | 
			
		||||
     */
 | 
			
		||||
    async ngOnInit(): Promise<void> {
 | 
			
		||||
        super.ngOnInit();
 | 
			
		||||
 | 
			
		||||
        await this.loadContent(false, true);
 | 
			
		||||
 | 
			
		||||
        if (!this.scorm) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.skip) {
 | 
			
		||||
            this.open();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await AddonModScorm.logView(this.scorm.id, this.scorm.name);
 | 
			
		||||
 | 
			
		||||
            this.checkCompletion();
 | 
			
		||||
        } catch {
 | 
			
		||||
            // Ignore errors.
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check the completion.
 | 
			
		||||
     */
 | 
			
		||||
    protected checkCompletion(): void {
 | 
			
		||||
        CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Download a SCORM package or restores an ongoing download.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async downloadScormPackage(): Promise<void> {
 | 
			
		||||
        this.downloading = true;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await AddonModScormPrefetchHandler.download(this.module, this.courseId, undefined, (data) => {
 | 
			
		||||
                if (!data) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                this.percentage = undefined;
 | 
			
		||||
                this.showPercentage = false;
 | 
			
		||||
 | 
			
		||||
                if (data.downloading) {
 | 
			
		||||
                    // Downloading package.
 | 
			
		||||
                    if (this.scorm!.packagesize && data.progress) {
 | 
			
		||||
                        const percentageNumber = Number(data.progress.loaded / this.scorm!.packagesize) * 100;
 | 
			
		||||
                        this.percentage = percentageNumber.toFixed(1);
 | 
			
		||||
                        this.showPercentage = percentageNumber >= 0 && percentageNumber <= 100;
 | 
			
		||||
                    }
 | 
			
		||||
                } else if (data.message) {
 | 
			
		||||
                    // Show a message.
 | 
			
		||||
                    this.progressMessage = data.message;
 | 
			
		||||
                } else if (data.progress && data.progress.loaded && data.progress.total) {
 | 
			
		||||
                    // Unzipping package.
 | 
			
		||||
                    const percentageNumber = Number(data.progress.loaded / data.progress.total) * 100;
 | 
			
		||||
                    this.percentage = percentageNumber.toFixed(1);
 | 
			
		||||
                    this.showPercentage = percentageNumber >= 0 && percentageNumber <= 100;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.progressMessage = undefined;
 | 
			
		||||
            this.percentage = undefined;
 | 
			
		||||
            this.downloading = false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            // Get the SCORM instance.
 | 
			
		||||
            this.scorm = await AddonModScorm.getScorm(this.courseId, this.module.id, { moduleUrl: this.module.url });
 | 
			
		||||
 | 
			
		||||
            this.dataRetrieved.emit(this.scorm);
 | 
			
		||||
            this.description = this.scorm.intro || this.description;
 | 
			
		||||
            this.errorMessage = AddonModScorm.isScormUnsupported(this.scorm);
 | 
			
		||||
 | 
			
		||||
            if (this.scorm.warningMessage) {
 | 
			
		||||
                return; // SCORM is closed or not open yet, we can't get more data.
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (sync) {
 | 
			
		||||
                // Try to synchronize the SCORM.
 | 
			
		||||
                await CoreUtils.ignoreErrors(this.syncActivity(showErrors));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const [syncTime, accessInfo] = await Promise.all([
 | 
			
		||||
                AddonModScormSync.getReadableSyncTime(this.scorm.id),
 | 
			
		||||
                AddonModScorm.getAccessInformation(this.scorm.id, { cmId: this.module.id }),
 | 
			
		||||
                this.fetchAttemptData(this.scorm),
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
            this.syncTime = syncTime;
 | 
			
		||||
            this.accessInfo = accessInfo;
 | 
			
		||||
 | 
			
		||||
            // Check whether to launch the SCORM immediately.
 | 
			
		||||
            if (typeof this.skip == 'undefined') {
 | 
			
		||||
                this.skip = !this.hasOffline && !this.errorMessage &&
 | 
			
		||||
                    (!this.scorm.lastattemptlock || this.attemptsLeft > 0) &&
 | 
			
		||||
                    this.accessInfo.canskipview && !this.accessInfo.canviewreport &&
 | 
			
		||||
                    this.scorm.skipview! >= AddonModScormProvider.SKIPVIEW_FIRST &&
 | 
			
		||||
                    (this.scorm.skipview == AddonModScormProvider.SKIPVIEW_ALWAYS || this.lastAttempt == 0);
 | 
			
		||||
            }
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.fillContextMenu(refresh);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Fetch attempt data.
 | 
			
		||||
     *
 | 
			
		||||
     * @param scorm Scorm.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchAttemptData(scorm: AddonModScormScorm): Promise<void> {
 | 
			
		||||
        // Get the number of attempts.
 | 
			
		||||
        this.attempts = await AddonModScorm.getAttemptCount(scorm.id, { cmId: this.module.id });
 | 
			
		||||
        this.hasOffline = !!this.attempts.offline.length;
 | 
			
		||||
 | 
			
		||||
        // Determine the attempt that will be continued or reviewed.
 | 
			
		||||
        const attempt = await AddonModScormHelper.determineAttemptToContinue(scorm, this.attempts);
 | 
			
		||||
 | 
			
		||||
        this.lastAttempt = attempt.num;
 | 
			
		||||
        this.lastIsOffline = attempt.offline;
 | 
			
		||||
 | 
			
		||||
        if (this.lastAttempt != this.attempts.lastAttempt.num) {
 | 
			
		||||
            this.attemptToContinue = this.lastAttempt;
 | 
			
		||||
        } else {
 | 
			
		||||
            this.attemptToContinue = undefined;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Check if the last attempt is incomplete.
 | 
			
		||||
        this.incomplete = await AddonModScorm.isAttemptIncomplete(scorm.id, this.lastAttempt, {
 | 
			
		||||
            offline: this.lastIsOffline,
 | 
			
		||||
            cmId: this.module.id,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.numAttempts = this.attempts.total;
 | 
			
		||||
        this.gradeMethodReadable = AddonModScorm.getScormGradeMethod(scorm);
 | 
			
		||||
        this.attemptsLeft = AddonModScorm.countAttemptsLeft(scorm, this.attempts.lastAttempt.num);
 | 
			
		||||
 | 
			
		||||
        if (scorm.forcenewattempt == AddonModScormProvider.SCORM_FORCEATTEMPT_ALWAYS ||
 | 
			
		||||
                (scorm.forcenewattempt && !this.incomplete)) {
 | 
			
		||||
            this.startNewAttempt = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await Promise.all([
 | 
			
		||||
            this.getReportedGrades(scorm, this.attempts),
 | 
			
		||||
            this.fetchStructure(scorm),
 | 
			
		||||
            this.loadPackageSize(scorm),
 | 
			
		||||
            this.setStatusListener(),
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Load SCORM package size if needed.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async loadPackageSize(scorm: AddonModScormScorm): Promise<void> {
 | 
			
		||||
        if (scorm.packagesize || this.errorMessage) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // SCORM is supported but we don't have package size. Try to calculate it.
 | 
			
		||||
        scorm.packagesize = await AddonModScorm.calculateScormSize(scorm);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Fetch the structure of the SCORM (TOC).
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchStructure(scorm: AddonModScormScorm): Promise<void> {
 | 
			
		||||
        this.organizations = await AddonModScorm.getOrganizations(scorm.id, { cmId: this.module.id });
 | 
			
		||||
 | 
			
		||||
        if (!this.currentOrganization.identifier) {
 | 
			
		||||
            // Load first organization (if any).
 | 
			
		||||
            if (this.organizations.length) {
 | 
			
		||||
                this.currentOrganization.identifier = this.organizations[0].identifier;
 | 
			
		||||
            } else {
 | 
			
		||||
                this.currentOrganization.identifier = '';
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.loadOrganizationToc(scorm, this.currentOrganization.identifier);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the grade of an attempt and add it to the scorm attempts list.
 | 
			
		||||
     *
 | 
			
		||||
     * @param attempt The attempt number.
 | 
			
		||||
     * @param offline Whether it's an offline attempt.
 | 
			
		||||
     * @param attempts Object where to add the attempt.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async getAttemptGrade(
 | 
			
		||||
        attempt: number,
 | 
			
		||||
        offline: boolean,
 | 
			
		||||
        attempts: Record<number, AddonModScormAttemptGrade>,
 | 
			
		||||
    ): Promise<void> {
 | 
			
		||||
        const grade = await AddonModScorm.getAttemptGrade(this.scorm!, attempt, offline);
 | 
			
		||||
 | 
			
		||||
        attempts[attempt] = {
 | 
			
		||||
            num: attempt,
 | 
			
		||||
            grade: grade,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the grades of each attempt and the grade of the SCORM.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async getReportedGrades(scorm: AddonModScormScorm, attempts: AddonModScormAttemptCountResult): Promise<void> {
 | 
			
		||||
        const promises: Promise<void>[] = [];
 | 
			
		||||
        const onlineAttempts: Record<number, AttemptGrade> = {};
 | 
			
		||||
        const offlineAttempts: Record<number, AttemptGrade> = {};
 | 
			
		||||
 | 
			
		||||
        // Calculate the grade for each attempt.
 | 
			
		||||
        attempts.online.forEach((attempt) => {
 | 
			
		||||
            // Check that attempt isn't in offline to prevent showing the same attempt twice. Offline should be more recent.
 | 
			
		||||
            if (attempts.offline.indexOf(attempt) == -1) {
 | 
			
		||||
                promises.push(this.getAttemptGrade(attempt, false, onlineAttempts));
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        attempts.offline.forEach((attempt) => {
 | 
			
		||||
            promises.push(this.getAttemptGrade(attempt, true, offlineAttempts));
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        await Promise.all(promises);
 | 
			
		||||
 | 
			
		||||
        // Calculate the grade of the whole SCORM. We only use online attempts to calculate this data.
 | 
			
		||||
        this.grade = AddonModScorm.calculateScormGrade(scorm, onlineAttempts);
 | 
			
		||||
 | 
			
		||||
        // Add the attempts to the SCORM in array format in ASC order, and format the grades.
 | 
			
		||||
        this.onlineAttempts = CoreUtils.objectToArray(onlineAttempts);
 | 
			
		||||
        this.offlineAttempts = CoreUtils.objectToArray(offlineAttempts);
 | 
			
		||||
        this.onlineAttempts.sort((a, b) => a.num - b.num);
 | 
			
		||||
        this.offlineAttempts.sort((a, b) => a.num - b.num);
 | 
			
		||||
 | 
			
		||||
        // Now format the grades.
 | 
			
		||||
        this.onlineAttempts.forEach((attempt) => {
 | 
			
		||||
            attempt.gradeFormatted = AddonModScorm.formatGrade(scorm, attempt.grade);
 | 
			
		||||
        });
 | 
			
		||||
        this.offlineAttempts.forEach((attempt) => {
 | 
			
		||||
            attempt.gradeFormatted = AddonModScorm.formatGrade(scorm, attempt.grade);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.gradeFormatted = AddonModScorm.formatGrade(scorm, this.grade);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if sync has succeed from result sync data.
 | 
			
		||||
     *
 | 
			
		||||
     * @param result Data returned on the sync function.
 | 
			
		||||
     * @return If suceed or not.
 | 
			
		||||
     */
 | 
			
		||||
    protected hasSyncSucceed(result: AddonModScormSyncResult): boolean {
 | 
			
		||||
        if (result.updated || this.dataSent) {
 | 
			
		||||
            // Check completion status if something was sent.
 | 
			
		||||
            this.checkCompletion();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.dataSent = false;
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * User entered the page that contains the component.
 | 
			
		||||
     */
 | 
			
		||||
    ionViewDidEnter(): void {
 | 
			
		||||
        super.ionViewDidEnter();
 | 
			
		||||
 | 
			
		||||
        if (!this.hasPlayed) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.hasPlayed = false;
 | 
			
		||||
        this.startNewAttempt = false; // Uncheck new attempt.
 | 
			
		||||
 | 
			
		||||
        // Add a delay to make sure the player has started the last writing calls so we can detect conflicts.
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
            this.dataSentObserver?.off(); // Stop listening for changes.
 | 
			
		||||
            this.dataSentObserver = undefined;
 | 
			
		||||
 | 
			
		||||
            // Refresh data.
 | 
			
		||||
            this.showLoadingAndRefresh(true, false);
 | 
			
		||||
        }, 500);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Perform the invalidate content function.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async invalidateContent(): Promise<void> {
 | 
			
		||||
        const promises: Promise<void>[] = [];
 | 
			
		||||
 | 
			
		||||
        promises.push(AddonModScorm.invalidateScormData(this.courseId));
 | 
			
		||||
 | 
			
		||||
        if (this.scorm) {
 | 
			
		||||
            promises.push(AddonModScorm.invalidateAllScormData(this.scorm.id));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await Promise.all(promises);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Compares sync event data with current data to check if refresh content is needed.
 | 
			
		||||
     *
 | 
			
		||||
     * @param syncEventData Data receiven on sync observer.
 | 
			
		||||
     * @return True if refresh is needed, false otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    protected isRefreshSyncNeeded(syncEventData: AddonModScormAutoSyncEventData): boolean {
 | 
			
		||||
        if (syncEventData.updated && this.scorm && syncEventData.scormId == this.scorm.id) {
 | 
			
		||||
            // Check completion status.
 | 
			
		||||
            this.checkCompletion();
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Load a organization's TOC.
 | 
			
		||||
     */
 | 
			
		||||
    async loadOrganization(): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            await this.loadOrganizationToc(this.scorm!, this.currentOrganization.identifier!);
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            CoreDomUtils.showErrorModalDefault(error, this.fetchContentDefaultError, true);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Load the TOC of a certain organization.
 | 
			
		||||
     *
 | 
			
		||||
     * @param organizationId The organization id.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async loadOrganizationToc(scorm: AddonModScormScorm, organizationId: string): Promise<void> {
 | 
			
		||||
        if (!scorm.displaycoursestructure) {
 | 
			
		||||
            // TOC is not displayed, no need to load it.
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.loadingToc = true;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            this.toc = await AddonModScormHelper.getToc(scorm.id, this.lastAttempt!, this.incomplete, {
 | 
			
		||||
                organization: organizationId,
 | 
			
		||||
                offline: this.lastIsOffline,
 | 
			
		||||
                cmId: this.module.id,
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // Search organization title.
 | 
			
		||||
            this.organizations!.forEach((org) => {
 | 
			
		||||
                if (org.identifier == organizationId) {
 | 
			
		||||
                    this.currentOrganization.title = org.title;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.loadingToc = false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Open a SCORM. It will download the SCORM package if it's not downloaded or it has changed.
 | 
			
		||||
     *
 | 
			
		||||
     * @param event Event.
 | 
			
		||||
     * @param scoId SCO that needs to be loaded when the SCORM is opened. If not defined, load first SCO.
 | 
			
		||||
     */
 | 
			
		||||
    async open(event?: Event, preview: boolean = false, scoId?: number): Promise<void> {
 | 
			
		||||
        if (event) {
 | 
			
		||||
            event.preventDefault();
 | 
			
		||||
            event.stopPropagation();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.downloading) {
 | 
			
		||||
            // Scope is being downloaded, abort.
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const isOutdated = this.currentStatus == CoreConstants.OUTDATED;
 | 
			
		||||
        const scorm = this.scorm!;
 | 
			
		||||
 | 
			
		||||
        if (!isOutdated && this.currentStatus != CoreConstants.NOT_DOWNLOADED) {
 | 
			
		||||
            // Already downloaded, open it.
 | 
			
		||||
            this.openScorm(scoId, preview);
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // SCORM needs to be downloaded.
 | 
			
		||||
        await AddonModScormHelper.confirmDownload(scorm, isOutdated);
 | 
			
		||||
        // Invalidate WS data if SCORM is outdated.
 | 
			
		||||
        if (isOutdated) {
 | 
			
		||||
            await CoreUtils.ignoreErrors(AddonModScorm.invalidateAllScormData(scorm.id));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await this.downloadScormPackage();
 | 
			
		||||
            // Success downloading, open SCORM if user hasn't left the view.
 | 
			
		||||
            if (!this.isDestroyed) {
 | 
			
		||||
                this.openScorm(scoId, preview);
 | 
			
		||||
            }
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            if (!this.isDestroyed) {
 | 
			
		||||
                CoreDomUtils.showErrorModalDefault(
 | 
			
		||||
                    error,
 | 
			
		||||
                    Translate.instant('addon.mod_scorm.errordownloadscorm', { name: scorm.name }),
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Open a SCORM package.
 | 
			
		||||
     *
 | 
			
		||||
     * @param scoId SCO ID.
 | 
			
		||||
     */
 | 
			
		||||
    protected openScorm(scoId?: number, preview: boolean = false): void {
 | 
			
		||||
        // Display the full page when returning to the page.
 | 
			
		||||
        this.skip = false;
 | 
			
		||||
        this.hasPlayed = true;
 | 
			
		||||
 | 
			
		||||
        // Detect if anything was sent to server.
 | 
			
		||||
        this.dataSentObserver?.off();
 | 
			
		||||
 | 
			
		||||
        this.dataSentObserver = CoreEvents.on(AddonModScormProvider.DATA_SENT_EVENT, (data) => {
 | 
			
		||||
            if (data.scormId === this.scorm!.id) {
 | 
			
		||||
                this.dataSent = true;
 | 
			
		||||
            }
 | 
			
		||||
        }, this.siteId);
 | 
			
		||||
 | 
			
		||||
        CoreNavigator.navigate('player', {
 | 
			
		||||
            params: {
 | 
			
		||||
                mode: preview ? AddonModScormProvider.MODEBROWSE : AddonModScormProvider.MODENORMAL,
 | 
			
		||||
                moduleUrl: this.module.url,
 | 
			
		||||
                newAttempt: !!this.startNewAttempt,
 | 
			
		||||
                organizationId: this.currentOrganization.identifier,
 | 
			
		||||
                scoId: scoId,
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected async showStatus(status: string): Promise<void> {
 | 
			
		||||
 | 
			
		||||
        if (status == CoreConstants.OUTDATED && this.scorm) {
 | 
			
		||||
            // Only show the outdated message if the file should be downloaded.
 | 
			
		||||
            const download = await AddonModScorm.shouldDownloadMainFile(this.scorm, true);
 | 
			
		||||
 | 
			
		||||
            this.statusMessage = download ? 'addon.mod_scorm.scormstatusoutdated' : '';
 | 
			
		||||
        } else if (status == CoreConstants.NOT_DOWNLOADED) {
 | 
			
		||||
            this.statusMessage = 'addon.mod_scorm.scormstatusnotdownloaded';
 | 
			
		||||
        } else if (status == CoreConstants.DOWNLOADING) {
 | 
			
		||||
            if (!this.downloading) {
 | 
			
		||||
                // It's being downloaded right now but the view isn't tracking it. "Restore" the download.
 | 
			
		||||
                this.downloadScormPackage();
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            this.statusMessage = '';
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Performs the sync of the activity.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async sync(): Promise<AddonModScormSyncResult> {
 | 
			
		||||
        const result = await AddonModScormSync.syncScorm(this.scorm!);
 | 
			
		||||
 | 
			
		||||
        if (!result.updated && this.dataSent) {
 | 
			
		||||
            // The user sent data to server, but not in the sync process. Check if we need to fetch data.
 | 
			
		||||
            await CoreUtils.ignoreErrors(
 | 
			
		||||
                AddonModScormSync.prefetchAfterUpdate(AddonModScormPrefetchHandler.instance, this.module, this.courseId),
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Grade for an online attempt.
 | 
			
		||||
 */
 | 
			
		||||
export type AttemptGrade = AddonModScormAttemptGrade & {
 | 
			
		||||
    gradeFormatted?: string;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										22
									
								
								src/addons/mod/scorm/pages/index/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/addons/mod/scorm/pages/index/index.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
			
		||||
<ion-header>
 | 
			
		||||
    <ion-toolbar>
 | 
			
		||||
        <ion-buttons slot="start">
 | 
			
		||||
            <ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
 | 
			
		||||
        </ion-buttons>
 | 
			
		||||
        <ion-title>
 | 
			
		||||
            <core-format-text [text]="title" contextLevel="module" [contextInstanceId]="module?.id" [courseId]="courseId">
 | 
			
		||||
            </core-format-text>
 | 
			
		||||
        </ion-title>
 | 
			
		||||
        <ion-buttons slot="end">
 | 
			
		||||
            <!-- The buttons defined by the component will be added in here. -->
 | 
			
		||||
        </ion-buttons>
 | 
			
		||||
    </ion-toolbar>
 | 
			
		||||
</ion-header>
 | 
			
		||||
<ion-content>
 | 
			
		||||
    <ion-refresher slot="fixed" [disabled]="!activityComponent?.loaded" (ionRefresh)="activityComponent?.doRefresh($event.target)">
 | 
			
		||||
        <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
 | 
			
		||||
    </ion-refresher>
 | 
			
		||||
 | 
			
		||||
    <addon-mod-scorm-index [module]="module" [courseId]="courseId" (dataRetrieved)="updateData($event)">
 | 
			
		||||
    </addon-mod-scorm-index>
 | 
			
		||||
</ion-content>
 | 
			
		||||
							
								
								
									
										30
									
								
								src/addons/mod/scorm/pages/index/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/addons/mod/scorm/pages/index/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { Component, OnInit, ViewChild } from '@angular/core';
 | 
			
		||||
import { CoreCourseModuleMainActivityPage } from '@features/course/classes/main-activity-page';
 | 
			
		||||
import { AddonModScormIndexComponent } from '../../components/index/index';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Page that displays the scorm entry page.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'page-addon-mod-scorm-index',
 | 
			
		||||
    templateUrl: 'index.html',
 | 
			
		||||
})
 | 
			
		||||
export class AddonModScormIndexPage extends CoreCourseModuleMainActivityPage<AddonModScormIndexComponent> implements OnInit {
 | 
			
		||||
 | 
			
		||||
    @ViewChild(AddonModScormIndexComponent) activityComponent?: AddonModScormIndexComponent;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										39
									
								
								src/addons/mod/scorm/scorm-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/addons/mod/scorm/scorm-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,39 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { NgModule } from '@angular/core';
 | 
			
		||||
import { RouterModule, Routes } from '@angular/router';
 | 
			
		||||
 | 
			
		||||
import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
import { AddonModScormComponentsModule } from './components/components.module';
 | 
			
		||||
import { AddonModScormIndexPage } from './pages/index/index';
 | 
			
		||||
 | 
			
		||||
const routes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
        path: ':courseId/:cmId',
 | 
			
		||||
        component: AddonModScormIndexPage,
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [
 | 
			
		||||
        RouterModule.forChild(routes),
 | 
			
		||||
        CoreSharedModule,
 | 
			
		||||
        AddonModScormComponentsModule,
 | 
			
		||||
    ],
 | 
			
		||||
    declarations: [
 | 
			
		||||
        AddonModScormIndexPage,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonModScormLazyModule {}
 | 
			
		||||
@ -13,25 +13,44 @@
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { APP_INITIALIZER, NgModule, Type } from '@angular/core';
 | 
			
		||||
import { Routes } from '@angular/router';
 | 
			
		||||
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
 | 
			
		||||
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
 | 
			
		||||
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
 | 
			
		||||
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
 | 
			
		||||
import { CoreCronDelegate } from '@services/cron';
 | 
			
		||||
import { CORE_SITE_SCHEMAS } from '@services/sites';
 | 
			
		||||
import { AddonModScormComponentsModule } from './components/components.module';
 | 
			
		||||
import { OFFLINE_SITE_SCHEMA } from './services/database/scorm';
 | 
			
		||||
import { AddonModScormGradeLinkHandler } from './services/handlers/grade-link';
 | 
			
		||||
import { AddonModScormIndexLinkHandler } from './services/handlers/index-link';
 | 
			
		||||
import { AddonModScormListLinkHandler } from './services/handlers/list-link';
 | 
			
		||||
import { AddonModScormModuleHandler } from './services/handlers/module';
 | 
			
		||||
import { AddonModScormModuleHandler, AddonModScormModuleHandlerService } from './services/handlers/module';
 | 
			
		||||
import { AddonModScormPrefetchHandler } from './services/handlers/prefetch';
 | 
			
		||||
import { AddonModScormSyncCronHandler } from './services/handlers/sync-cron';
 | 
			
		||||
import { AddonModScormProvider } from './services/scorm';
 | 
			
		||||
import { AddonModScormHelperProvider } from './services/scorm-helper';
 | 
			
		||||
import { AddonModScormOfflineProvider } from './services/scorm-offline';
 | 
			
		||||
import { AddonModScormSyncProvider } from './services/scorm-sync';
 | 
			
		||||
 | 
			
		||||
export const ADDON_MOD_SCORM_SERVICES: Type<unknown>[] = [
 | 
			
		||||
    AddonModScormProvider,
 | 
			
		||||
    AddonModScormOfflineProvider,
 | 
			
		||||
    AddonModScormHelperProvider,
 | 
			
		||||
    AddonModScormSyncProvider,
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const routes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
        path: AddonModScormModuleHandlerService.PAGE_NAME,
 | 
			
		||||
        loadChildren: () => import('./scorm-lazy.module').then(m => m.AddonModScormLazyModule),
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [
 | 
			
		||||
        CoreMainMenuTabRoutingModule.forChild(routes),
 | 
			
		||||
        AddonModScormComponentsModule,
 | 
			
		||||
    ],
 | 
			
		||||
    providers: [
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
@ -480,3 +480,10 @@ ion-button.core-button-select {
 | 
			
		||||
.core-browser-copy-area {
 | 
			
		||||
    display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Different levels of padding.
 | 
			
		||||
@for $i from 0 through 15 {
 | 
			
		||||
    .core-padding-#{$i} {
 | 
			
		||||
        @include padding(null, null, null, 15px * $i + 16px);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user