MOBILE-3915 course: Improve Course index page
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…
Reference in New Issue