MOBILE-3915 course: Improve course summary info
parent
260f59ea9f
commit
1dd5eba1de
|
@ -9,6 +9,53 @@
|
|||
<core-dynamic-component [component]="courseFormatComponent" [data]="data">
|
||||
<!-- Default course format. -->
|
||||
<core-loading [hideUntil]="loaded">
|
||||
|
||||
<!-- Course summary. By default we only display the course progress. -->
|
||||
<core-dynamic-component [component]="courseSummaryComponent" [data]="data">
|
||||
<ion-item lines="full" class="core-format-progress-list ion-text-wrap">
|
||||
<ion-avatar slot="start" class="core-course-thumb" *ngIf="imageThumb">
|
||||
<img [src]="imageThumb" core-external-content alt="" />
|
||||
</ion-avatar>
|
||||
<ion-label>
|
||||
<p *ngIf="course.categoryname">
|
||||
<core-format-text [text]="course.categoryname" contextLevel="coursecat" [contextInstanceId]="course.categoryid">
|
||||
</core-format-text>
|
||||
</p>
|
||||
<p class="item-heading">{{ course.displayname || course.fullname }}</p>
|
||||
<div class="core-course-progress" *ngIf="progress !== undefined">
|
||||
<core-progress-bar [progress]="progress" a11yText="core.course.aria:sectionprogress">
|
||||
</core-progress-bar>
|
||||
</div>
|
||||
</ion-label>
|
||||
<ion-button fill="clear" slot="end" (click)="openCourseSummary()" [attr.aria-label]="'core.course.coursesummary' |translate"
|
||||
color="dark">
|
||||
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||
</ion-button>
|
||||
</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"
|
||||
|
@ -19,7 +66,7 @@
|
|||
(onChange)="sectionChanged($event)">
|
||||
<span slot="text">
|
||||
<core-format-text *ngIf="selectedSection" [text]="selectedSection.name" contextLevel="course"
|
||||
[contextInstanceId]="course?.id" [clean]="true" [singleLine]="true">
|
||||
[contextInstanceId]="course.id" [clean]="true" [singleLine]="true">
|
||||
</core-format-text>
|
||||
<ng-container *ngIf="!selectedSection">{{ 'core.course.sections' | translate }}</ng-container>
|
||||
</span>
|
||||
|
@ -27,39 +74,6 @@
|
|||
</div>
|
||||
</core-dynamic-component>
|
||||
|
||||
<!-- Course summary. By default we only display the course progress. -->
|
||||
<core-dynamic-component [component]="courseSummaryComponent" [data]="data">
|
||||
<ion-list *ngIf="imageThumb || (selectedSection?.id == allSectionsId && progress !== undefined) ||
|
||||
(selectedSection && selectedSection.id != allSectionsId &&
|
||||
(selectedSection.availabilityinfo || selectedSection.visible === 0))" lines="none" class="core-format-progress-list">
|
||||
<div *ngIf="imageThumb" class="core-course-thumb">
|
||||
<img [src]="imageThumb" core-external-content alt="" />
|
||||
</div>
|
||||
<ng-container *ngIf="selectedSection">
|
||||
<ion-item class="core-course-progress" *ngIf="selectedSection?.id == allSectionsId && progress !== undefined">
|
||||
<core-progress-bar [progress]="progress" a11yText="core.course.aria:sectionprogress">
|
||||
</core-progress-bar>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="selectedSection && selectedSection.id != allSectionsId &&
|
||||
(selectedSection.availabilityinfo || selectedSection.visible === 0)">
|
||||
<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-item>
|
||||
</ng-container>
|
||||
</ion-list>
|
||||
</core-dynamic-component>
|
||||
|
||||
<!-- Single section. -->
|
||||
<div *ngIf="selectedSection && selectedSection.id != allSectionsId">
|
||||
<core-dynamic-component [component]="singleSectionComponent" [data]="data">
|
||||
|
@ -88,12 +102,12 @@
|
|||
<ion-button *ngIf="previousSection" (click)="sectionChanged(previousSection)" fill="outline" color="primary"
|
||||
[attr.aria-label]="('core.previous' | translate) + ': ' + previousSection.name">
|
||||
<ion-icon name="fas-chevron-left" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||
<core-format-text class="sr-only" [text]="previousSection.name" contextLevel="course" [contextInstanceId]="course?.id">
|
||||
<core-format-text class="sr-only" [text]="previousSection.name" contextLevel="course" [contextInstanceId]="course.id">
|
||||
</core-format-text>
|
||||
</ion-button>
|
||||
<ion-button *ngIf="nextSection" (click)="sectionChanged(nextSection)" fill="solid" color="primary"
|
||||
[attr.aria-label]="('core.next' | translate) + ': ' + nextSection.name">
|
||||
<core-format-text class="sr-only" [text]="nextSection.name" contextLevel="course" [contextInstanceId]="course?.id">
|
||||
<core-format-text class="sr-only" [text]="nextSection.name" contextLevel="course" [contextInstanceId]="course.id">
|
||||
</core-format-text>
|
||||
<ion-icon name="fas-chevron-right" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||
</ion-button>
|
||||
|
@ -112,7 +126,7 @@
|
|||
[class.item-dimmed]="section.visible === 0 || section.uservisible === false">
|
||||
<ion-label>
|
||||
<h2>
|
||||
<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>
|
||||
</h2>
|
||||
<p *ngIf="section.visible === 0 || section.availabilityinfo">
|
||||
|
@ -123,7 +137,7 @@
|
|||
{{ '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 [text]=" section.availabilityinfo" contextLevel="course" [contextInstanceId]="course.id">
|
||||
</core-format-text>
|
||||
</ion-badge>
|
||||
</p>
|
||||
|
@ -132,15 +146,15 @@
|
|||
|
||||
<ion-item class="ion-text-wrap" *ngIf="section.summary">
|
||||
<ion-label>
|
||||
<core-format-text [text]="section.summary" contextLevel="course" [contextInstanceId]="course?.id">
|
||||
<core-format-text [text]="section.summary" contextLevel="course" [contextInstanceId]="course.id">
|
||||
</core-format-text>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ng-container *ngFor="let module of section.modules">
|
||||
<core-course-module *ngIf="module.visibleoncoursepage !== 0" [module]="module" [section]="section"
|
||||
(completionChanged)="onCompletionChange($event)" [showActivityDates]="course?.showactivitydates"
|
||||
[showCompletionConditions]="course?.showcompletionconditions">
|
||||
(completionChanged)="onCompletionChange($event)" [showActivityDates]="course.showactivitydates"
|
||||
[showCompletionConditions]="course.showcompletionconditions">
|
||||
</core-course-module>
|
||||
</ng-container>
|
||||
</section>
|
||||
|
|
|
@ -16,23 +16,8 @@
|
|||
}
|
||||
|
||||
.core-course-thumb {
|
||||
display: none;
|
||||
height: var(--core-courseimage-on-course-height);
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
position: relative;
|
||||
background: var(--ion-item-background);
|
||||
border-bottom: 1px solid var(--stroke);
|
||||
|
||||
img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
}
|
||||
height: var(--core-courseimage-on-course-size);
|
||||
width: var(--core-courseimage-on-course-size);
|
||||
}
|
||||
|
||||
@if ($core-show-courseimage-on-course) {
|
||||
|
|
|
@ -39,6 +39,7 @@ import {
|
|||
CoreCourseModuleData,
|
||||
CoreCourseModuleCompletionData,
|
||||
CoreCourseSection,
|
||||
CoreCourseSectionWithStatus,
|
||||
} from '@features/course/services/course-helper';
|
||||
import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate';
|
||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||
|
@ -46,6 +47,7 @@ import { IonContent, IonRefresher } from '@ionic/angular';
|
|||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreCourseSectionSelectorComponent } from '../section-selector/section-selector';
|
||||
import { CoreBlockHelper } from '@features/block/services/block-helper';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
|
||||
/**
|
||||
* Component to display course contents using a certain format. If the format isn't found, use default one.
|
||||
|
@ -66,7 +68,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
|
||||
static readonly LOAD_MORE_ACTIVITIES = 20; // How many activities should load each time showMoreActivities is called.
|
||||
|
||||
@Input() course?: CoreCourseAnyCourseData; // The course to render.
|
||||
@Input() course!: CoreCourseAnyCourseData; // The course to render.
|
||||
@Input() sections?: CoreCourseSection[]; // List of course sections.
|
||||
@Input() initialSectionId?: number; // The section to load first (by ID).
|
||||
@Input() initialSectionNumber?: number; // The section to load first (by number).
|
||||
|
@ -116,9 +118,17 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
}
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
if (this.course === undefined) {
|
||||
CoreDomUtils.showErrorModal('Course not set');
|
||||
|
||||
CoreNavigator.back();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Listen for select course tab events to select the right section if needed.
|
||||
this.selectTabObserver = CoreEvents.on(CoreEvents.SELECT_COURSE_TAB, (data) => {
|
||||
if (data.name) {
|
||||
|
@ -207,7 +217,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async loadCourseFormatComponent(): Promise<void> {
|
||||
this.courseFormatComponent = await CoreCourseFormatDelegate.getCourseFormatComponent(this.course!);
|
||||
this.courseFormatComponent = await CoreCourseFormatDelegate.getCourseFormatComponent(this.course);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -216,7 +226,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async loadCourseSummaryComponent(): Promise<void> {
|
||||
this.courseSummaryComponent = await CoreCourseFormatDelegate.getCourseSummaryComponent(this.course!);
|
||||
this.courseSummaryComponent = await CoreCourseFormatDelegate.getCourseSummaryComponent(this.course);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -225,7 +235,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async loadSectionSelectorComponent(): Promise<void> {
|
||||
this.sectionSelectorComponent = await CoreCourseFormatDelegate.getSectionSelectorComponent(this.course!);
|
||||
this.sectionSelectorComponent = await CoreCourseFormatDelegate.getSectionSelectorComponent(this.course);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -234,7 +244,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async loadSingleSectionComponent(): Promise<void> {
|
||||
this.singleSectionComponent = await CoreCourseFormatDelegate.getSingleSectionComponent(this.course!);
|
||||
this.singleSectionComponent = await CoreCourseFormatDelegate.getSingleSectionComponent(this.course);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -243,7 +253,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async loadAllSectionsComponent(): Promise<void> {
|
||||
this.allSectionsComponent = await CoreCourseFormatDelegate.getAllSectionsComponent(this.course!);
|
||||
this.allSectionsComponent = await CoreCourseFormatDelegate.getAllSectionsComponent(this.course);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -262,7 +272,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
|
||||
if (!newSection) {
|
||||
// Section not found, calculate which one to use.
|
||||
newSection = await CoreCourseFormatDelegate.getCurrentSection(this.course!, sections);
|
||||
newSection = await CoreCourseFormatDelegate.getCurrentSection(this.course, sections);
|
||||
}
|
||||
|
||||
this.sectionChanged(newSection);
|
||||
|
@ -289,7 +299,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
|
||||
if (!this.loaded) {
|
||||
// No section specified, not found or not visible, get current section.
|
||||
const section = await CoreCourseFormatDelegate.getCurrentSection(this.course!, sections);
|
||||
const section = await CoreCourseFormatDelegate.getCurrentSection(this.course, sections);
|
||||
|
||||
this.loaded = true;
|
||||
this.sectionChanged(section);
|
||||
|
@ -368,7 +378,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
if (!previousValue || previousValue.id != newSection.id) {
|
||||
// First load or section changed, add log in Moodle.
|
||||
CoreUtils.ignoreErrors(
|
||||
CoreCourse.logView(this.course!.id, newSection.section, undefined, this.course!.fullname),
|
||||
CoreCourse.logView(this.course.id, newSection.section, undefined, this.course.fullname),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -558,4 +568,14 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
this.progress = this.course.progress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the course summary
|
||||
*/
|
||||
openCourseSummary(): void {
|
||||
CoreNavigator.navigateToSitePath(
|
||||
'/course/' + this.course.id + '/preview',
|
||||
{ params: { course: this.course, avoidOpenCourse: true } },
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -301,7 +301,7 @@
|
|||
--core-send-message-input-background: var(--gray-200);
|
||||
--core-send-message-input-color: var(--gray-900);
|
||||
|
||||
--core-courseimage-on-course-height: 150px;
|
||||
--core-courseimage-on-course-size: 72px;
|
||||
|
||||
--core-course-module-navigation-max-height: 56px;
|
||||
--core-course-module-navigation-background: var(--contrast-background);
|
||||
|
|
Loading…
Reference in New Issue