forked from CIT/Vmeda.Online
		
	
						commit
						8471b549d1
					
				@ -6,9 +6,11 @@
 | 
			
		||||
        <div *ngFor="let item of items">
 | 
			
		||||
            <ion-card>
 | 
			
		||||
                <ion-item class="core-course-module-handler item-media ion-text-wrap" detail="false" (click)="action($event, item)"
 | 
			
		||||
                    [title]="item.name" button>
 | 
			
		||||
                    button>
 | 
			
		||||
                    <img slot="start" [src]="item.iconUrl" alt="" role="presentation" *ngIf="item.iconUrl" class="core-module-icon">
 | 
			
		||||
                    <ion-label>
 | 
			
		||||
                        <!-- Add the icon title so accessibility tools read it. -->
 | 
			
		||||
                        <span class="sr-only" *ngIf="item.iconTitle">{{ item.iconTitle }}</span>
 | 
			
		||||
                        <h2>
 | 
			
		||||
                            <core-format-text [text]="item.name" contextLevel="module" [contextInstanceId]="item.cmid"
 | 
			
		||||
                                [courseId]="item.courseid"></core-format-text>
 | 
			
		||||
 | 
			
		||||
@ -56,6 +56,7 @@ export class AddonBlockRecentlyAccessedItemsProvider {
 | 
			
		||||
            const modicon = item.icon && CoreDomUtils.getHTMLElementAttribute(item.icon, 'src');
 | 
			
		||||
 | 
			
		||||
            item.iconUrl = CoreCourse.getModuleIconSrc(item.modname, modicon || undefined);
 | 
			
		||||
            item.iconTitle = item.icon && CoreDomUtils.getHTMLElementAttribute(item.icon, 'title');
 | 
			
		||||
 | 
			
		||||
            return item;
 | 
			
		||||
        });
 | 
			
		||||
@ -99,4 +100,5 @@ export type AddonBlockRecentlyAccessedItemsItem = {
 | 
			
		||||
 */
 | 
			
		||||
export type AddonBlockRecentlyAccessedItemsItemCalculatedData = {
 | 
			
		||||
    iconUrl: string; // Icon URL. Calculated by the app.
 | 
			
		||||
    iconTitle?: string | null; // Icon title.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -58,7 +58,7 @@
 | 
			
		||||
                            <ion-label position="stacked">{{ 'addon.mod_lesson.enterpassword' | translate }}</ion-label>
 | 
			
		||||
                            <core-show-password name="password">
 | 
			
		||||
                                <ion-input name="password" type="password" placeholder="{{ 'core.login.password' | translate }}"
 | 
			
		||||
                                    [core-auto-focus] #passwordinput [clearOnEdit]="false">
 | 
			
		||||
                                    [autofocus]="true" #passwordinput [clearOnEdit]="false">
 | 
			
		||||
                                </ion-input>
 | 
			
		||||
                            </core-show-password>
 | 
			
		||||
                        </ion-item>
 | 
			
		||||
 | 
			
		||||
@ -424,7 +424,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
 | 
			
		||||
            pageId = continueLast ? this.accessInfo.lastpageseen : this.accessInfo.firstpageid;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await CoreNavigator.navigate(`../player/${this.courseId}/${this.lesson.id}`, {
 | 
			
		||||
        await CoreNavigator.navigate('player', {
 | 
			
		||||
            params: {
 | 
			
		||||
                pageId: pageId,
 | 
			
		||||
                password: this.password,
 | 
			
		||||
@ -472,7 +472,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        CoreNavigator.navigate(`../player/${this.courseId}/${this.lesson.id}`, {
 | 
			
		||||
        CoreNavigator.navigate('player', {
 | 
			
		||||
            params: {
 | 
			
		||||
                pageId: this.retakeToReview.pageid,
 | 
			
		||||
                password: this.password,
 | 
			
		||||
@ -695,7 +695,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    async openRetake(userId: number): Promise<void> {
 | 
			
		||||
        await CoreNavigator.navigate(`../user-retake/${this.courseId}/${this.lesson!.id}`, {
 | 
			
		||||
        await CoreNavigator.navigate('user-retake', {
 | 
			
		||||
            params: {
 | 
			
		||||
                userId,
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
@ -15,7 +15,8 @@
 | 
			
		||||
            <ion-label>{{ 'addon.mod_lesson.enterpassword' | translate }}</ion-label>
 | 
			
		||||
            <core-show-password name="password">
 | 
			
		||||
                <ion-input name="password" type="password" placeholder="{{ 'core.login.password' | translate }}"
 | 
			
		||||
                    [core-auto-focus] #passwordinput [clearOnEdit]="false"></ion-input>
 | 
			
		||||
                    [autofocus]="true" #passwordinput [clearOnEdit]="false">
 | 
			
		||||
                </ion-input>
 | 
			
		||||
            </core-show-password>
 | 
			
		||||
        </ion-item>
 | 
			
		||||
        <ion-button expand="block" type="submit">
 | 
			
		||||
 | 
			
		||||
@ -26,11 +26,11 @@ const routes: Routes = [
 | 
			
		||||
        component: AddonModLessonIndexPage,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        path: 'player/:courseId/:lessonId',
 | 
			
		||||
        path: ':courseId/:cmId/player',
 | 
			
		||||
        loadChildren: () => import('./pages/player/player.module').then( m => m.AddonModLessonPlayerPageModule),
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        path: 'user-retake/:courseId/:lessonId',
 | 
			
		||||
        path: ':courseId/:cmId/user-retake',
 | 
			
		||||
        loadChildren: () => import('./pages/user-retake/user-retake.module').then( m => m.AddonModLessonUserRetakePageModule),
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@ -98,7 +98,7 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy, CanLeave {
 | 
			
		||||
    mediaFile?: CoreWSExternalFile; // Media file of the lesson.
 | 
			
		||||
    activityLink?: AddonModLessonActivityLink; // Next activity link data.
 | 
			
		||||
 | 
			
		||||
    protected lessonId!: number; // Lesson ID.
 | 
			
		||||
    protected cmId!: number; // Course module ID.
 | 
			
		||||
    protected password?: string; // Lesson password (if any).
 | 
			
		||||
    protected forceLeave = false; // If true, don't perform any check when leaving the view.
 | 
			
		||||
    protected offline?: boolean; // Whether we are in offline mode.
 | 
			
		||||
@ -118,22 +118,19 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy, CanLeave {
 | 
			
		||||
     * Component being initialized.
 | 
			
		||||
     */
 | 
			
		||||
    async ngOnInit(): Promise<void> {
 | 
			
		||||
        this.lessonId = CoreNavigator.getRouteNumberParam('lessonId')!;
 | 
			
		||||
        this.cmId = CoreNavigator.getRouteNumberParam('cmId')!;
 | 
			
		||||
        this.courseId = CoreNavigator.getRouteNumberParam('courseId')!;
 | 
			
		||||
        this.password = CoreNavigator.getRouteParam('password');
 | 
			
		||||
        this.review = !!CoreNavigator.getRouteBooleanParam('review');
 | 
			
		||||
        this.currentPage = CoreNavigator.getRouteNumberParam('pageId');
 | 
			
		||||
        this.retakeToReview = CoreNavigator.getRouteNumberParam('retake');
 | 
			
		||||
 | 
			
		||||
        // Block the lesson so it cannot be synced.
 | 
			
		||||
        CoreSync.blockOperation(this.component, this.lessonId);
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            // Fetch the Lesson data.
 | 
			
		||||
            const success = await this.fetchLessonData();
 | 
			
		||||
            if (success) {
 | 
			
		||||
                // Review data loaded or new retake started, remove any retake being finished in sync.
 | 
			
		||||
                AddonModLessonSync.deleteRetakeFinishedInSync(this.lessonId);
 | 
			
		||||
                AddonModLessonSync.deleteRetakeFinishedInSync(this.lesson!.id);
 | 
			
		||||
            }
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.loaded = true;
 | 
			
		||||
@ -144,8 +141,10 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy, CanLeave {
 | 
			
		||||
     * Component being destroyed.
 | 
			
		||||
     */
 | 
			
		||||
    ngOnDestroy(): void {
 | 
			
		||||
        // Unblock the lesson so it can be synced.
 | 
			
		||||
        CoreSync.unblockOperation(this.component, this.lessonId);
 | 
			
		||||
        if (this.lesson) {
 | 
			
		||||
            // Unblock the lesson so it can be synced.
 | 
			
		||||
            CoreSync.unblockOperation(this.component, this.lesson.id);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -214,7 +213,7 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy, CanLeave {
 | 
			
		||||
 | 
			
		||||
            // Get the possible jumps now.
 | 
			
		||||
            this.jumps = await AddonModLesson.getPagesPossibleJumps(this.lesson!.id, {
 | 
			
		||||
                cmId: this.lesson!.coursemodule,
 | 
			
		||||
                cmId: this.cmId,
 | 
			
		||||
                readingStrategy: CoreSitesReadingStrategy.PreferCache,
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
@ -259,14 +258,18 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy, CanLeave {
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchLessonData(): Promise<boolean> {
 | 
			
		||||
        try {
 | 
			
		||||
            // Wait for any ongoing sync to finish. We won't sync a lesson while it's being played.
 | 
			
		||||
            await AddonModLessonSync.waitForSync(this.lessonId);
 | 
			
		||||
            this.lesson = await AddonModLesson.getLesson(this.courseId, this.cmId);
 | 
			
		||||
 | 
			
		||||
            this.lesson = await AddonModLesson.getLessonById(this.courseId, this.lessonId);
 | 
			
		||||
            this.title = this.lesson.name; // Temporary title.
 | 
			
		||||
 | 
			
		||||
            // Block the lesson so it cannot be synced.
 | 
			
		||||
            CoreSync.blockOperation(this.component, this.lesson.id);
 | 
			
		||||
 | 
			
		||||
            // Wait for any ongoing sync to finish. We won't sync a lesson while it's being played.
 | 
			
		||||
            await AddonModLessonSync.waitForSync(this.lesson.id);
 | 
			
		||||
 | 
			
		||||
            // If lesson has offline data already, use offline mode.
 | 
			
		||||
            this.offline = await AddonModLessonOffline.hasOfflineData(this.lessonId);
 | 
			
		||||
            this.offline = await AddonModLessonOffline.hasOfflineData(this.lesson.id);
 | 
			
		||||
 | 
			
		||||
            if (!this.offline && !CoreApp.isOnline() && AddonModLesson.isLessonOffline(this.lesson) &&
 | 
			
		||||
                !this.review) {
 | 
			
		||||
@ -275,7 +278,7 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy, CanLeave {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const options = {
 | 
			
		||||
                cmId: this.lesson.coursemodule,
 | 
			
		||||
                cmId: this.cmId,
 | 
			
		||||
                readingStrategy: this.offline ? CoreSitesReadingStrategy.PreferCache : CoreSitesReadingStrategy.OnlyNetwork,
 | 
			
		||||
            };
 | 
			
		||||
            this.accessInfo = await this.callFunction<AddonModLessonGetAccessInformationWSResponse>(
 | 
			
		||||
@ -306,7 +309,7 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy, CanLeave {
 | 
			
		||||
                // Lesson uses password, get the whole lesson object.
 | 
			
		||||
                const options = {
 | 
			
		||||
                    password: this.password,
 | 
			
		||||
                    cmId: this.lesson.coursemodule,
 | 
			
		||||
                    cmId: this.cmId,
 | 
			
		||||
                    readingStrategy: this.offline ? CoreSitesReadingStrategy.PreferCache : CoreSitesReadingStrategy.OnlyNetwork,
 | 
			
		||||
                };
 | 
			
		||||
                promises.push(this.callFunction<AddonModLessonLessonWSData>(
 | 
			
		||||
@ -322,7 +325,7 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy, CanLeave {
 | 
			
		||||
            if (this.offline) {
 | 
			
		||||
                // Offline mode, get the list of possible jumps to allow navigation.
 | 
			
		||||
                promises.push(AddonModLesson.getPagesPossibleJumps(this.lesson.id, {
 | 
			
		||||
                    cmId: this.lesson.coursemodule,
 | 
			
		||||
                    cmId: this.cmId,
 | 
			
		||||
                    readingStrategy: CoreSitesReadingStrategy.PreferCache,
 | 
			
		||||
                }).then((jumpList) => {
 | 
			
		||||
                    this.jumps = jumpList;
 | 
			
		||||
@ -344,7 +347,7 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy, CanLeave {
 | 
			
		||||
 | 
			
		||||
            if (this.review && this.retakeToReview && CoreUtils.isWebServiceError(error)) {
 | 
			
		||||
                // The user cannot review the retake. Unmark the retake as being finished in sync.
 | 
			
		||||
                await AddonModLessonSync.deleteRetakeFinishedInSync(this.lessonId);
 | 
			
		||||
                await AddonModLessonSync.deleteRetakeFinishedInSync(this.lesson!.id);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            CoreDomUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true);
 | 
			
		||||
@ -373,7 +376,7 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy, CanLeave {
 | 
			
		||||
            if (result?.warnings?.length) {
 | 
			
		||||
                // Some data was deleted. Check if the retake has changed.
 | 
			
		||||
                const info = await AddonModLesson.getAccessInformation(this.lesson!.id, {
 | 
			
		||||
                    cmId: this.lesson!.coursemodule,
 | 
			
		||||
                    cmId: this.cmId,
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                if (info.attemptscount != this.accessInfo!.attemptscount) {
 | 
			
		||||
@ -485,7 +488,7 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy, CanLeave {
 | 
			
		||||
        if (this.lesson!.timelimit && !this.accessInfo!.canmanage) {
 | 
			
		||||
            // Get the last lesson timer.
 | 
			
		||||
            const timers = await AddonModLesson.getTimers(this.lesson!.id, {
 | 
			
		||||
                cmId: this.lesson!.coursemodule,
 | 
			
		||||
                cmId: this.cmId,
 | 
			
		||||
                readingStrategy: CoreSitesReadingStrategy.OnlyNetwork,
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
@ -510,12 +513,12 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy, CanLeave {
 | 
			
		||||
            this.loadingMenu = true;
 | 
			
		||||
            const options = {
 | 
			
		||||
                password: this.password,
 | 
			
		||||
                cmId: this.lesson!.coursemodule,
 | 
			
		||||
                cmId: this.cmId,
 | 
			
		||||
                readingStrategy: this.offline ? CoreSitesReadingStrategy.PreferCache : CoreSitesReadingStrategy.OnlyNetwork,
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            const pages = await this.callFunction<AddonModLessonGetPagesPageWSData[]>(
 | 
			
		||||
                AddonModLesson.getPages.bind(AddonModLesson.instance, this.lessonId, options),
 | 
			
		||||
                AddonModLesson.getPages.bind(AddonModLesson.instance, this.lesson!.id, options),
 | 
			
		||||
                options,
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
@ -543,7 +546,7 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy, CanLeave {
 | 
			
		||||
            password: this.password,
 | 
			
		||||
            review: this.review,
 | 
			
		||||
            includeContents: true,
 | 
			
		||||
            cmId: this.lesson!.coursemodule,
 | 
			
		||||
            cmId: this.cmId,
 | 
			
		||||
            readingStrategy: this.offline ? CoreSitesReadingStrategy.PreferCache : CoreSitesReadingStrategy.OnlyNetwork,
 | 
			
		||||
            accessInfo: this.accessInfo,
 | 
			
		||||
            jumps: this.jumps,
 | 
			
		||||
@ -638,15 +641,15 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy, CanLeave {
 | 
			
		||||
                // Lesson allows offline and the user changed some data in server. Update cached data.
 | 
			
		||||
                const retake = this.accessInfo!.attemptscount;
 | 
			
		||||
                const options = {
 | 
			
		||||
                    cmId: this.lesson!.coursemodule,
 | 
			
		||||
                    cmId: this.cmId,
 | 
			
		||||
                    readingStrategy: CoreSitesReadingStrategy.OnlyNetwork,
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                // Update in background the list of content pages viewed or question attempts.
 | 
			
		||||
                if (AddonModLesson.isQuestionPage(this.pageData?.page?.type || -1)) {
 | 
			
		||||
                    AddonModLesson.getQuestionsAttemptsOnline(this.lessonId, retake, options);
 | 
			
		||||
                    AddonModLesson.getQuestionsAttemptsOnline(this.lesson!.id, retake, options);
 | 
			
		||||
                } else {
 | 
			
		||||
                    AddonModLesson.getContentPagesViewedOnline(this.lessonId, retake, options);
 | 
			
		||||
                    AddonModLesson.getContentPagesViewedOnline(this.lesson!.id, retake, options);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -55,7 +55,7 @@ export class AddonModLessonUserRetakePage implements OnInit {
 | 
			
		||||
    loaded?: boolean; // Whether the data has been loaded.
 | 
			
		||||
    timeTakenReadable?: string; // Time taken in a readable format.
 | 
			
		||||
 | 
			
		||||
    protected lessonId!: number; // The lesson ID the retake belongs to.
 | 
			
		||||
    protected cmId!: number; // The lesson ID the retake belongs to.
 | 
			
		||||
    protected userId?: number; // User ID to see the retakes.
 | 
			
		||||
    protected retakeNumber?: number; // Number of the initial retake to see.
 | 
			
		||||
    protected previousSelectedRetake?: number; // To be able to detect the previous selected retake when it has changed.
 | 
			
		||||
@ -64,7 +64,7 @@ export class AddonModLessonUserRetakePage implements OnInit {
 | 
			
		||||
     * Component being initialized.
 | 
			
		||||
     */
 | 
			
		||||
    ngOnInit(): void {
 | 
			
		||||
        this.lessonId = CoreNavigator.getRouteNumberParam('lessonId')!;
 | 
			
		||||
        this.cmId = CoreNavigator.getRouteNumberParam('cmId')!;
 | 
			
		||||
        this.courseId = CoreNavigator.getRouteNumberParam('courseId')!;
 | 
			
		||||
        this.userId = CoreNavigator.getRouteNumberParam('userId') || CoreSites.getCurrentSiteUserId();
 | 
			
		||||
        this.retakeNumber = CoreNavigator.getRouteNumberParam('retake');
 | 
			
		||||
@ -111,11 +111,11 @@ export class AddonModLessonUserRetakePage implements OnInit {
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchData(): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            this.lesson = await AddonModLesson.getLessonById(this.courseId, this.lessonId);
 | 
			
		||||
            this.lesson = await AddonModLesson.getLesson(this.courseId, this.cmId);
 | 
			
		||||
 | 
			
		||||
            // Get the retakes overview for all participants.
 | 
			
		||||
            const data = await AddonModLesson.getRetakesOverview(this.lesson.id, {
 | 
			
		||||
                cmId: this.lesson.coursemodule,
 | 
			
		||||
                cmId: this.cmId,
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // Search the student.
 | 
			
		||||
@ -185,8 +185,8 @@ export class AddonModLessonUserRetakePage implements OnInit {
 | 
			
		||||
    protected async setRetake(retakeNumber: number): Promise<void> {
 | 
			
		||||
        this.selectedRetake = retakeNumber;
 | 
			
		||||
 | 
			
		||||
        const retakeData = await AddonModLesson.getUserRetake(this.lessonId, retakeNumber, {
 | 
			
		||||
            cmId: this.lesson!.coursemodule,
 | 
			
		||||
        const retakeData = await AddonModLesson.getUserRetake(this.lesson!.id, retakeNumber, {
 | 
			
		||||
            cmId: this.cmId,
 | 
			
		||||
            userId: this.userId,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -65,7 +65,7 @@ export class AddonModLessonGradeLinkHandlerService extends CoreContentLinksModul
 | 
			
		||||
            if (accessInfo.canviewreports) {
 | 
			
		||||
                // User can view reports, go to view the report.
 | 
			
		||||
                CoreNavigator.navigateToSitePath(
 | 
			
		||||
                    AddonModLessonModuleHandlerService.PAGE_NAME + `/user-retake/${courseId}/${module.instance}`,
 | 
			
		||||
                    AddonModLessonModuleHandlerService.PAGE_NAME + `/${courseId}/${module.id}/user-retake`,
 | 
			
		||||
                    {
 | 
			
		||||
                        params: { userId: Number(params.userid) },
 | 
			
		||||
                        siteId,
 | 
			
		||||
 | 
			
		||||
@ -122,11 +122,9 @@ export class AddonModLessonReportLinkHandlerService extends CoreContentLinksHand
 | 
			
		||||
     *
 | 
			
		||||
     * @param moduleId Module ID.
 | 
			
		||||
     * @param userId User ID.
 | 
			
		||||
     * @param courseId Course ID.
 | 
			
		||||
     * @param retake Retake to open.
 | 
			
		||||
     * @param groupId Group ID.
 | 
			
		||||
     * @param siteId Site ID.
 | 
			
		||||
     * @param navCtrl The NavController to use to navigate.
 | 
			
		||||
     * @param courseId Course ID.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async openUserRetake(
 | 
			
		||||
@ -150,7 +148,7 @@ export class AddonModLessonReportLinkHandlerService extends CoreContentLinksHand
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            CoreNavigator.navigateToSitePath(
 | 
			
		||||
                AddonModLessonModuleHandlerService.PAGE_NAME + `/user-retake/${courseId}/${module.instance}`,
 | 
			
		||||
                AddonModLessonModuleHandlerService.PAGE_NAME + `/${courseId}/${module.id}/user-retake`,
 | 
			
		||||
                { params, siteId },
 | 
			
		||||
            );
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
 | 
			
		||||
@ -1486,7 +1486,7 @@ export class AddonModLessonProvider {
 | 
			
		||||
 | 
			
		||||
        const response = await site.read<AddonModLessonGetLessonWSResponse>('mod_lesson_get_lesson', params, preSets);
 | 
			
		||||
 | 
			
		||||
        if (typeof response.lesson.ongoing != 'undefined') {
 | 
			
		||||
        if (typeof response.lesson.ongoing == 'undefined') {
 | 
			
		||||
            // Basic data not received, password is wrong. Remove stored password.
 | 
			
		||||
            this.removeStoredPassword(lessonId, site.id);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -14,7 +14,7 @@
 | 
			
		||||
 | 
			
		||||
        <!-- Form to edit the file's name. -->
 | 
			
		||||
        <ion-input type="text" name="filename" [placeholder]="'core.filename' | translate" autocapitalize="none" autocorrect="off"
 | 
			
		||||
            (click)="$event.stopPropagation()" [core-auto-focus] [(ngModel)]="newFileName" *ngIf="editMode">
 | 
			
		||||
            (click)="$event.stopPropagation()" [autofocus]="true" [(ngModel)]="newFileName" *ngIf="editMode">
 | 
			
		||||
        </ion-input>
 | 
			
		||||
 | 
			
		||||
        <div class="buttons" slot="end" *ngIf="manage">
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
<form #messageForm>
 | 
			
		||||
    <textarea class="core-send-message-input" [core-auto-focus]="showKeyboard" [placeholder]="placeholder" rows="1" core-auto-rows
 | 
			
		||||
    <textarea class="core-send-message-input" [autofocus]="showKeyboard" [placeholder]="placeholder" rows="1" core-auto-rows
 | 
			
		||||
        [(ngModel)]="message" name="message" (onResize)="textareaResized()" (keydown.enter)="enterClicked($event)"
 | 
			
		||||
        (keydown.control.enter)="enterClicked($event, 'control')" (keydown.meta.enter)="enterClicked($event, 'meta')"
 | 
			
		||||
        aria-multiline="true"></textarea>
 | 
			
		||||
@ -10,4 +10,3 @@
 | 
			
		||||
        </ion-button>
 | 
			
		||||
    </ion-buttons>
 | 
			
		||||
</form>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -21,6 +21,8 @@ import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
 * Directive to auto focus an element when a view is loaded.
 | 
			
		||||
 *
 | 
			
		||||
 * You can apply it conditionallity assigning it a boolean value: <ion-input [core-auto-focus]="{{showKeyboard}}">
 | 
			
		||||
 *
 | 
			
		||||
 * @deprecated since 3.9.5. ion-input now supports an [autofocus] attribute, please use that one instead.
 | 
			
		||||
 */
 | 
			
		||||
@Directive({
 | 
			
		||||
    selector: '[core-auto-focus]',
 | 
			
		||||
@ -39,16 +41,7 @@ export class CoreAutoFocusDirective implements OnInit {
 | 
			
		||||
     * Component being initialized.
 | 
			
		||||
     */
 | 
			
		||||
    ngOnInit(): void {
 | 
			
		||||
        // @todo
 | 
			
		||||
        // if (this.navCtrl.isTransitioning()) {
 | 
			
		||||
        //     // Navigating to a new page. Wait for the transition to be over.
 | 
			
		||||
        //     const subscription = this.navCtrl.viewDidEnter.subscribe(() => {
 | 
			
		||||
        //         this.autoFocus();
 | 
			
		||||
        //         subscription.unsubscribe();
 | 
			
		||||
        //     });
 | 
			
		||||
        // } else {
 | 
			
		||||
        this.autoFocus();
 | 
			
		||||
        // }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -21,8 +21,9 @@
 | 
			
		||||
                    [class.core-section-download]="downloadEnabled">
 | 
			
		||||
                    <ion-button class="core-button-select button-no-uppercase" (click)="showSectionSelector()"
 | 
			
		||||
                        aria-haspopup="true" [attr.aria-expanded]="sectionSelectorExpanded"
 | 
			
		||||
                        id="core-course-section-button" expand="block"> <!-- @todo: attr.aria-label? -->
 | 
			
		||||
                        <ion-icon name="fas-folder" slot="start"></ion-icon>
 | 
			
		||||
                        id="core-course-section-button" expand="block">
 | 
			
		||||
                        <ion-icon name="fas-folder" slot="start" aria-hidden="true"></ion-icon>
 | 
			
		||||
                        <span class="sr-only" *ngIf="selectedSection">{{ 'core.course.sections' | translate }}:</span>
 | 
			
		||||
                        <span class="core-button-select-text">
 | 
			
		||||
                            <core-format-text *ngIf="selectedSection" [text]="selectedSection.name" contextLevel="course"
 | 
			
		||||
                                [contextInstanceId]="course?.id" [clean]="true" [singleLine]="true">
 | 
			
		||||
 | 
			
		||||
@ -9,23 +9,25 @@
 | 
			
		||||
    </ion-toolbar>
 | 
			
		||||
</ion-header>
 | 
			
		||||
<ion-content>
 | 
			
		||||
    <ion-list id="core-course-section-selector" role="menu">
 | 
			
		||||
    <ion-list id="core-course-section-selector">
 | 
			
		||||
        <ng-container *ngFor="let section of sections">
 | 
			
		||||
            <ion-item *ngIf="!section.hiddenbynumsections && section.id != stealthModulesSectionId" class="ion-text-wrap"
 | 
			
		||||
                (click)="selectSection(section)" [class.core-selected-item]="selected?.id == section.id"
 | 
			
		||||
                [class.item-dimmed]="section.visible === 0 || section.uservisible === false" detail="false" role="menuitem"
 | 
			
		||||
                [class.item-dimmed]="section.visible === 0 || section.uservisible === false" detail="false"
 | 
			
		||||
                [attr.aria-hidden]="section.uservisible === false" button>
 | 
			
		||||
 | 
			
		||||
                <ion-icon name="fas-folder" slot="start"></ion-icon>
 | 
			
		||||
                <ion-icon name="fas-folder" slot="start" aria-hidden="true"></ion-icon>
 | 
			
		||||
                <ion-label>
 | 
			
		||||
                    <h2><core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course?.id">
 | 
			
		||||
                    </core-format-text></h2>
 | 
			
		||||
                    <core-progress-bar *ngIf="section.progress >= 0" [progress]="section.progress"></core-progress-bar>
 | 
			
		||||
 | 
			
		||||
                    <ion-badge color="secondary" *ngIf="section.visible === 0 && section.uservisible !== false" class="ion-text-wrap">
 | 
			
		||||
                    <ion-badge color="secondary" *ngIf="section.visible === 0 && section.uservisible !== false"
 | 
			
		||||
                        class="ion-text-wrap">
 | 
			
		||||
                        {{ 'core.course.hiddenfromstudents' | translate }}
 | 
			
		||||
                    </ion-badge>
 | 
			
		||||
                    <ion-badge color="secondary" *ngIf="section.visible === 0 && section.uservisible === false" class="ion-text-wrap">
 | 
			
		||||
                    <ion-badge color="secondary" *ngIf="section.visible === 0 && section.uservisible === false"
 | 
			
		||||
                        class="ion-text-wrap">
 | 
			
		||||
                        {{ 'core.notavailable' | translate }}
 | 
			
		||||
                    </ion-badge>
 | 
			
		||||
                    <ion-badge color="secondary" *ngIf="section.availabilityinfo" class="ion-text-wrap">
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,7 @@
 | 
			
		||||
            [class.core-course-with-buttons]="courseOptionMenuEnabled || (downloadCourseEnabled && showDownload)"
 | 
			
		||||
            [class.core-course-with-spinner]="(downloadCourseEnabled && prefetchCourseData.icon == 'spinner') || showSpinner">
 | 
			
		||||
            <p *ngIf="course.categoryname || (course.displayname && course.shortname && course.fullname != course.displayname)"
 | 
			
		||||
                class="core-course-additional-info">
 | 
			
		||||
                class="core-course-additional-info" aria-hidden="true">
 | 
			
		||||
                <span *ngIf="course.categoryname" class="core-course-category">
 | 
			
		||||
                    <core-format-text [text]="course.categoryname"></core-format-text>
 | 
			
		||||
                </span>
 | 
			
		||||
@ -24,7 +24,10 @@
 | 
			
		||||
                </span>
 | 
			
		||||
            </p>
 | 
			
		||||
            <h2>
 | 
			
		||||
                <ion-icon name="fas-star" *ngIf="course.isfavourite"></ion-icon>
 | 
			
		||||
                <ion-icon name="fas-star" *ngIf="course.isfavourite" [attr.aria-label]="'core.courses.favourite' | translate">
 | 
			
		||||
                </ion-icon>
 | 
			
		||||
                <span class="sr-only" *ngIf="course.isfavourite">{{ 'core.courses.aria:favourite' | translate }}</span>
 | 
			
		||||
                <span class="sr-only">{{ 'core.courses.aria:coursename' | translate }}</span>
 | 
			
		||||
                <core-format-text [text]="course.fullname" contextLevel="course" [contextInstanceId]="course.id"></core-format-text>
 | 
			
		||||
            </h2>
 | 
			
		||||
        </ion-label>
 | 
			
		||||
@ -50,9 +53,10 @@
 | 
			
		||||
            </ion-button>
 | 
			
		||||
        </div>
 | 
			
		||||
    </ion-item>
 | 
			
		||||
    <ion-item *ngIf="showAll && course.progress! >= 0 && course.completionusertracked !== false"
 | 
			
		||||
        lines="none">
 | 
			
		||||
        <ion-label><core-progress-bar [progress]="course.progress"></core-progress-bar></ion-label>
 | 
			
		||||
    <ion-item *ngIf="showAll && course.progress! >= 0 && course.completionusertracked !== false" lines="none">
 | 
			
		||||
        <ion-label>
 | 
			
		||||
            <core-progress-bar [progress]="course.progress"></core-progress-bar>
 | 
			
		||||
        </ion-label>
 | 
			
		||||
    </ion-item>
 | 
			
		||||
    <ng-content></ng-content>
 | 
			
		||||
</ion-card>
 | 
			
		||||
 | 
			
		||||
@ -20,7 +20,7 @@
 | 
			
		||||
                    type="password"
 | 
			
		||||
                    placeholder="{{ 'core.courses.password' | translate }}"
 | 
			
		||||
                    [(ngModel)]="password"
 | 
			
		||||
                    [core-auto-focus]
 | 
			
		||||
                    [autofocus]="true"
 | 
			
		||||
                    [clearOnEdit]="false">
 | 
			
		||||
                </ion-input>
 | 
			
		||||
            </core-show-password>
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,9 @@
 | 
			
		||||
{
 | 
			
		||||
    "addtofavourites": "Star this course",
 | 
			
		||||
    "allowguests": "This course allows guest users to enter",
 | 
			
		||||
    "aria:coursename": "Course name",
 | 
			
		||||
    "aria:courseprogress": "Course progress:",
 | 
			
		||||
    "aria:favourite": "Course is starred",
 | 
			
		||||
    "availablecourses": "Available courses",
 | 
			
		||||
    "cannotretrievemorecategories": "Categories deeper than level {{$a}} cannot be retrieved.",
 | 
			
		||||
    "categories": "Course categories",
 | 
			
		||||
@ -13,6 +16,7 @@
 | 
			
		||||
    "errorloadplugins": "The plugins required by this course could not be loaded correctly. Please reload the app to try again.",
 | 
			
		||||
    "errorsearching": "An error occurred while searching.",
 | 
			
		||||
    "errorselfenrol": "An error occurred while self enrolling.",
 | 
			
		||||
    "favourite": "Starred course",
 | 
			
		||||
    "filtermycourses": "Filter my courses",
 | 
			
		||||
    "frontpage": "Front page",
 | 
			
		||||
    "hidecourse": "Remove from view",
 | 
			
		||||
@ -36,4 +40,4 @@
 | 
			
		||||
    "sendpaymentbutton": "Send payment via PayPal",
 | 
			
		||||
    "show": "Restore to view",
 | 
			
		||||
    "totalcoursesearchresults": "Total courses: {{$a}}"
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -31,7 +31,7 @@
 | 
			
		||||
            <ion-item>
 | 
			
		||||
                <ion-label></ion-label>
 | 
			
		||||
                <ion-input type="text" name="value" placeholder="{{ 'core.login.usernameoremail' | translate }}"
 | 
			
		||||
                    formControlName="value" autocapitalize="none" autocorrect="off" [core-auto-focus]="autoFocus">
 | 
			
		||||
                    formControlName="value" autocapitalize="none" autocorrect="off" [autofocus]="autoFocus">
 | 
			
		||||
                </ion-input>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
            <ion-button type="submit" class="ion-margin" expand="block" [disabled]="!myForm.valid">
 | 
			
		||||
 | 
			
		||||
@ -27,7 +27,7 @@
 | 
			
		||||
                    <h2>{{ 'core.login.siteaddress' | translate }}</h2>
 | 
			
		||||
                </ion-label>
 | 
			
		||||
                <ion-input name="url" type="url" placeholder="{{ 'core.login.siteaddressplaceholder' | translate }}"
 | 
			
		||||
                    formControlName="siteUrl" [core-auto-focus]="showKeyboard && !showScanQR">
 | 
			
		||||
                    formControlName="siteUrl" [autofocus]="showKeyboard && !showScanQR">
 | 
			
		||||
                </ion-input>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
        </ng-container>
 | 
			
		||||
@ -37,7 +37,7 @@
 | 
			
		||||
                    <h2>{{ 'core.login.siteaddress' | translate }}</h2>
 | 
			
		||||
                </ion-label>
 | 
			
		||||
                <ion-input name="url" placeholder="{{ 'core.login.siteaddressplaceholder' | translate }}" formControlName="siteUrl"
 | 
			
		||||
                    [core-auto-focus]="showKeyboard && !showScanQR" (ionChange)="searchSite($event, siteForm.value.siteUrl)">
 | 
			
		||||
                    [autofocus]="showKeyboard && !showScanQR" (ionChange)="searchSite($event, siteForm.value.siteUrl)">
 | 
			
		||||
                </ion-input>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
 | 
			
		||||
@ -49,7 +49,7 @@
 | 
			
		||||
                </ion-item>
 | 
			
		||||
                <ion-item button *ngIf="enteredSiteUrl" (click)="connect($event, enteredSiteUrl.url)"
 | 
			
		||||
                    [attr.aria-label]="'core.login.connect' | translate" detail-push class="core-login-entered-site">
 | 
			
		||||
                    <ion-thumbnail slot="start">
 | 
			
		||||
                    <ion-thumbnail slot="start" aria-hidden="true">
 | 
			
		||||
                        <ion-icon name="fas-pen"></ion-icon>
 | 
			
		||||
                    </ion-thumbnail>
 | 
			
		||||
                    <ion-label>
 | 
			
		||||
@ -101,7 +101,8 @@
 | 
			
		||||
 | 
			
		||||
    <ng-container *ngIf="showScanQR && !hasSites && !enteredSiteUrl">
 | 
			
		||||
        <div class="ion-text-center ion-padding ion-margin-top">{{ 'core.login.or' | translate }}</div>
 | 
			
		||||
        <ion-button expand="block" color="light" class="ion-margin" lines="none" (click)="showInstructionsAndScanQR()">
 | 
			
		||||
        <ion-button expand="block" color="light" class="ion-margin" lines="none" (click)="showInstructionsAndScanQR()"
 | 
			
		||||
            aria-haspopup="true">
 | 
			
		||||
            <ion-icon slot="start" name="fas-qrcode" aria-hidden="true"></ion-icon>
 | 
			
		||||
            <ion-label>{{ 'core.scanqr' | translate }}</ion-label>
 | 
			
		||||
        </ion-button>
 | 
			
		||||
@ -109,7 +110,8 @@
 | 
			
		||||
 | 
			
		||||
    <!-- Help. -->
 | 
			
		||||
    <ion-list lines="none" class="ion-margin-top">
 | 
			
		||||
        <ion-item button class="ion-text-center ion-text-wrap core-login-need-help" (click)="showHelp()" detail="false">
 | 
			
		||||
        <ion-item button class="ion-text-center ion-text-wrap core-login-need-help" (click)="showHelp()" detail="false"
 | 
			
		||||
            aria-haspopup="true">
 | 
			
		||||
            <ion-label>{{ 'core.needhelp' | translate }}</ion-label>
 | 
			
		||||
        </ion-item>
 | 
			
		||||
    </ion-list>
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,6 @@
 | 
			
		||||
    </ion-toolbar>
 | 
			
		||||
</ion-header>
 | 
			
		||||
<ion-content>
 | 
			
		||||
    <!-- @todo -->
 | 
			
		||||
    <core-loading [hideUntil]="loaded">
 | 
			
		||||
        <core-tabs-outlet *ngIf="tabs.length > 0" [selectedIndex]="selectedTab" [hideUntil]="loaded" [tabs]="tabs">
 | 
			
		||||
        </core-tabs-outlet>
 | 
			
		||||
 | 
			
		||||
@ -14,7 +14,6 @@
 | 
			
		||||
 | 
			
		||||
import { Injectable } from '@angular/core';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { CoreMainMenuHomeDelegate } from '../home-delegate';
 | 
			
		||||
import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '../mainmenu-delegate';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@ -29,27 +28,14 @@ export class CoreMainMenuHomeHandlerService implements CoreMainMenuHandler {
 | 
			
		||||
    priority = 1100;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if the handler is enabled on a site level.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Whether or not the handler is enabled on a site level.
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    isEnabled(): Promise<boolean> {
 | 
			
		||||
        return this.isEnabledForSite();
 | 
			
		||||
    async isEnabled(): Promise<boolean> {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if the handler is enabled on a certain site.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Whether or not the handler is enabled on a site level.
 | 
			
		||||
     */
 | 
			
		||||
    async isEnabledForSite(): Promise<boolean> {
 | 
			
		||||
        return CoreMainMenuHomeDelegate.getHandlers().length > 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the data needed to render the handler.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Data needed to render the handler.
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    getDisplayData(): CoreMainMenuHandlerData {
 | 
			
		||||
        return {
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@
 | 
			
		||||
        <ion-item>
 | 
			
		||||
            <ion-label></ion-label>
 | 
			
		||||
            <ion-input type="search" name="search" [(ngModel)]="searchText" [placeholder]="placeholder"
 | 
			
		||||
                [autocorrect]="autocorrect" [spellcheck]="spellcheck" [core-auto-focus]="autoFocus"
 | 
			
		||||
                [autocorrect]="autocorrect" [spellcheck]="spellcheck" [autofocus]="autoFocus"
 | 
			
		||||
                [disabled]="disabled" role="searchbox" (ionFocus)="focus($event)">
 | 
			
		||||
            </ion-input>
 | 
			
		||||
            <ion-button slot="end" fill="clear" type="submit" size="small" [attr.aria-label]="searchLabel"
 | 
			
		||||
 | 
			
		||||
@ -655,7 +655,7 @@ export class CoreSitesProvider {
 | 
			
		||||
     * @return Release number or empty.
 | 
			
		||||
     */
 | 
			
		||||
    getReleaseNumber(rawRelease: string): string {
 | 
			
		||||
        const matches = rawRelease.match(/^\d(\.\d(\.\d+)?)?/);
 | 
			
		||||
        const matches = rawRelease.match(/^\d+(\.\d+(\.\d+)?)?/);
 | 
			
		||||
        if (matches) {
 | 
			
		||||
            return matches[0];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										21
									
								
								src/theme/bootstrap.scss
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/theme/bootstrap.scss
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
// Text for accessibility, hidden from the view.
 | 
			
		||||
.sr-only, .accesshide {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    width: 1px;
 | 
			
		||||
    height: 1px;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    margin: -1px;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    clip: rect(0, 0, 0, 0);
 | 
			
		||||
    white-space: nowrap;
 | 
			
		||||
    border: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.sr-only-focusable:active, .sr-only-focusable:focus {
 | 
			
		||||
    position: static;
 | 
			
		||||
    width: auto;
 | 
			
		||||
    height: auto;
 | 
			
		||||
    overflow: visible;
 | 
			
		||||
    clip: auto;
 | 
			
		||||
    white-space: normal;
 | 
			
		||||
}
 | 
			
		||||
@ -436,14 +436,6 @@ ion-button.core-button-select {
 | 
			
		||||
    background-color: var(--text-hightlight-background-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Text for accessibility, hidden from the view.
 | 
			
		||||
.accesshide {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    left: -10000px;
 | 
			
		||||
    font-weight: normal;
 | 
			
		||||
    font-size: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Monospaced font.
 | 
			
		||||
.core-monospaced {
 | 
			
		||||
    font-family: Andale Mono,Monaco,Courier New,DejaVu Sans Mono,monospace;
 | 
			
		||||
 | 
			
		||||
@ -23,6 +23,9 @@
 | 
			
		||||
@import "./components/rubrics.scss";
 | 
			
		||||
@import "./components/mod-label.scss";
 | 
			
		||||
 | 
			
		||||
/* Some styles from 3rd party libraries. */
 | 
			
		||||
@import "./bootstrap.scss";
 | 
			
		||||
 | 
			
		||||
/* Core CSS required for Ionic components to work properly */
 | 
			
		||||
@import "~@ionic/angular/css/core.css";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user