MOBILE-3915 course: Implement new course index
parent
1dd5eba1de
commit
86365d260d
|
@ -20,7 +20,7 @@ import { CoreCourseFormatComponent } from './format/format';
|
||||||
import { CoreCourseModuleComponent } from './module/module';
|
import { CoreCourseModuleComponent } from './module/module';
|
||||||
import { CoreCourseModuleCompletionComponent } from './module-completion/module-completion';
|
import { CoreCourseModuleCompletionComponent } from './module-completion/module-completion';
|
||||||
import { CoreCourseModuleDescriptionComponent } from './module-description/module-description';
|
import { CoreCourseModuleDescriptionComponent } from './module-description/module-description';
|
||||||
import { CoreCourseSectionSelectorComponent } from './section-selector/section-selector';
|
import { CoreCourseCourseIndexComponent } from './course-index/course-index';
|
||||||
import { CoreCourseTagAreaComponent } from './tag-area/tag-area';
|
import { CoreCourseTagAreaComponent } from './tag-area/tag-area';
|
||||||
import { CoreCourseUnsupportedModuleComponent } from './unsupported-module/unsupported-module';
|
import { CoreCourseUnsupportedModuleComponent } from './unsupported-module/unsupported-module';
|
||||||
import { CoreCourseModuleCompletionLegacyComponent } from './module-completion-legacy/module-completion-legacy';
|
import { CoreCourseModuleCompletionLegacyComponent } from './module-completion-legacy/module-completion-legacy';
|
||||||
|
@ -37,7 +37,7 @@ import { CoreCourseModuleNavigationComponent } from './module-navigation/module-
|
||||||
CoreCourseModuleDescriptionComponent,
|
CoreCourseModuleDescriptionComponent,
|
||||||
CoreCourseModuleInfoComponent,
|
CoreCourseModuleInfoComponent,
|
||||||
CoreCourseModuleManualCompletionComponent,
|
CoreCourseModuleManualCompletionComponent,
|
||||||
CoreCourseSectionSelectorComponent,
|
CoreCourseCourseIndexComponent,
|
||||||
CoreCourseTagAreaComponent,
|
CoreCourseTagAreaComponent,
|
||||||
CoreCourseUnsupportedModuleComponent,
|
CoreCourseUnsupportedModuleComponent,
|
||||||
CoreCourseModuleNavigationComponent,
|
CoreCourseModuleNavigationComponent,
|
||||||
|
@ -54,7 +54,7 @@ import { CoreCourseModuleNavigationComponent } from './module-navigation/module-
|
||||||
CoreCourseModuleDescriptionComponent,
|
CoreCourseModuleDescriptionComponent,
|
||||||
CoreCourseModuleInfoComponent,
|
CoreCourseModuleInfoComponent,
|
||||||
CoreCourseModuleManualCompletionComponent,
|
CoreCourseModuleManualCompletionComponent,
|
||||||
CoreCourseSectionSelectorComponent,
|
CoreCourseCourseIndexComponent,
|
||||||
CoreCourseTagAreaComponent,
|
CoreCourseTagAreaComponent,
|
||||||
CoreCourseUnsupportedModuleComponent,
|
CoreCourseUnsupportedModuleComponent,
|
||||||
CoreCourseModuleNavigationComponent,
|
CoreCourseModuleNavigationComponent,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<ion-header>
|
<ion-header>
|
||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<ion-title>
|
<ion-title>
|
||||||
<h2 id="core-course-section-selector-label">{{ 'core.course.sections' | translate }}</h2>
|
<h2 id="core-course-section-selector-label">{{ 'core.course.courseindex' | translate }}</h2>
|
||||||
</ion-title>
|
</ion-title>
|
||||||
<ion-buttons slot="end">
|
<ion-buttons slot="end">
|
||||||
<ion-button fill="clear" (click)="closeModal()" [attr.aria-label]="'core.close' | translate">
|
<ion-button fill="clear" (click)="closeModal()" [attr.aria-label]="'core.close' | translate">
|
||||||
|
@ -13,10 +13,10 @@
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<ion-list id="core-course-section-selector" role="listbox" aria-labelledby="core-course-section-selector-label">
|
<ion-list id="core-course-section-selector" role="listbox" aria-labelledby="core-course-section-selector-label">
|
||||||
<ng-container *ngFor="let section of sections">
|
<ng-container *ngFor="let section of sections">
|
||||||
<ion-item *ngIf="!section.hiddenbynumsections && section.id != stealthModulesSectionId" class="ion-text-wrap"
|
<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'"
|
(click)="selectSection(section)" [attr.aria-current]="selected?.id == section.id ? 'page' : 'false'"
|
||||||
[class.item-dimmed]="section.visible === 0 || section.uservisible === false" detail="false"
|
[class.item-dimmed]="section.visible === 0 || section.uservisible === false" detail="false"
|
||||||
[attr.aria-hidden]="section.uservisible === false" button>
|
[attr.aria-hidden]="section.uservisible === false" button sticky="true">
|
||||||
|
|
||||||
<ion-icon name="fas-folder" slot="start" aria-hidden="true"></ion-icon>
|
<ion-icon name="fas-folder" slot="start" aria-hidden="true"></ion-icon>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
|
@ -39,7 +39,24 @@
|
||||||
</core-format-text>
|
</core-format-text>
|
||||||
</ion-badge>
|
</ion-badge>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</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-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-item>
|
||||||
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
</ion-content>
|
</ion-content>
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
|
||||||
import { CoreCourseSection } from '@features/course/services/course-helper';
|
import { CoreCourseModuleData, CoreCourseSection, CoreCourseSectionWithStatus } from '@features/course/services/course-helper';
|
||||||
import {
|
import {
|
||||||
CoreCourseModuleCompletionStatus,
|
CoreCourseModuleCompletionStatus,
|
||||||
CoreCourseModuleCompletionTracking,
|
CoreCourseModuleCompletionTracking,
|
||||||
|
@ -25,14 +25,14 @@ import { CoreUtils } from '@services/utils/utils';
|
||||||
import { ModalController } from '@singletons';
|
import { ModalController } from '@singletons';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component to display course section selector in a modal.
|
* Component to display course index modal.
|
||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'core-course-section-selector',
|
selector: 'core-course-course-index',
|
||||||
templateUrl: 'section-selector.html',
|
templateUrl: 'course-index.html',
|
||||||
styleUrls: ['section-selector.scss'],
|
styleUrls: ['course-index.scss'],
|
||||||
})
|
})
|
||||||
export class CoreCourseSectionSelectorComponent implements OnInit {
|
export class CoreCourseCourseIndexComponent implements OnInit {
|
||||||
|
|
||||||
@Input() sections?: SectionWithProgress[];
|
@Input() sections?: SectionWithProgress[];
|
||||||
@Input() selected?: CoreCourseSection;
|
@Input() selected?: CoreCourseSection;
|
||||||
|
@ -41,7 +41,7 @@ export class CoreCourseSectionSelectorComponent implements OnInit {
|
||||||
stealthModulesSectionId = CoreCourseProvider.STEALTH_MODULES_SECTION_ID;
|
stealthModulesSectionId = CoreCourseProvider.STEALTH_MODULES_SECTION_ID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component being initialized.
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ export class CoreCourseSectionSelectorComponent implements OnInit {
|
||||||
|
|
||||||
const formatOptions = CoreUtils.objectToKeyValueMap(this.course.courseformatoptions, 'name', 'value');
|
const formatOptions = CoreUtils.objectToKeyValueMap(this.course.courseformatoptions, 'name', 'value');
|
||||||
|
|
||||||
if (!formatOptions || formatOptions.coursedisplay != 1 || formatOptions.completionusertracked === false) {
|
if (!formatOptions || formatOptions.completionusertracked === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,11 +60,16 @@ export class CoreCourseSectionSelectorComponent implements OnInit {
|
||||||
let complete = 0;
|
let complete = 0;
|
||||||
let total = 0;
|
let total = 0;
|
||||||
section.modules.forEach((module) => {
|
section.modules.forEach((module) => {
|
||||||
|
console.error(module);
|
||||||
if (!module.uservisible || module.completiondata === undefined ||
|
if (!module.uservisible || module.completiondata === undefined ||
|
||||||
module.completiondata.tracking == CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_NONE) {
|
module.completiondata.tracking == CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_NONE) {
|
||||||
|
module.completionStatus = undefined;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.completionStatus = module.completiondata.state;
|
||||||
|
|
||||||
total++;
|
total++;
|
||||||
if (module.completiondata.state == CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE ||
|
if (module.completiondata.state == CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE ||
|
||||||
module.completiondata.state == CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE_PASS) {
|
module.completiondata.state == CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE_PASS) {
|
||||||
|
@ -98,6 +103,9 @@ export class CoreCourseSectionSelectorComponent implements OnInit {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SectionWithProgress = CoreCourseSection & {
|
type SectionWithProgress = Omit<CoreCourseSectionWithStatus, 'modules'> & {
|
||||||
progress?: number;
|
progress?: number;
|
||||||
|
modules: (CoreCourseModuleData & {
|
||||||
|
completionStatus?: CoreCourseModuleCompletionStatus;
|
||||||
|
})[];
|
||||||
};
|
};
|
|
@ -1,8 +1,8 @@
|
||||||
<!-- Buttons to add to the header. *ngIf is needed, otherwise the component is executed too soon and doesn't find the header. -->
|
<!-- Buttons to add to the header. *ngIf is needed, otherwise the component is executed too soon and doesn't find the header. -->
|
||||||
<core-navbar-buttons slot="end" *ngIf="loaded">
|
<core-navbar-buttons slot="end" *ngIf="loaded">
|
||||||
<core-context-menu>
|
<core-context-menu>
|
||||||
<core-context-menu-item [hidden]="!displaySectionSelector || !sections || !sections.length" [priority]="500"
|
<core-context-menu-item [hidden]="!displayCourseIndex || !sections || !sections.length" [priority]="500"
|
||||||
[content]="'core.course.sections' | translate" (action)="showSectionSelector()" iconAction="menu">
|
[content]="'core.course.courseindex' | translate" (action)="openCourseIndex()" iconAction="menu">
|
||||||
</core-context-menu-item>
|
</core-context-menu-item>
|
||||||
</core-context-menu>
|
</core-context-menu>
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
|
@ -32,46 +32,6 @@
|
||||||
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
|
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item *ngIf="selectedSection && selectedSection.id != allSectionsId" class="ion-text-wrap">
|
|
||||||
<ion-icon name="fas-folder" aria-label="hidden" slot="start"></ion-icon>
|
|
||||||
<ion-label>
|
|
||||||
<p class="item-heading">
|
|
||||||
<core-format-text *ngIf="selectedSection" [text]="selectedSection.name" contextLevel="course"
|
|
||||||
[contextInstanceId]="course.id" [clean]="true" [singleLine]="true">
|
|
||||||
</core-format-text>
|
|
||||||
</p>
|
|
||||||
<ion-badge color="info" class="ion-text-wrap"
|
|
||||||
*ngIf="selectedSection.visible === 0 && selectedSection.uservisible !== false">
|
|
||||||
{{ 'core.course.hiddenfromstudents' | translate }}
|
|
||||||
</ion-badge>
|
|
||||||
<ion-badge color="info" class="ion-text-wrap"
|
|
||||||
*ngIf="selectedSection.visible === 0 && selectedSection.uservisible === false">
|
|
||||||
{{ 'core.notavailable' | translate }}
|
|
||||||
</ion-badge>
|
|
||||||
<ion-badge color="info" class="ion-text-wrap" *ngIf="selectedSection.availabilityinfo">
|
|
||||||
<core-format-text [text]="selectedSection.availabilityinfo" contextLevel="course" [contextInstanceId]="course.id">
|
|
||||||
</core-format-text>
|
|
||||||
</ion-badge>
|
|
||||||
</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
</core-dynamic-component>
|
|
||||||
|
|
||||||
<!-- Section selector. -->
|
|
||||||
<core-dynamic-component [component]="sectionSelectorComponent" [data]="data">
|
|
||||||
<div *ngIf="displaySectionSelector && sections && hasSeveralSections"
|
|
||||||
class="ion-text-wrap ion-justify-content-between ion-align-items-center core-button-selector-row">
|
|
||||||
<core-combobox [modalOptions]="sectionSelectorModalOptions" interface="modal" listboxId="core-course-section-button"
|
|
||||||
icon="fas-folder" [label]="'core.course.section' | translate"
|
|
||||||
[selection]="selectedSection ? selectedSection.name : 'core.course.sections' | translate"
|
|
||||||
(onChange)="sectionChanged($event)">
|
|
||||||
<span slot="text">
|
|
||||||
<core-format-text *ngIf="selectedSection" [text]="selectedSection.name" contextLevel="course"
|
|
||||||
[contextInstanceId]="course.id" [clean]="true" [singleLine]="true">
|
|
||||||
</core-format-text>
|
|
||||||
<ng-container *ngIf="!selectedSection">{{ 'core.course.sections' | translate }}</ng-container>
|
|
||||||
</span>
|
|
||||||
</core-combobox>
|
|
||||||
</div>
|
|
||||||
</core-dynamic-component>
|
</core-dynamic-component>
|
||||||
|
|
||||||
<!-- Single section. -->
|
<!-- Single section. -->
|
||||||
|
@ -98,7 +58,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ion-buttons class="ion-padding core-course-section-nav-buttons safe-area-padding-horizontal"
|
<ion-buttons class="ion-padding core-course-section-nav-buttons safe-area-padding-horizontal"
|
||||||
*ngIf="displaySectionSelector && sections?.length">
|
*ngIf="displayCourseIndex && sections?.length">
|
||||||
<ion-button *ngIf="previousSection" (click)="sectionChanged(previousSection)" fill="outline" color="primary"
|
<ion-button *ngIf="previousSection" (click)="sectionChanged(previousSection)" fill="outline" color="primary"
|
||||||
[attr.aria-label]="('core.previous' | translate) + ': ' + previousSection.name">
|
[attr.aria-label]="('core.previous' | translate) + ': ' + previousSection.name">
|
||||||
<ion-icon name="fas-chevron-left" slot="icon-only" aria-hidden="true"></ion-icon>
|
<ion-icon name="fas-chevron-left" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
|
@ -115,17 +75,25 @@
|
||||||
|
|
||||||
<core-block-side-blocks-button *ngIf="course && displayBlocks && hasBlocks" [courseId]="course.id">
|
<core-block-side-blocks-button *ngIf="course && displayBlocks && hasBlocks" [courseId]="course.id">
|
||||||
</core-block-side-blocks-button>
|
</core-block-side-blocks-button>
|
||||||
|
|
||||||
</core-loading>
|
</core-loading>
|
||||||
</core-dynamic-component>
|
</core-dynamic-component>
|
||||||
|
|
||||||
|
<!-- Course Index button. -->
|
||||||
|
<ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end" *ngIf="displayCourseIndex">
|
||||||
|
<ion-fab-button (click)="openCourseIndex()" [attr.aria-label]="'core.course.courseindex' | translate">
|
||||||
|
<ion-icon name="fas-list-ul" aria-hidden="true"></ion-icon>
|
||||||
|
<span class="sr-only">{{'core.course.courseindex' | translate }}</span>
|
||||||
|
</ion-fab-button>
|
||||||
|
</ion-fab>
|
||||||
|
|
||||||
<!-- Template to render a section. -->
|
<!-- Template to render a section. -->
|
||||||
<ng-template #sectionTemplate let-section="section">
|
<ng-template #sectionTemplate let-section="section">
|
||||||
<section *ngIf="!section.hiddenbynumsections && section.id != allSectionsId && section.id != stealthModulesSectionId">
|
<section *ngIf="!section.hiddenbynumsections && section.id != allSectionsId && section.id != stealthModulesSectionId">
|
||||||
<!-- Title is only displayed when viewing all sections. -->
|
<ion-item-divider class="ion-text-wrap" color="light" [class.item-dimmed]="section.visible === 0 || section.uservisible === false">
|
||||||
<ion-item-divider *ngIf="selectedSection?.id == allSectionsId && section.name" class="ion-text-wrap" color="light"
|
<ion-icon name="fas-folder" aria-label="hidden" slot="start"></ion-icon>
|
||||||
[class.item-dimmed]="section.visible === 0 || section.uservisible === false">
|
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>
|
<h2 *ngIf="section.name">
|
||||||
<core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course.id">
|
<core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course.id">
|
||||||
</core-format-text>
|
</core-format-text>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
|
@ -26,7 +26,6 @@ import {
|
||||||
Type,
|
Type,
|
||||||
ElementRef,
|
ElementRef,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { ModalOptions } from '@ionic/core';
|
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-component';
|
import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-component';
|
||||||
import { CoreCourseAnyCourseData } from '@features/courses/services/courses';
|
import { CoreCourseAnyCourseData } from '@features/courses/services/courses';
|
||||||
|
@ -45,7 +44,7 @@ import { CoreCourseFormatDelegate } from '@features/course/services/format-deleg
|
||||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
import { IonContent, IonRefresher } from '@ionic/angular';
|
import { IonContent, IonRefresher } from '@ionic/angular';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { CoreCourseSectionSelectorComponent } from '../section-selector/section-selector';
|
import { CoreCourseCourseIndexComponent } from '../course-index/course-index';
|
||||||
import { CoreBlockHelper } from '@features/block/services/block-helper';
|
import { CoreBlockHelper } from '@features/block/services/block-helper';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
|
||||||
|
@ -80,7 +79,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
// All the possible component classes.
|
// All the possible component classes.
|
||||||
courseFormatComponent?: Type<unknown>;
|
courseFormatComponent?: Type<unknown>;
|
||||||
courseSummaryComponent?: Type<unknown>;
|
courseSummaryComponent?: Type<unknown>;
|
||||||
sectionSelectorComponent?: Type<unknown>;
|
|
||||||
singleSectionComponent?: Type<unknown>;
|
singleSectionComponent?: Type<unknown>;
|
||||||
allSectionsComponent?: Type<unknown>;
|
allSectionsComponent?: Type<unknown>;
|
||||||
|
|
||||||
|
@ -88,7 +86,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
showSectionId = 0;
|
showSectionId = 0;
|
||||||
data: Record<string, unknown> = {}; // Data to pass to the components.
|
data: Record<string, unknown> = {}; // Data to pass to the components.
|
||||||
|
|
||||||
displaySectionSelector = false;
|
displayCourseIndex = false;
|
||||||
displayBlocks = false;
|
displayBlocks = false;
|
||||||
hasBlocks = false;
|
hasBlocks = false;
|
||||||
selectedSection?: CoreCourseSection;
|
selectedSection?: CoreCourseSection;
|
||||||
|
@ -97,17 +95,11 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
allSectionsId: number = CoreCourseProvider.ALL_SECTIONS_ID;
|
allSectionsId: number = CoreCourseProvider.ALL_SECTIONS_ID;
|
||||||
stealthModulesSectionId: number = CoreCourseProvider.STEALTH_MODULES_SECTION_ID;
|
stealthModulesSectionId: number = CoreCourseProvider.STEALTH_MODULES_SECTION_ID;
|
||||||
loaded = false;
|
loaded = false;
|
||||||
hasSeveralSections?: boolean;
|
|
||||||
imageThumb?: string;
|
imageThumb?: string;
|
||||||
progress?: number;
|
progress?: number;
|
||||||
sectionSelectorModalOptions: ModalOptions = {
|
|
||||||
component: CoreCourseSectionSelectorComponent,
|
|
||||||
componentProps: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
protected selectTabObserver?: CoreEventObserver;
|
protected selectTabObserver?: CoreEventObserver;
|
||||||
protected lastCourseFormat?: string;
|
protected lastCourseFormat?: string;
|
||||||
protected sectionSelectorExpanded = false;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected content: IonContent,
|
protected content: IonContent,
|
||||||
|
@ -154,14 +146,12 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
*/
|
*/
|
||||||
async ngOnChanges(changes: { [name: string]: SimpleChange }): Promise<void> {
|
async ngOnChanges(changes: { [name: string]: SimpleChange }): Promise<void> {
|
||||||
this.setInputData();
|
this.setInputData();
|
||||||
this.sectionSelectorModalOptions.componentProps!.course = this.course;
|
|
||||||
this.sectionSelectorModalOptions.componentProps!.sections = this.sections;
|
|
||||||
|
|
||||||
if (changes.course && this.course) {
|
if (changes.course && this.course) {
|
||||||
// Course has changed, try to get the components.
|
// Course has changed, try to get the components.
|
||||||
this.getComponents();
|
this.getComponents();
|
||||||
|
|
||||||
this.displaySectionSelector = CoreCourseFormatDelegate.displaySectionSelector(this.course);
|
this.displayCourseIndex = CoreCourseFormatDelegate.displaySectionSelector(this.course);
|
||||||
this.displayBlocks = CoreCourseFormatDelegate.displayBlocks(this.course);
|
this.displayBlocks = CoreCourseFormatDelegate.displayBlocks(this.course);
|
||||||
|
|
||||||
this.hasBlocks = await CoreBlockHelper.hasCourseBlocks(this.course.id);
|
this.hasBlocks = await CoreBlockHelper.hasCourseBlocks(this.course.id);
|
||||||
|
@ -174,7 +164,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changes.sections && this.sections) {
|
if (changes.sections && this.sections) {
|
||||||
this.sectionSelectorModalOptions.componentProps!.sections = this.sections;
|
|
||||||
this.treatSections(this.sections);
|
this.treatSections(this.sections);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -205,7 +194,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.loadCourseFormatComponent(),
|
this.loadCourseFormatComponent(),
|
||||||
this.loadCourseSummaryComponent(),
|
this.loadCourseSummaryComponent(),
|
||||||
this.loadSectionSelectorComponent(),
|
|
||||||
this.loadSingleSectionComponent(),
|
this.loadSingleSectionComponent(),
|
||||||
this.loadAllSectionsComponent(),
|
this.loadAllSectionsComponent(),
|
||||||
]);
|
]);
|
||||||
|
@ -229,15 +217,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
this.courseSummaryComponent = await CoreCourseFormatDelegate.getCourseSummaryComponent(this.course);
|
this.courseSummaryComponent = await CoreCourseFormatDelegate.getCourseSummaryComponent(this.course);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Load section selector component.
|
|
||||||
*
|
|
||||||
* @return Promise resolved when done.
|
|
||||||
*/
|
|
||||||
protected async loadSectionSelectorComponent(): Promise<void> {
|
|
||||||
this.sectionSelectorComponent = await CoreCourseFormatDelegate.getSectionSelectorComponent(this.course);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load single section component.
|
* Load single section component.
|
||||||
*
|
*
|
||||||
|
@ -264,7 +243,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
*/
|
*/
|
||||||
protected async treatSections(sections: CoreCourseSection[]): Promise<void> {
|
protected async treatSections(sections: CoreCourseSection[]): Promise<void> {
|
||||||
const hasAllSections = sections[0].id == CoreCourseProvider.ALL_SECTIONS_ID;
|
const hasAllSections = sections[0].id == CoreCourseProvider.ALL_SECTIONS_ID;
|
||||||
this.hasSeveralSections = sections.length > 2 || (sections.length == 2 && !hasAllSections);
|
const hasSeveralSections = sections.length > 2 || (sections.length == 2 && !hasAllSections);
|
||||||
|
|
||||||
if (this.selectedSection) {
|
if (this.selectedSection) {
|
||||||
// We have a selected section, but the list has changed. Search the section in the list.
|
// We have a selected section, but the list has changed. Search the section in the list.
|
||||||
|
@ -281,7 +260,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
// There is no selected section yet, calculate which one to load.
|
// There is no selected section yet, calculate which one to load.
|
||||||
if (!this.hasSeveralSections) {
|
if (!hasSeveralSections) {
|
||||||
// Always load "All sections" to display the section title. If it isn't there just load the section.
|
// Always load "All sections" to display the section title. If it isn't there just load the section.
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
this.sectionChanged(sections[0]);
|
this.sectionChanged(sections[0]);
|
||||||
|
@ -309,18 +288,18 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display the section selector modal.
|
* Display the course index modal.
|
||||||
*/
|
*/
|
||||||
async showSectionSelector(): Promise<void> {
|
async openCourseIndex(): Promise<void> {
|
||||||
if (this.sectionSelectorExpanded) {
|
const data = await CoreDomUtils.openModal<CoreCourseSection>({
|
||||||
return;
|
component: CoreCourseCourseIndexComponent,
|
||||||
}
|
componentProps: {
|
||||||
|
course: this.course,
|
||||||
|
sections: this.sections,
|
||||||
|
selected: this.selectedSection,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
this.sectionSelectorExpanded = true;
|
|
||||||
|
|
||||||
const data = await CoreDomUtils.openModal<CoreCourseSection>(this.sectionSelectorModalOptions);
|
|
||||||
|
|
||||||
this.sectionSelectorExpanded = false;
|
|
||||||
if (data) {
|
if (data) {
|
||||||
this.sectionChanged(data);
|
this.sectionChanged(data);
|
||||||
}
|
}
|
||||||
|
@ -334,7 +313,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
sectionChanged(newSection: CoreCourseSection): void {
|
sectionChanged(newSection: CoreCourseSection): void {
|
||||||
const previousValue = this.selectedSection;
|
const previousValue = this.selectedSection;
|
||||||
this.selectedSection = newSection;
|
this.selectedSection = newSection;
|
||||||
this.sectionSelectorModalOptions.componentProps!.selected = this.selectedSection;
|
|
||||||
this.data.section = this.selectedSection;
|
this.data.section = this.selectedSection;
|
||||||
|
|
||||||
if (newSection.id != this.allSectionsId) {
|
if (newSection.id != this.allSectionsId) {
|
||||||
|
|
|
@ -52,30 +52,28 @@ export class CoreCourseModuleCompletionLegacyComponent extends CoreCourseModuleC
|
||||||
let langKey: string | undefined;
|
let langKey: string | undefined;
|
||||||
let image: string | undefined;
|
let image: string | undefined;
|
||||||
|
|
||||||
if (this.completion.tracking === CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_MANUAL &&
|
if (this.completion.tracking === CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_MANUAL) {
|
||||||
this.completion.state === CoreCourseModuleCompletionStatus.COMPLETION_INCOMPLETE) {
|
if (this.completion.state === CoreCourseModuleCompletionStatus.COMPLETION_INCOMPLETE) {
|
||||||
image = 'completion-manual-n';
|
image = 'completion-manual-n';
|
||||||
langKey = 'core.completion-alt-manual-n';
|
langKey = 'core.completion-alt-manual-n';
|
||||||
} else if (this.completion.tracking === CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_MANUAL &&
|
} else if (this.completion.state === CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE) {
|
||||||
this.completion.state === CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE) {
|
image = 'completion-manual-y';
|
||||||
image = 'completion-manual-y';
|
langKey = 'core.completion-alt-manual-y';
|
||||||
langKey = 'core.completion-alt-manual-y';
|
}
|
||||||
} else if (this.completion.tracking === CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_AUTOMATIC &&
|
} else if (this.completion.tracking === CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_AUTOMATIC) {
|
||||||
this.completion.state === CoreCourseModuleCompletionStatus.COMPLETION_INCOMPLETE) {
|
if (this.completion.state === CoreCourseModuleCompletionStatus.COMPLETION_INCOMPLETE) {
|
||||||
image = 'completion-auto-n';
|
image = 'completion-auto-n';
|
||||||
langKey = 'core.completion-alt-auto-n';
|
langKey = 'core.completion-alt-auto-n';
|
||||||
} else if (this.completion.tracking === CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_AUTOMATIC &&
|
} else if (this.completion.state === CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE) {
|
||||||
this.completion.state === CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE) {
|
image = 'completion-auto-y';
|
||||||
image = 'completion-auto-y';
|
langKey = 'core.completion-alt-auto-y';
|
||||||
langKey = 'core.completion-alt-auto-y';
|
} else if (this.completion.state === CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE_PASS) {
|
||||||
} else if (this.completion.tracking === CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_AUTOMATIC &&
|
image = 'completion-auto-pass';
|
||||||
this.completion.state === CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE_PASS) {
|
langKey = 'core.completion-alt-auto-pass';
|
||||||
image = 'completion-auto-pass';
|
} else if (this.completion.state === CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE_FAIL) {
|
||||||
langKey = 'core.completion-alt-auto-pass';
|
image = 'completion-auto-fail';
|
||||||
} else if (this.completion.tracking === CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_AUTOMATIC &&
|
langKey = 'core.completion-alt-auto-fail';
|
||||||
this.completion.state === CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE_FAIL) {
|
}
|
||||||
image = 'completion-auto-fail';
|
|
||||||
langKey = 'core.completion-alt-auto-fail';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (image) {
|
if (image) {
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
"confirmdownloadzerosize": "You are about to start downloading.{{availableSpace}} Are you sure you want to continue?",
|
"confirmdownloadzerosize": "You are about to start downloading.{{availableSpace}} Are you sure you want to continue?",
|
||||||
"confirmpartialdownloadsize": "You are about to download <strong>at least</strong> {{size}}.{{availableSpace}} Are you sure you want to continue?",
|
"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. ",
|
"confirmlimiteddownload": "You are not currently connected to Wi-Fi. ",
|
||||||
|
"courseindex": "Course index",
|
||||||
"gotonextactivity": "Continue to next activity",
|
"gotonextactivity": "Continue to next activity",
|
||||||
"gotonextactivitynotfound": "Next activity not found. It's possible that it has been hidden or deleted.",
|
"gotonextactivitynotfound": "Next activity not found. It's possible that it has been hidden or deleted.",
|
||||||
"gotopreviousactivity": "Continue to previous activity",
|
"gotopreviousactivity": "Continue to previous activity",
|
||||||
|
@ -49,7 +50,6 @@
|
||||||
"overriddennotice": "Your final grade from this activity was manually adjusted.",
|
"overriddennotice": "Your final grade from this activity was manually adjusted.",
|
||||||
"refreshcourse": "Refresh course",
|
"refreshcourse": "Refresh course",
|
||||||
"section": "Section",
|
"section": "Section",
|
||||||
"sections": "Sections",
|
|
||||||
"useactivityonbrowser": "You can still use it using your device's web browser.",
|
"useactivityonbrowser": "You can still use it using your device's web browser.",
|
||||||
"warningmanualcompletionmodified": "The manual completion of an activity was modified on the site.",
|
"warningmanualcompletionmodified": "The manual completion of an activity was modified on the site.",
|
||||||
"warningofflinemanualcompletiondeleted": "Some offline manual completion of course '{{name}}' has been deleted. {{error}}"
|
"warningofflinemanualcompletiondeleted": "Some offline manual completion of course '{{name}}' has been deleted. {{error}}"
|
||||||
|
|
|
@ -62,7 +62,7 @@ declare module '@singletons/events' {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Completion status valid values.
|
* Course Module completion status enumeration.
|
||||||
*/
|
*/
|
||||||
export enum CoreCourseModuleCompletionStatus {
|
export enum CoreCourseModuleCompletionStatus {
|
||||||
COMPLETION_INCOMPLETE = 0,
|
COMPLETION_INCOMPLETE = 0,
|
||||||
|
|
|
@ -125,15 +125,6 @@ export interface CoreCourseFormatHandler extends CoreDelegateHandler {
|
||||||
*/
|
*/
|
||||||
getCourseSummaryComponent?(course: CoreCourseAnyCourseData): Promise<Type<unknown> | undefined>;
|
getCourseSummaryComponent?(course: CoreCourseAnyCourseData): Promise<Type<unknown> | undefined>;
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the Component to use to display the section selector inside the default course format.
|
|
||||||
* It's recommended to return the class of the component, but you can also return an instance of the component.
|
|
||||||
*
|
|
||||||
* @param course The course to render.
|
|
||||||
* @return Promise resolved with component to use, undefined if not found.
|
|
||||||
*/
|
|
||||||
getSectionSelectorComponent?(course: CoreCourseAnyCourseData): Promise<Type<unknown> | undefined>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the Component to use to display a single section. This component will only be used if the user is viewing a
|
* Return 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.
|
* single section. If all the sections are displayed at once then it won't be used.
|
||||||
|
@ -302,20 +293,6 @@ export class CoreCourseFormatDelegateService extends CoreDelegate<CoreCourseForm
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the component to use to display the section selector inside the default course format.
|
|
||||||
*
|
|
||||||
* @param course The course to render.
|
|
||||||
* @return Promise resolved with component to use, undefined if not found.
|
|
||||||
*/
|
|
||||||
async getSectionSelectorComponent(course: CoreCourseAnyCourseData): Promise<Type<unknown> | undefined> {
|
|
||||||
try {
|
|
||||||
return await this.executeFunctionOnEnabled<Type<unknown>>(course.format || '', 'getSectionSelectorComponent', [course]);
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error('Error getting section selector component', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the component to use to display a single section. This component will only be used if the user is viewing
|
* 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.
|
* a single section. If all the sections are displayed at once then it won't be used.
|
||||||
|
|
Loading…
Reference in New Issue