commit
26f27f79fc
|
@ -24,7 +24,7 @@
|
||||||
|
|
||||||
<div *ngIf="scorm && loaded && !scorm.warningMessage">
|
<div *ngIf="scorm && loaded && !scorm.warningMessage">
|
||||||
<!-- Attempts status. -->
|
<!-- Attempts status. -->
|
||||||
<ion-card *ngIf="scorm.displayattemptstatus || (scorm.offlineAttempts && scorm.offlineAttempts.length)">
|
<ion-card *ngIf="(scorm.displayattemptstatus || (scorm.offlineAttempts && scorm.offlineAttempts.length)) && !skip">
|
||||||
<ion-card-header text-wrap>
|
<ion-card-header text-wrap>
|
||||||
<h2>{{ 'addon.mod_scorm.attempts' | translate }}</h2>
|
<h2>{{ 'addon.mod_scorm.attempts' | translate }}</h2>
|
||||||
</ion-card-header>
|
</ion-card-header>
|
||||||
|
@ -75,7 +75,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- TOC. -->
|
<!-- TOC. -->
|
||||||
<ion-card *ngIf="scorm && organizations && ((scorm.displaycoursestructure && organizations.length) || organizations.length > 1)" class="addon-mod_scorm-toc">
|
<ion-card *ngIf="scorm && organizations && ((scorm.displaycoursestructure && organizations.length) || organizations.length > 1) && !skip" class="addon-mod_scorm-toc">
|
||||||
<ion-card-header text-wrap>
|
<ion-card-header text-wrap>
|
||||||
<h2>{{ 'addon.mod_scorm.contents' | translate }}</h2>
|
<h2>{{ 'addon.mod_scorm.contents' | translate }}</h2>
|
||||||
</ion-card-header>
|
</ion-card-header>
|
||||||
|
@ -128,7 +128,7 @@
|
||||||
<ion-card *ngIf="!errorMessage && scorm && (!scorm.lastattemptlock || scorm.attemptsLeft > 0)">
|
<ion-card *ngIf="!errorMessage && scorm && (!scorm.lastattemptlock || scorm.attemptsLeft > 0)">
|
||||||
<ion-list>
|
<ion-list>
|
||||||
<!-- Open mode (Preview or Normal) -->
|
<!-- Open mode (Preview or Normal) -->
|
||||||
<div *ngIf="!scorm.hidebrowse" radio-group [(ngModel)]="scormOptions.mode" name="mode">
|
<div *ngIf="!scorm.hidebrowse && !skip" radio-group [(ngModel)]="scormOptions.mode" name="mode">
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<p class="item-heading">{{ 'addon.mod_scorm.mode' | translate }}</p>
|
<p class="item-heading">{{ 'addon.mod_scorm.mode' | translate }}</p>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
@ -143,14 +143,14 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Create new attempt -->
|
<!-- Create new attempt -->
|
||||||
<ion-item text-wrap *ngIf="!scorm.forcenewattempt && scorm.numAttempts > 0 && !scorm.incomplete && scorm.attemptsLeft > 0">
|
<ion-item text-wrap *ngIf="!scorm.forcenewattempt && scorm.numAttempts > 0 && !scorm.incomplete && scorm.attemptsLeft > 0 && !skip">
|
||||||
<ion-label>{{ 'addon.mod_scorm.newattempt' | translate }}</ion-label>
|
<ion-label>{{ 'addon.mod_scorm.newattempt' | translate }}</ion-label>
|
||||||
<ion-checkbox item-end name="newAttempt" [(ngModel)]="scormOptions.newAttempt">
|
<ion-checkbox item-end name="newAttempt" [(ngModel)]="scormOptions.newAttempt">
|
||||||
</ion-checkbox>
|
</ion-checkbox>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<!-- Button to open the SCORM. -->
|
<!-- Button to open the SCORM. -->
|
||||||
<ng-container *ngIf="!downloading">
|
<ng-container *ngIf="!downloading && !skip">
|
||||||
<ion-item text-wrap *ngIf="statusMessage">
|
<ion-item text-wrap *ngIf="statusMessage">
|
||||||
<p >{{ statusMessage | translate }}</p>
|
<p >{{ statusMessage | translate }}</p>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
|
@ -54,6 +54,8 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
organizations: any[]; // List of organizations.
|
organizations: any[]; // List of organizations.
|
||||||
loadingToc: boolean; // Whether the TOC is being loaded.
|
loadingToc: boolean; // Whether the TOC is being loaded.
|
||||||
toc: any[]; // Table of contents (structure).
|
toc: any[]; // Table of contents (structure).
|
||||||
|
accessInfo: any; // Access information.
|
||||||
|
skip: boolean; // Launch immediately.
|
||||||
|
|
||||||
protected fetchContentDefaultError = 'addon.mod_scorm.errorgetscorm'; // Default error to show when loading contents.
|
protected fetchContentDefaultError = 'addon.mod_scorm.errorgetscorm'; // Default error to show when loading contents.
|
||||||
protected syncEventName = AddonModScormSyncProvider.AUTO_SYNCED;
|
protected syncEventName = AddonModScormSyncProvider.AUTO_SYNCED;
|
||||||
|
@ -83,6 +85,10 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.skip) {
|
||||||
|
this.open();
|
||||||
|
}
|
||||||
|
|
||||||
this.scormProvider.logView(this.scorm.id, this.scorm.name).then(() => {
|
this.scormProvider.logView(this.scorm.id, this.scorm.name).then(() => {
|
||||||
this.checkCompletion();
|
this.checkCompletion();
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
|
@ -181,54 +187,72 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
this.syncTime = syncTime;
|
this.syncTime = syncTime;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get the number of attempts.
|
|
||||||
return this.scormProvider.getAttemptCount(this.scorm.id);
|
|
||||||
}).then((attemptsData) => {
|
|
||||||
this.attempts = attemptsData;
|
|
||||||
this.hasOffline = !!this.attempts.offline.length;
|
|
||||||
|
|
||||||
// Determine the attempt that will be continued or reviewed.
|
|
||||||
return this.scormHelper.determineAttemptToContinue(this.scorm, this.attempts);
|
|
||||||
}).then((attempt) => {
|
|
||||||
this.lastAttempt = attempt.number;
|
|
||||||
this.lastIsOffline = attempt.offline;
|
|
||||||
|
|
||||||
if (this.lastAttempt != this.attempts.lastAttempt.number) {
|
|
||||||
this.attemptToContinue = this.lastAttempt;
|
|
||||||
} else {
|
|
||||||
this.attemptToContinue = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the last attempt is incomplete.
|
|
||||||
return this.scormProvider.isAttemptIncomplete(this.scorm.id, this.lastAttempt, this.lastIsOffline);
|
|
||||||
}).then((incomplete) => {
|
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
this.scorm.incomplete = incomplete;
|
// Get access information.
|
||||||
this.scorm.numAttempts = this.attempts.total;
|
promises.push(this.scormProvider.getAccessInformation(this.scorm.id).then((accessInfo) => {
|
||||||
this.scorm.gradeMethodReadable = this.scormProvider.getScormGradeMethod(this.scorm);
|
this.accessInfo = accessInfo;
|
||||||
this.scorm.attemptsLeft = this.scormProvider.countAttemptsLeft(this.scorm, this.attempts.lastAttempt.number);
|
}));
|
||||||
|
|
||||||
if (this.scorm.forcenewattempt == AddonModScormProvider.SCORM_FORCEATTEMPT_ALWAYS ||
|
// Get the number of attempts.
|
||||||
(this.scorm.forcenewattempt && !this.scorm.incomplete)) {
|
promises.push(this.scormProvider.getAttemptCount(this.scorm.id).then((attemptsData) => {
|
||||||
this.scormOptions.newAttempt = true;
|
this.attempts = attemptsData;
|
||||||
}
|
this.hasOffline = !!this.attempts.offline.length;
|
||||||
|
|
||||||
promises.push(this.getReportedGrades());
|
// Determine the attempt that will be continued or reviewed.
|
||||||
|
return this.scormHelper.determineAttemptToContinue(this.scorm, this.attempts);
|
||||||
|
}).then((attempt) => {
|
||||||
|
this.lastAttempt = attempt.number;
|
||||||
|
this.lastIsOffline = attempt.offline;
|
||||||
|
|
||||||
promises.push(this.fetchStructure());
|
if (this.lastAttempt != this.attempts.lastAttempt.number) {
|
||||||
|
this.attemptToContinue = this.lastAttempt;
|
||||||
|
} else {
|
||||||
|
this.attemptToContinue = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.scorm.packagesize && this.errorMessage === '') {
|
// Check if the last attempt is incomplete.
|
||||||
// SCORM is supported but we don't have package size. Try to calculate it.
|
return this.scormProvider.isAttemptIncomplete(this.scorm.id, this.lastAttempt, this.lastIsOffline);
|
||||||
promises.push(this.scormProvider.calculateScormSize(this.scorm).then((size) => {
|
}).then((incomplete) => {
|
||||||
this.scorm.packagesize = size;
|
const promises = [];
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle status.
|
this.scorm.incomplete = incomplete;
|
||||||
this.setStatusListener();
|
this.scorm.numAttempts = this.attempts.total;
|
||||||
|
this.scorm.gradeMethodReadable = this.scormProvider.getScormGradeMethod(this.scorm);
|
||||||
|
this.scorm.attemptsLeft = this.scormProvider.countAttemptsLeft(this.scorm, this.attempts.lastAttempt.number);
|
||||||
|
|
||||||
return Promise.all(promises);
|
if (this.scorm.forcenewattempt == AddonModScormProvider.SCORM_FORCEATTEMPT_ALWAYS ||
|
||||||
|
(this.scorm.forcenewattempt && !this.scorm.incomplete)) {
|
||||||
|
this.scormOptions.newAttempt = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
promises.push(this.getReportedGrades());
|
||||||
|
|
||||||
|
promises.push(this.fetchStructure());
|
||||||
|
|
||||||
|
if (!this.scorm.packagesize && this.errorMessage === '') {
|
||||||
|
// SCORM is supported but we don't have package size. Try to calculate it.
|
||||||
|
promises.push(this.scormProvider.calculateScormSize(this.scorm).then((size) => {
|
||||||
|
this.scorm.packagesize = size;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle status.
|
||||||
|
promises.push(this.setStatusListener());
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
}));
|
||||||
|
|
||||||
|
return Promise.all(promises).then(() => {
|
||||||
|
// Check whether to launch the SCORM immediately.
|
||||||
|
if (typeof this.skip == 'undefined' && !this.hasOffline && !this.errorMessage &&
|
||||||
|
(!this.scorm.lastattemptlock || this.scorm.attemptsLeft > 0) &&
|
||||||
|
this.accessInfo.canskipview && !this.accessInfo.canviewreport &&
|
||||||
|
this.scorm.skipview >= AddonModScormProvider.SKIPVIEW_FIRST &&
|
||||||
|
(this.scorm.skipview == AddonModScormProvider.SKIPVIEW_ALWAYS || this.lastAttempt == 0)) {
|
||||||
|
this.skip = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
// All data obtained, now fill the context menu.
|
// All data obtained, now fill the context menu.
|
||||||
|
@ -368,6 +392,9 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
ionViewDidLeave(): void {
|
ionViewDidLeave(): void {
|
||||||
super.ionViewDidLeave();
|
super.ionViewDidLeave();
|
||||||
|
|
||||||
|
// Display the full page when returning to the page.
|
||||||
|
this.skip = false;
|
||||||
|
|
||||||
if (this.navCtrl.getActive().component.name == 'AddonModScormPlayerPage') {
|
if (this.navCtrl.getActive().component.name == 'AddonModScormPlayerPage') {
|
||||||
this.hasPlayed = true;
|
this.hasPlayed = true;
|
||||||
|
|
||||||
|
@ -460,11 +487,17 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open a SCORM. It will download the SCORM package if it's not downloaded or it has changed.
|
/**
|
||||||
// The scoId param indicates the SCO that needs to be loaded when the SCORM is opened. If not defined, load first SCO.
|
* Open a SCORM. It will download the SCORM package if it's not downloaded or it has changed.
|
||||||
open(e: Event, scoId: number): void {
|
*
|
||||||
e.preventDefault();
|
* @param {Event} [event] Event.
|
||||||
e.stopPropagation();
|
* @param {string} [scoId] SCO that needs to be loaded when the SCORM is opened. If not defined, load first SCO.
|
||||||
|
*/
|
||||||
|
open(event?: Event, scoId?: number): void {
|
||||||
|
if (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
if (this.downloading) {
|
if (this.downloading) {
|
||||||
// Scope is being downloaded, abort.
|
// Scope is being downloaded, abort.
|
||||||
|
|
|
@ -123,6 +123,9 @@ export class AddonModScormPrefetchHandler extends CoreCourseActivityPrefetchHand
|
||||||
// Ignore errors.
|
// Ignore errors.
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// Prefetch access information.
|
||||||
|
promises.push(this.scormProvider.getAccessInformation(scorm.id));
|
||||||
|
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
// Success, return the hash.
|
// Success, return the hash.
|
||||||
|
|
|
@ -83,6 +83,10 @@ export class AddonModScormProvider {
|
||||||
static SCORM_FORCEATTEMPT_ONCOMPLETE = 1;
|
static SCORM_FORCEATTEMPT_ONCOMPLETE = 1;
|
||||||
static SCORM_FORCEATTEMPT_ALWAYS = 2;
|
static SCORM_FORCEATTEMPT_ALWAYS = 2;
|
||||||
|
|
||||||
|
static SKIPVIEW_NEVER = 0;
|
||||||
|
static SKIPVIEW_FIRST = 1;
|
||||||
|
static SKIPVIEW_ALWAYS = 2;
|
||||||
|
|
||||||
// Events.
|
// Events.
|
||||||
static LAUNCH_NEXT_SCO_EVENT = 'addon_mod_scorm_launch_next_sco';
|
static LAUNCH_NEXT_SCO_EVENT = 'addon_mod_scorm_launch_next_sco';
|
||||||
static LAUNCH_PREV_SCO_EVENT = 'addon_mod_scorm_launch_prev_sco';
|
static LAUNCH_PREV_SCO_EVENT = 'addon_mod_scorm_launch_prev_sco';
|
||||||
|
@ -442,6 +446,44 @@ export class AddonModScormProvider {
|
||||||
return formatted;
|
return formatted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get access information for a given SCORM.
|
||||||
|
*
|
||||||
|
* @param {number} scormId SCORM ID.
|
||||||
|
* @param {boolean} [forceCache] True to always get the value from cache. false otherwise.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Object with access information.
|
||||||
|
* @since 3.7
|
||||||
|
*/
|
||||||
|
getAccessInformation(scormId: number, forceCache?: boolean, siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
if (!site.wsAvailable('mod_scorm_get_scorm_access_information')) {
|
||||||
|
// Access information not available for 3.6 or older sites.
|
||||||
|
return Promise.resolve({});
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
scormid: scormId
|
||||||
|
};
|
||||||
|
const preSets = {
|
||||||
|
cacheKey: this.getAccessInformationCacheKey(scormId),
|
||||||
|
omitExpires: forceCache
|
||||||
|
};
|
||||||
|
|
||||||
|
return site.read('mod_scorm_get_scorm_access_information', params, preSets);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cache key for access information WS calls.
|
||||||
|
*
|
||||||
|
* @param {number} scormId SCORM ID.
|
||||||
|
* @return {string} Cache key.
|
||||||
|
*/
|
||||||
|
protected getAccessInformationCacheKey(scormId: number): string {
|
||||||
|
return this.ROOT_CACHE_KEY + 'accessInfo:' + scormId;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the number of attempts done by a user in the given SCORM.
|
* Get the number of attempts done by a user in the given SCORM.
|
||||||
*
|
*
|
||||||
|
@ -1179,6 +1221,19 @@ export class AddonModScormProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates access information.
|
||||||
|
*
|
||||||
|
* @param {number} forumId SCORM ID.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||||
|
*/
|
||||||
|
invalidateAccessInformation(scormId: number, siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.invalidateWsCacheForKey(this.getAccessInformationCacheKey(scormId));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalidates all the data related to a certain SCORM.
|
* Invalidates all the data related to a certain SCORM.
|
||||||
*
|
*
|
||||||
|
@ -1193,6 +1248,7 @@ export class AddonModScormProvider {
|
||||||
promises.push(this.invalidateAttemptCount(scormId, siteId, userId));
|
promises.push(this.invalidateAttemptCount(scormId, siteId, userId));
|
||||||
promises.push(this.invalidateScos(scormId, siteId));
|
promises.push(this.invalidateScos(scormId, siteId));
|
||||||
promises.push(this.invalidateScormUserData(scormId, siteId));
|
promises.push(this.invalidateScormUserData(scormId, siteId));
|
||||||
|
promises.push(this.invalidateAccessInformation(scormId, siteId));
|
||||||
|
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
|
@ -254,8 +254,10 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Watch for changes on the status.
|
* Watch for changes on the status.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
protected setStatusListener(): void {
|
protected setStatusListener(): Promise<any> {
|
||||||
if (typeof this.statusObserver == 'undefined') {
|
if (typeof this.statusObserver == 'undefined') {
|
||||||
// Listen for changes on this module status.
|
// Listen for changes on this module status.
|
||||||
this.statusObserver = this.eventsProvider.on(CoreEventsProvider.PACKAGE_STATUS_CHANGED, (data) => {
|
this.statusObserver = this.eventsProvider.on(CoreEventsProvider.PACKAGE_STATUS_CHANGED, (data) => {
|
||||||
|
@ -269,11 +271,13 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
|
||||||
}, this.siteId);
|
}, this.siteId);
|
||||||
|
|
||||||
// Also, get the current status.
|
// Also, get the current status.
|
||||||
this.modulePrefetchDelegate.getModuleStatus(this.module, this.courseId).then((status) => {
|
return this.modulePrefetchDelegate.getModuleStatus(this.module, this.courseId).then((status) => {
|
||||||
this.currentStatus = status;
|
this.currentStatus = status;
|
||||||
this.showStatus(status);
|
this.showStatus(status);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue