MOBILE-3915 course: Improve course summary info

main
Pau Ferrer Ocaña 2022-01-24 17:46:31 +01:00
parent 260f59ea9f
commit 1dd5eba1de
4 changed files with 88 additions and 69 deletions

View File

@ -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>

View File

@ -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) {

View File

@ -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 } },
);
}
}

View File

@ -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);