MOBILE-2975 blocks: Allow disabling blocks and always show courses
parent
c6e01bfa66
commit
8723e5b684
|
@ -15,6 +15,7 @@
|
||||||
import { CoreLoggerProvider } from '@providers/logger';
|
import { CoreLoggerProvider } from '@providers/logger';
|
||||||
import { CoreSitesProvider } from '@providers/sites';
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
import { CoreEventsProvider } from '@providers/events';
|
import { CoreEventsProvider } from '@providers/events';
|
||||||
|
import { CoreSite } from '@classes/site';
|
||||||
|
|
||||||
export interface CoreDelegateHandler {
|
export interface CoreDelegateHandler {
|
||||||
/**
|
/**
|
||||||
|
@ -272,10 +273,10 @@ export class CoreDelegate {
|
||||||
* Check if feature is enabled or disabled in the site, depending on the feature prefix and the handler name.
|
* Check if feature is enabled or disabled in the site, depending on the feature prefix and the handler name.
|
||||||
*
|
*
|
||||||
* @param {CoreDelegateHandler} handler Handler to check.
|
* @param {CoreDelegateHandler} handler Handler to check.
|
||||||
* @param {any} site Site to check.
|
* @param {CoreSite} site Site to check.
|
||||||
* @return {boolean} Whether is enabled or disabled in site.
|
* @return {boolean} Whether is enabled or disabled in site.
|
||||||
*/
|
*/
|
||||||
protected isFeatureDisabled(handler: CoreDelegateHandler, site: any): boolean {
|
protected isFeatureDisabled(handler: CoreDelegateHandler, site: CoreSite): boolean {
|
||||||
return typeof this.featurePrefix != 'undefined' && site.isFeatureDisabled(this.featurePrefix + handler.name);
|
return typeof this.featurePrefix != 'undefined' && site.isFeatureDisabled(this.featurePrefix + handler.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { CoreEventsProvider } from '@providers/events';
|
||||||
import { CoreSitesProvider } from '@providers/sites';
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
|
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
|
||||||
import { CoreBlockDefaultHandler } from './default-block-handler';
|
import { CoreBlockDefaultHandler } from './default-block-handler';
|
||||||
|
import { CoreSite } from '@classes/site';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface that all blocks must implement.
|
* Interface that all blocks must implement.
|
||||||
|
@ -87,6 +88,30 @@ export class CoreBlockDelegate extends CoreDelegate {
|
||||||
super('CoreBlockDelegate', logger, sitesProvider, eventsProvider);
|
super('CoreBlockDelegate', logger, sitesProvider, eventsProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if blocks are disabled in a certain site.
|
||||||
|
*
|
||||||
|
* @param {CoreSite} [site] Site. If not defined, use current site.
|
||||||
|
* @return {boolean} Whether it's disabled.
|
||||||
|
*/
|
||||||
|
areBlocksDisabledInSite(site?: CoreSite): boolean {
|
||||||
|
site = site || this.sitesProvider.getCurrentSite();
|
||||||
|
|
||||||
|
return site.isFeatureDisabled('NoDelegate_SiteBlocks');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if blocks are disabled in a certain site.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] Site Id. If not defined, use current site.
|
||||||
|
* @return {Promise<boolean>} Promise resolved with true if disabled, rejected or resolved with false otherwise.
|
||||||
|
*/
|
||||||
|
areBlocksDisabled(siteId?: string): Promise<boolean> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return this.areBlocksDisabledInSite(site);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the display data for a certain block.
|
* Get the display data for a certain block.
|
||||||
*
|
*
|
||||||
|
@ -121,4 +146,15 @@ export class CoreBlockDelegate extends CoreDelegate {
|
||||||
isBlockSupported(name: string): boolean {
|
isBlockSupported(name: string): boolean {
|
||||||
return this.hasHandler(name, true);
|
return this.hasHandler(name, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if feature is enabled or disabled in the site, depending on the feature prefix and the handler name.
|
||||||
|
*
|
||||||
|
* @param {CoreDelegateHandler} handler Handler to check.
|
||||||
|
* @param {CoreSite} site Site to check.
|
||||||
|
* @return {boolean} Whether is enabled or disabled in site.
|
||||||
|
*/
|
||||||
|
protected isFeatureDisabled(handler: CoreDelegateHandler, site: CoreSite): boolean {
|
||||||
|
return this.areBlocksDisabledInSite(site) || super.isFeatureDisabled(handler, site);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,15 +19,17 @@ import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { CoreComponentsModule } from '@components/components.module';
|
import { CoreComponentsModule } from '@components/components.module';
|
||||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||||
import { CorePipesModule } from '@pipes/pipes.module';
|
import { CorePipesModule } from '@pipes/pipes.module';
|
||||||
import { CoreCoursesCourseProgressComponent } from '../components/course-progress/course-progress';
|
import { CoreCoursesCourseProgressComponent } from './course-progress/course-progress';
|
||||||
import { CoreCoursesCourseListItemComponent } from '../components/course-list-item/course-list-item';
|
import { CoreCoursesCourseListItemComponent } from './course-list-item/course-list-item';
|
||||||
import { CoreCoursesCourseOptionsMenuComponent } from '../components/course-options-menu/course-options-menu';
|
import { CoreCoursesCourseOptionsMenuComponent } from './course-options-menu/course-options-menu';
|
||||||
|
import { CoreCoursesMyCoursesComponent } from './my-courses/my-courses';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
CoreCoursesCourseProgressComponent,
|
CoreCoursesCourseProgressComponent,
|
||||||
CoreCoursesCourseListItemComponent,
|
CoreCoursesCourseListItemComponent,
|
||||||
CoreCoursesCourseOptionsMenuComponent
|
CoreCoursesCourseOptionsMenuComponent,
|
||||||
|
CoreCoursesMyCoursesComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
@ -42,7 +44,8 @@ import { CoreCoursesCourseOptionsMenuComponent } from '../components/course-opti
|
||||||
exports: [
|
exports: [
|
||||||
CoreCoursesCourseProgressComponent,
|
CoreCoursesCourseProgressComponent,
|
||||||
CoreCoursesCourseListItemComponent,
|
CoreCoursesCourseListItemComponent,
|
||||||
CoreCoursesCourseOptionsMenuComponent
|
CoreCoursesCourseOptionsMenuComponent,
|
||||||
|
CoreCoursesMyCoursesComponent
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
CoreCoursesCourseOptionsMenuComponent
|
CoreCoursesCourseOptionsMenuComponent
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
<core-loading [hideUntil]="coursesLoaded">
|
||||||
|
<ion-searchbar #searchbar *ngIf="showFilter" [(ngModel)]="filter" (ionInput)="filterChanged($event)" (ionCancel)="filterChanged()" [placeholder]="'core.courses.filtermycourses' | translate">
|
||||||
|
</ion-searchbar>
|
||||||
|
<ion-grid no-padding>
|
||||||
|
<ion-row no-padding>
|
||||||
|
<ion-col *ngFor="let course of filteredCourses" no-padding col-12 col-sm-6 col-md-6 col-lg-4 col-xl-4 align-self-stretch>
|
||||||
|
<core-courses-course-progress [course]="course" class="core-courseoverview" showAll="true"></core-courses-course-progress>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
</ion-grid>
|
||||||
|
<core-empty-box *ngIf="!courses || !courses.length" icon="ionic" [message]="'core.courses.nocourses' | translate">
|
||||||
|
<p *ngIf="searchEnabled">{{ 'core.courses.searchcoursesadvice' | translate }}</p>
|
||||||
|
</core-empty-box>
|
||||||
|
</core-loading>
|
|
@ -0,0 +1,242 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
|
||||||
|
import { Searchbar } from 'ionic-angular';
|
||||||
|
import { CoreEventsProvider } from '@providers/events';
|
||||||
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
|
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||||
|
import { CoreCoursesProvider } from '../../providers/courses';
|
||||||
|
import { CoreCoursesHelperProvider } from '../../providers/helper';
|
||||||
|
import { CoreCourseHelperProvider } from '@core/course/providers/helper';
|
||||||
|
import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that displays the list of courses the user is enrolled in.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'core-courses-my-courses',
|
||||||
|
templateUrl: 'my-courses.html',
|
||||||
|
})
|
||||||
|
export class CoreCoursesMyCoursesComponent implements OnInit, OnDestroy {
|
||||||
|
@ViewChild('searchbar') searchbar: Searchbar;
|
||||||
|
|
||||||
|
courses: any[];
|
||||||
|
filteredCourses: any[];
|
||||||
|
searchEnabled: boolean;
|
||||||
|
filter = '';
|
||||||
|
showFilter = false;
|
||||||
|
coursesLoaded = false;
|
||||||
|
prefetchCoursesData: any = {};
|
||||||
|
downloadAllCoursesEnabled: boolean;
|
||||||
|
|
||||||
|
protected prefetchIconInitialized = false;
|
||||||
|
protected myCoursesObserver;
|
||||||
|
protected siteUpdatedObserver;
|
||||||
|
protected isDestroyed = false;
|
||||||
|
protected courseIds = '';
|
||||||
|
|
||||||
|
constructor(private coursesProvider: CoreCoursesProvider,
|
||||||
|
private domUtils: CoreDomUtilsProvider, private eventsProvider: CoreEventsProvider,
|
||||||
|
private sitesProvider: CoreSitesProvider, private courseHelper: CoreCourseHelperProvider,
|
||||||
|
private courseOptionsDelegate: CoreCourseOptionsDelegate, private coursesHelper: CoreCoursesHelperProvider) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component being initialized.
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.searchEnabled = !this.coursesProvider.isSearchCoursesDisabledInSite();
|
||||||
|
this.downloadAllCoursesEnabled = !this.coursesProvider.isDownloadCoursesDisabledInSite();
|
||||||
|
|
||||||
|
this.fetchCourses().finally(() => {
|
||||||
|
this.coursesLoaded = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.myCoursesObserver = this.eventsProvider.on(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, () => {
|
||||||
|
this.fetchCourses();
|
||||||
|
}, this.sitesProvider.getCurrentSiteId());
|
||||||
|
|
||||||
|
// Refresh the enabled flags if site is updated.
|
||||||
|
this.siteUpdatedObserver = this.eventsProvider.on(CoreEventsProvider.SITE_UPDATED, () => {
|
||||||
|
const wasEnabled = this.downloadAllCoursesEnabled;
|
||||||
|
|
||||||
|
this.searchEnabled = !this.coursesProvider.isSearchCoursesDisabledInSite();
|
||||||
|
this.downloadAllCoursesEnabled = !this.coursesProvider.isDownloadCoursesDisabledInSite();
|
||||||
|
|
||||||
|
if (!wasEnabled && this.downloadAllCoursesEnabled && this.coursesLoaded) {
|
||||||
|
// Download all courses is enabled now, initialize it.
|
||||||
|
this.initPrefetchCoursesIcon();
|
||||||
|
}
|
||||||
|
}, this.sitesProvider.getCurrentSiteId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the user courses.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected fetchCourses(): Promise<any> {
|
||||||
|
return this.coursesProvider.getUserCourses().then((courses) => {
|
||||||
|
const promises = [],
|
||||||
|
courseIds = courses.map((course) => {
|
||||||
|
return course.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.courseIds = courseIds.join(',');
|
||||||
|
|
||||||
|
promises.push(this.coursesHelper.loadCoursesExtraInfo(courses));
|
||||||
|
|
||||||
|
if (this.coursesProvider.canGetAdminAndNavOptions()) {
|
||||||
|
promises.push(this.coursesProvider.getCoursesAdminAndNavOptions(courseIds).then((options) => {
|
||||||
|
courses.forEach((course) => {
|
||||||
|
course.navOptions = options.navOptions[course.id];
|
||||||
|
course.admOptions = options.admOptions[course.id];
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(promises).then(() => {
|
||||||
|
this.courses = courses;
|
||||||
|
this.filteredCourses = this.courses;
|
||||||
|
this.filter = '';
|
||||||
|
|
||||||
|
this.initPrefetchCoursesIcon();
|
||||||
|
});
|
||||||
|
}).catch((error) => {
|
||||||
|
this.domUtils.showErrorModalDefault(error, 'core.courses.errorloadcourses', true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the courses.
|
||||||
|
*
|
||||||
|
* @param {any} refresher Refresher.
|
||||||
|
*/
|
||||||
|
refreshCourses(refresher: any): void {
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
promises.push(this.coursesProvider.invalidateUserCourses());
|
||||||
|
promises.push(this.courseOptionsDelegate.clearAndInvalidateCoursesOptions());
|
||||||
|
if (this.courseIds) {
|
||||||
|
promises.push(this.coursesProvider.invalidateCoursesByField('ids', this.courseIds));
|
||||||
|
}
|
||||||
|
|
||||||
|
Promise.all(promises).finally(() => {
|
||||||
|
|
||||||
|
this.prefetchIconInitialized = false;
|
||||||
|
this.fetchCourses().finally(() => {
|
||||||
|
refresher.complete();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show or hide the filter.
|
||||||
|
*/
|
||||||
|
switchFilter(): void {
|
||||||
|
this.filter = '';
|
||||||
|
this.showFilter = !this.showFilter;
|
||||||
|
this.filteredCourses = this.courses;
|
||||||
|
if (this.showFilter) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.searchbar.setFocus();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The filter has changed.
|
||||||
|
*
|
||||||
|
* @param {any} Received Event.
|
||||||
|
*/
|
||||||
|
filterChanged(event: any): void {
|
||||||
|
const newValue = event.target.value && event.target.value.trim().toLowerCase();
|
||||||
|
if (!newValue || !this.courses) {
|
||||||
|
this.filteredCourses = this.courses;
|
||||||
|
} else {
|
||||||
|
// Use displayname if avalaible, or fullname if not.
|
||||||
|
if (this.courses.length > 0 && typeof this.courses[0].displayname != 'undefined') {
|
||||||
|
this.filteredCourses = this.courses.filter((course) => {
|
||||||
|
return course.displayname.toLowerCase().indexOf(newValue) > -1;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.filteredCourses = this.courses.filter((course) => {
|
||||||
|
return course.fullname.toLowerCase().indexOf(newValue) > -1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prefetch all the courses.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
prefetchCourses(): Promise<any> {
|
||||||
|
const initialIcon = this.prefetchCoursesData.icon;
|
||||||
|
|
||||||
|
this.prefetchCoursesData.icon = 'spinner';
|
||||||
|
this.prefetchCoursesData.badge = '';
|
||||||
|
|
||||||
|
return this.courseHelper.confirmAndPrefetchCourses(this.courses, (progress) => {
|
||||||
|
this.prefetchCoursesData.badge = progress.count + ' / ' + progress.total;
|
||||||
|
}).then(() => {
|
||||||
|
this.prefetchCoursesData.icon = 'ion-android-refresh';
|
||||||
|
}).catch((error) => {
|
||||||
|
if (!this.isDestroyed) {
|
||||||
|
this.domUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true);
|
||||||
|
this.prefetchCoursesData.icon = initialIcon;
|
||||||
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
this.prefetchCoursesData.badge = '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the prefetch icon for the list of courses.
|
||||||
|
*/
|
||||||
|
protected initPrefetchCoursesIcon(): void {
|
||||||
|
if (this.prefetchIconInitialized || !this.downloadAllCoursesEnabled) {
|
||||||
|
// Already initialized.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.prefetchIconInitialized = true;
|
||||||
|
|
||||||
|
if (!this.courses || this.courses.length < 2) {
|
||||||
|
// Not enough courses.
|
||||||
|
this.prefetchCoursesData.icon = '';
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.courseHelper.determineCoursesStatus(this.courses).then((status) => {
|
||||||
|
let icon = this.courseHelper.getCourseStatusIconAndTitleFromStatus(status).icon;
|
||||||
|
if (icon == 'spinner') {
|
||||||
|
// It seems all courses are being downloaded, show a download button instead.
|
||||||
|
icon = 'cloud-download';
|
||||||
|
}
|
||||||
|
this.prefetchCoursesData.icon = icon;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page destroyed.
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.isDestroyed = true;
|
||||||
|
this.myCoursesObserver && this.myCoursesObserver.off();
|
||||||
|
this.siteUpdatedObserver && this.siteUpdatedObserver.off();
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,12 @@
|
||||||
<ion-icon name="search"></ion-icon>
|
<ion-icon name="search"></ion-icon>
|
||||||
</button>
|
</button>
|
||||||
<core-context-menu>
|
<core-context-menu>
|
||||||
<core-context-menu-item *ngIf="downloadCourseEnabled || downloadCoursesEnabled" [priority]="1000" [content]="'core.settings.showdownloadoptions' | translate" (action)="toggleDownload()" [iconAction]="downloadEnabledIcon"></core-context-menu-item>
|
<!-- Action for dashboard and site home. -->
|
||||||
|
<core-context-menu-item *ngIf="(siteHomeEnabled || dashboardEnabled) && (downloadCourseEnabled || downloadCoursesEnabled)" [priority]="1000" [content]="'core.settings.showdownloadoptions' | translate" (action)="toggleDownload()" [iconAction]="downloadEnabledIcon"></core-context-menu-item>
|
||||||
|
|
||||||
|
<!-- Actions when both site home and dashboard are disabled. -->
|
||||||
|
<core-context-menu-item *ngIf="!siteHomeEnabled && !dashboardEnabled && mcComponent && mcComponent.downloadAllCoursesEnabled && mcComponent.courses && mcComponent.courses.length >= 2" [priority]="800" [content]="'core.courses.downloadcourses' | translate" (action)="mcComponent.prefetchCourses()" [iconAction]="mcComponent.prefetchCoursesData.icon" [closeOnClick]="false" [badge]="mcComponent.prefetchCoursesData.badge"></core-context-menu-item>
|
||||||
|
<core-context-menu-item *ngIf="!siteHomeEnabled && !dashboardEnabled && mcComponent && mcComponent.courses && mcComponent.courses.length > 5" [priority]="700" [content]="'core.courses.filtermycourses' | translate" (action)="mcComponent.switchFilter()" [iconAction]="'funnel'"></core-context-menu-item>
|
||||||
</core-context-menu>
|
</core-context-menu>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
</ion-navbar>
|
</ion-navbar>
|
||||||
|
@ -46,5 +51,17 @@
|
||||||
</ion-content>
|
</ion-content>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</core-tab>
|
</core-tab>
|
||||||
|
|
||||||
|
<!-- Tab to display if both site home and dashboard are disabled. -->
|
||||||
|
<core-tab [show]="!siteHomeEnabled && !dashboardEnabled" [title]="'core.courses.mymoodle' | translate">
|
||||||
|
<ng-template>
|
||||||
|
<ion-content>
|
||||||
|
<ion-refresher [enabled]="mcComponent && mcComponent.coursesLoaded" (ionRefresh)="refreshMyCourses($event)">
|
||||||
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
|
</ion-refresher>
|
||||||
|
<core-courses-my-courses></core-courses-my-courses>
|
||||||
|
</ion-content>
|
||||||
|
</ng-template>
|
||||||
|
</core-tab>
|
||||||
</core-tabs>
|
</core-tabs>
|
||||||
</ion-content>
|
</ion-content>
|
|
@ -24,6 +24,7 @@ import { CoreSiteHomeProvider } from '@core/sitehome/providers/sitehome';
|
||||||
import { CoreSiteHomeIndexComponent } from '@core/sitehome/components/index/index';
|
import { CoreSiteHomeIndexComponent } from '@core/sitehome/components/index/index';
|
||||||
import { CoreCoursesProvider } from '../../providers/courses';
|
import { CoreCoursesProvider } from '../../providers/courses';
|
||||||
import { CoreCoursesDashboardProvider } from '../../providers/dashboard';
|
import { CoreCoursesDashboardProvider } from '../../providers/dashboard';
|
||||||
|
import { CoreCoursesMyCoursesComponent } from '../../components/my-courses/my-courses';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays the dashboard.
|
* Page that displays the dashboard.
|
||||||
|
@ -37,6 +38,7 @@ export class CoreCoursesDashboardPage implements OnDestroy {
|
||||||
@ViewChild(CoreTabsComponent) tabsComponent: CoreTabsComponent;
|
@ViewChild(CoreTabsComponent) tabsComponent: CoreTabsComponent;
|
||||||
@ViewChild(CoreSiteHomeIndexComponent) siteHomeComponent: CoreSiteHomeIndexComponent;
|
@ViewChild(CoreSiteHomeIndexComponent) siteHomeComponent: CoreSiteHomeIndexComponent;
|
||||||
@ViewChildren(CoreBlockComponent) blocksComponents: QueryList<CoreBlockComponent>;
|
@ViewChildren(CoreBlockComponent) blocksComponents: QueryList<CoreBlockComponent>;
|
||||||
|
@ViewChild(CoreCoursesMyCoursesComponent) mcComponent: CoreCoursesMyCoursesComponent;
|
||||||
|
|
||||||
firstSelectedTab: number;
|
firstSelectedTab: number;
|
||||||
siteHomeEnabled = false;
|
siteHomeEnabled = false;
|
||||||
|
@ -186,6 +188,26 @@ export class CoreCoursesDashboardPage implements OnDestroy {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the dashboard data and My Courses.
|
||||||
|
*
|
||||||
|
* @param {any} refresher Refresher.
|
||||||
|
*/
|
||||||
|
refreshMyCourses(refresher: any): void {
|
||||||
|
// First of all, refresh dashboard blocks, maybe a new block was added and now we can display the dashboard.
|
||||||
|
this.dashboardProvider.invalidateDashboardBlocks().finally(() => {
|
||||||
|
return this.loadDashboardContent();
|
||||||
|
}).finally(() => {
|
||||||
|
if (!this.dashboardEnabled) {
|
||||||
|
// Dashboard still not enabled. Refresh my courses.
|
||||||
|
this.mcComponent && this.mcComponent.refreshCourses(refresher);
|
||||||
|
} else {
|
||||||
|
this.tabsComponent.selectTab(1);
|
||||||
|
refresher.complete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle download enabled.
|
* Toggle download enabled.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -3,33 +3,20 @@
|
||||||
<ion-title>{{ 'core.courses.mycourses' | translate }}</ion-title>
|
<ion-title>{{ 'core.courses.mycourses' | translate }}</ion-title>
|
||||||
|
|
||||||
<ion-buttons end>
|
<ion-buttons end>
|
||||||
<button *ngIf="searchEnabled" ion-button icon-only (click)="openSearch()" [attr.aria-label]="'core.courses.searchcourses' | translate">
|
<button *ngIf="mcComponent && mcComponent.searchEnabled" ion-button icon-only (click)="openSearch()" [attr.aria-label]="'core.courses.searchcourses' | translate">
|
||||||
<ion-icon name="search"></ion-icon>
|
<ion-icon name="search"></ion-icon>
|
||||||
</button>
|
</button>
|
||||||
<core-context-menu>
|
<core-context-menu *ngIf="mcComponent">
|
||||||
<core-context-menu-item [hidden]="!downloadAllCoursesEnabled || !courses || courses.length < 2" [priority]="800" [content]="'core.courses.downloadcourses' | translate" (action)="prefetchCourses()" [iconAction]="prefetchCoursesData.icon" [closeOnClick]="false" [badge]="prefetchCoursesData.badge"></core-context-menu-item>
|
<core-context-menu-item [hidden]="!mcComponent.downloadAllCoursesEnabled || !mcComponent.courses || mcComponent.courses.length < 2" [priority]="800" [content]="'core.courses.downloadcourses' | translate" (action)="mcComponent.prefetchCourses()" [iconAction]="mcComponent.prefetchCoursesData.icon" [closeOnClick]="false" [badge]="mcComponent.prefetchCoursesData.badge"></core-context-menu-item>
|
||||||
<core-context-menu-item [hidden]="!courses || courses.length <= 5" [priority]="700" [content]="'core.courses.filtermycourses' | translate" (action)="switchFilter()" [iconAction]="'funnel'"></core-context-menu-item>
|
<core-context-menu-item [hidden]="!mcComponent.courses || mcComponent.courses.length <= 5" [priority]="700" [content]="'core.courses.filtermycourses' | translate" (action)="mcComponent.switchFilter()" [iconAction]="'funnel'"></core-context-menu-item>
|
||||||
</core-context-menu>
|
</core-context-menu>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
</ion-navbar>
|
</ion-navbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<ion-refresher [enabled]="coursesLoaded" (ionRefresh)="refreshCourses($event)">
|
<ion-refresher [enabled]="mcComponent && mcComponent.coursesLoaded" (ionRefresh)="mcComponent.refreshCourses($event)">
|
||||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
</ion-refresher>
|
</ion-refresher>
|
||||||
|
|
||||||
<core-loading [hideUntil]="coursesLoaded">
|
<core-courses-my-courses></core-courses-my-courses>
|
||||||
<ion-searchbar #searchbar *ngIf="showFilter" [(ngModel)]="filter" (ionInput)="filterChanged($event)" (ionCancel)="filterChanged()" [placeholder]="'core.courses.filtermycourses' | translate">
|
|
||||||
</ion-searchbar>
|
|
||||||
<ion-grid no-padding>
|
|
||||||
<ion-row no-padding>
|
|
||||||
<ion-col *ngFor="let course of filteredCourses" no-padding col-12 col-sm-6 col-md-6 col-lg-4 col-xl-4 align-self-stretch>
|
|
||||||
<core-courses-course-progress [course]="course" class="core-courseoverview" showAll="true"></core-courses-course-progress>
|
|
||||||
</ion-col>
|
|
||||||
</ion-row>
|
|
||||||
</ion-grid>
|
|
||||||
<core-empty-box *ngIf="!courses || !courses.length" icon="ionic" [message]="'core.courses.nocourses' | translate">
|
|
||||||
<p *ngIf="searchEnabled">{{ 'core.courses.searchcoursesadvice' | translate }}</p>
|
|
||||||
</core-empty-box>
|
|
||||||
</core-loading>
|
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
|
@ -12,15 +12,9 @@
|
||||||
// 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, OnDestroy, ViewChild } from '@angular/core';
|
import { Component, ViewChild } from '@angular/core';
|
||||||
import { IonicPage, Searchbar, NavController } from 'ionic-angular';
|
import { IonicPage, NavController } from 'ionic-angular';
|
||||||
import { CoreEventsProvider } from '@providers/events';
|
import { CoreCoursesMyCoursesComponent } from '../../components/my-courses/my-courses';
|
||||||
import { CoreSitesProvider } from '@providers/sites';
|
|
||||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
|
||||||
import { CoreCoursesProvider } from '../../providers/courses';
|
|
||||||
import { CoreCoursesHelperProvider } from '../../providers/helper';
|
|
||||||
import { CoreCourseHelperProvider } from '@core/course/providers/helper';
|
|
||||||
import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays the list of courses the user is enrolled in.
|
* Page that displays the list of courses the user is enrolled in.
|
||||||
|
@ -30,131 +24,10 @@ import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delega
|
||||||
selector: 'page-core-courses-my-courses',
|
selector: 'page-core-courses-my-courses',
|
||||||
templateUrl: 'my-courses.html',
|
templateUrl: 'my-courses.html',
|
||||||
})
|
})
|
||||||
export class CoreCoursesMyCoursesPage implements OnDestroy {
|
export class CoreCoursesMyCoursesPage {
|
||||||
@ViewChild('searchbar') searchbar: Searchbar;
|
@ViewChild(CoreCoursesMyCoursesComponent) mcComponent: CoreCoursesMyCoursesComponent;
|
||||||
|
|
||||||
courses: any[];
|
constructor(private navCtrl: NavController) { }
|
||||||
filteredCourses: any[];
|
|
||||||
searchEnabled: boolean;
|
|
||||||
filter = '';
|
|
||||||
showFilter = false;
|
|
||||||
coursesLoaded = false;
|
|
||||||
prefetchCoursesData: any = {};
|
|
||||||
downloadAllCoursesEnabled: boolean;
|
|
||||||
|
|
||||||
protected prefetchIconInitialized = false;
|
|
||||||
protected myCoursesObserver;
|
|
||||||
protected siteUpdatedObserver;
|
|
||||||
protected isDestroyed = false;
|
|
||||||
protected courseIds = '';
|
|
||||||
|
|
||||||
constructor(private navCtrl: NavController, private coursesProvider: CoreCoursesProvider,
|
|
||||||
private domUtils: CoreDomUtilsProvider, private eventsProvider: CoreEventsProvider,
|
|
||||||
private sitesProvider: CoreSitesProvider, private courseHelper: CoreCourseHelperProvider,
|
|
||||||
private courseOptionsDelegate: CoreCourseOptionsDelegate, private coursesHelper: CoreCoursesHelperProvider) { }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* View loaded.
|
|
||||||
*/
|
|
||||||
ionViewDidLoad(): void {
|
|
||||||
this.searchEnabled = !this.coursesProvider.isSearchCoursesDisabledInSite();
|
|
||||||
this.downloadAllCoursesEnabled = !this.coursesProvider.isDownloadCoursesDisabledInSite();
|
|
||||||
|
|
||||||
this.fetchCourses().finally(() => {
|
|
||||||
this.coursesLoaded = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.myCoursesObserver = this.eventsProvider.on(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, () => {
|
|
||||||
this.fetchCourses();
|
|
||||||
}, this.sitesProvider.getCurrentSiteId());
|
|
||||||
|
|
||||||
// Refresh the enabled flags if site is updated.
|
|
||||||
this.siteUpdatedObserver = this.eventsProvider.on(CoreEventsProvider.SITE_UPDATED, () => {
|
|
||||||
const wasEnabled = this.downloadAllCoursesEnabled;
|
|
||||||
|
|
||||||
this.searchEnabled = !this.coursesProvider.isSearchCoursesDisabledInSite();
|
|
||||||
this.downloadAllCoursesEnabled = !this.coursesProvider.isDownloadCoursesDisabledInSite();
|
|
||||||
|
|
||||||
if (!wasEnabled && this.downloadAllCoursesEnabled && this.coursesLoaded) {
|
|
||||||
// Download all courses is enabled now, initialize it.
|
|
||||||
this.initPrefetchCoursesIcon();
|
|
||||||
}
|
|
||||||
}, this.sitesProvider.getCurrentSiteId());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch the user courses.
|
|
||||||
*
|
|
||||||
* @return {Promise<any>} Promise resolved when done.
|
|
||||||
*/
|
|
||||||
protected fetchCourses(): Promise<any> {
|
|
||||||
return this.coursesProvider.getUserCourses().then((courses) => {
|
|
||||||
const promises = [],
|
|
||||||
courseIds = courses.map((course) => {
|
|
||||||
return course.id;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.courseIds = courseIds.join(',');
|
|
||||||
|
|
||||||
promises.push(this.coursesHelper.loadCoursesExtraInfo(courses));
|
|
||||||
|
|
||||||
if (this.coursesProvider.canGetAdminAndNavOptions()) {
|
|
||||||
promises.push(this.coursesProvider.getCoursesAdminAndNavOptions(courseIds).then((options) => {
|
|
||||||
courses.forEach((course) => {
|
|
||||||
course.navOptions = options.navOptions[course.id];
|
|
||||||
course.admOptions = options.admOptions[course.id];
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.all(promises).then(() => {
|
|
||||||
this.courses = courses;
|
|
||||||
this.filteredCourses = this.courses;
|
|
||||||
this.filter = '';
|
|
||||||
|
|
||||||
this.initPrefetchCoursesIcon();
|
|
||||||
});
|
|
||||||
}).catch((error) => {
|
|
||||||
this.domUtils.showErrorModalDefault(error, 'core.courses.errorloadcourses', true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Refresh the courses.
|
|
||||||
*
|
|
||||||
* @param {any} refresher Refresher.
|
|
||||||
*/
|
|
||||||
refreshCourses(refresher: any): void {
|
|
||||||
const promises = [];
|
|
||||||
|
|
||||||
promises.push(this.coursesProvider.invalidateUserCourses());
|
|
||||||
promises.push(this.courseOptionsDelegate.clearAndInvalidateCoursesOptions());
|
|
||||||
if (this.courseIds) {
|
|
||||||
promises.push(this.coursesProvider.invalidateCoursesByField('ids', this.courseIds));
|
|
||||||
}
|
|
||||||
|
|
||||||
Promise.all(promises).finally(() => {
|
|
||||||
|
|
||||||
this.prefetchIconInitialized = false;
|
|
||||||
this.fetchCourses().finally(() => {
|
|
||||||
refresher.complete();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show or hide the filter.
|
|
||||||
*/
|
|
||||||
switchFilter(): void {
|
|
||||||
this.filter = '';
|
|
||||||
this.showFilter = !this.showFilter;
|
|
||||||
this.filteredCourses = this.courses;
|
|
||||||
if (this.showFilter) {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.searchbar.setFocus();
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Go to search courses.
|
* Go to search courses.
|
||||||
|
@ -162,89 +35,4 @@ export class CoreCoursesMyCoursesPage implements OnDestroy {
|
||||||
openSearch(): void {
|
openSearch(): void {
|
||||||
this.navCtrl.push('CoreCoursesSearchPage');
|
this.navCtrl.push('CoreCoursesSearchPage');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The filter has changed.
|
|
||||||
*
|
|
||||||
* @param {any} Received Event.
|
|
||||||
*/
|
|
||||||
filterChanged(event: any): void {
|
|
||||||
const newValue = event.target.value && event.target.value.trim().toLowerCase();
|
|
||||||
if (!newValue || !this.courses) {
|
|
||||||
this.filteredCourses = this.courses;
|
|
||||||
} else {
|
|
||||||
// Use displayname if avalaible, or fullname if not.
|
|
||||||
if (this.courses.length > 0 && typeof this.courses[0].displayname != 'undefined') {
|
|
||||||
this.filteredCourses = this.courses.filter((course) => {
|
|
||||||
return course.displayname.toLowerCase().indexOf(newValue) > -1;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.filteredCourses = this.courses.filter((course) => {
|
|
||||||
return course.fullname.toLowerCase().indexOf(newValue) > -1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prefetch all the courses.
|
|
||||||
*
|
|
||||||
* @return {Promise<any>} Promise resolved when done.
|
|
||||||
*/
|
|
||||||
prefetchCourses(): Promise<any> {
|
|
||||||
const initialIcon = this.prefetchCoursesData.icon;
|
|
||||||
|
|
||||||
this.prefetchCoursesData.icon = 'spinner';
|
|
||||||
this.prefetchCoursesData.badge = '';
|
|
||||||
|
|
||||||
return this.courseHelper.confirmAndPrefetchCourses(this.courses, (progress) => {
|
|
||||||
this.prefetchCoursesData.badge = progress.count + ' / ' + progress.total;
|
|
||||||
}).then(() => {
|
|
||||||
this.prefetchCoursesData.icon = 'ion-android-refresh';
|
|
||||||
}).catch((error) => {
|
|
||||||
if (!this.isDestroyed) {
|
|
||||||
this.domUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true);
|
|
||||||
this.prefetchCoursesData.icon = initialIcon;
|
|
||||||
}
|
|
||||||
}).finally(() => {
|
|
||||||
this.prefetchCoursesData.badge = '';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the prefetch icon for the list of courses.
|
|
||||||
*/
|
|
||||||
protected initPrefetchCoursesIcon(): void {
|
|
||||||
if (this.prefetchIconInitialized || !this.downloadAllCoursesEnabled) {
|
|
||||||
// Already initialized.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.prefetchIconInitialized = true;
|
|
||||||
|
|
||||||
if (!this.courses || this.courses.length < 2) {
|
|
||||||
// Not enough courses.
|
|
||||||
this.prefetchCoursesData.icon = '';
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.courseHelper.determineCoursesStatus(this.courses).then((status) => {
|
|
||||||
let icon = this.courseHelper.getCourseStatusIconAndTitleFromStatus(status).icon;
|
|
||||||
if (icon == 'spinner') {
|
|
||||||
// It seems all courses are being downloaded, show a download button instead.
|
|
||||||
icon = 'cloud-download';
|
|
||||||
}
|
|
||||||
this.prefetchCoursesData.icon = icon;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Page destroyed.
|
|
||||||
*/
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
this.isDestroyed = true;
|
|
||||||
this.myCoursesObserver && this.myCoursesObserver.off();
|
|
||||||
this.siteUpdatedObserver && this.siteUpdatedObserver.off();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue