MOBILE-3686 courses: Filter my courses and add download to categories
parent
ad45289f4b
commit
264309593f
|
@ -7,6 +7,16 @@
|
||||||
<core-format-text [text]="title" contextLevel="coursecat" [contextInstanceId]="currentCategory && currentCategory!.id">
|
<core-format-text [text]="title" contextLevel="coursecat" [contextInstanceId]="currentCategory && currentCategory!.id">
|
||||||
</core-format-text>
|
</core-format-text>
|
||||||
</h1>
|
</h1>
|
||||||
|
<ion-buttons slot="end">
|
||||||
|
<core-context-menu>
|
||||||
|
<core-context-menu-item *ngIf="downloadCourseEnabled || downloadCoursesEnabled" [priority]="1000"
|
||||||
|
[content]="'core.settings.showdownloadoptions' | translate" (action)="toggleDownload(!downloadEnabled)"
|
||||||
|
iconAction="toggle" [toggle]="downloadEnabled"></core-context-menu-item>
|
||||||
|
<core-context-menu-item *ngIf="myCoursesEnabled" [priority]="900"
|
||||||
|
[content]="'core.courses.showonlyenrolled' | translate" (action)="toggleEnrolled(!showOnlyEnrolled)"
|
||||||
|
iconAction="toggle" [toggle]="showOnlyEnrolled"></core-context-menu-item>
|
||||||
|
</core-context-menu>
|
||||||
|
</ion-buttons>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content>
|
<ion-content>
|
||||||
|
@ -17,22 +27,18 @@
|
||||||
<ion-item *ngIf="currentCategory" class="ion-text-wrap">
|
<ion-item *ngIf="currentCategory" class="ion-text-wrap">
|
||||||
<ion-icon name="fas-folder" slot="start" [attr.aria-label]="'core.category' | translate"></ion-icon>
|
<ion-icon name="fas-folder" slot="start" [attr.aria-label]="'core.category' | translate"></ion-icon>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>
|
<p class="item-heading">
|
||||||
<core-format-text [text]="currentCategory!.name" contextLevel="coursecat"
|
<core-format-text [text]="currentCategory.name" contextLevel="coursecat"
|
||||||
[contextInstanceId]="currentCategory!.id"></core-format-text>
|
[contextInstanceId]="currentCategory.id"></core-format-text>
|
||||||
</h2>
|
</p>
|
||||||
</ion-label>
|
<p *ngIf="currentCategory.description">
|
||||||
</ion-item>
|
<core-format-text [text]="currentCategory.description" maxHeight="60" contextLevel="coursecat"
|
||||||
<ion-item class="ion-text-wrap" *ngIf="currentCategory && currentCategory!.description">
|
[contextInstanceId]="currentCategory.id"></core-format-text>
|
||||||
<ion-label>
|
</p>
|
||||||
<h2>
|
|
||||||
<core-format-text [text]="currentCategory!.description" maxHeight="60" contextLevel="coursecat"
|
|
||||||
[contextInstanceId]="currentCategory!.id"></core-format-text>
|
|
||||||
</h2>
|
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<div *ngIf="categories.length > 0">
|
<ng-container *ngIf="categories.length > 0">
|
||||||
<ion-item-divider>
|
<ion-item-divider>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>{{ 'core.courses.categories' | translate }}</h2>
|
<h2>{{ 'core.courses.categories' | translate }}</h2>
|
||||||
|
@ -48,22 +54,24 @@
|
||||||
</core-format-text>
|
</core-format-text>
|
||||||
</h2>
|
</h2>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-badge slot="end" *ngIf="category.coursecount > 0" color="light">
|
<ion-badge slot="end" *ngIf="!showOnlyEnrolled && category.coursecount > 0" color="light">
|
||||||
<span aria-hidden="true">{{ category.coursecount }}</span>
|
<span aria-hidden="true">{{ category.coursecount }}</span>
|
||||||
<span class="sr-only">{{ 'core.courses.therearecourses' | translate:{ $a: category.coursecount } }}</span>
|
<span class="sr-only">{{ 'core.courses.therearecourses' | translate:{ $a: category.coursecount } }}</span>
|
||||||
</ion-badge>
|
</ion-badge>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</ng-container>
|
||||||
|
|
||||||
<div *ngIf="courses.length > 0">
|
<ng-container *ngIf="courses.length > 0">
|
||||||
<ion-item-divider>
|
<ion-item-divider>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>{{ 'core.courses.courses' | translate }}</h2>
|
<h2 *ngIf="!showOnlyEnrolled">{{ 'core.courses.courses' | translate }}</h2>
|
||||||
|
<h2 *ngIf="showOnlyEnrolled">{{ 'core.courses.mycourses' | translate }}</h2>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item-divider>
|
</ion-item-divider>
|
||||||
<core-courses-course-list-item *ngFor="let course of courses" [course]="course"></core-courses-course-list-item>
|
<core-courses-course-list-item *ngFor="let course of courses" [course]="course" [showDownload]="downloadEnabled">
|
||||||
</div>
|
</core-courses-course-list-item>
|
||||||
|
</ng-container>
|
||||||
<core-empty-box *ngIf="!categories.length && !courses.length" icon="fas-graduation-cap"
|
<core-empty-box *ngIf="!categories.length && !courses.length" icon="fas-graduation-cap"
|
||||||
[message]="'core.courses.nocoursesyet' | translate">
|
[message]="'core.courses.nocoursesyet' | translate">
|
||||||
</core-empty-box>
|
</core-empty-box>
|
||||||
|
|
|
@ -12,14 +12,15 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { IonRefresher } from '@ionic/angular';
|
import { IonRefresher } from '@ionic/angular';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { CoreCategoryData, CoreCourses, CoreCourseSearchedData } from '../../services/courses';
|
import { CoreCategoryData, CoreCourseListItem, CoreCourses, CoreCoursesProvider } from '../../services/courses';
|
||||||
import { Translate } from '@singletons';
|
import { Translate } from '@singletons';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays a list of categories and the courses in the current category if any.
|
* Page that displays a list of categories and the courses in the current category if any.
|
||||||
|
@ -28,25 +29,64 @@ import { CoreNavigator } from '@services/navigator';
|
||||||
selector: 'page-core-courses-categories',
|
selector: 'page-core-courses-categories',
|
||||||
templateUrl: 'categories.html',
|
templateUrl: 'categories.html',
|
||||||
})
|
})
|
||||||
export class CoreCoursesCategoriesPage implements OnInit {
|
export class CoreCoursesCategoriesPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
title: string;
|
title: string;
|
||||||
currentCategory?: CoreCategoryData;
|
currentCategory?: CoreCategoryData;
|
||||||
categories: CoreCategoryData[] = [];
|
categories: CoreCategoryData[] = [];
|
||||||
courses: CoreCourseSearchedData[] = [];
|
courses: CoreCourseListItem[] = [];
|
||||||
categoriesLoaded = false;
|
categoriesLoaded = false;
|
||||||
|
myCoursesEnabled = true;
|
||||||
|
|
||||||
|
showOnlyEnrolled = false;
|
||||||
|
|
||||||
|
downloadEnabled = false;
|
||||||
|
downloadCourseEnabled = false;
|
||||||
|
downloadCoursesEnabled = false;
|
||||||
|
|
||||||
|
protected categoryCourses: CoreCourseListItem[] = [];
|
||||||
|
protected currentSiteId: string;
|
||||||
protected categoryId = 0;
|
protected categoryId = 0;
|
||||||
|
protected myCoursesObserver: CoreEventObserver;
|
||||||
|
protected siteUpdatedObserver: CoreEventObserver;
|
||||||
|
protected isDestroyed = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.title = Translate.instant('core.courses.categories');
|
this.title = Translate.instant('core.courses.categories');
|
||||||
|
this.currentSiteId = CoreSites.getRequiredCurrentSite().getId();
|
||||||
|
|
||||||
|
// Update list if user enrols in a course.
|
||||||
|
this.myCoursesObserver = CoreEvents.on(
|
||||||
|
CoreCoursesProvider.EVENT_MY_COURSES_UPDATED,
|
||||||
|
(data) => {
|
||||||
|
if (data.action == CoreCoursesProvider.ACTION_ENROL) {
|
||||||
|
this.fetchCategories();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
this.currentSiteId,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Refresh the enabled flags if site is updated.
|
||||||
|
this.siteUpdatedObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
|
||||||
|
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
|
||||||
|
this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
|
||||||
|
this.myCoursesEnabled = !CoreCourses.isMyCoursesDisabledInSite();
|
||||||
|
|
||||||
|
this.downloadEnabled = (this.downloadCourseEnabled || this.downloadCoursesEnabled) && this.downloadEnabled;
|
||||||
|
}, this.currentSiteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View loaded.
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.categoryId = CoreNavigator.getRouteNumberParam('id') || 0;
|
this.categoryId = CoreNavigator.getRouteNumberParam('id') || 0;
|
||||||
|
this.showOnlyEnrolled = CoreNavigator.getRouteBooleanParam('enrolled') || this.showOnlyEnrolled;
|
||||||
|
|
||||||
|
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
|
||||||
|
this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
|
||||||
|
this.myCoursesEnabled = !CoreCourses.isMyCoursesDisabledInSite();
|
||||||
|
|
||||||
this.fetchCategories().finally(() => {
|
this.fetchCategories().finally(() => {
|
||||||
this.categoriesLoaded = true;
|
this.categoriesLoaded = true;
|
||||||
|
@ -87,13 +127,14 @@ export class CoreCoursesCategoriesPage implements OnInit {
|
||||||
this.title = this.currentCategory.name;
|
this.title = this.currentCategory.name;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.courses = await CoreCourses.getCoursesByField('category', this.categoryId);
|
this.categoryCourses = await CoreCourses.getCoursesByField('category', this.categoryId);
|
||||||
|
await this.toggleEnrolled(this.showOnlyEnrolled);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcourses', true);
|
!this.isDestroyed && CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcourses', true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcategories', true);
|
!this.isDestroyed && CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcategories', true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,7 +149,7 @@ export class CoreCoursesCategoriesPage implements OnInit {
|
||||||
promises.push(CoreCourses.invalidateUserCourses());
|
promises.push(CoreCourses.invalidateUserCourses());
|
||||||
promises.push(CoreCourses.invalidateCategories(this.categoryId, true));
|
promises.push(CoreCourses.invalidateCategories(this.categoryId, true));
|
||||||
promises.push(CoreCourses.invalidateCoursesByField('category', this.categoryId));
|
promises.push(CoreCourses.invalidateCoursesByField('category', this.categoryId));
|
||||||
promises.push(CoreSites.getCurrentSite()!.invalidateConfig());
|
promises.push(CoreSites.getRequiredCurrentSite().invalidateConfig());
|
||||||
|
|
||||||
Promise.all(promises).finally(() => {
|
Promise.all(promises).finally(() => {
|
||||||
this.fetchCategories().finally(() => {
|
this.fetchCategories().finally(() => {
|
||||||
|
@ -123,7 +164,56 @@ export class CoreCoursesCategoriesPage implements OnInit {
|
||||||
* @param categoryId Category Id.
|
* @param categoryId Category Id.
|
||||||
*/
|
*/
|
||||||
openCategory(categoryId: number): void {
|
openCategory(categoryId: number): void {
|
||||||
CoreNavigator.navigateToSitePath('courses/categories/' + categoryId);
|
CoreNavigator.navigateToSitePath(
|
||||||
|
'courses/categories/' + categoryId,
|
||||||
|
{ params: {
|
||||||
|
enrolled: this.showOnlyEnrolled,
|
||||||
|
} },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle show only my courses.
|
||||||
|
*
|
||||||
|
* @param enable If enable or disable.
|
||||||
|
*/
|
||||||
|
async toggleEnrolled(enable: boolean): Promise<void> {
|
||||||
|
this.showOnlyEnrolled = enable;
|
||||||
|
|
||||||
|
if (!this.showOnlyEnrolled) {
|
||||||
|
this.courses = this.categoryCourses;
|
||||||
|
} else {
|
||||||
|
await Promise.all(this.categoryCourses.map(async (course) => {
|
||||||
|
const isEnrolled = course.progress !== undefined;
|
||||||
|
|
||||||
|
if (!isEnrolled) {
|
||||||
|
try {
|
||||||
|
const userCourse = await CoreCourses.getUserCourse(course.id);
|
||||||
|
course.progress = userCourse.progress;
|
||||||
|
course.completionusertracked = userCourse.completionusertracked;
|
||||||
|
} catch {
|
||||||
|
// Ignore errors.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
this.courses = this.categoryCourses.filter((course) => 'progress' in course);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle download enabled.
|
||||||
|
*/
|
||||||
|
toggleDownload(enabled: boolean): void {
|
||||||
|
this.downloadEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.myCoursesObserver?.off();
|
||||||
|
this.siteUpdatedObserver?.off();
|
||||||
|
this.isDestroyed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue