forked from EVOgeek/Vmeda.Online
MOBILE-2348 quiz: Implement auto-save and refresh when leave player
parent
5878d7c3eb
commit
2411115a9c
|
@ -0,0 +1,227 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// 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 { PopoverController, Popover } from 'ionic-angular';
|
||||||
|
import { CoreLoggerProvider } from '@providers/logger';
|
||||||
|
import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
|
||||||
|
import { AddonModQuizProvider } from '../providers/quiz';
|
||||||
|
import { AddonModQuizConnectionErrorComponent } from '../components/connection-error/connection-error';
|
||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to support auto-save in quiz. Every certain seconds, it will check if there are changes in the current page answers
|
||||||
|
* and, if so, it will save them automatically.
|
||||||
|
*/
|
||||||
|
export class AddonModQuizAutoSave {
|
||||||
|
protected CHECK_CHANGES_INTERVAL = 5000;
|
||||||
|
|
||||||
|
protected logger;
|
||||||
|
protected checkChangesInterval; // Interval to check if there are changes in the answers.
|
||||||
|
protected loadPreviousAnswersTimeout; // Timeout to load previous answers.
|
||||||
|
protected autoSaveTimeout; // Timeout to auto-save the answers.
|
||||||
|
protected popover: Popover; // Popover to display there's been an error.
|
||||||
|
protected popoverShown = false; // Whether the popover is shown.
|
||||||
|
protected previousAnswers: any; // The previous answers. It is used to check if answers have changed.
|
||||||
|
protected errorObservable: BehaviorSubject<boolean>; // An observable to notify if there's been an error.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param {string} formName Name of the form where the answers are stored.
|
||||||
|
* @param {string} buttonSelector Selector to find the button to show the connection error.
|
||||||
|
* @param {CoreLoggerProvider} loggerProvider CoreLoggerProvider instance.
|
||||||
|
* @param {PopoverController} popoverCtrl PopoverController instance.
|
||||||
|
* @param {CoreQuestionHelperProvider} questionHelper CoreQuestionHelperProvider instance.
|
||||||
|
* @param {AddonModQuizProvider} quizProvider AddonModQuizProvider instance.
|
||||||
|
*/
|
||||||
|
constructor(protected formName: string, protected buttonSelector: string, loggerProvider: CoreLoggerProvider,
|
||||||
|
protected popoverCtrl: PopoverController, protected questionHelper: CoreQuestionHelperProvider,
|
||||||
|
protected quizProvider: AddonModQuizProvider) {
|
||||||
|
|
||||||
|
this.logger = loggerProvider.getInstance('AddonModQuizAutoSave');
|
||||||
|
|
||||||
|
// Create the popover.
|
||||||
|
this.popover = this.popoverCtrl.create(AddonModQuizConnectionErrorComponent);
|
||||||
|
this.popover.onDidDismiss(() => {
|
||||||
|
this.popoverShown = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create the observable to notify if an error happened.
|
||||||
|
this.errorObservable = new BehaviorSubject<boolean>(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel a pending auto save.
|
||||||
|
*/
|
||||||
|
cancelAutoSave(): void {
|
||||||
|
clearTimeout(this.autoSaveTimeout);
|
||||||
|
this.autoSaveTimeout = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the answers have changed in a page.
|
||||||
|
*
|
||||||
|
* @param {any} quiz Quiz.
|
||||||
|
* @param {any} attempt Attempt.
|
||||||
|
* @param {any} preflightData Preflight data.
|
||||||
|
* @param {boolean} [offline] Whether the quiz is being attempted in offline mode.
|
||||||
|
*/
|
||||||
|
checkChanges(quiz: any, attempt: any, preflightData: any, offline?: boolean): void {
|
||||||
|
if (this.autoSaveTimeout) {
|
||||||
|
// We already have an auto save pending, no need to check changes.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const answers = this.getAnswers();
|
||||||
|
|
||||||
|
if (!this.previousAnswers) {
|
||||||
|
// Previous answers isn't set, set it now.
|
||||||
|
this.previousAnswers = answers;
|
||||||
|
} else {
|
||||||
|
// Check if answers have changed.
|
||||||
|
let equal = true;
|
||||||
|
|
||||||
|
for (const name in answers) {
|
||||||
|
if (this.previousAnswers[name] != answers[name]) {
|
||||||
|
equal = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!equal) {
|
||||||
|
this.setAutoSaveTimer(quiz, attempt, preflightData, offline);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.previousAnswers = answers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get answers from a form.
|
||||||
|
*
|
||||||
|
* @return {any} Answers.
|
||||||
|
*/
|
||||||
|
protected getAnswers(): any {
|
||||||
|
return this.questionHelper.getAnswersFromForm(document.forms[this.formName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the auto save error.
|
||||||
|
*/
|
||||||
|
hideAutoSaveError(): void {
|
||||||
|
this.errorObservable.next(false);
|
||||||
|
this.popover.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an observable that will notify when an error happens or stops.
|
||||||
|
* It will send true when there's an error, and false when the error has been ammended.
|
||||||
|
*
|
||||||
|
* @return {BehaviorSubject<boolean>} Observable.
|
||||||
|
*/
|
||||||
|
onError(): BehaviorSubject<boolean> {
|
||||||
|
return this.errorObservable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedule an auto save process if it's not scheduled already.
|
||||||
|
*
|
||||||
|
* @param {any} quiz Quiz.
|
||||||
|
* @param {any} attempt Attempt.
|
||||||
|
* @param {any} preflightData Preflight data.
|
||||||
|
* @param {boolean} [offline] Whether the quiz is being attempted in offline mode.
|
||||||
|
*/
|
||||||
|
setAutoSaveTimer(quiz: any, attempt: any, preflightData: any, offline?: boolean): void {
|
||||||
|
// Don't schedule if already shceduled or quiz is almost closed.
|
||||||
|
if (quiz.autosaveperiod && !this.autoSaveTimeout && !this.quizProvider.isAttemptTimeNearlyOver(quiz, attempt)) {
|
||||||
|
|
||||||
|
// Schedule save.
|
||||||
|
this.autoSaveTimeout = setTimeout(() => {
|
||||||
|
const answers = this.getAnswers();
|
||||||
|
this.cancelAutoSave();
|
||||||
|
this.previousAnswers = answers; // Update previous answers to match what we're sending to the server.
|
||||||
|
|
||||||
|
this.quizProvider.saveAttempt(quiz, attempt, answers, preflightData, offline).then(() => {
|
||||||
|
// Save successful, we can hide the connection error if it was shown.
|
||||||
|
this.hideAutoSaveError();
|
||||||
|
}).catch((error) => {
|
||||||
|
// Error auto-saving. Show error and set timer again.
|
||||||
|
this.logger.warn('Error auto-saving data.', error);
|
||||||
|
|
||||||
|
// If there was no error already, show the error message.
|
||||||
|
if (!this.errorObservable.getValue()) {
|
||||||
|
this.errorObservable.next(true);
|
||||||
|
this.showAutoSaveError();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try again.
|
||||||
|
this.setAutoSaveTimer(quiz, attempt, preflightData, offline);
|
||||||
|
});
|
||||||
|
}, quiz.autosaveperiod * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show an error popover due to an auto save error.
|
||||||
|
*/
|
||||||
|
showAutoSaveError(ev?: Event): void {
|
||||||
|
// Don't show popover if it was already shown.
|
||||||
|
if (!this.popoverShown) {
|
||||||
|
this.popoverShown = true;
|
||||||
|
|
||||||
|
// If no event is provided, simulate it targeting the button.
|
||||||
|
this.popover.present({
|
||||||
|
ev: ev || { target: document.querySelector(this.buttonSelector) }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a process to periodically check changes in answers.
|
||||||
|
*
|
||||||
|
* @param {any} quiz Quiz.
|
||||||
|
* @param {any} attempt Attempt.
|
||||||
|
* @param {any} preflightData Preflight data.
|
||||||
|
* @param {boolean} [offline] Whether the quiz is being attempted in offline mode.
|
||||||
|
*/
|
||||||
|
startCheckChangesProcess(quiz: any, attempt: any, preflightData: any, offline?: boolean): void {
|
||||||
|
if (this.checkChangesInterval || !quiz.autosaveperiod) {
|
||||||
|
// We already have the interval in place or the quiz has autosave disabled.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.previousAnswers = undefined;
|
||||||
|
|
||||||
|
// Load initial answers in 2.5 seconds so the first check interval finds them already loaded.
|
||||||
|
this.loadPreviousAnswersTimeout = setTimeout(() => {
|
||||||
|
this.checkChanges(quiz, attempt, preflightData, offline);
|
||||||
|
}, 2500);
|
||||||
|
|
||||||
|
// Check changes every certain time.
|
||||||
|
this.checkChangesInterval = setInterval(() => {
|
||||||
|
this.checkChanges(quiz, attempt, preflightData, offline);
|
||||||
|
}, this.CHECK_CHANGES_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the periodical check for changes.
|
||||||
|
*/
|
||||||
|
stopCheckChangesProcess(): void {
|
||||||
|
clearTimeout(this.loadPreviousAnswersTimeout);
|
||||||
|
clearInterval(this.checkChangesInterval);
|
||||||
|
|
||||||
|
this.loadPreviousAnswersTimeout = undefined;
|
||||||
|
this.checkChangesInterval = undefined;
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,10 +20,12 @@ import { CoreComponentsModule } from '@components/components.module';
|
||||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||||
import { CoreCourseComponentsModule } from '@core/course/components/components.module';
|
import { CoreCourseComponentsModule } from '@core/course/components/components.module';
|
||||||
import { AddonModQuizIndexComponent } from './index/index';
|
import { AddonModQuizIndexComponent } from './index/index';
|
||||||
|
import { AddonModQuizConnectionErrorComponent } from './connection-error/connection-error';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
AddonModQuizIndexComponent
|
AddonModQuizIndexComponent,
|
||||||
|
AddonModQuizConnectionErrorComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
@ -36,10 +38,12 @@ import { AddonModQuizIndexComponent } from './index/index';
|
||||||
providers: [
|
providers: [
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
AddonModQuizIndexComponent
|
AddonModQuizIndexComponent,
|
||||||
|
AddonModQuizConnectionErrorComponent
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
AddonModQuizIndexComponent
|
AddonModQuizIndexComponent,
|
||||||
|
AddonModQuizConnectionErrorComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class AddonModQuizComponentsModule {}
|
export class AddonModQuizComponentsModule {}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
addon-mod-quiz-connection-error {
|
||||||
|
background-color: $red-light;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
background-color: $red-light;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// 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 } from '@angular/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that displays a quiz entry page.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'addon-mod-quiz-connection-error',
|
||||||
|
template: '<ion-item text-wrap>{{ "addon.mod_quiz.connectionerror" | translate }}</ion-item>',
|
||||||
|
})
|
||||||
|
export class AddonModQuizConnectionErrorComponent {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// Nothing to do.
|
||||||
|
}
|
||||||
|
}
|
|
@ -65,6 +65,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
|
||||||
protected gradebookData: {grade: number, feedback?: string}; // The gradebook grade and feedback.
|
protected gradebookData: {grade: number, feedback?: string}; // The gradebook grade and feedback.
|
||||||
protected overallStats: boolean; // Equivalent to overallstats in mod_quiz_view_object in Moodle.
|
protected overallStats: boolean; // Equivalent to overallstats in mod_quiz_view_object in Moodle.
|
||||||
protected finishedObserver: any; // It will observe attempt finished events.
|
protected finishedObserver: any; // It will observe attempt finished events.
|
||||||
|
protected hasPlayed = false; // Whether the user has gone to the quiz player (attempted).
|
||||||
|
|
||||||
constructor(injector: Injector, protected quizProvider: AddonModQuizProvider, @Optional() protected content: Content,
|
constructor(injector: Injector, protected quizProvider: AddonModQuizProvider, @Optional() protected content: Content,
|
||||||
protected quizHelper: AddonModQuizHelperProvider, protected quizOffline: AddonModQuizOfflineProvider,
|
protected quizHelper: AddonModQuizHelperProvider, protected quizOffline: AddonModQuizOfflineProvider,
|
||||||
|
@ -401,7 +402,36 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
|
||||||
ionViewDidEnter(): void {
|
ionViewDidEnter(): void {
|
||||||
super.ionViewDidEnter();
|
super.ionViewDidEnter();
|
||||||
|
|
||||||
// @todo: Go to auto review if we're coming from player.
|
if (this.hasPlayed) {
|
||||||
|
this.hasPlayed = false;
|
||||||
|
|
||||||
|
// Update data when we come back from the player since the attempt status could have changed.
|
||||||
|
let promise;
|
||||||
|
|
||||||
|
// Check if we need to go to review an attempt automatically.
|
||||||
|
if (this.autoReview && this.autoReview.synced) {
|
||||||
|
promise = this.goToAutoReview();
|
||||||
|
this.autoReview = undefined;
|
||||||
|
} else {
|
||||||
|
promise = Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh data.
|
||||||
|
this.loaded = false;
|
||||||
|
this.refreshIcon = 'spinner';
|
||||||
|
this.syncIcon = 'spinner';
|
||||||
|
this.content.scrollToTop();
|
||||||
|
|
||||||
|
promise.then(() => {
|
||||||
|
this.refreshContent().finally(() => {
|
||||||
|
this.loaded = true;
|
||||||
|
this.refreshIcon = 'refresh';
|
||||||
|
this.syncIcon = 'sync';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.autoReview = undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -410,6 +440,10 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
|
||||||
ionViewDidLeave(): void {
|
ionViewDidLeave(): void {
|
||||||
super.ionViewDidLeave();
|
super.ionViewDidLeave();
|
||||||
this.autoReview = undefined;
|
this.autoReview = undefined;
|
||||||
|
|
||||||
|
if (this.navCtrl.getActive().component.name == 'AddonModQuizPlayerPage') {
|
||||||
|
this.hasPlayed = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
<ion-header>
|
<ion-header>
|
||||||
<ion-navbar>
|
<ion-navbar>
|
||||||
<ion-title><core-format-text *ngIf="quiz" [text]="quiz.name"></core-format-text></ion-title>
|
<ion-title><core-format-text *ngIf="quiz" [text]="quiz.name"></core-format-text></ion-title>
|
||||||
</ion-navbar>
|
|
||||||
<ion-buttons end>
|
<ion-buttons end>
|
||||||
<button id="addon-mod_quiz-connection-error-button" ion-button icon-only [hidden]="!autoSaveError" (click)="showConnectionError($event)" [attr.aria-label]="'core.error' | translate">
|
<button id="addon-mod_quiz-connection-error-button" ion-button icon-only [hidden]="!autoSaveError" (click)="showConnectionError($event)" [attr.aria-label]="'core.error' | translate">
|
||||||
<ion-icon name="alert"></ion-icon>
|
<ion-icon name="alert"></ion-icon>
|
||||||
</button>
|
</button>
|
||||||
<!-- @todo <button menu-toggle="right" ng-if="toc && toc.length" class="button button-icon icon ion-bookmark" aria-label="{{ 'mma.mod_quiz.opentoc' | translate }}"></button> -->
|
<!-- @todo <button menu-toggle="right" ng-if="toc && toc.length" class="button button-icon icon ion-bookmark" aria-label="{{ 'mma.mod_quiz.opentoc' | translate }}"></button> -->
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
|
</ion-navbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<core-loading [hideUntil]="loaded">
|
<core-loading [hideUntil]="loaded">
|
||||||
|
|
|
@ -12,10 +12,11 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
|
import { Component, OnInit, OnDestroy, ViewChild, ElementRef, ChangeDetectorRef } from '@angular/core';
|
||||||
import { IonicPage, NavParams, Content } from 'ionic-angular';
|
import { IonicPage, NavParams, Content, PopoverController } from 'ionic-angular';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { CoreEventsProvider } from '@providers/events';
|
import { CoreEventsProvider } from '@providers/events';
|
||||||
|
import { CoreLoggerProvider } from '@providers/logger';
|
||||||
import { CoreSitesProvider } from '@providers/sites';
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
import { CoreSyncProvider } from '@providers/sync';
|
import { CoreSyncProvider } from '@providers/sync';
|
||||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||||
|
@ -24,6 +25,8 @@ import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
|
||||||
import { AddonModQuizProvider } from '../../providers/quiz';
|
import { AddonModQuizProvider } from '../../providers/quiz';
|
||||||
import { AddonModQuizSyncProvider } from '../../providers/quiz-sync';
|
import { AddonModQuizSyncProvider } from '../../providers/quiz-sync';
|
||||||
import { AddonModQuizHelperProvider } from '../../providers/helper';
|
import { AddonModQuizHelperProvider } from '../../providers/helper';
|
||||||
|
import { AddonModQuizAutoSave } from '../../classes/auto-save';
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that allows attempting a quiz.
|
* Page that allows attempting a quiz.
|
||||||
|
@ -53,6 +56,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy {
|
||||||
canReturn: boolean; // Whether the user can return to a page after seeing the summary.
|
canReturn: boolean; // Whether the user can return to a page after seeing the summary.
|
||||||
preventSubmitMessages: string[]; // List of messages explaining why the quiz cannot be submitted.
|
preventSubmitMessages: string[]; // List of messages explaining why the quiz cannot be submitted.
|
||||||
endTime: number; // The time when the attempt must be finished.
|
endTime: number; // The time when the attempt must be finished.
|
||||||
|
autoSaveError: boolean; // Whether there's been an error in auto-save.
|
||||||
|
|
||||||
protected element: HTMLElement; // Host element of the page.
|
protected element: HTMLElement; // Host element of the page.
|
||||||
protected courseId: number; // The course ID the quiz belongs to.
|
protected courseId: number; // The course ID the quiz belongs to.
|
||||||
|
@ -64,13 +68,15 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy {
|
||||||
protected newAttempt: boolean; // Whether the user is starting a new attempt.
|
protected newAttempt: boolean; // Whether the user is starting a new attempt.
|
||||||
protected quizDataLoaded: boolean; // Whether the quiz data has been loaded.
|
protected quizDataLoaded: boolean; // Whether the quiz data has been loaded.
|
||||||
protected timeUpCalled: boolean; // Whether the time up function has been called.
|
protected timeUpCalled: boolean; // Whether the time up function has been called.
|
||||||
|
protected autoSave: AddonModQuizAutoSave; // Class to auto-save answers every certain time.
|
||||||
|
protected autoSaveErrorSubscription: Subscription; // To be notified when an error happens in auto-save.
|
||||||
|
|
||||||
constructor(navParams: NavParams, element: ElementRef, protected translate: TranslateService,
|
constructor(navParams: NavParams, element: ElementRef, logger: CoreLoggerProvider, protected translate: TranslateService,
|
||||||
protected eventsProvider: CoreEventsProvider, protected sitesProvider: CoreSitesProvider,
|
protected eventsProvider: CoreEventsProvider, protected sitesProvider: CoreSitesProvider,
|
||||||
protected syncProvider: CoreSyncProvider, protected domUtils: CoreDomUtilsProvider,
|
protected syncProvider: CoreSyncProvider, protected domUtils: CoreDomUtilsProvider, popoverCtrl: PopoverController,
|
||||||
protected timeUtils: CoreTimeUtilsProvider, protected quizProvider: AddonModQuizProvider,
|
protected timeUtils: CoreTimeUtilsProvider, protected quizProvider: AddonModQuizProvider,
|
||||||
protected quizHelper: AddonModQuizHelperProvider, protected quizSync: AddonModQuizSyncProvider,
|
protected quizHelper: AddonModQuizHelperProvider, protected quizSync: AddonModQuizSyncProvider,
|
||||||
protected questionHelper: CoreQuestionHelperProvider) {
|
protected questionHelper: CoreQuestionHelperProvider, protected cdr: ChangeDetectorRef) {
|
||||||
|
|
||||||
this.quizId = navParams.get('quizId');
|
this.quizId = navParams.get('quizId');
|
||||||
this.courseId = navParams.get('courseId');
|
this.courseId = navParams.get('courseId');
|
||||||
|
@ -78,6 +84,10 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
// Block the quiz so it cannot be synced.
|
// Block the quiz so it cannot be synced.
|
||||||
this.syncProvider.blockOperation(AddonModQuizProvider.COMPONENT, this.quizId);
|
this.syncProvider.blockOperation(AddonModQuizProvider.COMPONENT, this.quizId);
|
||||||
|
|
||||||
|
// Create the auto save instance.
|
||||||
|
this.autoSave = new AddonModQuizAutoSave('addon-mod_quiz-player-form', '#addon-mod_quiz-connection-error-button',
|
||||||
|
logger, popoverCtrl, questionHelper, quizProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -86,6 +96,12 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy {
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
// Start the player when the page is loaded.
|
// Start the player when the page is loaded.
|
||||||
this.start();
|
this.start();
|
||||||
|
|
||||||
|
// Listen for errors on auto-save.
|
||||||
|
this.autoSaveErrorSubscription = this.autoSave.onError().subscribe((error) => {
|
||||||
|
this.autoSaveError = error;
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -93,8 +109,9 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy {
|
||||||
*/
|
*/
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
// Stop auto save.
|
// Stop auto save.
|
||||||
// @todo $mmaModQuizAutoSave.stopAutoSaving();
|
this.autoSave.cancelAutoSave();
|
||||||
// @todo $mmaModQuizAutoSave.stopCheckChangesProcess();
|
this.autoSave.stopCheckChangesProcess();
|
||||||
|
this.autoSaveErrorSubscription && this.autoSaveErrorSubscription.unsubscribe();
|
||||||
|
|
||||||
// Unblock the quiz so it can be synced.
|
// Unblock the quiz so it can be synced.
|
||||||
this.syncProvider.unblockOperation(AddonModQuizProvider.COMPONENT, this.quizId);
|
this.syncProvider.unblockOperation(AddonModQuizProvider.COMPONENT, this.quizId);
|
||||||
|
@ -176,20 +193,28 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy {
|
||||||
promise.then(() => {
|
promise.then(() => {
|
||||||
// Attempt data successfully saved, load the page or summary.
|
// Attempt data successfully saved, load the page or summary.
|
||||||
|
|
||||||
if (page === -1) {
|
// Attempt data successfully saved, load the page or summary.
|
||||||
return this.loadSummary();
|
let subPromise;
|
||||||
} else {
|
|
||||||
// @todo $mmaModQuizAutoSave.stopCheckChangesProcess(); // Stop checking for changes during page change.
|
// Stop checking for changes during page change.
|
||||||
|
this.autoSave.stopCheckChangesProcess();
|
||||||
|
|
||||||
|
if (page === -1) {
|
||||||
|
subPromise = this.loadSummary();
|
||||||
|
} else {
|
||||||
|
subPromise = this.loadPage(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
return subPromise.catch((error) => {
|
||||||
|
// If the user isn't seeing the summary, start the check again.
|
||||||
|
if (!this.showSummary) {
|
||||||
|
this.autoSave.startCheckChangesProcess(this.quiz, this.attempt, this.preflightData, this.offline);
|
||||||
|
}
|
||||||
|
|
||||||
return this.loadPage(page).catch((error) => {
|
|
||||||
// @todo $mmaModQuizAutoSave.startCheckChangesProcess($scope, quiz, attempt); // Start the check again.
|
|
||||||
this.domUtils.showErrorModalDefault(error, 'addon.mod_quiz.errorgetquestions', true);
|
this.domUtils.showErrorModalDefault(error, 'addon.mod_quiz.errorgetquestions', true);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}, (error) => {
|
}, (error) => {
|
||||||
this.domUtils.showErrorModalDefault(error, 'addon.mod_quiz.errorsaveattempt', true);
|
this.domUtils.showErrorModalDefault(error, 'addon.mod_quiz.errorsaveattempt', true);
|
||||||
}).catch((error) => {
|
|
||||||
this.domUtils.showErrorModalDefault(error, 'addon.mod_quiz.errorgetquestions', true);
|
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
|
|
||||||
|
@ -365,7 +390,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy {
|
||||||
this.quizProvider.logViewAttempt(this.attempt.id, page, this.preflightData, this.offline);
|
this.quizProvider.logViewAttempt(this.attempt.id, page, this.preflightData, this.offline);
|
||||||
|
|
||||||
// Start looking for changes.
|
// Start looking for changes.
|
||||||
// @todo $mmaModQuizAutoSave.startCheckChangesProcess($scope, quiz, attempt);
|
this.autoSave.startCheckChangesProcess(this.quiz, this.attempt, this.preflightData, this.offline);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -423,8 +448,8 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy {
|
||||||
this.offline);
|
this.offline);
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
// Answers saved, cancel auto save.
|
// Answers saved, cancel auto save.
|
||||||
// @todo $mmaModQuizAutoSave.cancelAutoSave();
|
this.autoSave.cancelAutoSave();
|
||||||
// @todo $mmaModQuizAutoSave.hideAutoSaveError($scope);
|
this.autoSave.hideAutoSaveError();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -443,7 +468,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy {
|
||||||
* @param {Event} ev Click event.
|
* @param {Event} ev Click event.
|
||||||
*/
|
*/
|
||||||
showConnectionError(ev: Event): void {
|
showConnectionError(ev: Event): void {
|
||||||
// @todo
|
this.autoSave.showAutoSaveError(ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue