commit
908167bc98
|
@ -333,15 +333,16 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
|
|||
return;
|
||||
}
|
||||
|
||||
const bestGrade = this.bestGrade.grade;
|
||||
const formattedGradebookGrade = AddonModQuiz.formatGrade(this.gradebookData.grade, quiz.decimalpoints);
|
||||
const formattedBestGrade = AddonModQuiz.formatGrade(this.bestGrade.grade, quiz.decimalpoints);
|
||||
const formattedBestGrade = AddonModQuiz.formatGrade(bestGrade, quiz.decimalpoints);
|
||||
let gradeToShow = formattedGradebookGrade; // By default we show the grade in the gradebook.
|
||||
|
||||
this.showResults = true;
|
||||
this.gradeOverridden = formattedGradebookGrade != formattedBestGrade;
|
||||
this.gradebookFeedback = this.gradebookData.feedback;
|
||||
|
||||
if (this.bestGrade.grade! > this.gradebookData.grade && this.gradebookData.grade == quiz.grade) {
|
||||
if (bestGrade && bestGrade > this.gradebookData.grade && this.gradebookData.grade == quiz.grade) {
|
||||
// The best grade is higher than the max grade for the quiz.
|
||||
// We'll do like Moodle web and show the best grade instead of the gradebook grade.
|
||||
this.gradeOverridden = false;
|
||||
|
@ -556,8 +557,16 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
|
|||
*
|
||||
* @returns Promise resolved when done.
|
||||
*/
|
||||
protected sync(): Promise<AddonModQuizSyncResult> {
|
||||
return AddonModQuizSync.syncQuiz(this.candidateQuiz!, true);
|
||||
protected async sync(): Promise<AddonModQuizSyncResult> {
|
||||
if (!this.candidateQuiz) {
|
||||
return {
|
||||
warnings: [],
|
||||
attemptFinished: false,
|
||||
updated: false,
|
||||
};
|
||||
}
|
||||
|
||||
return AddonModQuizSync.syncQuiz(this.candidateQuiz, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -579,36 +588,31 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
|
|||
}
|
||||
|
||||
const lastFinished = AddonModQuiz.getLastFinishedAttemptFromList(attempts);
|
||||
const promises: Promise<unknown>[] = [];
|
||||
let openReview = false;
|
||||
|
||||
if (this.autoReview && lastFinished && lastFinished.id >= this.autoReview.attemptId) {
|
||||
// User just finished an attempt in offline and it seems it's been synced, since it's finished in online.
|
||||
// Go to the review of this attempt if the user hasn't left this view.
|
||||
if (!this.isDestroyed && this.isCurrentView) {
|
||||
promises.push(this.goToAutoReview());
|
||||
openReview = true;
|
||||
}
|
||||
this.autoReview = undefined;
|
||||
}
|
||||
|
||||
// Get combined review options.
|
||||
promises.push(AddonModQuiz.getCombinedReviewOptions(quiz.id, { cmId: this.module.id }).then((options) => {
|
||||
this.options = options;
|
||||
|
||||
return;
|
||||
}));
|
||||
|
||||
// Get best grade.
|
||||
promises.push(this.getQuizGrade());
|
||||
|
||||
await Promise.all(promises);
|
||||
const [options] = await Promise.all([
|
||||
AddonModQuiz.getCombinedReviewOptions(quiz.id, { cmId: this.module.id }),
|
||||
this.getQuizGrade(),
|
||||
openReview ? this.goToAutoReview() : undefined,
|
||||
]);
|
||||
|
||||
this.options = options;
|
||||
const grade = this.gradebookData?.grade !== undefined ? this.gradebookData.grade : this.bestGrade?.grade;
|
||||
const quizGrade = AddonModQuiz.formatGrade(grade, quiz.decimalpoints);
|
||||
|
||||
// Calculate data to construct the header of the attempts table.
|
||||
AddonModQuizHelper.setQuizCalculatedData(quiz, this.options!);
|
||||
AddonModQuizHelper.setQuizCalculatedData(quiz, this.options);
|
||||
|
||||
this.overallStats = !!lastFinished && this.options!.alloptions.marks >= AddonModQuizProvider.QUESTION_OPTIONS_MARK_AND_MAX;
|
||||
this.overallStats = !!lastFinished && this.options.alloptions.marks >= AddonModQuizProvider.QUESTION_OPTIONS_MARK_AND_MAX;
|
||||
|
||||
// Calculate data to show for each attempt.
|
||||
const formattedAttempts = await Promise.all(attempts.map((attempt, index) => {
|
||||
|
|
|
@ -61,6 +61,6 @@ export type AddonModQuizNavigationQuestion = CoreQuestionQuestionParsed & {
|
|||
};
|
||||
|
||||
export type AddonModQuizNavigationModalReturn = {
|
||||
page?: number;
|
||||
page: number;
|
||||
slot?: number;
|
||||
};
|
||||
|
|
|
@ -66,11 +66,13 @@ export class AddonModQuizPreflightModalComponent implements OnInit {
|
|||
}
|
||||
|
||||
try {
|
||||
const quiz = this.quiz;
|
||||
|
||||
await Promise.all(this.rules.map(async (rule) => {
|
||||
// Check if preflight is required for rule and, if so, get the component to render it.
|
||||
const required = await AddonModQuizAccessRuleDelegate.isPreflightCheckRequiredForRule(
|
||||
rule,
|
||||
this.quiz!,
|
||||
quiz,
|
||||
this.attempt,
|
||||
this.prefetch,
|
||||
this.siteId,
|
||||
|
|
|
@ -94,20 +94,20 @@ export class AddonModQuizAttemptPage implements OnInit {
|
|||
// Load attempt data.
|
||||
const [options, accessInfo, attempt] = await Promise.all([
|
||||
AddonModQuiz.getCombinedReviewOptions(this.quiz.id, { cmId: this.quiz.coursemodule }),
|
||||
this.fetchAccessInfo(),
|
||||
this.fetchAttempt(),
|
||||
this.fetchAccessInfo(this.quiz),
|
||||
this.fetchAttempt(this.quiz.id),
|
||||
]);
|
||||
|
||||
// Set calculated data.
|
||||
this.showReviewColumn = accessInfo.canreviewmyattempts;
|
||||
AddonModQuizHelper.setQuizCalculatedData(this.quiz, options);
|
||||
|
||||
this.attempt = await AddonModQuizHelper.setAttemptCalculatedData(this.quiz!, attempt, false, undefined, true);
|
||||
this.attempt = await AddonModQuizHelper.setAttemptCalculatedData(this.quiz, attempt, false, undefined, true);
|
||||
|
||||
// Check if the feedback should be displayed.
|
||||
const grade = Number(this.attempt!.rescaledGrade);
|
||||
const grade = Number(this.attempt.rescaledGrade);
|
||||
|
||||
if (this.quiz.showFeedbackColumn && AddonModQuiz.isAttemptFinished(this.attempt!.state) &&
|
||||
if (this.quiz.showFeedbackColumn && AddonModQuiz.isAttemptFinished(this.attempt.state) &&
|
||||
options.someoptions.overallfeedback && !isNaN(grade)) {
|
||||
|
||||
// Feedback should be displayed, get the feedback for the grade.
|
||||
|
@ -127,11 +127,12 @@ export class AddonModQuizAttemptPage implements OnInit {
|
|||
/**
|
||||
* Get the attempt.
|
||||
*
|
||||
* @param quizId Quiz ID.
|
||||
* @returns Promise resolved when done.
|
||||
*/
|
||||
protected async fetchAttempt(): Promise<AddonModQuizAttemptWSData> {
|
||||
protected async fetchAttempt(quizId: number): Promise<AddonModQuizAttemptWSData> {
|
||||
// Get all the attempts and search the one we want.
|
||||
const attempts = await AddonModQuiz.getUserAttempts(this.quiz!.id, { cmId: this.cmId });
|
||||
const attempts = await AddonModQuiz.getUserAttempts(quizId, { cmId: this.cmId });
|
||||
|
||||
const attempt = attempts.find(attempt => attempt.id == this.attemptId);
|
||||
|
||||
|
@ -148,10 +149,11 @@ export class AddonModQuizAttemptPage implements OnInit {
|
|||
/**
|
||||
* Get the access info.
|
||||
*
|
||||
* @param quiz Quiz instance.
|
||||
* @returns Promise resolved when done.
|
||||
*/
|
||||
protected async fetchAccessInfo(): Promise<AddonModQuizGetQuizAccessInformationWSResponse> {
|
||||
const accessInfo = await AddonModQuiz.getQuizAccessInformation(this.quiz!.id, { cmId: this.cmId });
|
||||
protected async fetchAccessInfo(quiz: AddonModQuizQuizData): Promise<AddonModQuizGetQuizAccessInformationWSResponse> {
|
||||
const accessInfo = await AddonModQuiz.getQuizAccessInformation(quiz.id, { cmId: this.cmId });
|
||||
|
||||
if (!accessInfo.canreviewmyattempts) {
|
||||
return accessInfo;
|
||||
|
@ -161,7 +163,7 @@ export class AddonModQuizAttemptPage implements OnInit {
|
|||
await CoreUtils.ignoreErrors(AddonModQuiz.invalidateAttemptReviewForPage(this.attemptId, -1));
|
||||
|
||||
try {
|
||||
await AddonModQuiz.getAttemptReview(this.attemptId, { page: -1, cmId: this.quiz!.coursemodule });
|
||||
await AddonModQuiz.getAttemptReview(this.attemptId, { page: -1, cmId: quiz.coursemodule });
|
||||
} catch {
|
||||
// Error getting the review, assume the user cannot review the attempt.
|
||||
accessInfo.canreviewmyattempts = false;
|
||||
|
@ -202,7 +204,11 @@ export class AddonModQuizAttemptPage implements OnInit {
|
|||
* @returns Promise resolved when done.
|
||||
*/
|
||||
async reviewAttempt(): Promise<void> {
|
||||
CoreNavigator.navigate(`../../review/${this.attempt!.id}`);
|
||||
if (!this.attempt) {
|
||||
return;
|
||||
}
|
||||
|
||||
CoreNavigator.navigate(`../../review/${this.attempt.id}`);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -203,6 +203,10 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
|
|||
* @param button Clicked button.
|
||||
*/
|
||||
async behaviourButtonClicked(button: CoreQuestionBehaviourButton): Promise<void> {
|
||||
if (!this.quiz || !this.attempt) {
|
||||
return;
|
||||
}
|
||||
|
||||
let modal: CoreIonLoadingElement | undefined;
|
||||
|
||||
try {
|
||||
|
@ -212,13 +216,13 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
|
|||
modal = await CoreDomUtils.showModalLoading('core.sending', true);
|
||||
|
||||
// Get the answers.
|
||||
const answers = await this.prepareAnswers();
|
||||
const answers = await this.prepareAnswers(this.quiz.coursemodule);
|
||||
|
||||
// Add the clicked button data.
|
||||
answers[button.name] = button.value;
|
||||
|
||||
// Behaviour checks are always in online.
|
||||
await AddonModQuiz.processAttempt(this.quiz!, this.attempt!, answers, this.preflightData);
|
||||
await AddonModQuiz.processAttempt(this.quiz, this.attempt, answers, this.preflightData);
|
||||
|
||||
this.reloadNavigation = true; // Data sent to server, navigation should be reloaded.
|
||||
|
||||
|
@ -230,7 +234,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
|
|||
this.content?.scrollToTop(); // Scroll top so the spinner is seen.
|
||||
|
||||
try {
|
||||
await this.loadPage(this.attempt!.currentpage!);
|
||||
await this.loadPage(this.attempt.currentpage ?? 0);
|
||||
} finally {
|
||||
this.loaded = true;
|
||||
if (scrollTop != -1) {
|
||||
|
@ -311,8 +315,8 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
|
|||
}
|
||||
} 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);
|
||||
if (!this.showSummary && this.quiz) {
|
||||
this.autoSave.startCheckChangesProcess(this.quiz, this.attempt, this.preflightData, this.offline);
|
||||
}
|
||||
|
||||
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_quiz.errorgetquestions', true);
|
||||
|
@ -332,62 +336,58 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
|
|||
* @returns Promise resolved when done.
|
||||
*/
|
||||
protected async fetchData(): Promise<void> {
|
||||
try {
|
||||
this.quiz = await AddonModQuiz.getQuiz(this.courseId, this.cmId);
|
||||
this.quiz = await AddonModQuiz.getQuiz(this.courseId, this.cmId);
|
||||
|
||||
// Block the quiz so it cannot be synced.
|
||||
CoreSync.blockOperation(AddonModQuizProvider.COMPONENT, this.quiz.id);
|
||||
// Block the quiz so it cannot be synced.
|
||||
CoreSync.blockOperation(AddonModQuizProvider.COMPONENT, this.quiz.id);
|
||||
|
||||
// Wait for any ongoing sync to finish. We won't sync a quiz while it's being played.
|
||||
await AddonModQuizSync.waitForSync(this.quiz.id);
|
||||
// Wait for any ongoing sync to finish. We won't sync a quiz while it's being played.
|
||||
await AddonModQuizSync.waitForSync(this.quiz.id);
|
||||
|
||||
this.isSequential = AddonModQuiz.isNavigationSequential(this.quiz);
|
||||
this.isSequential = AddonModQuiz.isNavigationSequential(this.quiz);
|
||||
|
||||
if (AddonModQuiz.isQuizOffline(this.quiz)) {
|
||||
// Quiz supports offline.
|
||||
this.offline = true;
|
||||
} else {
|
||||
// Quiz doesn't support offline right now, but maybe it did and then the setting was changed.
|
||||
// If we have an unfinished offline attempt then we'll use offline mode.
|
||||
this.offline = await AddonModQuiz.isLastAttemptOfflineUnfinished(this.quiz);
|
||||
}
|
||||
|
||||
if (this.quiz!.timelimit && this.quiz!.timelimit > 0) {
|
||||
this.readableTimeLimit = CoreTime.formatTime(this.quiz.timelimit);
|
||||
}
|
||||
|
||||
// Get access information for the quiz.
|
||||
this.quizAccessInfo = await AddonModQuiz.getQuizAccessInformation(this.quiz.id, {
|
||||
cmId: this.quiz.coursemodule,
|
||||
readingStrategy: this.offline ? CoreSitesReadingStrategy.PREFER_CACHE : CoreSitesReadingStrategy.ONLY_NETWORK,
|
||||
});
|
||||
|
||||
// Get user attempts to determine last attempt.
|
||||
const attempts = await AddonModQuiz.getUserAttempts(this.quiz.id, {
|
||||
cmId: this.quiz.coursemodule,
|
||||
readingStrategy: this.offline ? CoreSitesReadingStrategy.PREFER_CACHE : CoreSitesReadingStrategy.ONLY_NETWORK,
|
||||
});
|
||||
|
||||
if (!attempts.length) {
|
||||
// There are no attempts, start a new one.
|
||||
this.newAttempt = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the last attempt. If it's finished, start a new one.
|
||||
this.lastAttempt = await AddonModQuizHelper.setAttemptCalculatedData(
|
||||
this.quiz,
|
||||
attempts[attempts.length - 1],
|
||||
false,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
|
||||
this.newAttempt = AddonModQuiz.isAttemptFinished(this.lastAttempt.state);
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_quiz.errorgetquiz', true);
|
||||
if (AddonModQuiz.isQuizOffline(this.quiz)) {
|
||||
// Quiz supports offline.
|
||||
this.offline = true;
|
||||
} else {
|
||||
// Quiz doesn't support offline right now, but maybe it did and then the setting was changed.
|
||||
// If we have an unfinished offline attempt then we'll use offline mode.
|
||||
this.offline = await AddonModQuiz.isLastAttemptOfflineUnfinished(this.quiz);
|
||||
}
|
||||
|
||||
if (this.quiz.timelimit && this.quiz.timelimit > 0) {
|
||||
this.readableTimeLimit = CoreTime.formatTime(this.quiz.timelimit);
|
||||
}
|
||||
|
||||
// Get access information for the quiz.
|
||||
this.quizAccessInfo = await AddonModQuiz.getQuizAccessInformation(this.quiz.id, {
|
||||
cmId: this.quiz.coursemodule,
|
||||
readingStrategy: this.offline ? CoreSitesReadingStrategy.PREFER_CACHE : CoreSitesReadingStrategy.ONLY_NETWORK,
|
||||
});
|
||||
|
||||
// Get user attempts to determine last attempt.
|
||||
const attempts = await AddonModQuiz.getUserAttempts(this.quiz.id, {
|
||||
cmId: this.quiz.coursemodule,
|
||||
readingStrategy: this.offline ? CoreSitesReadingStrategy.PREFER_CACHE : CoreSitesReadingStrategy.ONLY_NETWORK,
|
||||
});
|
||||
|
||||
if (!attempts.length) {
|
||||
// There are no attempts, start a new one.
|
||||
this.newAttempt = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the last attempt. If it's finished, start a new one.
|
||||
this.lastAttempt = await AddonModQuizHelper.setAttemptCalculatedData(
|
||||
this.quiz,
|
||||
attempts[attempts.length - 1],
|
||||
false,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
|
||||
this.newAttempt = AddonModQuiz.isAttemptFinished(this.lastAttempt.state);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -398,11 +398,15 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
|
|||
* @returns Promise resolved when done.
|
||||
*/
|
||||
async finishAttempt(userFinish?: boolean, timeUp?: boolean): Promise<void> {
|
||||
if (!this.quiz || !this.attempt) {
|
||||
return;
|
||||
}
|
||||
|
||||
let modal: CoreIonLoadingElement | undefined;
|
||||
|
||||
try {
|
||||
// Show confirm if the user clicked the finish button and the quiz is in progress.
|
||||
if (!timeUp && this.attempt!.state == AddonModQuizProvider.ATTEMPT_IN_PROGRESS) {
|
||||
if (!timeUp && this.attempt.state == AddonModQuizProvider.ATTEMPT_IN_PROGRESS) {
|
||||
await CoreDomUtils.showConfirm(Translate.instant('addon.mod_quiz.confirmclose'));
|
||||
}
|
||||
|
||||
|
@ -412,8 +416,8 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
|
|||
|
||||
// Trigger an event to notify the attempt was finished.
|
||||
CoreEvents.trigger(AddonModQuizProvider.ATTEMPT_FINISHED_EVENT, {
|
||||
quizId: this.quiz!.id,
|
||||
attemptId: this.attempt!.id,
|
||||
quizId: this.quiz.id,
|
||||
attemptId: this.attempt.id,
|
||||
synced: !this.offline,
|
||||
}, CoreSites.getCurrentSiteId());
|
||||
|
||||
|
@ -435,9 +439,13 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
|
|||
* @returns Promise resolved when done.
|
||||
*/
|
||||
protected async fixSequenceChecks(): Promise<void> {
|
||||
if (!this.attempt) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get current page data again to get the latest sequencechecks.
|
||||
const data = await AddonModQuiz.getAttemptData(this.attempt!.id, this.attempt!.currentpage!, this.preflightData, {
|
||||
cmId: this.quiz!.coursemodule,
|
||||
const data = await AddonModQuiz.getAttemptData(this.attempt.id, this.attempt.currentpage ?? 0, this.preflightData, {
|
||||
cmId: this.quiz?.coursemodule,
|
||||
readingStrategy: this.offline ? CoreSitesReadingStrategy.PREFER_CACHE : CoreSitesReadingStrategy.ONLY_NETWORK,
|
||||
});
|
||||
|
||||
|
@ -469,14 +477,14 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
|
|||
* Initializes the timer if enabled.
|
||||
*/
|
||||
protected initTimer(): void {
|
||||
if (!this.attemptAccessInfo?.endtime || this.attemptAccessInfo.endtime < 0) {
|
||||
if (!this.quizAccessInfo || !this.attempt || !this.attemptAccessInfo?.endtime || this.attemptAccessInfo.endtime < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Quiz has an end time. Check if time left should be shown.
|
||||
const shouldShowTime = AddonModQuiz.shouldShowTimeLeft(
|
||||
this.quizAccessInfo!.activerulenames,
|
||||
this.attempt!,
|
||||
this.quizAccessInfo.activerulenames,
|
||||
this.attempt,
|
||||
this.attemptAccessInfo.endtime,
|
||||
);
|
||||
|
||||
|
@ -494,8 +502,12 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
|
|||
* @returns Promise resolved when done.
|
||||
*/
|
||||
protected async loadPage(page: number): Promise<void> {
|
||||
const data = await AddonModQuiz.getAttemptData(this.attempt!.id, page, this.preflightData, {
|
||||
cmId: this.quiz!.coursemodule,
|
||||
if (!this.quiz || !this.attempt) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await AddonModQuiz.getAttemptData(this.attempt.id, page, this.preflightData, {
|
||||
cmId: this.quiz.coursemodule,
|
||||
readingStrategy: this.offline ? CoreSitesReadingStrategy.PREFER_CACHE : CoreSitesReadingStrategy.ONLY_NETWORK,
|
||||
});
|
||||
|
||||
|
@ -527,7 +539,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
|
|||
);
|
||||
|
||||
// Start looking for changes.
|
||||
this.autoSave.startCheckChangesProcess(this.quiz!, this.attempt, this.preflightData, this.offline);
|
||||
this.autoSave.startCheckChangesProcess(this.quiz, this.attempt, this.preflightData, this.offline);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -536,23 +548,27 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
|
|||
* @returns Promise resolved when done.
|
||||
*/
|
||||
protected async loadSummary(): Promise<void> {
|
||||
if (!this.quiz || !this.attempt) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.summaryQuestions = [];
|
||||
|
||||
this.summaryQuestions = await AddonModQuiz.getAttemptSummary(this.attempt!.id, this.preflightData, {
|
||||
cmId: this.quiz!.coursemodule,
|
||||
this.summaryQuestions = await AddonModQuiz.getAttemptSummary(this.attempt.id, this.preflightData, {
|
||||
cmId: this.quiz.coursemodule,
|
||||
loadLocal: this.offline,
|
||||
readingStrategy: this.offline ? CoreSitesReadingStrategy.PREFER_CACHE : CoreSitesReadingStrategy.ONLY_NETWORK,
|
||||
});
|
||||
|
||||
this.showSummary = true;
|
||||
this.canReturn = this.attempt!.state == AddonModQuizProvider.ATTEMPT_IN_PROGRESS && !this.attempt!.finishedOffline;
|
||||
this.canReturn = this.attempt.state == AddonModQuizProvider.ATTEMPT_IN_PROGRESS && !this.attempt.finishedOffline;
|
||||
this.preventSubmitMessages = AddonModQuiz.getPreventSubmitMessages(this.summaryQuestions);
|
||||
|
||||
this.dueDateWarning = AddonModQuiz.getAttemptDueDateWarning(this.quiz!, this.attempt!);
|
||||
this.dueDateWarning = AddonModQuiz.getAttemptDueDateWarning(this.quiz, this.attempt);
|
||||
|
||||
// Log summary as viewed.
|
||||
CoreUtils.ignoreErrors(
|
||||
AddonModQuiz.logViewAttemptSummary(this.attempt!.id, this.preflightData, this.quiz!.id, this.quiz!.name),
|
||||
AddonModQuiz.logViewAttemptSummary(this.attempt.id, this.preflightData, this.quiz.id, this.quiz.name),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -562,9 +578,13 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
|
|||
* @returns Promise resolved when done.
|
||||
*/
|
||||
protected async loadNavigation(): Promise<void> {
|
||||
if (!this.attempt) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We use the attempt summary to build the navigation because it contains all the questions.
|
||||
this.navigation = await AddonModQuiz.getAttemptSummary(this.attempt!.id, this.preflightData, {
|
||||
cmId: this.quiz!.coursemodule,
|
||||
this.navigation = await AddonModQuiz.getAttemptSummary(this.attempt.id, this.preflightData, {
|
||||
cmId: this.quiz?.coursemodule,
|
||||
loadLocal: this.offline,
|
||||
readingStrategy: this.offline ? CoreSitesReadingStrategy.PREFER_CACHE : CoreSitesReadingStrategy.ONLY_NETWORK,
|
||||
});
|
||||
|
@ -606,21 +626,22 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
|
|||
return;
|
||||
}
|
||||
|
||||
this.changePage(modalData.page!, true, modalData.slot);
|
||||
this.changePage(modalData.page, true, modalData.slot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the answers to be sent for the attempt.
|
||||
*
|
||||
* @param componentId Component ID.
|
||||
* @returns Promise resolved with the answers.
|
||||
*/
|
||||
protected prepareAnswers(): Promise<CoreQuestionsAnswers> {
|
||||
protected prepareAnswers(componentId: number): Promise<CoreQuestionsAnswers> {
|
||||
return CoreQuestionHelper.prepareAnswers(
|
||||
this.questions,
|
||||
this.getAnswers(),
|
||||
this.offline,
|
||||
this.component,
|
||||
this.quiz!.coursemodule,
|
||||
componentId,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -633,18 +654,22 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
|
|||
* @returns Promise resolved when done.
|
||||
*/
|
||||
protected async processAttempt(userFinish?: boolean, timeUp?: boolean, retrying?: boolean): Promise<void> {
|
||||
if (!this.quiz || !this.attempt) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the answers to send.
|
||||
let answers: CoreQuestionsAnswers = {};
|
||||
|
||||
if (!this.showSummary) {
|
||||
answers = await this.prepareAnswers();
|
||||
answers = await this.prepareAnswers(this.quiz.coursemodule);
|
||||
}
|
||||
|
||||
try {
|
||||
// Send the answers.
|
||||
await AddonModQuiz.processAttempt(
|
||||
this.quiz!,
|
||||
this.attempt!,
|
||||
this.quiz,
|
||||
this.attempt,
|
||||
answers,
|
||||
this.preflightData,
|
||||
userFinish,
|
||||
|
@ -680,7 +705,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
|
|||
CoreForms.triggerFormSubmittedEvent(this.formElement, !this.offline, CoreSites.getCurrentSiteId());
|
||||
}
|
||||
|
||||
return CoreQuestionHelper.clearTmpData(this.questions, this.component, this.quiz!.coursemodule);
|
||||
return CoreQuestionHelper.clearTmpData(this.questions, this.component, this.quiz.coursemodule);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -722,6 +747,8 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
|
|||
|
||||
// Quiz data has been loaded, try to start or continue.
|
||||
await this.startOrContinueAttempt();
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_quiz.errorgetquiz', true);
|
||||
} finally {
|
||||
this.loaded = true;
|
||||
}
|
||||
|
@ -733,41 +760,41 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
|
|||
* @returns Promise resolved when done.
|
||||
*/
|
||||
protected async startOrContinueAttempt(): Promise<void> {
|
||||
try {
|
||||
let attempt = this.newAttempt ? undefined : this.lastAttempt;
|
||||
if (!this.quiz || !this.quizAccessInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the preflight data and start attempt if needed.
|
||||
attempt = await AddonModQuizHelper.getAndCheckPreflightData(
|
||||
this.quiz!,
|
||||
this.quizAccessInfo!,
|
||||
this.preflightData,
|
||||
attempt,
|
||||
this.offline,
|
||||
false,
|
||||
'addon.mod_quiz.startattempt',
|
||||
);
|
||||
let attempt = this.newAttempt ? undefined : this.lastAttempt;
|
||||
|
||||
// Re-fetch attempt access information with the right attempt (might have changed because a new attempt was created).
|
||||
this.attemptAccessInfo = await AddonModQuiz.getAttemptAccessInformation(this.quiz!.id, attempt.id, {
|
||||
cmId: this.quiz!.coursemodule,
|
||||
readingStrategy: this.offline ? CoreSitesReadingStrategy.PREFER_CACHE : CoreSitesReadingStrategy.ONLY_NETWORK,
|
||||
});
|
||||
// Get the preflight data and start attempt if needed.
|
||||
attempt = await AddonModQuizHelper.getAndCheckPreflightData(
|
||||
this.quiz,
|
||||
this.quizAccessInfo,
|
||||
this.preflightData,
|
||||
attempt,
|
||||
this.offline,
|
||||
false,
|
||||
'addon.mod_quiz.startattempt',
|
||||
);
|
||||
|
||||
this.attempt = attempt;
|
||||
// Re-fetch attempt access information with the right attempt (might have changed because a new attempt was created).
|
||||
this.attemptAccessInfo = await AddonModQuiz.getAttemptAccessInformation(this.quiz.id, attempt.id, {
|
||||
cmId: this.quiz.coursemodule,
|
||||
readingStrategy: this.offline ? CoreSitesReadingStrategy.PREFER_CACHE : CoreSitesReadingStrategy.ONLY_NETWORK,
|
||||
});
|
||||
|
||||
await this.loadNavigation();
|
||||
this.attempt = attempt;
|
||||
|
||||
if (this.attempt.state != AddonModQuizProvider.ATTEMPT_OVERDUE && !this.attempt.finishedOffline) {
|
||||
// Attempt not overdue and not finished in offline, load page.
|
||||
await this.loadPage(this.attempt.currentpage!);
|
||||
await this.loadNavigation();
|
||||
|
||||
this.initTimer();
|
||||
} else {
|
||||
// Attempt is overdue or finished in offline, we can only load the summary.
|
||||
await this.loadSummary();
|
||||
}
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_quiz.errorgetquestions', true);
|
||||
if (this.attempt.state != AddonModQuizProvider.ATTEMPT_OVERDUE && !this.attempt.finishedOffline) {
|
||||
// Attempt not overdue and not finished in offline, load page.
|
||||
await this.loadPage(this.attempt.currentpage ?? 0);
|
||||
|
||||
this.initTimer();
|
||||
} else {
|
||||
// Attempt is overdue or finished in offline, we can only load the summary.
|
||||
await this.loadSummary();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -112,7 +112,7 @@ export class AddonModQuizReviewPage implements OnInit {
|
|||
* @param slot Slot of the question to scroll to.
|
||||
*/
|
||||
async changePage(page: number, slot?: number): Promise<void> {
|
||||
if (slot !== undefined && (this.attempt!.currentpage == -1 || page == this.currentPage)) {
|
||||
if (slot !== undefined && (this.attempt?.currentpage == -1 || page == this.currentPage)) {
|
||||
// Scrol to a certain question in the current page.
|
||||
this.scrollToQuestion(slot);
|
||||
|
||||
|
@ -174,7 +174,7 @@ export class AddonModQuizReviewPage implements OnInit {
|
|||
* @returns Promise resolved when done.
|
||||
*/
|
||||
protected async loadPage(page: number): Promise<void> {
|
||||
const data = await AddonModQuiz.getAttemptReview(this.attemptId, { page, cmId: this.quiz!.coursemodule });
|
||||
const data = await AddonModQuiz.getAttemptReview(this.attemptId, { page, cmId: this.quiz?.coursemodule });
|
||||
|
||||
this.attempt = data.attempt;
|
||||
this.attempt.currentpage = page;
|
||||
|
@ -203,7 +203,7 @@ export class AddonModQuizReviewPage implements OnInit {
|
|||
*/
|
||||
protected async loadNavigation(): Promise<void> {
|
||||
// Get all questions in single page to retrieve all the questions.
|
||||
const data = await AddonModQuiz.getAttemptReview(this.attemptId, { page: -1, cmId: this.quiz!.coursemodule });
|
||||
const data = await AddonModQuiz.getAttemptReview(this.attemptId, { page: -1, cmId: this.quiz?.coursemodule });
|
||||
|
||||
this.navigation = data.questions;
|
||||
|
||||
|
@ -260,7 +260,7 @@ export class AddonModQuizReviewPage implements OnInit {
|
|||
return;
|
||||
}
|
||||
|
||||
this.readableState = AddonModQuiz.getAttemptReadableStateName(this.attempt!.state || '');
|
||||
this.readableState = AddonModQuiz.getAttemptReadableStateName(this.attempt.state ?? '');
|
||||
|
||||
if (this.attempt.state != AddonModQuizProvider.ATTEMPT_FINISHED) {
|
||||
return;
|
||||
|
@ -283,7 +283,7 @@ export class AddonModQuizReviewPage implements OnInit {
|
|||
}
|
||||
|
||||
// Treat grade.
|
||||
if (this.options!.someoptions.marks >= AddonModQuizProvider.QUESTION_OPTIONS_MARK_AND_MAX &&
|
||||
if (this.options && this.options.someoptions.marks >= AddonModQuizProvider.QUESTION_OPTIONS_MARK_AND_MAX &&
|
||||
AddonModQuiz.quizHasGrades(this.quiz)) {
|
||||
|
||||
if (data.grade === null || data.grade === undefined) {
|
||||
|
@ -348,7 +348,7 @@ export class AddonModQuizReviewPage implements OnInit {
|
|||
return;
|
||||
}
|
||||
|
||||
this.changePage(modalData.page!, modalData.slot);
|
||||
this.changePage(modalData.page, modalData.slot);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -241,7 +241,7 @@ export class AddonModQuizPrefetchHandlerService extends CoreCourseActivityPrefet
|
|||
|
||||
const isLastFinished = !attempts.length || AddonModQuiz.isAttemptFinished(attempts[attempts.length - 1].state);
|
||||
|
||||
return quiz.attempts === 0 || quiz.attempts! > attempts.length || !isLastFinished;
|
||||
return quiz.attempts === 0 || (quiz.attempts ?? 0) > attempts.length || !isLastFinished;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -444,7 +444,7 @@ export class AddonModQuizPrefetchHandlerService extends CoreCourseActivityPrefet
|
|||
if (attempt.state == AddonModQuizProvider.ATTEMPT_IN_PROGRESS) {
|
||||
// Get data for each page.
|
||||
promises = promises.concat(pages.map(async (page) => {
|
||||
if (isSequential && page < attempt.currentpage!) {
|
||||
if (isSequential && attempt.currentpage && page < attempt.currentpage) {
|
||||
// Sequential quiz, cannot get pages before the current one.
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ export class AddonModQuizPushClickHandlerService implements CorePushNotification
|
|||
*/
|
||||
async handles(notification: AddonModQuizPushNotificationData): Promise<boolean> {
|
||||
return CoreUtils.isTrueOrOne(notification.notif) && notification.moodlecomponent == 'mod_quiz' &&
|
||||
this.SUPPORTED_NAMES.indexOf(notification.name!) != -1;
|
||||
this.SUPPORTED_NAMES.indexOf(notification.name ?? '') != -1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -358,7 +358,7 @@ export class AddonModQuizHelperProvider {
|
|||
if (attempt) {
|
||||
if (attempt.state != AddonModQuizProvider.ATTEMPT_OVERDUE && !attempt.finishedOffline) {
|
||||
// We're continuing an attempt. Call getAttemptData to validate the preflight data.
|
||||
await AddonModQuiz.getAttemptData(attempt.id, attempt.currentpage!, preflightData, modOptions);
|
||||
await AddonModQuiz.getAttemptData(attempt.id, attempt.currentpage ?? 0, preflightData, modOptions);
|
||||
|
||||
if (offline) {
|
||||
// Get current page stored in local.
|
||||
|
|
|
@ -198,11 +198,11 @@ export class AddonModQuizOfflineProvider {
|
|||
} else {
|
||||
entry = {
|
||||
quizid: quiz.id,
|
||||
userid: attempt.userid!,
|
||||
userid: attempt.userid ?? CoreSites.getCurrentSiteUserId(),
|
||||
id: attempt.id,
|
||||
courseid: quiz.course,
|
||||
timecreated: now,
|
||||
attempt: attempt.attempt!,
|
||||
attempt: attempt.attempt ?? 0,
|
||||
currentpage: attempt.currentpage,
|
||||
timemodified: now,
|
||||
finished: finish ? 1 : 0,
|
||||
|
@ -284,10 +284,12 @@ export class AddonModQuizOfflineProvider {
|
|||
|
||||
if (questions[slot]) {
|
||||
if (!questionsWithAnswers[slot]) {
|
||||
questionsWithAnswers[slot] = questions[slot];
|
||||
questionsWithAnswers[slot].answers = {};
|
||||
questionsWithAnswers[slot] = {
|
||||
...questions[slot],
|
||||
answers: {},
|
||||
};
|
||||
}
|
||||
questionsWithAnswers[slot].answers![nameWithoutPrefix] = answers[name];
|
||||
questionsWithAnswers[slot].answers[nameWithoutPrefix] = answers[name];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -295,7 +297,7 @@ export class AddonModQuizOfflineProvider {
|
|||
await Promise.all(Object.values(questionsWithAnswers).map(async (question) => {
|
||||
|
||||
const state = await CoreQuestionBehaviourDelegate.determineNewState(
|
||||
quiz.preferredbehaviour!,
|
||||
quiz.preferredbehaviour ?? '',
|
||||
AddonModQuizProvider.COMPONENT,
|
||||
attempt.id,
|
||||
question,
|
||||
|
@ -317,7 +319,7 @@ export class AddonModQuizOfflineProvider {
|
|||
AddonModQuizProvider.COMPONENT,
|
||||
quiz.id,
|
||||
attempt.id,
|
||||
attempt.userid!,
|
||||
attempt.userid ?? CoreSites.getCurrentSiteUserId(),
|
||||
answers,
|
||||
timeMod,
|
||||
siteId,
|
||||
|
@ -332,7 +334,7 @@ export class AddonModQuizOfflineProvider {
|
|||
AddonModQuizProvider.COMPONENT,
|
||||
quiz.id,
|
||||
attempt.id,
|
||||
attempt.userid!,
|
||||
attempt.userid ?? CoreSites.getCurrentSiteUserId(),
|
||||
question,
|
||||
newStates[slot],
|
||||
siteId,
|
||||
|
|
|
@ -305,8 +305,9 @@ export class AddonModQuizSyncProvider extends CoreCourseActivitySyncBaseProvider
|
|||
|
||||
// Get all the offline attempts for the quiz. It should always be 0 or 1 attempt
|
||||
const offlineAttempts = await AddonModQuizOffline.getQuizAttempts(quiz.id, siteId);
|
||||
const offlineAttempt = offlineAttempts.pop();
|
||||
|
||||
if (!offlineAttempts.length) {
|
||||
if (!offlineAttempt) {
|
||||
// Nothing to sync, finish.
|
||||
return this.finishSync(siteId, quiz, courseId, warnings);
|
||||
}
|
||||
|
@ -316,8 +317,6 @@ export class AddonModQuizSyncProvider extends CoreCourseActivitySyncBaseProvider
|
|||
throw new CoreError(Translate.instant('core.cannotconnect'));
|
||||
}
|
||||
|
||||
const offlineAttempt = offlineAttempts.pop()!;
|
||||
|
||||
// Now get the list of online attempts to make sure this attempt exists and isn't finished.
|
||||
const onlineAttempts = await AddonModQuiz.getUserAttempts(quiz.id, modOptions);
|
||||
|
||||
|
|
|
@ -290,7 +290,7 @@ export class AddonModQuizProvider {
|
|||
return dueDate * 1000;
|
||||
|
||||
case AddonModQuizProvider.ATTEMPT_OVERDUE:
|
||||
return (dueDate + quiz.graceperiod!) * 1000;
|
||||
return (dueDate + (quiz.graceperiod ?? 0)) * 1000;
|
||||
|
||||
default:
|
||||
this.logger.warn('Unexpected state when getting due date: ' + attempt.state);
|
||||
|
@ -356,7 +356,7 @@ export class AddonModQuizProvider {
|
|||
Translate.instant('addon.mod_quiz.statefinished'),
|
||||
Translate.instant(
|
||||
'addon.mod_quiz.statefinisheddetails',
|
||||
{ $a: CoreTimeUtils.userDate(attempt.timefinish! * 1000) },
|
||||
{ $a: CoreTimeUtils.userDate((attempt.timefinish ?? 0) * 1000) },
|
||||
),
|
||||
];
|
||||
|
||||
|
@ -625,7 +625,7 @@ export class AddonModQuizProvider {
|
|||
}
|
||||
|
||||
if (quiz.questiondecimalpoints == -1) {
|
||||
return quiz.decimalpoints!;
|
||||
return quiz.decimalpoints ?? 1;
|
||||
}
|
||||
|
||||
return quiz.questiondecimalpoints;
|
||||
|
@ -1780,7 +1780,7 @@ export class AddonModQuizProvider {
|
|||
* @returns Whether quiz is graded.
|
||||
*/
|
||||
quizHasGrades(quiz: AddonModQuizQuizWSData): boolean {
|
||||
return quiz.grade! >= 0.000005 && quiz.sumgrades! >= 0.000005;
|
||||
return (quiz.grade ?? 0) >= 0.000005 && (quiz.sumgrades ?? 0) >= 0.000005;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1802,8 +1802,8 @@ export class AddonModQuizProvider {
|
|||
|
||||
const rawGradeNum = typeof rawGrade == 'string' ? parseFloat(rawGrade) : rawGrade;
|
||||
if (rawGradeNum !== undefined && rawGradeNum !== null && !isNaN(rawGradeNum)) {
|
||||
if (quiz.sumgrades! >= 0.000005) {
|
||||
grade = rawGradeNum * quiz.grade! / quiz.sumgrades!;
|
||||
if (quiz.sumgrades && quiz.sumgrades >= 0.000005) {
|
||||
grade = rawGradeNum * (quiz.grade ?? 0) / quiz.sumgrades;
|
||||
} else {
|
||||
grade = 0;
|
||||
}
|
||||
|
@ -1816,7 +1816,7 @@ export class AddonModQuizProvider {
|
|||
if (format === 'question') {
|
||||
return this.formatGrade(grade, this.getGradeDecimals(quiz));
|
||||
} else if (format) {
|
||||
return this.formatGrade(grade, quiz.decimalpoints!);
|
||||
return this.formatGrade(grade, quiz.decimalpoints ?? 1);
|
||||
}
|
||||
|
||||
return String(grade);
|
||||
|
|
|
@ -132,5 +132,5 @@ export const CoreQuestionBehaviourDelegate = makeSingleton(CoreQuestionBehaviour
|
|||
* Answers classified by question slot.
|
||||
*/
|
||||
export type CoreQuestionQuestionWithAnswers = CoreQuestionQuestionParsed & {
|
||||
answers?: CoreQuestionsAnswers;
|
||||
answers: CoreQuestionsAnswers;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue