forked from CIT/Vmeda.Online
		
	MOBILE-3915 course: Improve Course index page
This commit is contained in:
		
							parent
							
								
									6b46f48b3c
								
							
						
					
					
						commit
						0af808571c
					
				@ -1536,19 +1536,23 @@
 | 
			
		||||
  "core.course.confirmpartialdownloadsize": "local_moodlemobileapp",
 | 
			
		||||
  "core.course.couldnotloadsectioncontent": "local_moodlemobileapp",
 | 
			
		||||
  "core.course.couldnotloadsections": "local_moodlemobileapp",
 | 
			
		||||
  "core.course.courseindex": "courseformat",
 | 
			
		||||
  "core.course.coursesummary": "moodle",
 | 
			
		||||
  "core.course.done": "completion",
 | 
			
		||||
  "core.course.downloadcourse": "tool_mobile",
 | 
			
		||||
  "core.course.downloadcoursesprogressdescription": "local_moodlemobileapp",
 | 
			
		||||
  "core.course.downloadsectionprogressdescription": "local_moodlemobileapp",
 | 
			
		||||
  "core.course.errordownloadingcourse": "local_moodlemobileapp",
 | 
			
		||||
  "core.course.errordownloadingsection": "local_moodlemobileapp",
 | 
			
		||||
  "core.course.errorgetmodule": "local_moodlemobileapp",
 | 
			
		||||
  "core.course.failed": "completion",
 | 
			
		||||
  "core.course.gotonextactivity": "local_moodlemobileapp",
 | 
			
		||||
  "core.course.gotonextactivitynotfound": "local_moodlemobileapp",
 | 
			
		||||
  "core.course.gotopreviousactivity": "local_moodlemobileapp",
 | 
			
		||||
  "core.course.gotopreviousactivitynotfound": "local_moodlemobileapp",
 | 
			
		||||
  "core.course.hiddenfromstudents": "moodle",
 | 
			
		||||
  "core.course.hiddenoncoursepage": "moodle",
 | 
			
		||||
  "core.course.highlighted": "moodle",
 | 
			
		||||
  "core.course.insufficientavailablequota": "local_moodlemobileapp",
 | 
			
		||||
  "core.course.insufficientavailablespace": "local_moodlemobileapp",
 | 
			
		||||
  "core.course.manualcompletionnotsynced": "local_moodlemobileapp",
 | 
			
		||||
@ -1557,7 +1561,8 @@
 | 
			
		||||
  "core.course.overriddennotice": "grades",
 | 
			
		||||
  "core.course.refreshcourse": "local_moodlemobileapp",
 | 
			
		||||
  "core.course.section": "moodle",
 | 
			
		||||
  "core.course.sections": "moodle",
 | 
			
		||||
  "core.course.thisweek": "format_weeks/currentsection",
 | 
			
		||||
  "core.course.todo": "completion",
 | 
			
		||||
  "core.course.useactivityonbrowser": "local_moodlemobileapp",
 | 
			
		||||
  "core.course.warningmanualcompletionmodified": "local_moodlemobileapp",
 | 
			
		||||
  "core.course.warningofflinemanualcompletiondeleted": "local_moodlemobileapp",
 | 
			
		||||
 | 
			
		||||
@ -13,49 +13,66 @@
 | 
			
		||||
<ion-content>
 | 
			
		||||
    <ion-list id="core-course-section-selector" role="listbox" aria-labelledby="core-course-section-selector-label">
 | 
			
		||||
        <ng-container *ngFor="let section of sections">
 | 
			
		||||
            <ion-item-divider *ngIf="!section.hiddenbynumsections && section.id != stealthModulesSectionId" class="ion-text-wrap"
 | 
			
		||||
                (click)="selectSection(section)" [attr.aria-current]="selected?.id == section.id ? 'page' : 'false'"
 | 
			
		||||
                [class.item-dimmed]="section.visible === 0 || section.uservisible === false" detail="false"
 | 
			
		||||
                [attr.aria-hidden]="section.uservisible === false" button sticky="true">
 | 
			
		||||
 | 
			
		||||
                <ion-icon name="fas-folder" slot="start" aria-hidden="true"></ion-icon>
 | 
			
		||||
            <ion-item *ngIf="allSectionId == section.id" class="ion-text-wrap divider" (click)="selectSection($event, section)" button
 | 
			
		||||
                [class.item-current]="selectedId === section.id" detail="false">
 | 
			
		||||
                <ion-label>
 | 
			
		||||
                    <p class="item-heading">
 | 
			
		||||
                        <core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course?.id">
 | 
			
		||||
                        </core-format-text>
 | 
			
		||||
                    </p>
 | 
			
		||||
                    <core-progress-bar *ngIf="section.progress >= 0" [progress]="section.progress"
 | 
			
		||||
                        a11yText="core.course.aria:sectionprogress">
 | 
			
		||||
                    </core-progress-bar>
 | 
			
		||||
 | 
			
		||||
                    <ion-badge color="info" *ngIf="section.visible === 0 && section.uservisible !== false" class="ion-text-wrap">
 | 
			
		||||
                        {{ 'core.course.hiddenfromstudents' | translate }}
 | 
			
		||||
                    </ion-badge>
 | 
			
		||||
                    <ion-badge color="info" *ngIf="section.visible === 0 && section.uservisible === false" class="ion-text-wrap">
 | 
			
		||||
                        {{ 'core.notavailable' | translate }}
 | 
			
		||||
                    </ion-badge>
 | 
			
		||||
                    <ion-badge color="info" *ngIf="section.availabilityinfo" class="ion-text-wrap">
 | 
			
		||||
                        <core-format-text [text]=" section.availabilityinfo" contextLevel="course" [contextInstanceId]="course?.id">
 | 
			
		||||
                        </core-format-text>
 | 
			
		||||
                    </ion-badge>
 | 
			
		||||
                </ion-label>
 | 
			
		||||
            </ion-item-divider>
 | 
			
		||||
            <ng-container *ngFor="let module of section.modules">
 | 
			
		||||
                <ion-item *ngIf="module.visibleoncoursepage !== 0" class="ion-text-wrap">
 | 
			
		||||
                    <!-- TODO Add Aria, styles when disabled, etc. -->
 | 
			
		||||
                    <ion-icon name="" *ngIf="module.completionStatus === undefined" slot="start"></ion-icon>
 | 
			
		||||
                    <ion-icon name="far-circle" *ngIf="module.completionStatus === 0" slot="start"></ion-icon>
 | 
			
		||||
                    <ion-icon name="fas-circle" *ngIf="module.completionStatus === 1" color="success" slot="start"></ion-icon>
 | 
			
		||||
                    <ion-icon name="fas-circle" *ngIf="module.completionStatus === 2" color="success" slot="start"></ion-icon>
 | 
			
		||||
                    <ion-icon name="fas-circle" *ngIf="module.completionStatus === 3" color="danger" slot="start"></ion-icon>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
            <ng-container *ngIf="allSectionId != section.id && !section.hiddenbynumsections &&
 | 
			
		||||
                    section.id != stealthModulesSectionId && section.uservisible !== false">
 | 
			
		||||
                <ion-item class="ion-text-wrap divider section" (click)="selectSection($event, section)"
 | 
			
		||||
                    [button]="section.visible !== 0 && section.uservisible !== false" [class.item-current]="selectedId === section.id"
 | 
			
		||||
                    [class.item-dimmed]="section.visible === 0" detail="false" sticky="true">
 | 
			
		||||
                    <ion-icon [name]="section.expanded ? 'fas-chevron-down' : 'fas-chevron-right'" flip-rtl slot="start"
 | 
			
		||||
                        class="expandable-status-icon" (click)="toggleExpand($event, section)"
 | 
			
		||||
                        [attr.aria-label]="(section.expanded ? 'core.collapse' : 'core.expand') | translate"
 | 
			
		||||
                        [attr.aria-expanded]="section.expanded" [attr.aria-controls]="'core-course-index-section-' + section.id">
 | 
			
		||||
                    </ion-icon>
 | 
			
		||||
                    <ion-label>
 | 
			
		||||
                        <p class="item-heading">
 | 
			
		||||
                            <core-format-text [text]="module.name" contextLevel="module" [contextInstanceId]="module.id"
 | 
			
		||||
                                [courseId]="module.courseid">
 | 
			
		||||
                            <core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course?.id">
 | 
			
		||||
                            </core-format-text>
 | 
			
		||||
                        </p>
 | 
			
		||||
                    </ion-label>
 | 
			
		||||
                    <ion-badge *ngIf="section.highlighted && highlighted">{{highlighted}}</ion-badge>
 | 
			
		||||
                    <ion-icon name="fas-lock" *ngIf="section.availabilityinfo" slot="end" class="restricted"
 | 
			
		||||
                        [attr.aria-label]="'core.restricted' | translate"></ion-icon>
 | 
			
		||||
                </ion-item>
 | 
			
		||||
                <div [hidden]="!section.expanded" [id]="'core-course-index-section-' + section.id">
 | 
			
		||||
                    <ng-container *ngIf="section.expanded">
 | 
			
		||||
                        <ng-container *ngFor="let module of section.modules">
 | 
			
		||||
                            <ion-item [class.item-dimmed]="module.visible === 0"
 | 
			
		||||
                                *ngIf="module.visibleoncoursepage !== 0 && !module.noviewlink"
 | 
			
		||||
                                (click)="selectModule($event, section, module)" button>
 | 
			
		||||
                                <ion-icon class="completioninfo completion_none" name="" *ngIf="module.completionStatus === undefined"
 | 
			
		||||
                                    slot="start" aria-hidden="true"></ion-icon>
 | 
			
		||||
                                <ion-icon class="completioninfo completion_incomplete" name="far-circle"
 | 
			
		||||
                                    *ngIf="module.completionStatus === 0" slot="start" [attr.aria-label]="'core.course.todo' | translate">
 | 
			
		||||
                                </ion-icon>
 | 
			
		||||
                                <ion-icon class="completioninfo completion_complete" name="fas-circle"
 | 
			
		||||
                                    *ngIf="module.completionStatus === 1 || module.completionStatus === 2" color="success" slot="start"
 | 
			
		||||
                                    [attr.aria-label]="'core.course.done' | translate">
 | 
			
		||||
                                </ion-icon>
 | 
			
		||||
                                <ion-icon class="completioninfo completion_fail" name="fas-circle" *ngIf="module.completionStatus === 3"
 | 
			
		||||
                                    color="danger" slot="start" [attr.aria-label]="'core.course.failed' | translate">
 | 
			
		||||
                                </ion-icon>
 | 
			
		||||
                                <ion-label>
 | 
			
		||||
                                    <p class="item-heading">
 | 
			
		||||
                                        <core-format-text [text]="module.name" contextLevel="module" [contextInstanceId]="module.id"
 | 
			
		||||
                                            [courseId]="module.courseid">
 | 
			
		||||
                                        </core-format-text>
 | 
			
		||||
                                    </p>
 | 
			
		||||
                                </ion-label>
 | 
			
		||||
                                <ion-icon name="fas-lock" *ngIf="module.uservisible === false" slot="end" class="restricted"
 | 
			
		||||
                                    [attr.aria-label]="'core.restricted' | translate"></ion-icon>
 | 
			
		||||
                            </ion-item>
 | 
			
		||||
                        </ng-container>
 | 
			
		||||
                    </ng-container>
 | 
			
		||||
                </div>
 | 
			
		||||
            </ng-container>
 | 
			
		||||
        </ng-container>
 | 
			
		||||
    </ion-list>
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@
 | 
			
		||||
core-progress-bar {
 | 
			
		||||
    --bar-margin: 8px 0 4px 0;
 | 
			
		||||
    --line-height: 20px;
 | 
			
		||||
    --background: var(--contrast-background);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@if ($core-hide-progress-on-section-selector) {
 | 
			
		||||
@ -10,6 +11,25 @@ core-progress-bar {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ion-badge {
 | 
			
		||||
    text-align: start;
 | 
			
		||||
ion-icon.completioninfo {
 | 
			
		||||
    font-size: 10px;
 | 
			
		||||
    width: 18px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ion-item.section::part(native) {
 | 
			
		||||
    --padding-start: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ion-icon.expandable-status-icon {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    @include padding(12px, 32px, 12px, 16px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ion-item.item-current ion-icon.expandable-status-icon {
 | 
			
		||||
    @include padding(null, null, null, 11px);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ion-icon.restricted {
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -12,9 +12,9 @@
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { Component, Input, OnInit } from '@angular/core';
 | 
			
		||||
import { Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core';
 | 
			
		||||
 | 
			
		||||
import { CoreCourseModuleData, CoreCourseSection, CoreCourseSectionWithStatus } from '@features/course/services/course-helper';
 | 
			
		||||
import { CoreCourseModuleData, CoreCourseSectionWithStatus } from '@features/course/services/course-helper';
 | 
			
		||||
import {
 | 
			
		||||
    CoreCourseModuleCompletionStatus,
 | 
			
		||||
    CoreCourseModuleCompletionTracking,
 | 
			
		||||
@ -23,6 +23,9 @@ import {
 | 
			
		||||
import { CoreCourseAnyCourseData } from '@features/courses/services/courses';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { ModalController } from '@singletons';
 | 
			
		||||
import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate';
 | 
			
		||||
import { IonContent } from '@ionic/angular';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component to display course index modal.
 | 
			
		||||
@ -34,19 +37,30 @@ import { ModalController } from '@singletons';
 | 
			
		||||
})
 | 
			
		||||
export class CoreCourseCourseIndexComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
    @Input() sections?: SectionWithProgress[];
 | 
			
		||||
    @Input() selected?: CoreCourseSection;
 | 
			
		||||
    @ViewChild(IonContent) content?: IonContent;
 | 
			
		||||
 | 
			
		||||
    @Input() sections?: CourseIndexSection[];
 | 
			
		||||
    @Input() selectedId?: number;
 | 
			
		||||
    @Input() course?: CoreCourseAnyCourseData;
 | 
			
		||||
 | 
			
		||||
    stealthModulesSectionId = CoreCourseProvider.STEALTH_MODULES_SECTION_ID;
 | 
			
		||||
    allSectionId = CoreCourseProvider.ALL_SECTIONS_ID;
 | 
			
		||||
    highlighted?: string;
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        protected elementRef: ElementRef,
 | 
			
		||||
    ) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    ngOnInit(): void {
 | 
			
		||||
    async ngOnInit(): Promise<void> {
 | 
			
		||||
 | 
			
		||||
        if (!this.course || !this.sections || !this.course.enablecompletion || !('courseformatoptions' in this.course) ||
 | 
			
		||||
                !this.course.courseformatoptions) {
 | 
			
		||||
            this.closeModal();
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -55,32 +69,48 @@ export class CoreCourseCourseIndexComponent implements OnInit {
 | 
			
		||||
        if (!formatOptions || formatOptions.completionusertracked === false) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        const currentSection = await CoreCourseFormatDelegate.getCurrentSection(this.course, this.sections);
 | 
			
		||||
        currentSection.highlighted = true;
 | 
			
		||||
        if (this.selectedId === undefined) {
 | 
			
		||||
            currentSection.expanded = true;
 | 
			
		||||
            this.selectedId = currentSection.id;
 | 
			
		||||
        } else {
 | 
			
		||||
            const selectedSection = this.sections.find((section) => section.id == this.selectedId);
 | 
			
		||||
            if (selectedSection) {
 | 
			
		||||
                selectedSection.expanded = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.sections.forEach((section) => {
 | 
			
		||||
            let complete = 0;
 | 
			
		||||
            let total = 0;
 | 
			
		||||
            section.modules.forEach((module) => {
 | 
			
		||||
                console.error(module);
 | 
			
		||||
                if (!module.uservisible || module.completiondata === undefined ||
 | 
			
		||||
                        module.completiondata.tracking == CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_NONE) {
 | 
			
		||||
                    module.completionStatus = undefined;
 | 
			
		||||
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                module.completionStatus = module.completiondata.state;
 | 
			
		||||
 | 
			
		||||
                total++;
 | 
			
		||||
                if (module.completiondata.state == CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE ||
 | 
			
		||||
                        module.completiondata.state == CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE_PASS) {
 | 
			
		||||
                    complete++;
 | 
			
		||||
                }
 | 
			
		||||
                module.completionStatus = module.completiondata === undefined ||
 | 
			
		||||
                    module.completiondata.tracking == CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_NONE
 | 
			
		||||
                    ? undefined
 | 
			
		||||
                    : module.completiondata.state;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (total > 0) {
 | 
			
		||||
                section.progress = complete / total * 100;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.highlighted = CoreCourseFormatDelegate.getSectionHightlightedName(this.course);
 | 
			
		||||
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
            CoreDomUtils.scrollToElementBySelector(
 | 
			
		||||
                this.elementRef.nativeElement,
 | 
			
		||||
                this.content,
 | 
			
		||||
                '.item.item-current',
 | 
			
		||||
            );
 | 
			
		||||
        }, 200);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Toggle expand status.
 | 
			
		||||
     *
 | 
			
		||||
     * @param event Event object.
 | 
			
		||||
     * @param section Section to expand / collapse.
 | 
			
		||||
     */
 | 
			
		||||
    toggleExpand(event: Event, section: CourseIndexSection): void {
 | 
			
		||||
        section.expanded = !section.expanded;
 | 
			
		||||
        event.stopPropagation();
 | 
			
		||||
        event.preventDefault();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -93,19 +123,40 @@ export class CoreCourseCourseIndexComponent implements OnInit {
 | 
			
		||||
    /**
 | 
			
		||||
     * Select a section.
 | 
			
		||||
     *
 | 
			
		||||
     * @param event Event.
 | 
			
		||||
     * @param section Selected section object.
 | 
			
		||||
     */
 | 
			
		||||
    selectSection(section: SectionWithProgress): void {
 | 
			
		||||
    selectSection(event: Event, section: CoreCourseSectionWithStatus): void {
 | 
			
		||||
        if (section.uservisible !== false) {
 | 
			
		||||
            ModalController.dismiss(section);
 | 
			
		||||
            ModalController.dismiss({ event, section });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Select a section and open a module
 | 
			
		||||
     *
 | 
			
		||||
     * @param event Event.
 | 
			
		||||
     * @param section Selected section object.
 | 
			
		||||
     * @param module Selected module object.
 | 
			
		||||
     */
 | 
			
		||||
    selectModule(event: Event,section: CoreCourseSectionWithStatus, module: CoreCourseModuleData): void {
 | 
			
		||||
        if (module.uservisible !== false) {
 | 
			
		||||
            ModalController.dismiss({ event, section, module });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type SectionWithProgress = Omit<CoreCourseSectionWithStatus, 'modules'> & {
 | 
			
		||||
    progress?: number;
 | 
			
		||||
type CourseIndexSection = Omit<CoreCourseSectionWithStatus, 'modules'> & {
 | 
			
		||||
    highlighted?: boolean;
 | 
			
		||||
    expanded?: boolean;
 | 
			
		||||
    modules: (CoreCourseModuleData & {
 | 
			
		||||
        completionStatus?: CoreCourseModuleCompletionStatus;
 | 
			
		||||
    })[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type CoreCourseIndexSectionWithModule = {
 | 
			
		||||
    event: Event;
 | 
			
		||||
    section: CourseIndexSection;
 | 
			
		||||
    module?: CoreCourseModuleData;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -66,7 +66,8 @@
 | 
			
		||||
<!-- Template to render a section. -->
 | 
			
		||||
<ng-template #sectionTemplate let-section="section">
 | 
			
		||||
    <section *ngIf="!section.hiddenbynumsections && section.id != allSectionsId && section.id != stealthModulesSectionId">
 | 
			
		||||
        <ion-item-divider class="ion-text-wrap" color="light" [class.item-dimmed]="section.visible === 0 || section.uservisible === false">
 | 
			
		||||
        <ion-item-divider class="course-section ion-text-wrap" color="light"
 | 
			
		||||
            [class.item-dimmed]="section.visible === 0 || section.uservisible === false">
 | 
			
		||||
            <ion-icon name="fas-folder" aria-label="hidden" slot="start"></ion-icon>
 | 
			
		||||
            <ion-label>
 | 
			
		||||
                <h2 *ngIf="section.name">
 | 
			
		||||
 | 
			
		||||
@ -9,3 +9,9 @@
 | 
			
		||||
        text-transform: none;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.course-section {
 | 
			
		||||
    ion-badge {
 | 
			
		||||
        text-align: start;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -44,9 +44,11 @@ import { CoreCourseFormatDelegate } from '@features/course/services/format-deleg
 | 
			
		||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
 | 
			
		||||
import { IonContent, IonRefresher } from '@ionic/angular';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { CoreCourseCourseIndexComponent } from '../course-index/course-index';
 | 
			
		||||
import { CoreCourseCourseIndexComponent, CoreCourseIndexSectionWithModule } from '../course-index/course-index';
 | 
			
		||||
import { CoreBlockHelper } from '@features/block/services/block-helper';
 | 
			
		||||
import { CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { database } from 'faker';
 | 
			
		||||
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component to display course contents using a certain format. If the format isn't found, use default one.
 | 
			
		||||
@ -182,7 +184,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
 | 
			
		||||
            // Course has changed, try to get the components.
 | 
			
		||||
            this.getComponents();
 | 
			
		||||
 | 
			
		||||
            this.displayCourseIndex = CoreCourseFormatDelegate.displaySectionSelector(this.course);
 | 
			
		||||
            this.displayCourseIndex = CoreCourseFormatDelegate.displayCourseIndex(this.course);
 | 
			
		||||
            this.displayBlocks = CoreCourseFormatDelegate.displayBlocks(this.course);
 | 
			
		||||
 | 
			
		||||
            this.hasBlocks = await CoreBlockHelper.hasCourseBlocks(this.course.id);
 | 
			
		||||
@ -319,17 +321,28 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
 | 
			
		||||
     * Display the course index modal.
 | 
			
		||||
     */
 | 
			
		||||
    async openCourseIndex(): Promise<void> {
 | 
			
		||||
        const data = await CoreDomUtils.openModal<CoreCourseSection>({
 | 
			
		||||
        const data = await CoreDomUtils.openModal<CoreCourseIndexSectionWithModule>({
 | 
			
		||||
            component: CoreCourseCourseIndexComponent,
 | 
			
		||||
            componentProps: {
 | 
			
		||||
                course: this.course,
 | 
			
		||||
                sections: this.sections,
 | 
			
		||||
                selected: this.selectedSection,
 | 
			
		||||
                selectedId: this.selectedSection?.id,
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (data) {
 | 
			
		||||
            this.sectionChanged(data);
 | 
			
		||||
            this.sectionChanged(data.section);
 | 
			
		||||
            if (data.module) {
 | 
			
		||||
                if (!data.module.handlerData) {
 | 
			
		||||
                    data.module.handlerData =
 | 
			
		||||
                        await CoreCourseModuleDelegate.getModuleDataFor(data.module.modname, data.module, this.course.id);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (data.module.uservisible !== false && data.module.handlerData?.action) {
 | 
			
		||||
                    data.module.handlerData.action(data.event, data.module, data.module.course);
 | 
			
		||||
                }
 | 
			
		||||
                this.moduleId = data.module.id;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -71,7 +71,7 @@ export class CoreCourseFormatSingleActivityHandlerService implements CoreCourseF
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    displaySectionSelector(): boolean {
 | 
			
		||||
    displayCourseIndex(): boolean {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
 | 
			
		||||
 | 
			
		||||
import { CoreTimeUtils } from '@services/utils/time';
 | 
			
		||||
import { CoreCourseFormatHandler } from '@features/course/services/format-delegate';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { makeSingleton, Translate } from '@singletons';
 | 
			
		||||
import { CoreCourseAnyCourseData } from '@features/courses/services/courses';
 | 
			
		||||
import { CoreCourseWSSection } from '@features/course/services/course';
 | 
			
		||||
import { CoreConstants } from '@/core/constants';
 | 
			
		||||
@ -32,20 +32,14 @@ export class CoreCourseFormatWeeksHandlerService implements CoreCourseFormatHand
 | 
			
		||||
    format = 'weeks';
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Whether or not the handler is enabled on a site level.
 | 
			
		||||
     *
 | 
			
		||||
     * @return True or promise resolved with true if enabled.
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async isEnabled(): Promise<boolean> {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given a list of sections, get the "current" section that should be displayed first.
 | 
			
		||||
     *
 | 
			
		||||
     * @param course The course to get the title.
 | 
			
		||||
     * @param sections List of sections.
 | 
			
		||||
     * @return Current section (or promise resolved with current section).
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async getCurrentSection(course: CoreCourseAnyCourseData, sections: CoreCourseSection[]): Promise<CoreCourseSection> {
 | 
			
		||||
        const now = CoreTimeUtils.timestamp();
 | 
			
		||||
@ -71,6 +65,13 @@ export class CoreCourseFormatWeeksHandlerService implements CoreCourseFormatHand
 | 
			
		||||
        return sections[0];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    getSectionHightlightedName(): string {
 | 
			
		||||
        return Translate.instant('core.course.thisweek');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return the start and end date of a section.
 | 
			
		||||
     *
 | 
			
		||||
@ -83,7 +84,7 @@ export class CoreCourseFormatWeeksHandlerService implements CoreCourseFormatHand
 | 
			
		||||
        startDate = startDate + 7200;
 | 
			
		||||
 | 
			
		||||
        const dates = {
 | 
			
		||||
            start: startDate + (CoreConstants.SECONDS_WEEK * (section.section! - 1)),
 | 
			
		||||
            start: startDate + (CoreConstants.SECONDS_WEEK * ((section.section || 0) - 1)),
 | 
			
		||||
            end: 0,
 | 
			
		||||
        };
 | 
			
		||||
        dates.end = dates.start + CoreConstants.SECONDS_WEEK;
 | 
			
		||||
 | 
			
		||||
@ -27,21 +27,24 @@
 | 
			
		||||
    "confirmpartialdownloadsize": "You are about to download <strong>at least</strong> {{size}}.{{availableSpace}} Are you sure you want to continue?",
 | 
			
		||||
    "confirmlimiteddownload": "You are not currently connected to Wi-Fi. ",
 | 
			
		||||
    "courseindex": "Course index",
 | 
			
		||||
    "gotonextactivity": "Continue to next activity",
 | 
			
		||||
    "gotonextactivitynotfound": "Next activity not found. It's possible that it has been hidden or deleted.",
 | 
			
		||||
    "gotopreviousactivity": "Continue to previous activity",
 | 
			
		||||
    "gotopreviousactivitynotfound": "Previous activity not found. It's possible that it has been hidden or deleted.",
 | 
			
		||||
    "couldnotloadsectioncontent": "Could not load the section content. Please try again later.",
 | 
			
		||||
    "couldnotloadsections": "Could not load the sections. Please try again later.",
 | 
			
		||||
    "coursesummary": "Course summary",
 | 
			
		||||
    "done": "Done",
 | 
			
		||||
    "downloadcourse": "Download course",
 | 
			
		||||
    "downloadcoursesprogressdescription": "Downloading courses: downloaded {{count}} out of {{total}}.",
 | 
			
		||||
    "downloadsectionprogressdescription": "Downloading section: downloaded {{count}} out of {{total}}.",
 | 
			
		||||
    "errordownloadingcourse": "Error downloading course.",
 | 
			
		||||
    "errordownloadingsection": "Error downloading section.",
 | 
			
		||||
    "errorgetmodule": "Error getting activity data.",
 | 
			
		||||
    "failed": "Failed",
 | 
			
		||||
    "gotonextactivity": "Continue to next activity",
 | 
			
		||||
    "gotonextactivitynotfound": "Next activity not found. It's possible that it has been hidden or deleted.",
 | 
			
		||||
    "gotopreviousactivity": "Continue to previous activity",
 | 
			
		||||
    "gotopreviousactivitynotfound": "Previous activity not found. It's possible that it has been hidden or deleted.",
 | 
			
		||||
    "hiddenfromstudents": "Hidden from students",
 | 
			
		||||
    "hiddenoncoursepage": "Available but not shown on course page",
 | 
			
		||||
    "highlighted": "Highlighted",
 | 
			
		||||
    "insufficientavailablespace": "You are trying to download {{size}}. This will leave your device with insufficient space to operate normally. Please clear some storage space first.",
 | 
			
		||||
    "insufficientavailablequota": "Your device could not allocate space to save this download. It may be reserving space for app and system updates. Please clear some storage space first.",
 | 
			
		||||
    "manualcompletionnotsynced": "Manual completion not synchronised.",
 | 
			
		||||
@ -50,6 +53,8 @@
 | 
			
		||||
    "overriddennotice": "Your final grade from this activity was manually adjusted.",
 | 
			
		||||
    "refreshcourse": "Refresh course",
 | 
			
		||||
    "section": "Section",
 | 
			
		||||
    "thisweek": "This week",
 | 
			
		||||
    "todo": "To do",
 | 
			
		||||
    "useactivityonbrowser": "You can still use it using your device's web browser.",
 | 
			
		||||
    "warningmanualcompletionmodified": "The manual completion of an activity was modified on the site.",
 | 
			
		||||
    "warningofflinemanualcompletiondeleted": "Some offline manual completion of course '{{name}}' has been deleted. {{error}}"
 | 
			
		||||
 | 
			
		||||
@ -66,13 +66,22 @@ export interface CoreCourseFormatHandler extends CoreDelegateHandler {
 | 
			
		||||
     */
 | 
			
		||||
    displayEnableDownload?(course: CoreCourseAnyCourseData): boolean;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Whether the default course index should be displayed. Defaults to true.
 | 
			
		||||
     *
 | 
			
		||||
     * @deprecated on 4.0. Please use displayCourseIndex instead.
 | 
			
		||||
     * @param course The course to check.
 | 
			
		||||
     * @return Whether the default course index should be displayed.
 | 
			
		||||
     */
 | 
			
		||||
    displaySectionSelector?(course: CoreCourseAnyCourseData): boolean;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Whether the default section selector should be displayed. Defaults to true.
 | 
			
		||||
     *
 | 
			
		||||
     * @param course The course to check.
 | 
			
		||||
     * @return Whether the default section selector should be displayed.
 | 
			
		||||
     */
 | 
			
		||||
    displaySectionSelector?(course: CoreCourseAnyCourseData): boolean;
 | 
			
		||||
    displayCourseIndex?(course: CoreCourseAnyCourseData): boolean;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Whether the course refresher should be displayed. If it returns false, a refresher must be included in the course format,
 | 
			
		||||
@ -93,6 +102,13 @@ export interface CoreCourseFormatHandler extends CoreDelegateHandler {
 | 
			
		||||
     */
 | 
			
		||||
    getCurrentSection?(course: CoreCourseAnyCourseData, sections: CoreCourseSection[]): Promise<CoreCourseSection>;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the name for the highlighted section.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The name for the highlighted section based on the given course format.
 | 
			
		||||
     */
 | 
			
		||||
    getSectionHightlightedName?(): string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Open the page to display a course. If not defined, the page CoreCourseSectionPage will be opened.
 | 
			
		||||
     * Implement it only if you want to create your own page to display the course. In general it's better to use the method
 | 
			
		||||
@ -209,12 +225,19 @@ export class CoreCourseFormatDelegateService extends CoreDelegate<CoreCourseForm
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Whether the default section selector should be displayed. Defaults to true.
 | 
			
		||||
     * Whether the default course index should be displayed. Defaults to true.
 | 
			
		||||
     *
 | 
			
		||||
     * @param course The course to check.
 | 
			
		||||
     * @return Whether the section selector should be displayed.
 | 
			
		||||
     * @return Whether the course index should be displayed.
 | 
			
		||||
     */
 | 
			
		||||
    displaySectionSelector(course: CoreCourseAnyCourseData): boolean {
 | 
			
		||||
    displayCourseIndex(course: CoreCourseAnyCourseData): boolean {
 | 
			
		||||
        const display = this.executeFunctionOnEnabled<boolean>(course.format || '', 'displayCourseIndex', [course]);
 | 
			
		||||
 | 
			
		||||
        if (display !== undefined) {
 | 
			
		||||
            return display;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Use displaySectionSelector while is not completely deprecated.
 | 
			
		||||
        return !!this.executeFunctionOnEnabled<boolean>(course.format || '', 'displaySectionSelector', [course]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -278,9 +301,9 @@ export class CoreCourseFormatDelegateService extends CoreDelegate<CoreCourseForm
 | 
			
		||||
     * @param sections List of sections.
 | 
			
		||||
     * @return Promise resolved with current section.
 | 
			
		||||
     */
 | 
			
		||||
    async getCurrentSection(course: CoreCourseAnyCourseData, sections: CoreCourseSection[]): Promise<CoreCourseSection> {
 | 
			
		||||
    async getCurrentSection<T = CoreCourseSection>(course: CoreCourseAnyCourseData, sections: T[]): Promise<T> {
 | 
			
		||||
        try {
 | 
			
		||||
            const section = await this.executeFunctionOnEnabled<CoreCourseSection>(
 | 
			
		||||
            const section = await this.executeFunctionOnEnabled<T>(
 | 
			
		||||
                course.format || '',
 | 
			
		||||
                'getCurrentSection',
 | 
			
		||||
                [course, sections],
 | 
			
		||||
@ -293,6 +316,19 @@ export class CoreCourseFormatDelegateService extends CoreDelegate<CoreCourseForm
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the name for the highlighted section.
 | 
			
		||||
     *
 | 
			
		||||
     * @param course The course to get the text.
 | 
			
		||||
     * @return The name for the highlighted section based on the given course format.
 | 
			
		||||
     */
 | 
			
		||||
    getSectionHightlightedName(course: CoreCourseAnyCourseData): string | undefined {
 | 
			
		||||
        return this.executeFunctionOnEnabled<string>(
 | 
			
		||||
            course.format || '',
 | 
			
		||||
            'getSectionHightlightedName',
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the component to use to display a single section. This component will only be used if the user is viewing
 | 
			
		||||
     * a single section. If all the sections are displayed at once then it won't be used.
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,7 @@ import { Injectable } from '@angular/core';
 | 
			
		||||
import { CoreCourseAnyCourseData, CoreCourses } from '@features/courses/services/courses';
 | 
			
		||||
import { CoreNavigationOptions, CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { Translate } from '@singletons';
 | 
			
		||||
import { CoreCourseSection } from '../course-helper';
 | 
			
		||||
import { CoreCourseFormatHandler } from '../format-delegate';
 | 
			
		||||
 | 
			
		||||
@ -65,7 +66,7 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler {
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    displaySectionSelector(): boolean {
 | 
			
		||||
    displayCourseIndex(): boolean {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -106,6 +107,13 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler {
 | 
			
		||||
        return sections[0];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    getSectionHightlightedName(): string {
 | 
			
		||||
        return Translate.instant('core.course.highlighted');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
@ -38,8 +38,9 @@ export class CoreSitePluginsCourseFormatHandler extends CoreSitePluginsBaseHandl
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    displaySectionSelector(): boolean {
 | 
			
		||||
        return this.handlerSchema.displaysectionselector ?? true;
 | 
			
		||||
    displayCourseIndex(): boolean {
 | 
			
		||||
        // Use displaysectionselector while is not completely deprecated.
 | 
			
		||||
        return this.handlerSchema.displaycourseindex ?? this.handlerSchema.displaysectionselector ?? true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -884,7 +884,11 @@ export type CoreSitePluginsCourseModuleHandlerData = CoreSitePluginsHandlerCommo
 | 
			
		||||
export type CoreSitePluginsCourseFormatHandlerData = CoreSitePluginsHandlerCommonData & {
 | 
			
		||||
    canviewallsections?: boolean;
 | 
			
		||||
    displayenabledownload?: boolean;
 | 
			
		||||
    /**
 | 
			
		||||
     * @deprecated on 4.0, use displaycourseindex instead.
 | 
			
		||||
     */
 | 
			
		||||
    displaysectionselector?: boolean;
 | 
			
		||||
    displaycourseindex?: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 | 
			
		||||
@ -1118,7 +1118,7 @@ export class CoreDomUtilsProvider {
 | 
			
		||||
            content.scrollToPoint(position[0], position[1], duration || 0);
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
        } catch {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,7 @@ information provided here is intended especially for developers.
 | 
			
		||||
    The user handler function isEnabledForCourse is now called isEnabledForContext and receives a context + contextId instead of a courseId.
 | 
			
		||||
    Some user handler's functions have also changed to accept context + contextId instead of a courseId: isEnabledForUser, getDisplayData, action.
 | 
			
		||||
- CoreCourseHelperProvider.openCourse parameters changed, now it admits CoreNavigationOptions + siteId on the same object that includes Params passed to page.
 | 
			
		||||
- displaySectionSelector has been deprecated on CoreCourseFormatHandler, use displayCourseIndex instead.
 | 
			
		||||
 | 
			
		||||
=== 3.9.5 ===
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user