forked from CIT/Vmeda.Online
		
	MOBILE-4616 quiz: Improve summary and navigation info to match LMS
This commit is contained in:
		
							parent
							
								
									67bcfc5420
								
							
						
					
					
						commit
						4aa75a721f
					
				@ -19,24 +19,24 @@
 | 
			
		||||
                [detail]="false">
 | 
			
		||||
 | 
			
		||||
                <ion-label class="ion-text-wrap">
 | 
			
		||||
                    <span *ngIf="question.type !== 'description' && question.questionnumber">
 | 
			
		||||
                    <p class="item-heading" *ngIf="question.type !== 'description' && question.questionnumber">
 | 
			
		||||
                        {{ 'core.question.questionno' | translate:{$a: question.questionnumber} }}
 | 
			
		||||
                    </span>
 | 
			
		||||
                    <span *ngIf="question.type === 'description' || !question.questionnumber">
 | 
			
		||||
                    </p>
 | 
			
		||||
                    <p class="item-heading" *ngIf="question.type === 'description' || !question.questionnumber">
 | 
			
		||||
                        {{ 'core.question.information' | translate }}
 | 
			
		||||
                    </span>
 | 
			
		||||
                    </p>
 | 
			
		||||
                    <p>{{ question.status }}</p>
 | 
			
		||||
                </ion-label>
 | 
			
		||||
 | 
			
		||||
                <ion-icon *ngIf="question.type === 'description' || !question.questionnumber" name="fas-circle-info" slot="end"
 | 
			
		||||
                    aria-hidden="true" />
 | 
			
		||||
                <ion-icon *ngIf="question.stateClass === 'core-question-requiresgrading'" name="fas-circle-question"
 | 
			
		||||
                    [title]="question.status" slot="end" />
 | 
			
		||||
                <ion-icon *ngIf="question.stateClass === 'core-question-correct'" [name]="correctIcon" color="success"
 | 
			
		||||
                    [title]="question.status" slot="end" />
 | 
			
		||||
                <ion-icon *ngIf="question.stateClass === 'core-question-partiallycorrect'" [name]="partialCorrectIcon" color="warning"
 | 
			
		||||
                    [title]="question.status" slot="end" />
 | 
			
		||||
                <ion-icon *ngIf="question.stateClass === 'core-question-incorrect' ||
 | 
			
		||||
                    question.stateClass === 'core-question-notanswered'" [name]="incorrectIcon" color="danger" [title]="question.status"
 | 
			
		||||
                <ion-icon *ngIf="question.stateclass === 'requiresgrading'" name="fas-circle-question" aria-hidden="true" slot="end" />
 | 
			
		||||
                <ion-icon *ngIf="question.stateclass === 'correct'" [name]="correctIcon" color="success" aria-hidden="true" slot="end" />
 | 
			
		||||
                <ion-icon *ngIf="question.stateclass === 'partiallycorrect'" [name]="partialCorrectIcon" color="warning" aria-hidden="true"
 | 
			
		||||
                    slot="end" />
 | 
			
		||||
                <ion-icon *ngIf="question.stateclass === 'incorrect' || question.stateclass === 'notanswered'" [name]="incorrectIcon"
 | 
			
		||||
                    color="danger" aria-hidden="true" slot="end" />
 | 
			
		||||
                <ion-icon *ngIf="question.stateclass === 'invalidanswer'" name="fas-triangle-exclamation" color="danger" aria-hidden="true"
 | 
			
		||||
                    slot="end" />
 | 
			
		||||
            </ion-item>
 | 
			
		||||
        </ion-list>
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,6 @@
 | 
			
		||||
ion-item.core-question-blocked,
 | 
			
		||||
ion-item.core-question-complete,
 | 
			
		||||
ion-item.core-question-answersaved,
 | 
			
		||||
ion-item.core-question-requiresgrading {
 | 
			
		||||
    --background: var(--gray-300);
 | 
			
		||||
}
 | 
			
		||||
@ -26,6 +26,7 @@ import { ModalController } from '@singletons';
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'addon-mod-quiz-navigation-modal',
 | 
			
		||||
    templateUrl: 'navigation-modal.html',
 | 
			
		||||
    styleUrl: 'navigation-modal.scss',
 | 
			
		||||
    standalone: true,
 | 
			
		||||
    imports: [
 | 
			
		||||
        CoreSharedModule,
 | 
			
		||||
 | 
			
		||||
@ -15,7 +15,8 @@
 | 
			
		||||
                (click)="showConnectionError($event)" [ariaLabel]="'addon.mod_quiz.connectionerror' | translate" aria-haspopup="dialog">
 | 
			
		||||
                <ion-icon name="fas-circle-exclamation" slot="icon-only" aria-hidden="true" />
 | 
			
		||||
            </ion-button>
 | 
			
		||||
            <ion-button *ngIf="navigation.length" [ariaLabel]="'addon.mod_quiz.opentoc' | translate" (click)="openNavigation()">
 | 
			
		||||
            <ion-button *ngIf="navigation.length && !showSummary" [ariaLabel]="'addon.mod_quiz.opentoc' | translate"
 | 
			
		||||
                (click)="openNavigation()">
 | 
			
		||||
                <ion-icon name="fas-bookmark" slot="icon-only" aria-hidden="true" />
 | 
			
		||||
            </ion-button>
 | 
			
		||||
        </ion-buttons>
 | 
			
		||||
@ -72,11 +73,24 @@
 | 
			
		||||
            <ng-container *ngFor="let question of summaryQuestions">
 | 
			
		||||
                <ion-item *ngIf="question.type !== 'description' && question.questionnumber"
 | 
			
		||||
                    (click)="!isSequential && canReturn && changePage(question.page, false, question.slot)"
 | 
			
		||||
                    [detail]="!isSequential && canReturn" [button]="!isSequential && canReturn" class="ion-text-wrap">
 | 
			
		||||
                    <ion-label>
 | 
			
		||||
                        <span [attr.aria-label]="'core.question.questionno' | translate:{$a: question.questionnumber}">
 | 
			
		||||
                            {{ question.questionnumber }}.</span> {{ question.status }}
 | 
			
		||||
                    [detail]="!isSequential && canReturn" [button]="!isSequential && canReturn"
 | 
			
		||||
                    [class]="'ion-text-wrap ' + question.stateClass">
 | 
			
		||||
                    <ion-label class="ion-text-wrap">
 | 
			
		||||
                        <p class="item-heading">
 | 
			
		||||
                            {{ 'core.question.questionno' | translate:{$a: question.questionnumber} }}
 | 
			
		||||
                        </p>
 | 
			
		||||
                        <p>{{ question.status }}</p>
 | 
			
		||||
                    </ion-label>
 | 
			
		||||
 | 
			
		||||
                    <ion-icon *ngIf="question.stateclass === 'requiresgrading'" name="fas-circle-question" aria-hidden="true" slot="end" />
 | 
			
		||||
                    <ion-icon *ngIf="question.stateclass === 'correct'" [name]="correctIcon" color="success" aria-hidden="true"
 | 
			
		||||
                        slot="end" />
 | 
			
		||||
                    <ion-icon *ngIf="question.stateclass === 'partiallycorrect'" [name]="partialCorrectIcon" color="warning"
 | 
			
		||||
                        aria-hidden="true" slot="end" />
 | 
			
		||||
                    <ion-icon *ngIf="question.stateclass === 'incorrect' || question.stateclass === 'notanswered'" [name]="incorrectIcon"
 | 
			
		||||
                        color="danger" aria-hidden="true" slot="end" />
 | 
			
		||||
                    <ion-icon *ngIf="question.stateclass === 'invalidanswer'" name="fas-triangle-exclamation" color="danger"
 | 
			
		||||
                        aria-hidden="true" slot="end" />
 | 
			
		||||
                </ion-item>
 | 
			
		||||
            </ng-container>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -17,4 +17,11 @@ $quiz-timer-iterations: 15 !default;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ion-item.core-question-blocked,
 | 
			
		||||
    ion-item.core-question-complete,
 | 
			
		||||
    ion-item.core-question-answersaved,
 | 
			
		||||
    ion-item.core-question-requiresgrading {
 | 
			
		||||
        --background: var(--gray-300);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -63,7 +63,7 @@ import { CoreLoadings } from '@services/loadings';
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'page-addon-mod-quiz-player',
 | 
			
		||||
    templateUrl: 'player.html',
 | 
			
		||||
    styleUrls: ['player.scss'],
 | 
			
		||||
    styleUrl: 'player.scss',
 | 
			
		||||
})
 | 
			
		||||
export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
 | 
			
		||||
 | 
			
		||||
@ -93,6 +93,9 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
 | 
			
		||||
    dueDateWarning?: string; // Warning about due date.
 | 
			
		||||
    courseId!: number; // The course ID the quiz belongs to.
 | 
			
		||||
    cmId!: number; // Course module ID.
 | 
			
		||||
    correctIcon = '';
 | 
			
		||||
    incorrectIcon = '';
 | 
			
		||||
    partialCorrectIcon = '';
 | 
			
		||||
 | 
			
		||||
    protected preflightData: Record<string, string> = {}; // Preflight data to attempt the quiz.
 | 
			
		||||
    protected quizAccessInfo?: AddonModQuizGetQuizAccessInformationWSResponse; // Quiz access information.
 | 
			
		||||
@ -672,6 +675,12 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!this.correctIcon) {
 | 
			
		||||
            this.correctIcon = CoreQuestionHelper.getCorrectIcon().fullName;
 | 
			
		||||
            this.incorrectIcon = CoreQuestionHelper.getIncorrectIcon().fullName;
 | 
			
		||||
            this.partialCorrectIcon = CoreQuestionHelper.getPartiallyCorrectIcon().fullName;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.summaryQuestions = [];
 | 
			
		||||
 | 
			
		||||
        this.summaryQuestions = await AddonModQuiz.getAttemptSummary(this.attempt.id, this.preflightData, {
 | 
			
		||||
@ -680,6 +689,10 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
 | 
			
		||||
            readingStrategy: this.offline ? CoreSitesReadingStrategy.PREFER_CACHE : CoreSitesReadingStrategy.ONLY_NETWORK,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.summaryQuestions.forEach((question) => {
 | 
			
		||||
            CoreQuestionHelper.populateQuestionStateClass(question);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.showSummary = true;
 | 
			
		||||
        this.canReturn = this.attempt.state === AddonModQuizAttemptStates.IN_PROGRESS && !this.attempt.finishedOffline;
 | 
			
		||||
        this.preventSubmitMessages = AddonModQuiz.getPreventSubmitMessages(this.summaryQuestions);
 | 
			
		||||
@ -707,7 +720,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.navigation.forEach((question) => {
 | 
			
		||||
            question.stateClass = CoreQuestionHelper.getQuestionStateClass(question.state || '');
 | 
			
		||||
            CoreQuestionHelper.populateQuestionStateClass(question);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -210,7 +210,7 @@ export class AddonModQuizReviewPage implements OnInit {
 | 
			
		||||
        this.navigation = data.questions;
 | 
			
		||||
 | 
			
		||||
        this.navigation.forEach((question) => {
 | 
			
		||||
            question.stateClass = CoreQuestionHelper.getQuestionStateClass(question.state || '');
 | 
			
		||||
            CoreQuestionHelper.populateQuestionStateClass(question);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const lastQuestion = data.questions[data.questions.length - 1];
 | 
			
		||||
 | 
			
		||||
@ -32,6 +32,7 @@ import { ContextLevel } from '@/core/constants';
 | 
			
		||||
import { CoreIonicColorNames } from '@singletons/colors';
 | 
			
		||||
import { CoreViewer } from '@features/viewer/services/viewer';
 | 
			
		||||
import { convertTextToHTMLElement } from '@/core/utils/create-html-element';
 | 
			
		||||
import { AddonModQuizNavigationQuestion } from '@addons/mod/quiz/components/navigation-modal/navigation-modal';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Service with some common functions to handle questions.
 | 
			
		||||
@ -476,15 +477,18 @@ export class CoreQuestionHelperProvider {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the CSS class for a question based on its state.
 | 
			
		||||
     * Populates the CSS class for a question based on its state.
 | 
			
		||||
     *
 | 
			
		||||
     * @param name Question's state name.
 | 
			
		||||
     * @returns State class.
 | 
			
		||||
     * @param question Question.
 | 
			
		||||
     */
 | 
			
		||||
    getQuestionStateClass(name: string): string {
 | 
			
		||||
        const state = CoreQuestion.getState(name);
 | 
			
		||||
    populateQuestionStateClass(question: AddonModQuizNavigationQuestion): void {
 | 
			
		||||
        if (!question.stateclass) {
 | 
			
		||||
            const state = CoreQuestion.getState(question.state);
 | 
			
		||||
 | 
			
		||||
        return state ? state.class : '';
 | 
			
		||||
            question.stateclass = state.stateclass;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        question.stateClass = 'core-question-' + (question.stateclass ?? 'unknown');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -42,7 +42,6 @@ const QUESTION_PREFIX_REGEX = /q\d+:(\d+)_/;
 | 
			
		||||
const STATES: Record<string, CoreQuestionState> = {
 | 
			
		||||
    todo: {
 | 
			
		||||
        name: 'todo',
 | 
			
		||||
        class: 'core-question-notyetanswered',
 | 
			
		||||
        status: 'notyetanswered',
 | 
			
		||||
        stateclass: 'notyetanswered',
 | 
			
		||||
        active: true,
 | 
			
		||||
@ -50,7 +49,6 @@ const STATES: Record<string, CoreQuestionState> = {
 | 
			
		||||
    },
 | 
			
		||||
    invalid: {
 | 
			
		||||
        name: 'invalid',
 | 
			
		||||
        class: 'core-question-invalidanswer',
 | 
			
		||||
        status: 'invalidanswer',
 | 
			
		||||
        stateclass: 'invalidanswer',
 | 
			
		||||
        active: true,
 | 
			
		||||
@ -58,7 +56,6 @@ const STATES: Record<string, CoreQuestionState> = {
 | 
			
		||||
    },
 | 
			
		||||
    complete: {
 | 
			
		||||
        name: 'complete',
 | 
			
		||||
        class: 'core-question-answersaved',
 | 
			
		||||
        status: 'answersaved',
 | 
			
		||||
        stateclass: 'answersaved',
 | 
			
		||||
        active: true,
 | 
			
		||||
@ -66,7 +63,6 @@ const STATES: Record<string, CoreQuestionState> = {
 | 
			
		||||
    },
 | 
			
		||||
    needsgrading: {
 | 
			
		||||
        name: 'needsgrading',
 | 
			
		||||
        class: 'core-question-requiresgrading',
 | 
			
		||||
        status: 'requiresgrading',
 | 
			
		||||
        stateclass: 'requiresgrading',
 | 
			
		||||
        active: false,
 | 
			
		||||
@ -74,7 +70,6 @@ const STATES: Record<string, CoreQuestionState> = {
 | 
			
		||||
    },
 | 
			
		||||
    finished: {
 | 
			
		||||
        name: 'finished',
 | 
			
		||||
        class: 'core-question-complete',
 | 
			
		||||
        status: 'complete',
 | 
			
		||||
        stateclass: 'complete',
 | 
			
		||||
        active: false,
 | 
			
		||||
@ -82,7 +77,6 @@ const STATES: Record<string, CoreQuestionState> = {
 | 
			
		||||
    },
 | 
			
		||||
    gaveup: {
 | 
			
		||||
        name: 'gaveup',
 | 
			
		||||
        class: 'core-question-notanswered',
 | 
			
		||||
        status: 'notanswered',
 | 
			
		||||
        stateclass: 'notanswered',
 | 
			
		||||
        active: false,
 | 
			
		||||
@ -90,7 +84,6 @@ const STATES: Record<string, CoreQuestionState> = {
 | 
			
		||||
    },
 | 
			
		||||
    gradedwrong: {
 | 
			
		||||
        name: 'gradedwrong',
 | 
			
		||||
        class: 'core-question-incorrect',
 | 
			
		||||
        status: 'incorrect',
 | 
			
		||||
        stateclass: 'incorrect',
 | 
			
		||||
        active: false,
 | 
			
		||||
@ -98,7 +91,6 @@ const STATES: Record<string, CoreQuestionState> = {
 | 
			
		||||
    },
 | 
			
		||||
    gradedpartial: {
 | 
			
		||||
        name: 'gradedpartial',
 | 
			
		||||
        class: 'core-question-partiallycorrect',
 | 
			
		||||
        status: 'partiallycorrect',
 | 
			
		||||
        stateclass: 'partiallycorrect',
 | 
			
		||||
        active: false,
 | 
			
		||||
@ -106,7 +98,6 @@ const STATES: Record<string, CoreQuestionState> = {
 | 
			
		||||
    },
 | 
			
		||||
    gradedright: {
 | 
			
		||||
        name: 'gradedright',
 | 
			
		||||
        class: 'core-question-correct',
 | 
			
		||||
        status: 'correct',
 | 
			
		||||
        stateclass: 'correct',
 | 
			
		||||
        active: false,
 | 
			
		||||
@ -114,7 +105,6 @@ const STATES: Record<string, CoreQuestionState> = {
 | 
			
		||||
    },
 | 
			
		||||
    mangrwrong: {
 | 
			
		||||
        name: 'mangrwrong',
 | 
			
		||||
        class: 'core-question-incorrect',
 | 
			
		||||
        status: 'incorrect',
 | 
			
		||||
        stateclass: 'incorrect',
 | 
			
		||||
        active: false,
 | 
			
		||||
@ -122,7 +112,6 @@ const STATES: Record<string, CoreQuestionState> = {
 | 
			
		||||
    },
 | 
			
		||||
    mangrpartial: {
 | 
			
		||||
        name: 'mangrpartial',
 | 
			
		||||
        class: 'core-question-partiallycorrect',
 | 
			
		||||
        status: 'partiallycorrect',
 | 
			
		||||
        stateclass: 'partiallycorrect',
 | 
			
		||||
        active: false,
 | 
			
		||||
@ -130,7 +119,6 @@ const STATES: Record<string, CoreQuestionState> = {
 | 
			
		||||
    },
 | 
			
		||||
    mangrright: {
 | 
			
		||||
        name: 'mangrright',
 | 
			
		||||
        class: 'core-question-correct',
 | 
			
		||||
        status: 'correct',
 | 
			
		||||
        stateclass: 'correct',
 | 
			
		||||
        active: false,
 | 
			
		||||
@ -138,7 +126,6 @@ const STATES: Record<string, CoreQuestionState> = {
 | 
			
		||||
    },
 | 
			
		||||
    cannotdeterminestatus: { // Special state for Mobile, sometimes we won't have enough data to detemrine the state.
 | 
			
		||||
        name: 'cannotdeterminestatus',
 | 
			
		||||
        class: 'core-question-unknown',
 | 
			
		||||
        status: 'cannotdeterminestatus',
 | 
			
		||||
        stateclass: undefined,
 | 
			
		||||
        active: true,
 | 
			
		||||
@ -594,7 +581,6 @@ export const CoreQuestion = makeSingleton(CoreQuestionProvider);
 | 
			
		||||
 */
 | 
			
		||||
export type CoreQuestionState = {
 | 
			
		||||
    name: string; // Name of the state.
 | 
			
		||||
    class: string; // Class to style the state.
 | 
			
		||||
    status: string; // The string key to translate the state.
 | 
			
		||||
    stateclass: // A machine-readable class name for the state that this question attempt is in.
 | 
			
		||||
        typeof QUESTION_TODO_STATE_CLASSES[number] |
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user