commit
1ffe48c915
|
@ -1551,6 +1551,7 @@
|
|||
"core.courses.selfenrolment": "local_moodlemobileapp",
|
||||
"core.courses.sendpaymentbutton": "enrol_paypal",
|
||||
"core.courses.show": "block_myoverview",
|
||||
"core.courses.showonlyenrolled": "local_moodlemobileapp",
|
||||
"core.courses.therearecourses": "moodle",
|
||||
"core.courses.totalcoursesearchresults": "local_moodlemobileapp",
|
||||
"core.currentdevice": "local_moodlemobileapp",
|
||||
|
|
|
@ -170,7 +170,7 @@ export class AddonBlogEntriesPage implements OnInit {
|
|||
entry.contextInstanceId = entry.userid;
|
||||
}
|
||||
|
||||
entry.summary = CoreTextUtils.instance.replacePluginfileUrls(entry.summary, entry.summaryfiles || []);
|
||||
entry.summary = CoreTextUtils.replacePluginfileUrls(entry.summary, entry.summaryfiles || []);
|
||||
|
||||
return CoreUser.getProfile(entry.userid, entry.courseid, true).then((user) => {
|
||||
entry.user = user;
|
||||
|
|
|
@ -44,8 +44,8 @@
|
|||
'addon.messages.muteconversation') | translate" (action)="changeMute($event)" [closeOnClick]="false"
|
||||
[iconAction]="muteIcon"></core-context-menu-item>
|
||||
<core-context-menu-item [hidden]="!canDelete || !messages || !messages.length" [priority]="400"
|
||||
[content]="'addon.messages.showdeletemessages' | translate" (action)="toggleDelete()"
|
||||
[iconAction]="(showDelete ? 'far-check-square' : 'far-square')"></core-context-menu-item>
|
||||
[content]="'addon.messages.showdeletemessages' | translate"
|
||||
iconAction="toggle" [(toggle)]="showDelete"></core-context-menu-item>
|
||||
<core-context-menu-item [hidden]="!groupMessagingEnabled || !conversationId || isGroup || !messages || !messages.length"
|
||||
[priority]="200" [content]="'addon.messages.deleteconversation' | translate" (action)="deleteConversation($event)"
|
||||
[closeOnClick]="false" [iconAction]="deleteIcon"></core-context-menu-item>
|
||||
|
|
|
@ -1263,13 +1263,6 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView
|
|||
return !nextMessage || nextMessage.useridfrom != message.useridfrom || !!nextMessage.showDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles delete state.
|
||||
*/
|
||||
toggleDelete(): void {
|
||||
this.showDelete = !this.showDelete;
|
||||
}
|
||||
|
||||
/**
|
||||
* View info. If it's an individual conversation, go to the user profile.
|
||||
* If it's a group conversation, view info about the group.
|
||||
|
|
|
@ -212,12 +212,12 @@
|
|||
</ion-item>
|
||||
|
||||
<!-- Button to start/continue. -->
|
||||
<ion-button *ngIf="buttonText && !showStatusSpinner" expand="block" (click)="attemptQuiz()" class="ion-margin">
|
||||
<ion-button *ngIf="buttonText && !showStatusSpinner" expand="block" (click)="attemptQuiz()" class="ion-margin ion-text-wrap">
|
||||
{{ buttonText | translate }}
|
||||
</ion-button>
|
||||
|
||||
<!-- Button to open in browser if it cannot be attempted in the app. -->
|
||||
<ion-button class="ion-margin" *ngIf="!buttonText && ((!hasSupportedQuestions && unsupportedQuestions.length) ||
|
||||
<ion-button class="ion-text-wrap ion-margin" *ngIf="!buttonText && ((!hasSupportedQuestions && unsupportedQuestions.length) ||
|
||||
unsupportedRules.length || behaviourSupported === false)" expand="block" [href]="externalUrl" core-link
|
||||
[showBrowserWarning]="false">
|
||||
{{ 'core.openinbrowser' | translate }}
|
||||
|
|
|
@ -118,7 +118,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy {
|
|||
try {
|
||||
await this.setStartTime(this.currentSco.id);
|
||||
} catch (error) {
|
||||
CoreDomUtils.instance.showErrorModalDefault(error, 'addon.mod_scorm.errorgetscorm', true);
|
||||
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_scorm.errorgetscorm', true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -198,7 +198,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy {
|
|||
try {
|
||||
AddonModScormHelper.convertAttemptToOffline(this.scorm, this.attempt);
|
||||
} catch (error) {
|
||||
CoreDomUtils.instance.showErrorModalDefault(error, 'core.error', true);
|
||||
CoreDomUtils.showErrorModalDefault(error, 'core.error', true);
|
||||
}
|
||||
|
||||
this.refreshToc();
|
||||
|
@ -303,7 +303,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy {
|
|||
this.userData = data;
|
||||
this.accessInfo = accessInfo;
|
||||
} catch (error) {
|
||||
CoreDomUtils.instance.showErrorModalDefault(error, 'addon.mod_scorm.errorgetscorm', true);
|
||||
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_scorm.errorgetscorm', true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -469,7 +469,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy {
|
|||
|
||||
await AddonModScorm.saveTracks(sco.id, this.attempt, tracks, this.scorm, true);
|
||||
} catch (error) {
|
||||
CoreDomUtils.instance.showErrorModalDefault(error, 'core.error', true);
|
||||
CoreDomUtils.showErrorModalDefault(error, 'core.error', true);
|
||||
}
|
||||
} finally {
|
||||
// Refresh TOC, some prerequisites might have changed.
|
||||
|
@ -510,7 +510,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy {
|
|||
|
||||
await this.fetchToc();
|
||||
} catch (error) {
|
||||
CoreDomUtils.instance.showErrorModalDefault(error, 'addon.mod_scorm.errorgetscorm', true);
|
||||
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_scorm.errorgetscorm', true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// 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 { Injectable } from '@angular/core';
|
||||
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { CoreSettingsHandler, CoreSettingsHandlerData } from '@features/settings/services/settings-delegate';
|
||||
|
||||
/**
|
||||
* Mange storage settings handler.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonStorageManagerSettingsHandlerService implements CoreSettingsHandler {
|
||||
|
||||
static readonly PAGE_NAME = 'storage';
|
||||
|
||||
name = 'AddonStorageManager';
|
||||
priority = 400;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async isEnabled(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
getDisplayData(): CoreSettingsHandlerData {
|
||||
return {
|
||||
icon: 'fas-archive',
|
||||
title: 'addon.storagemanager.managestorage',
|
||||
page: AddonStorageManagerSettingsHandlerService.PAGE_NAME,
|
||||
class: 'addon-storagemanager-settings-handler',
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const AddonStorageManagerSettingsHandler = makeSingleton(AddonStorageManagerSettingsHandlerService);
|
|
@ -17,7 +17,10 @@ import { Routes } from '@angular/router';
|
|||
import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate';
|
||||
import { CoreMainMenuRoutingModule } from '@features/mainmenu/mainmenu-routing.module';
|
||||
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
|
||||
import { CoreSitePreferencesRoutingModule } from '@features/settings/pages/site/site-routing';
|
||||
import { CoreSettingsDelegate } from '@features/settings/services/settings-delegate';
|
||||
import { AddonStorageManagerCourseMenuHandler } from './services/handlers/course-menu';
|
||||
import { AddonStorageManagerSettingsHandler } from './services/handlers/settings';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
|
@ -30,6 +33,7 @@ const routes: Routes = [
|
|||
imports: [
|
||||
CoreMainMenuTabRoutingModule.forChild(routes),
|
||||
CoreMainMenuRoutingModule.forChild({ children: routes }),
|
||||
CoreSitePreferencesRoutingModule.forChild(routes),
|
||||
],
|
||||
exports: [CoreMainMenuRoutingModule],
|
||||
providers: [
|
||||
|
@ -38,6 +42,7 @@ const routes: Routes = [
|
|||
multi: true,
|
||||
useValue: () => {
|
||||
CoreCourseOptionsDelegate.registerHandler(AddonStorageManagerCourseMenuHandler.instance);
|
||||
CoreSettingsDelegate.registerHandler(AddonStorageManagerSettingsHandler.instance);
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
@ -119,7 +119,7 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||
});
|
||||
CoreUtils.closeInAppBrowser();
|
||||
|
||||
} else if (CoreApp.instance.isAndroid()) {
|
||||
} else if (CoreApp.isAndroid()) {
|
||||
// Check if the URL has a custom URL scheme. In Android they need to be opened manually.
|
||||
const urlScheme = CoreUrlUtils.getUrlProtocol(url);
|
||||
if (urlScheme && urlScheme !== 'file' && urlScheme !== 'cdvfile') {
|
||||
|
|
|
@ -38,6 +38,7 @@ export class CoreContextMenuItemComponent implements OnInit, OnDestroy, OnChange
|
|||
@Input() iconDescription?: string; // Name of the icon to be shown on the left side of the item.
|
||||
@Input() iconAction?: string; // Name of the icon to show on the right side of the item. Represents the action to do on click.
|
||||
// If is "spinner" an spinner will be shown.
|
||||
// If is "toggle" a toggle switch will be shown.
|
||||
// If no icon or spinner is selected, no action or link will work.
|
||||
// If href but no iconAction is provided arrow-right will be used.
|
||||
@Input() iconSlash?: boolean; // Display a red slash over the icon.
|
||||
|
@ -52,8 +53,10 @@ export class CoreContextMenuItemComponent implements OnInit, OnDestroy, OnChange
|
|||
@Input() badgeA11yText?: string; // Description for the badge, if needed.
|
||||
@Input() hidden?: boolean; // Whether the item should be hidden.
|
||||
@Input() showBrowserWarning = true; // Whether to show a warning before opening browser (for links). Defaults to true.
|
||||
@Input() toggle = false; // Whether the toggle is on or off.
|
||||
@Output() action?: EventEmitter<() => void>; // Will emit an event when the item clicked.
|
||||
@Output() onClosed?: EventEmitter<() => void>; // Will emit an event when the popover is closed because the item was clicked.
|
||||
@Output() toggleChange = new EventEmitter<boolean>();// Will emit an event when toggle changes to enable 2-way data binding.
|
||||
|
||||
protected hasAction = false;
|
||||
protected destroyed = false;
|
||||
|
@ -87,6 +90,21 @@ export class CoreContextMenuItemComponent implements OnInit, OnDestroy, OnChange
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle changed.
|
||||
*
|
||||
* @param event Event.
|
||||
*/
|
||||
toggleChanged(event: Event): void {
|
||||
if (this.toggle === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.toggleChange.emit(this.toggle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component destroyed.
|
||||
*/
|
||||
|
|
|
@ -55,6 +55,12 @@ export class CoreContextMenuPopoverComponent {
|
|||
* @return Return true if success, false if error.
|
||||
*/
|
||||
itemClicked(event: Event, item: CoreContextMenuItemComponent): boolean {
|
||||
if (item.iconAction == 'toggle' && !event.defaultPrevented) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
item.toggle = !item.toggle;
|
||||
}
|
||||
|
||||
if (!!item.action && item.action.observers.length > 0) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
|
|
@ -11,11 +11,16 @@
|
|||
<ion-label>
|
||||
<p class="item-heading"><core-format-text [clean]="true" [text]="item.content" [filter]="false"></core-format-text></p>
|
||||
</ion-label>
|
||||
<ion-icon *ngIf="(item.href || item.action) && item.iconAction && item.iconAction != 'spinner'" [name]="item.iconAction"
|
||||
[class.icon-slash]="item.iconSlash" slot="end" aria-hidden="true">
|
||||
</ion-icon>
|
||||
<ion-spinner *ngIf="(item.href || item.action) && item.iconAction == 'spinner'" slot="end"
|
||||
[attr.aria-label]="'core.loading' | translate"></ion-spinner>
|
||||
<ng-container *ngIf="(item.href || item.action) && item.iconAction">
|
||||
<ion-icon *ngIf="item.iconAction != 'spinner' && item.iconAction != 'toggle'" [name]="item.iconAction"
|
||||
[class.icon-slash]="item.iconSlash" slot="end" aria-hidden="true">
|
||||
</ion-icon>
|
||||
<ion-spinner *ngIf="item.iconAction == 'spinner'" slot="end"
|
||||
[attr.aria-label]="'core.loading' | translate">
|
||||
</ion-spinner>
|
||||
<ion-toggle *ngIf="item.iconAction == 'toggle'" [(ngModel)]="item.toggle" (ionChange)="item.toggleChanged($event)" slot="end">
|
||||
</ion-toggle>
|
||||
</ng-container>
|
||||
<ion-badge class="{{item.badgeClass}}" slot="end" *ngIf="item.badge">
|
||||
<span [attr.ara-hidden]="!!item.badgeA11yText">{{item.badge}}</span>
|
||||
<span class="sr-only" *ngIf="item.badgeA11yText">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<core-navbar-buttons slot="end">
|
||||
<core-context-menu>
|
||||
<core-context-menu-item [hidden]="!displayEnableDownload" [priority]="2000" [iconAction]="downloadEnabledIcon"
|
||||
<core-context-menu-item [hidden]="!displayEnableDownload" [priority]="2000" iconAction="toggle" [(toggle)]="downloadEnabled"
|
||||
[content]="'core.settings.showdownloadoptions' | translate" (action)="toggleDownload()">
|
||||
</core-context-menu-item>
|
||||
<core-context-menu-item [hidden]="!downloadCourseEnabled" [priority]="1900"
|
||||
|
|
|
@ -18,7 +18,7 @@ import { IonContent, IonRefresher } from '@ionic/angular';
|
|||
import { CoreSites } from '@services/sites';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreCourses, CoreCourseAnyCourseData } from '@features/courses/services/courses';
|
||||
import { CoreCourses, CoreCourseAnyCourseData, CoreCoursesProvider } from '@features/courses/services/courses';
|
||||
import {
|
||||
CoreCourse,
|
||||
CoreCourseCompletionActivityStatus,
|
||||
|
@ -64,7 +64,6 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
|||
courseMenuHandlers: CoreCourseOptionsMenuHandlerToDisplay[] = [];
|
||||
dataLoaded = false;
|
||||
downloadEnabled = false;
|
||||
downloadEnabledIcon = 'far-square'; // Disabled by default.
|
||||
downloadCourseEnabled = false;
|
||||
moduleId?: number;
|
||||
displayEnableDownload = false;
|
||||
|
@ -79,14 +78,34 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
|||
protected formatOptions?: Record<string, unknown>;
|
||||
protected completionObserver?: CoreEventObserver;
|
||||
protected courseStatusObserver?: CoreEventObserver;
|
||||
protected siteUpdatedObserver?: CoreEventObserver;
|
||||
protected downloadEnabledObserver?: CoreEventObserver;
|
||||
protected syncObserver?: CoreEventObserver;
|
||||
protected isDestroyed = false;
|
||||
protected modulesHaveCompletion = false;
|
||||
protected isGuest?: boolean;
|
||||
protected debouncedUpdateCachedCompletion?: () => void; // Update the cached completion after a certain time.
|
||||
|
||||
constructor() {
|
||||
// Refresh the enabled flags if site is updated.
|
||||
this.siteUpdatedObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
|
||||
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
|
||||
|
||||
this.displayEnableDownload = !CoreSites.getRequiredCurrentSite().isOfflineDisabled() &&
|
||||
CoreCourseFormatDelegate.displayEnableDownload(this.course);
|
||||
|
||||
this.downloadEnabled = this.displayEnableDownload && this.downloadEnabled;
|
||||
|
||||
this.initListeners();
|
||||
}, CoreSites.getCurrentSiteId());
|
||||
|
||||
this.downloadEnabledObserver = CoreEvents.on(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED, (data) => {
|
||||
this.downloadEnabled = this.displayEnableDownload && data.enabled;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
* @inheritdoc
|
||||
*/
|
||||
async ngOnInit(): Promise<void> {
|
||||
|
||||
|
@ -104,10 +123,12 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
|||
this.moduleId = CoreNavigator.getRouteNumberParam('moduleId');
|
||||
this.isGuest = CoreNavigator.getRouteBooleanParam('isGuest');
|
||||
|
||||
this.displayEnableDownload = !CoreSites.getCurrentSite()?.isOfflineDisabled() &&
|
||||
this.displayEnableDownload = !CoreSites.getRequiredCurrentSite().isOfflineDisabled() &&
|
||||
CoreCourseFormatDelegate.displayEnableDownload(this.course);
|
||||
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
|
||||
|
||||
this.downloadEnabled = this.displayEnableDownload && CoreCourses.getCourseDownloadOptionsEnabled();
|
||||
|
||||
this.debouncedUpdateCachedCompletion = CoreUtils.debounce(() => {
|
||||
if (this.modulesHaveCompletion) {
|
||||
CoreUtils.ignoreErrors(CoreCourse.getSections(this.course.id, false, true));
|
||||
|
@ -138,7 +159,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
|||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async initListeners(): Promise<void> {
|
||||
if (this.downloadCourseEnabled) {
|
||||
if (this.downloadCourseEnabled && !this.courseStatusObserver) {
|
||||
// Listen for changes in course status.
|
||||
this.courseStatusObserver = CoreEvents.on(CoreEvents.COURSE_STATUS_CHANGED, (data) => {
|
||||
if (data.courseId == this.course.id || data.courseId == CoreCourseProvider.ALL_COURSES_CLEARED) {
|
||||
|
@ -153,26 +174,30 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
|||
return;
|
||||
}
|
||||
|
||||
this.completionObserver = CoreEvents.on(
|
||||
CoreEvents.COMPLETION_MODULE_VIEWED,
|
||||
(data) => {
|
||||
if (data && data.courseId == this.course.id) {
|
||||
this.refreshAfterCompletionChange(true);
|
||||
if (!this.completionObserver) {
|
||||
this.completionObserver = CoreEvents.on(
|
||||
CoreEvents.COMPLETION_MODULE_VIEWED,
|
||||
(data) => {
|
||||
if (data && data.courseId == this.course.id) {
|
||||
this.refreshAfterCompletionChange(true);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (!this.syncObserver) {
|
||||
this.syncObserver = CoreEvents.on(CoreCourseSyncProvider.AUTO_SYNCED, (data) => {
|
||||
if (!data || data.courseId != this.course.id) {
|
||||
return;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
this.syncObserver = CoreEvents.on(CoreCourseSyncProvider.AUTO_SYNCED, (data) => {
|
||||
if (!data || data.courseId != this.course.id) {
|
||||
return;
|
||||
}
|
||||
this.refreshAfterCompletionChange(false);
|
||||
|
||||
this.refreshAfterCompletionChange(false);
|
||||
|
||||
if (data.warnings && data.warnings[0]) {
|
||||
CoreDomUtils.showErrorModal(data.warnings[0]);
|
||||
}
|
||||
});
|
||||
if (data.warnings && data.warnings[0]) {
|
||||
CoreDomUtils.showErrorModal(data.warnings[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -471,8 +496,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
|||
* Toggle download enabled.
|
||||
*/
|
||||
toggleDownload(): void {
|
||||
this.downloadEnabled = !this.downloadEnabled;
|
||||
this.downloadEnabledIcon = this.downloadEnabled ? 'far-check-square' : 'far-square';
|
||||
CoreCourses.setCourseDownloadOptionsEnabled(this.downloadEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -517,6 +541,8 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
|||
this.completionObserver?.off();
|
||||
this.courseStatusObserver?.off();
|
||||
this.syncObserver?.off();
|
||||
this.siteUpdatedObserver?.off();
|
||||
this.downloadEnabledObserver?.off();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,6 +6,10 @@
|
|||
<img [src]="course.courseImage" core-external-content alt=""/>
|
||||
</ion-avatar>
|
||||
<ion-label>
|
||||
<h2>
|
||||
<core-format-text [text]="course.displayname || course.fullname" contextLevel="course" [contextInstanceId]="course.id">
|
||||
</core-format-text>
|
||||
</h2>
|
||||
<p *ngIf="course.categoryname || (course.displayname && course.shortname && course.fullname != course.displayname)"
|
||||
class="core-course-additional-info">
|
||||
<span *ngIf="course.categoryname" class="core-course-category">
|
||||
|
@ -19,10 +23,6 @@
|
|||
</core-format-text>
|
||||
</span>
|
||||
</p>
|
||||
<h2>
|
||||
<core-format-text [text]="course.displayname || course.fullname" contextLevel="course" [contextInstanceId]="course.id">
|
||||
</core-format-text>
|
||||
</h2>
|
||||
<p *ngIf="isEnrolled && course.progress! >= 0 && course.completionusertracked !== false">
|
||||
<core-progress-bar [progress]="course.progress" a11yText="core.courses.aria:courseprogress"></core-progress-bar>
|
||||
</p>
|
||||
|
@ -34,4 +34,14 @@
|
|||
slot="end">
|
||||
</ion-icon>
|
||||
</ng-container>
|
||||
|
||||
<div class="core-button-spinner" *ngIf="isEnrolled && showDownload" slot="end">
|
||||
<core-download-refresh
|
||||
[status]="prefetchCourseData.status"
|
||||
[statusTranslatable]="prefetchCourseData.statusTranslatable"
|
||||
[enabled]="true"
|
||||
canTrustDownload="false"
|
||||
[loading]="prefetchCourseData.loading"
|
||||
(action)="prefetchCourse()"></core-download-refresh>
|
||||
</div>
|
||||
</ion-item>
|
||||
|
|
|
@ -12,11 +12,15 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { CoreCourseHelper } from '@features/course/services/course-helper';
|
||||
import { Component, Input, OnChanges, OnDestroy, OnInit } from '@angular/core';
|
||||
import { CoreCourseProvider, CoreCourse } from '@features/course/services/course';
|
||||
import { CoreCourseHelper, CorePrefetchStatusInfo } from '@features/course/services/course-helper';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreCourses, CoreCourseSearchedData } from '../../services/courses';
|
||||
import { CoreCoursesHelper, CoreCourseWithImageAndColor } from '../../services/courses-helper';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreEventCourseStatusChanged, CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||
import { CoreCourseListItem, CoreCourses } from '../../services/courses';
|
||||
import { CoreCoursesHelper } from '../../services/courses-helper';
|
||||
|
||||
/**
|
||||
* This directive is meant to display an item for a list of courses.
|
||||
|
@ -30,31 +34,49 @@ import { CoreCoursesHelper, CoreCourseWithImageAndColor } from '../../services/c
|
|||
templateUrl: 'core-courses-course-list-item.html',
|
||||
styleUrls: ['course-list-item.scss'],
|
||||
})
|
||||
export class CoreCoursesCourseListItemComponent implements OnInit {
|
||||
export class CoreCoursesCourseListItemComponent implements OnInit, OnDestroy, OnChanges {
|
||||
|
||||
@Input() course!: CoreCourseSearchedData & CoreCourseWithImageAndColor & {
|
||||
completionusertracked?: boolean; // If the user is completion tracked.
|
||||
progress?: number | null; // Progress percentage.
|
||||
}; // The course to render.
|
||||
@Input() course!: CoreCourseListItem; // The course to render.
|
||||
|
||||
@Input() showDownload = false; // If true, will show download button.
|
||||
|
||||
icons: CoreCoursesEnrolmentIcons[] = [];
|
||||
isEnrolled = false;
|
||||
prefetchCourseData: CorePrefetchStatusInfo = {
|
||||
icon: '',
|
||||
statusTranslatable: 'core.loading',
|
||||
status: '',
|
||||
loading: true,
|
||||
};
|
||||
|
||||
protected courseStatusObserver?: CoreEventObserver;
|
||||
protected isDestroyed = false;
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
* @inheritdoc
|
||||
*/
|
||||
async ngOnInit(): Promise<void> {
|
||||
CoreCoursesHelper.loadCourseColorAndImage(this.course);
|
||||
|
||||
// Check if the user is enrolled in the course.
|
||||
try {
|
||||
const course = await CoreCourses.getUserCourse(this.course.id);
|
||||
this.course.progress = course.progress;
|
||||
this.course.completionusertracked = course.completionusertracked;
|
||||
this.isEnrolled = this.course.progress !== undefined;
|
||||
|
||||
this.isEnrolled = true;
|
||||
} catch {
|
||||
this.isEnrolled = false;
|
||||
if (!this.isEnrolled) {
|
||||
try {
|
||||
const course = await CoreCourses.getUserCourse(this.course.id);
|
||||
this.course.progress = course.progress;
|
||||
this.course.completionusertracked = course.completionusertracked;
|
||||
|
||||
this.isEnrolled = true;
|
||||
|
||||
if (this.showDownload) {
|
||||
this.initPrefetchCourse();
|
||||
}
|
||||
} catch {
|
||||
this.isEnrolled = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.isEnrolled) {
|
||||
this.icons = [];
|
||||
|
||||
this.course.enrollmentmethods.forEach((instance) => {
|
||||
|
@ -85,6 +107,15 @@ export class CoreCoursesCourseListItemComponent implements OnInit {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnChanges(): void {
|
||||
if (this.showDownload && this.isEnrolled) {
|
||||
this.initPrefetchCourse();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a course.
|
||||
*
|
||||
|
@ -101,6 +132,85 @@ export class CoreCoursesCourseListItemComponent implements OnInit {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize prefetch course.
|
||||
*/
|
||||
async initPrefetchCourse(): Promise<void> {
|
||||
if (this.courseStatusObserver !== undefined) {
|
||||
// Already initialized.
|
||||
return;
|
||||
}
|
||||
|
||||
// Listen for status change in course.
|
||||
this.courseStatusObserver = CoreEvents.on(CoreEvents.COURSE_STATUS_CHANGED, (data: CoreEventCourseStatusChanged) => {
|
||||
if (data.courseId == this.course.id || data.courseId == CoreCourseProvider.ALL_COURSES_CLEARED) {
|
||||
this.updateCourseStatus(data.status);
|
||||
}
|
||||
}, CoreSites.getCurrentSiteId());
|
||||
|
||||
// Determine course prefetch icon.
|
||||
const status = await CoreCourse.getCourseStatus(this.course.id);
|
||||
|
||||
this.updateCourseStatus(status);
|
||||
|
||||
if (this.prefetchCourseData.loading) {
|
||||
// Course is being downloaded. Get the download promise.
|
||||
const promise = CoreCourseHelper.getCourseDownloadPromise(this.course.id);
|
||||
if (promise) {
|
||||
// There is a download promise. If it fails, show an error.
|
||||
promise.catch((error) => {
|
||||
if (!this.isDestroyed) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// No download, this probably means that the app was closed while downloading. Set previous status.
|
||||
CoreCourse.setCoursePreviousStatus(this.course.id);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the course status icon and title.
|
||||
*
|
||||
* @param status Status to show.
|
||||
*/
|
||||
protected updateCourseStatus(status: string): void {
|
||||
const statusData = CoreCourseHelper.getCoursePrefetchStatusInfo(status);
|
||||
|
||||
this.prefetchCourseData.status = statusData.status;
|
||||
this.prefetchCourseData.icon = statusData.icon;
|
||||
this.prefetchCourseData.statusTranslatable = statusData.statusTranslatable;
|
||||
this.prefetchCourseData.loading = statusData.loading;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefetch the course.
|
||||
*
|
||||
* @param e Click event.
|
||||
*/
|
||||
async prefetchCourse(e?: Event): Promise<void> {
|
||||
e?.preventDefault();
|
||||
e?.stopPropagation();
|
||||
|
||||
try {
|
||||
await CoreCourseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course);
|
||||
} catch (error) {
|
||||
if (!this.isDestroyed) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.isDestroyed = true;
|
||||
this.courseStatusObserver?.off();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -48,8 +48,6 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy, On
|
|||
@Input() showAll = false; // If true, will show all actions, options, star and progress.
|
||||
@Input() showDownload = true; // If true, will show download button. Only works if the options menu is not shown.
|
||||
|
||||
courseStatus = CoreConstants.NOT_DOWNLOADED;
|
||||
isDownloading = false;
|
||||
prefetchCourseData: CorePrefetchStatusInfo = {
|
||||
icon: '',
|
||||
statusTranslatable: 'core.loading',
|
||||
|
@ -64,6 +62,7 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy, On
|
|||
progress = -1;
|
||||
completionUserTracked: boolean | undefined = false;
|
||||
|
||||
protected courseStatus = CoreConstants.NOT_DOWNLOADED;
|
||||
protected isDestroyed = false;
|
||||
protected courseStatusObserver?: CoreEventObserver;
|
||||
protected siteUpdatedObserver?: CoreEventObserver;
|
||||
|
@ -109,7 +108,7 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy, On
|
|||
* Initialize prefetch course.
|
||||
*/
|
||||
async initPrefetchCourse(): Promise<void> {
|
||||
if (typeof this.courseStatusObserver != 'undefined') {
|
||||
if (this.courseStatusObserver !== undefined) {
|
||||
// Already initialized.
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import { RouterModule, Routes } from '@angular/router';
|
|||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'my',
|
||||
redirectTo: 'list',
|
||||
pathMatch: 'full',
|
||||
},
|
||||
{
|
||||
|
@ -33,22 +33,10 @@ const routes: Routes = [
|
|||
.then(m => m.CoreCoursesCategoriesPageModule),
|
||||
},
|
||||
{
|
||||
path: 'all',
|
||||
path: 'list',
|
||||
loadChildren: () =>
|
||||
import('./pages/available-courses/available-courses.module')
|
||||
.then(m => m.CoreCoursesAvailableCoursesPageModule),
|
||||
},
|
||||
{
|
||||
path: 'search',
|
||||
loadChildren: () =>
|
||||
import('./pages/search/search.module')
|
||||
.then(m => m.CoreCoursesSearchPageModule),
|
||||
},
|
||||
{
|
||||
path: 'my',
|
||||
loadChildren: () =>
|
||||
import('./pages/my-courses/my-courses.module')
|
||||
.then(m => m.CoreCoursesMyCoursesPageModule),
|
||||
import('./pages/list/list.module')
|
||||
.then(m => m.CoreCoursesListPageModule),
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -15,9 +15,12 @@
|
|||
import { APP_INITIALIZER, NgModule, Type } from '@angular/core';
|
||||
import { Routes } from '@angular/router';
|
||||
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
|
||||
import { CoreMainMenuRoutingModule } from '@features/mainmenu/mainmenu-routing.module';
|
||||
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
|
||||
|
||||
import { CoreMainMenuHomeRoutingModule } from '@features/mainmenu/pages/home/home-routing.module';
|
||||
import { CoreMainMenuHomeDelegate } from '@features/mainmenu/services/home-delegate';
|
||||
import { CoreMainMenuDelegate } from '@features/mainmenu/services/mainmenu-delegate';
|
||||
import { CorePushNotificationsDelegate } from '@features/pushnotifications/services/push-delegate';
|
||||
import { CoreCoursesProvider } from './services/courses';
|
||||
import { CoreCoursesHelperProvider } from './services/courses-helper';
|
||||
|
@ -28,7 +31,10 @@ import { CoreCoursesIndexLinkHandler } from './services/handlers/courses-index-l
|
|||
import { CoreDashboardHomeHandler, CoreDashboardHomeHandlerService } from './services/handlers/dashboard-home';
|
||||
import { CoreCoursesDashboardLinkHandler } from './services/handlers/dashboard-link';
|
||||
import { CoreCoursesEnrolPushClickHandler } from './services/handlers/enrol-push-click';
|
||||
import { CoreCoursesMyCoursesHomeHandler, CoreCoursesMyCoursesHomeHandlerService } from './services/handlers/my-courses-home';
|
||||
import {
|
||||
CoreCoursesMyCoursesHomeHandler,
|
||||
CoreCoursesMyCoursesMainMenuHandlerService,
|
||||
} from './services/handlers/my-courses-mainmenu';
|
||||
import { CoreCoursesRequestPushClickHandler } from './services/handlers/request-push-click';
|
||||
|
||||
export const CORE_COURSES_SERVICES: Type<unknown>[] = [
|
||||
|
@ -42,10 +48,6 @@ const mainMenuHomeChildrenRoutes: Routes = [
|
|||
path: CoreDashboardHomeHandlerService.PAGE_NAME,
|
||||
loadChildren: () => import('./pages/dashboard/dashboard.module').then(m => m.CoreCoursesDashboardPageModule),
|
||||
},
|
||||
{
|
||||
path: CoreCoursesMyCoursesHomeHandlerService.PAGE_NAME,
|
||||
loadChildren: () => import('./pages/my-courses/my-courses.module').then(m => m.CoreCoursesMyCoursesPageModule),
|
||||
},
|
||||
];
|
||||
|
||||
const mainMenuHomeSiblingRoutes: Routes = [
|
||||
|
@ -55,20 +57,30 @@ const mainMenuHomeSiblingRoutes: Routes = [
|
|||
},
|
||||
];
|
||||
|
||||
const mainMenuTabRoutes: Routes = [
|
||||
{
|
||||
path: CoreCoursesMyCoursesMainMenuHandlerService.PAGE_NAME,
|
||||
loadChildren: () => import('./pages/list/list.module').then(m => m.CoreCoursesListPageModule),
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CoreMainMenuHomeRoutingModule.forChild({
|
||||
children: mainMenuHomeChildrenRoutes,
|
||||
siblings: mainMenuHomeSiblingRoutes,
|
||||
}),
|
||||
CoreMainMenuRoutingModule.forChild({ children: mainMenuTabRoutes }),
|
||||
CoreMainMenuTabRoutingModule.forChild(mainMenuTabRoutes),
|
||||
],
|
||||
exports: [CoreMainMenuRoutingModule],
|
||||
providers: [
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
multi: true,
|
||||
useValue: () => {
|
||||
CoreMainMenuHomeDelegate.registerHandler(CoreDashboardHomeHandler.instance);
|
||||
CoreMainMenuHomeDelegate.registerHandler(CoreCoursesMyCoursesHomeHandler.instance);
|
||||
CoreMainMenuDelegate.registerHandler(CoreCoursesMyCoursesHomeHandler.instance);
|
||||
CoreContentLinksDelegate.registerHandler(CoreCoursesCourseLinkHandler.instance);
|
||||
CoreContentLinksDelegate.registerHandler(CoreCoursesIndexLinkHandler.instance);
|
||||
CoreContentLinksDelegate.registerHandler(CoreCoursesDashboardLinkHandler.instance);
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
"selfenrolment": "Self enrolment",
|
||||
"sendpaymentbutton": "Send payment via PayPal",
|
||||
"show": "Restore to view",
|
||||
"showonlyenrolled": "Show only my courses",
|
||||
"therearecourses": "There are {{$a}} courses",
|
||||
"totalcoursesearchresults": "Total courses: {{$a}}"
|
||||
}
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button [text]="'core.back' | translate"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<h1>{{ 'core.courses.availablecourses' | translate }}</h1>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-refresher slot="fixed" [disabled]="!coursesLoaded" (ionRefresh)="refreshCourses($event.target)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="coursesLoaded">
|
||||
<ng-container *ngIf="courses.length > 0">
|
||||
<core-courses-course-list-item *ngFor="let course of courses" [course]="course"></core-courses-course-list-item>
|
||||
</ng-container>
|
||||
<core-empty-box *ngIf="!courses.length" icon="fas-graduation-cap" [message]="'core.courses.nocourses' | translate"></core-empty-box>
|
||||
</core-loading>
|
||||
</ion-content>
|
|
@ -1,41 +0,0 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// 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 { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { CoreSharedModule } from '@/core/shared.module';
|
||||
import { CoreCoursesComponentsModule } from '../../components/components.module';
|
||||
|
||||
import { CoreCoursesAvailableCoursesPage } from './available-courses';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: CoreCoursesAvailableCoursesPage,
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes),
|
||||
CoreSharedModule,
|
||||
CoreCoursesComponentsModule,
|
||||
],
|
||||
declarations: [
|
||||
CoreCoursesAvailableCoursesPage,
|
||||
],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class CoreCoursesAvailableCoursesPageModule { }
|
|
@ -1,78 +0,0 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// 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 } from '@angular/core';
|
||||
import { IonRefresher } from '@ionic/angular';
|
||||
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreCourses, CoreCourseSearchedData } from '../../services/courses';
|
||||
|
||||
/**
|
||||
* Page that displays available courses in current site.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'page-core-courses-available-courses',
|
||||
templateUrl: 'available-courses.html',
|
||||
})
|
||||
export class CoreCoursesAvailableCoursesPage implements OnInit {
|
||||
|
||||
courses: CoreCourseSearchedData[] = [];
|
||||
coursesLoaded = false;
|
||||
|
||||
/**
|
||||
* View loaded.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.loadCourses().finally(() => {
|
||||
this.coursesLoaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the courses.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async loadCourses(): Promise<void> {
|
||||
const frontpageCourseId = CoreSites.getCurrentSiteHomeId();
|
||||
|
||||
try {
|
||||
const courses = await CoreCourses.getCoursesByField();
|
||||
|
||||
this.courses = courses.filter((course) => course.id != frontpageCourseId);
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcourses', true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the courses.
|
||||
*
|
||||
* @param refresher Refresher.
|
||||
*/
|
||||
refreshCourses(refresher: IonRefresher): void {
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
promises.push(CoreCourses.invalidateUserCourses());
|
||||
promises.push(CoreCourses.invalidateCoursesByField());
|
||||
|
||||
Promise.all(promises).finally(() => {
|
||||
this.loadCourses().finally(() => {
|
||||
refresher?.complete();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -7,6 +7,16 @@
|
|||
<core-format-text [text]="title" contextLevel="coursecat" [contextInstanceId]="currentCategory && currentCategory!.id">
|
||||
</core-format-text>
|
||||
</h1>
|
||||
<ion-buttons slot="end">
|
||||
<core-context-menu>
|
||||
<core-context-menu-item *ngIf="downloadCourseEnabled || downloadCoursesEnabled" [priority]="1000"
|
||||
[content]="'core.settings.showdownloadoptions' | translate" (action)="toggleDownload()"
|
||||
iconAction="toggle" [(toggle)]="downloadEnabled"></core-context-menu-item>
|
||||
<core-context-menu-item [priority]="900"
|
||||
[content]="'core.courses.showonlyenrolled' | translate" (action)="filterEnrolled()"
|
||||
iconAction="toggle" [(toggle)]="showOnlyEnrolled"></core-context-menu-item>
|
||||
</core-context-menu>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
|
@ -17,22 +27,18 @@
|
|||
<ion-item *ngIf="currentCategory" class="ion-text-wrap">
|
||||
<ion-icon name="fas-folder" slot="start" [attr.aria-label]="'core.category' | translate"></ion-icon>
|
||||
<ion-label>
|
||||
<h2>
|
||||
<core-format-text [text]="currentCategory!.name" contextLevel="coursecat"
|
||||
[contextInstanceId]="currentCategory!.id"></core-format-text>
|
||||
</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngIf="currentCategory && currentCategory!.description">
|
||||
<ion-label>
|
||||
<h2>
|
||||
<core-format-text [text]="currentCategory!.description" maxHeight="60" contextLevel="coursecat"
|
||||
[contextInstanceId]="currentCategory!.id"></core-format-text>
|
||||
</h2>
|
||||
<p class="item-heading">
|
||||
<core-format-text [text]="currentCategory.name" contextLevel="coursecat"
|
||||
[contextInstanceId]="currentCategory.id"></core-format-text>
|
||||
</p>
|
||||
<p *ngIf="currentCategory.description">
|
||||
<core-format-text [text]="currentCategory.description" maxHeight="60" contextLevel="coursecat"
|
||||
[contextInstanceId]="currentCategory.id"></core-format-text>
|
||||
</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<div *ngIf="categories.length > 0">
|
||||
<ng-container *ngIf="categories.length > 0">
|
||||
<ion-item-divider>
|
||||
<ion-label>
|
||||
<h2>{{ 'core.courses.categories' | translate }}</h2>
|
||||
|
@ -48,22 +54,24 @@
|
|||
</core-format-text>
|
||||
</h2>
|
||||
</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 class="sr-only">{{ 'core.courses.therearecourses' | translate:{ $a: category.coursecount } }}</span>
|
||||
</ion-badge>
|
||||
</ion-item>
|
||||
</section>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div *ngIf="courses.length > 0">
|
||||
<ng-container *ngIf="courses.length > 0">
|
||||
<ion-item-divider>
|
||||
<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-item-divider>
|
||||
<core-courses-course-list-item *ngFor="let course of courses" [course]="course"></core-courses-course-list-item>
|
||||
</div>
|
||||
<core-courses-course-list-item *ngFor="let course of courses" [course]="course" [showDownload]="downloadEnabled">
|
||||
</core-courses-course-list-item>
|
||||
</ng-container>
|
||||
<core-empty-box *ngIf="!categories.length && !courses.length" icon="fas-graduation-cap"
|
||||
[message]="'core.courses.nocoursesyet' | translate">
|
||||
</core-empty-box>
|
||||
|
|
|
@ -12,14 +12,15 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { IonRefresher } from '@ionic/angular';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
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 { 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.
|
||||
|
@ -28,25 +29,69 @@ import { CoreNavigator } from '@services/navigator';
|
|||
selector: 'page-core-courses-categories',
|
||||
templateUrl: 'categories.html',
|
||||
})
|
||||
export class CoreCoursesCategoriesPage implements OnInit {
|
||||
export class CoreCoursesCategoriesPage implements OnInit, OnDestroy {
|
||||
|
||||
title: string;
|
||||
currentCategory?: CoreCategoryData;
|
||||
categories: CoreCategoryData[] = [];
|
||||
courses: CoreCourseSearchedData[] = [];
|
||||
courses: CoreCourseListItem[] = [];
|
||||
categoriesLoaded = false;
|
||||
|
||||
showOnlyEnrolled = false;
|
||||
|
||||
downloadEnabled = false;
|
||||
downloadCourseEnabled = false;
|
||||
downloadCoursesEnabled = false;
|
||||
|
||||
protected categoryCourses: CoreCourseListItem[] = [];
|
||||
protected currentSiteId: string;
|
||||
protected categoryId = 0;
|
||||
protected myCoursesObserver: CoreEventObserver;
|
||||
protected siteUpdatedObserver: CoreEventObserver;
|
||||
protected downloadEnabledObserver: CoreEventObserver;
|
||||
protected isDestroyed = false;
|
||||
|
||||
constructor() {
|
||||
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.downloadEnabled = (this.downloadCourseEnabled || this.downloadCoursesEnabled) && this.downloadEnabled;
|
||||
}, this.currentSiteId);
|
||||
|
||||
this.downloadEnabledObserver = CoreEvents.on(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED, (data) => {
|
||||
this.downloadEnabled = (this.downloadCourseEnabled || this.downloadCoursesEnabled) && data.enabled;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* View loaded.
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.categoryId = CoreNavigator.getRouteNumberParam('id') || 0;
|
||||
this.showOnlyEnrolled = CoreNavigator.getRouteBooleanParam('enrolled') || this.showOnlyEnrolled;
|
||||
|
||||
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
|
||||
this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
|
||||
|
||||
this.downloadEnabled =
|
||||
(this.downloadCourseEnabled || this.downloadCoursesEnabled) && CoreCourses.getCourseDownloadOptionsEnabled();
|
||||
|
||||
this.fetchCategories().finally(() => {
|
||||
this.categoriesLoaded = true;
|
||||
|
@ -87,13 +132,14 @@ export class CoreCoursesCategoriesPage implements OnInit {
|
|||
this.title = this.currentCategory.name;
|
||||
|
||||
try {
|
||||
this.courses = await CoreCourses.getCoursesByField('category', this.categoryId);
|
||||
this.categoryCourses = await CoreCourses.getCoursesByField('category', this.categoryId);
|
||||
await this.filterEnrolled();
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcourses', true);
|
||||
!this.isDestroyed && CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcourses', true);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcategories', true);
|
||||
!this.isDestroyed && CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcategories', true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,7 +154,7 @@ export class CoreCoursesCategoriesPage implements OnInit {
|
|||
promises.push(CoreCourses.invalidateUserCourses());
|
||||
promises.push(CoreCourses.invalidateCategories(this.categoryId, true));
|
||||
promises.push(CoreCourses.invalidateCoursesByField('category', this.categoryId));
|
||||
promises.push(CoreSites.getCurrentSite()!.invalidateConfig());
|
||||
promises.push(CoreSites.getRequiredCurrentSite().invalidateConfig());
|
||||
|
||||
Promise.all(promises).finally(() => {
|
||||
this.fetchCategories().finally(() => {
|
||||
|
@ -123,7 +169,53 @@ export class CoreCoursesCategoriesPage implements OnInit {
|
|||
* @param categoryId Category Id.
|
||||
*/
|
||||
openCategory(categoryId: number): void {
|
||||
CoreNavigator.navigateToSitePath('courses/categories/' + categoryId);
|
||||
CoreNavigator.navigateToSitePath(
|
||||
'courses/categories/' + categoryId,
|
||||
{ params: {
|
||||
enrolled: this.showOnlyEnrolled,
|
||||
} },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter my courses or not.
|
||||
*/
|
||||
async filterEnrolled(): Promise<void> {
|
||||
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(): void {
|
||||
CoreCourses.setCourseDownloadOptionsEnabled(this.downloadEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.myCoursesObserver.off();
|
||||
this.siteUpdatedObserver.off();
|
||||
this.downloadEnabledObserver.off();
|
||||
this.isDestroyed = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
</ion-button>
|
||||
<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>
|
||||
<core-context-menu-item *ngIf="(downloadCourseEnabled || downloadCoursesEnabled)" [priority]="500"
|
||||
[content]="'addon.storagemanager.managestorage' | translate"
|
||||
(action)="manageCoursesStorage()" iconAction="fas-archive"></core-context-menu-item>
|
||||
[content]="'core.settings.showdownloadoptions' | translate" (action)="switchDownload()"
|
||||
iconAction="toggle" [(toggle)]="downloadEnabled"></core-context-menu-item>
|
||||
<core-context-menu-item [priority]="500"
|
||||
[content]="'addon.storagemanager.managestorage' | translate"
|
||||
(action)="manageCoursesStorage()" iconAction="fas-archive"></core-context-menu-item>
|
||||
</core-context-menu>
|
||||
</core-navbar-buttons>
|
||||
<ion-content>
|
||||
|
|
|
@ -40,30 +40,39 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy {
|
|||
downloadEnabled = false;
|
||||
downloadCourseEnabled = false;
|
||||
downloadCoursesEnabled = false;
|
||||
downloadEnabledIcon = 'far-square';
|
||||
userId?: number;
|
||||
blocks: Partial<CoreCourseBlock>[] = [];
|
||||
loaded = false;
|
||||
|
||||
protected updateSiteObserver?: CoreEventObserver;
|
||||
|
||||
/**
|
||||
* Initialize the component.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite();
|
||||
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
|
||||
this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
|
||||
protected updateSiteObserver: CoreEventObserver;
|
||||
protected downloadEnabledObserver: CoreEventObserver;
|
||||
|
||||
constructor() {
|
||||
// Refresh the enabled flags if site is updated.
|
||||
this.updateSiteObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
|
||||
this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite();
|
||||
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
|
||||
this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
|
||||
|
||||
this.switchDownload(this.downloadEnabled && this.downloadCourseEnabled && this.downloadCoursesEnabled);
|
||||
this.downloadEnabled = (this.downloadCourseEnabled || this.downloadCoursesEnabled) && this.downloadEnabled;
|
||||
}, CoreSites.getCurrentSiteId());
|
||||
|
||||
this.downloadEnabledObserver = CoreEvents.on(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED, (data) => {
|
||||
this.downloadEnabled = (this.downloadCourseEnabled || this.downloadCoursesEnabled) && data.enabled;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite();
|
||||
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
|
||||
this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
|
||||
|
||||
this.downloadEnabled =
|
||||
(this.downloadCourseEnabled || this.downloadCoursesEnabled) && CoreCourses.getCourseDownloadOptionsEnabled();
|
||||
|
||||
this.loadContent();
|
||||
}
|
||||
|
||||
|
@ -139,21 +148,10 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
/**
|
||||
* Toggle download enabled.
|
||||
* Switch download enabled.
|
||||
*/
|
||||
toggleDownload(): void {
|
||||
this.switchDownload(!this.downloadEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to switch download enabled.
|
||||
*
|
||||
* @param enable If enable or disable.
|
||||
*/
|
||||
protected switchDownload(enable: boolean): void {
|
||||
this.downloadEnabled = (this.downloadCourseEnabled || this.downloadCoursesEnabled) && enable;
|
||||
this.downloadEnabledIcon = this.downloadEnabled ? 'far-check-square' : 'far-square';
|
||||
CoreEvents.trigger(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED, { enabled: this.downloadEnabled });
|
||||
switchDownload(): void {
|
||||
CoreCourses.setCourseDownloadOptionsEnabled(this.downloadEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -167,14 +165,15 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy {
|
|||
* Go to search courses.
|
||||
*/
|
||||
async openSearch(): Promise<void> {
|
||||
CoreNavigator.navigateToSitePath('/courses/search');
|
||||
CoreNavigator.navigateToSitePath('/courses/list', { params : { mode: 'search' } });
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.updateSiteObserver?.off();
|
||||
this.updateSiteObserver.off();
|
||||
this.downloadEnabledObserver.off();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button [text]="'core.back' | translate"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<h1 *ngIf="!showOnlyEnrolled">{{ 'core.courses.availablecourses' | translate }}</h1>
|
||||
<h1 *ngIf="showOnlyEnrolled">{{ 'core.courses.mycourses' | translate }}</h1>
|
||||
<ion-buttons slot="end"></ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<core-navbar-buttons slot="end">
|
||||
<core-context-menu>
|
||||
<core-context-menu-item *ngIf="downloadCourseEnabled || downloadCoursesEnabled" [priority]="1000"
|
||||
[content]="'core.settings.showdownloadoptions' | translate" (action)="toggleDownload()"
|
||||
iconAction="toggle" [(toggle)]="downloadEnabled"></core-context-menu-item>
|
||||
<core-context-menu-item [priority]="900"
|
||||
[content]="'core.courses.showonlyenrolled' | translate" (action)="toggleEnrolled()"
|
||||
iconAction="toggle" [(toggle)]="showOnlyEnrolled"></core-context-menu-item>
|
||||
</core-context-menu>
|
||||
</core-navbar-buttons>
|
||||
<ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshCourses($event.target)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
|
||||
<core-search-box *ngIf="searchEnabled" (onSubmit)="search($event)" (onClear)="clearSearch()"
|
||||
[placeholder]="'core.courses.search' | translate" [searchLabel]="'core.courses.search' | translate" [autoFocus]="searchMode"
|
||||
searchArea="CoreCoursesSearch"></core-search-box>
|
||||
|
||||
<core-loading [hideUntil]="loaded">
|
||||
<ng-container *ngIf="searchMode && searchTotal > 0">
|
||||
<ion-item-divider>
|
||||
<ion-label><h2>{{ 'core.courses.totalcoursesearchresults' | translate:{$a: searchTotal} }}</h2></ion-label>
|
||||
</ion-item-divider>
|
||||
</ng-container>
|
||||
|
||||
<core-courses-course-list-item *ngFor="let course of courses" [course]="course" [showDownload]="downloadEnabled">
|
||||
</core-courses-course-list-item>
|
||||
|
||||
<core-infinite-loading [enabled]="canLoadMore" (action)="loadMoreCourses($event)" [error]="loadMoreError">
|
||||
</core-infinite-loading>
|
||||
|
||||
|
||||
<core-empty-box *ngIf="searchMode && !courses.length" icon="fas-search" [message]="'core.courses.nosearchresults' | translate">
|
||||
</core-empty-box>
|
||||
|
||||
<core-empty-box *ngIf="!searchMode && !courses.length" icon="fas-graduation-cap" [message]="'core.courses.nocourses' | translate">
|
||||
</core-empty-box>
|
||||
|
||||
</core-loading>
|
||||
</ion-content>
|
|
@ -19,12 +19,12 @@ import { CoreSharedModule } from '@/core/shared.module';
|
|||
import { CoreCoursesComponentsModule } from '../../components/components.module';
|
||||
import { CoreSearchComponentsModule } from '@features/search/components/components.module';
|
||||
|
||||
import { CoreCoursesSearchPage } from './search';
|
||||
import { CoreCoursesListPage } from './list';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: CoreCoursesSearchPage,
|
||||
component: CoreCoursesListPage,
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -36,8 +36,8 @@ const routes: Routes = [
|
|||
CoreSearchComponentsModule,
|
||||
],
|
||||
declarations: [
|
||||
CoreCoursesSearchPage,
|
||||
CoreCoursesListPage,
|
||||
],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class CoreCoursesSearchPageModule { }
|
||||
export class CoreCoursesListPageModule { }
|
|
@ -0,0 +1,313 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// 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, OnDestroy, OnInit } from '@angular/core';
|
||||
import { CoreCoursesHelper, CoreEnrolledCourseDataWithExtraInfo } from '@features/courses/services/courses-helper';
|
||||
import { IonRefresher } from '@ionic/angular';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||
import { CoreCourseBasicSearchedData, CoreCourses, CoreCoursesProvider } from '../../services/courses';
|
||||
|
||||
type CoreCoursesListMode = 'search' | 'all' | 'my';
|
||||
|
||||
/**
|
||||
* Page that shows a list of courses.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'page-core-courses-list',
|
||||
templateUrl: 'list.html',
|
||||
})
|
||||
export class CoreCoursesListPage implements OnInit, OnDestroy {
|
||||
|
||||
downloadAllCoursesEnabled = false;
|
||||
|
||||
searchEnabled = false;
|
||||
searchMode = false;
|
||||
searchTotal = 0;
|
||||
|
||||
downloadEnabled = false;
|
||||
downloadCourseEnabled = false;
|
||||
downloadCoursesEnabled = false;
|
||||
|
||||
courses: (CoreCourseBasicSearchedData|CoreEnrolledCourseDataWithExtraInfo)[] = [];
|
||||
loaded = false;
|
||||
coursesLoaded = 0;
|
||||
canLoadMore = false;
|
||||
loadMoreError = false;
|
||||
|
||||
showOnlyEnrolled = false;
|
||||
|
||||
protected loadedCourses: (CoreCourseBasicSearchedData|CoreEnrolledCourseDataWithExtraInfo)[] = [];
|
||||
protected loadCoursesPerPage = 20;
|
||||
protected currentSiteId: string;
|
||||
protected frontpageCourseId: number;
|
||||
protected searchPage = 0;
|
||||
protected searchText = '';
|
||||
protected myCoursesObserver: CoreEventObserver;
|
||||
protected siteUpdatedObserver: CoreEventObserver;
|
||||
protected downloadEnabledObserver: CoreEventObserver;
|
||||
protected courseIds = '';
|
||||
protected isDestroyed = false;
|
||||
|
||||
constructor() {
|
||||
this.currentSiteId = CoreSites.getRequiredCurrentSite().getId();
|
||||
this.frontpageCourseId = CoreSites.getRequiredCurrentSite().getSiteHomeId();
|
||||
|
||||
// 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.fetchCourses();
|
||||
}
|
||||
},
|
||||
|
||||
this.currentSiteId,
|
||||
);
|
||||
|
||||
// Refresh the enabled flags if site is updated.
|
||||
this.siteUpdatedObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
|
||||
this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite();
|
||||
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
|
||||
this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
|
||||
|
||||
this.downloadEnabled = (this.downloadCourseEnabled || this.downloadCoursesEnabled) && this.downloadEnabled;
|
||||
if (!this.searchEnabled && this.searchMode) {
|
||||
this.searchMode = false;
|
||||
|
||||
this.fetchCourses();
|
||||
}
|
||||
}, this.currentSiteId);
|
||||
|
||||
this.downloadEnabledObserver = CoreEvents.on(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED, (data) => {
|
||||
this.downloadEnabled = (this.downloadCourseEnabled || this.downloadCoursesEnabled) && data.enabled;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
|
||||
this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
|
||||
|
||||
this.downloadEnabled =
|
||||
(this.downloadCourseEnabled || this.downloadCoursesEnabled) && CoreCourses.getCourseDownloadOptionsEnabled();
|
||||
|
||||
const mode = CoreNavigator.getRouteParam<CoreCoursesListMode>('mode') || 'my';
|
||||
|
||||
if (mode == 'search') {
|
||||
this.searchMode = true;
|
||||
}
|
||||
|
||||
if (mode == 'my') {
|
||||
this.showOnlyEnrolled = true;
|
||||
}
|
||||
|
||||
this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite();
|
||||
if (!this.searchEnabled) {
|
||||
this.searchMode = false;
|
||||
}
|
||||
|
||||
this.fetchCourses();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the course list.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async fetchCourses(): Promise<void> {
|
||||
try {
|
||||
if (this.searchMode) {
|
||||
if (this.searchText) {
|
||||
await this.search(this.searchText);
|
||||
}
|
||||
} else {
|
||||
await this.loadCourses(true);
|
||||
}
|
||||
} finally {
|
||||
this.loaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the courses.
|
||||
*
|
||||
* @param clearTheList If list needs to be reloaded.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async loadCourses(clearTheList = false): Promise<void> {
|
||||
this.loadMoreError = false;
|
||||
|
||||
try {
|
||||
if (clearTheList) {
|
||||
if (this.showOnlyEnrolled) {
|
||||
this.loadedCourses = await CoreCourses.getUserCourses();
|
||||
} else {
|
||||
const courses = await CoreCourses.getCoursesByField();
|
||||
this.loadedCourses = courses.filter((course) => course.id != this.frontpageCourseId);
|
||||
}
|
||||
|
||||
this.coursesLoaded = 0;
|
||||
this.courses = [];
|
||||
}
|
||||
|
||||
const addCourses = this.loadedCourses.slice(this.coursesLoaded, this.coursesLoaded + this.loadCoursesPerPage);
|
||||
await CoreCoursesHelper.loadCoursesExtraInfo(addCourses, true);
|
||||
|
||||
this.courses = this.courses.concat(addCourses);
|
||||
|
||||
this.courseIds = this.courses.map((course) => course.id).join(',');
|
||||
|
||||
this.coursesLoaded = this.courses.length;
|
||||
this.canLoadMore = this.loadedCourses.length > this.courses.length;
|
||||
} catch (error) {
|
||||
this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading.
|
||||
!this.isDestroyed && CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcourses', true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the courses.
|
||||
*
|
||||
* @param refresher Refresher.
|
||||
*/
|
||||
refreshCourses(refresher: IonRefresher): void {
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
if (!this.searchMode) {
|
||||
if (this.showOnlyEnrolled) {
|
||||
promises.push(CoreCourses.invalidateUserCourses());
|
||||
} else {
|
||||
promises.push(CoreCourses.invalidateCoursesByField());
|
||||
}
|
||||
|
||||
if (this.courseIds) {
|
||||
promises.push(CoreCourses.invalidateCoursesByField('ids', this.courseIds));
|
||||
}
|
||||
}
|
||||
|
||||
Promise.all(promises).finally(() => {
|
||||
this.fetchCourses().finally(() => {
|
||||
refresher?.complete();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Search a new text.
|
||||
*
|
||||
* @param text The text to search.
|
||||
*/
|
||||
async search(text: string): Promise<void> {
|
||||
this.searchMode = true;
|
||||
this.searchText = text;
|
||||
this.courses = [];
|
||||
this.searchPage = 0;
|
||||
this.searchTotal = 0;
|
||||
|
||||
const modal = await CoreDomUtils.showModalLoading('core.searching', true);
|
||||
await this.searchCourses().finally(() => {
|
||||
modal.dismiss();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear search box.
|
||||
*/
|
||||
clearSearch(): void {
|
||||
this.searchText = '';
|
||||
this.courses = [];
|
||||
this.searchPage = 0;
|
||||
this.searchTotal = 0;
|
||||
this.searchMode = false;
|
||||
|
||||
this.loaded = false;
|
||||
this.fetchCourses();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load more courses.
|
||||
*
|
||||
* @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading.
|
||||
*/
|
||||
async loadMoreCourses(infiniteComplete?: () => void ): Promise<void> {
|
||||
try {
|
||||
if (this.searchMode) {
|
||||
await this.searchCourses();
|
||||
} else {
|
||||
await this.loadCourses();
|
||||
}
|
||||
} finally {
|
||||
infiniteComplete && infiniteComplete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search courses or load the next page of current search.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async searchCourses(): Promise<void> {
|
||||
this.loadMoreError = false;
|
||||
|
||||
try {
|
||||
const response = await CoreCourses.search(this.searchText, this.searchPage, undefined, this.showOnlyEnrolled);
|
||||
|
||||
if (this.searchPage === 0) {
|
||||
this.courses = response.courses;
|
||||
} else {
|
||||
this.courses = this.courses.concat(response.courses);
|
||||
}
|
||||
this.searchTotal = response.total;
|
||||
|
||||
this.searchPage++;
|
||||
this.canLoadMore = this.courses.length < this.searchTotal;
|
||||
} catch (error) {
|
||||
this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading.
|
||||
!this.isDestroyed && CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorsearching', true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle show only my courses.
|
||||
*/
|
||||
toggleEnrolled(): void {
|
||||
this.loaded = false;
|
||||
this.fetchCourses();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle download enabled.
|
||||
*/
|
||||
toggleDownload(): void {
|
||||
CoreCourses.setCourseDownloadOptionsEnabled(this.downloadEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.myCoursesObserver.off();
|
||||
this.siteUpdatedObserver.off();
|
||||
this.downloadEnabledObserver.off();
|
||||
this.isDestroyed = true;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button [text]="'core.back' | translate"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<h1>{{ 'core.courses.mycourses' | translate }}</h1>
|
||||
|
||||
<ion-buttons slot="end">
|
||||
<core-navbar-buttons>
|
||||
<ion-button *ngIf="searchEnabled" (click)="openSearch()"
|
||||
[attr.aria-label]="'core.courses.searchcourses' | translate">
|
||||
<ion-icon name="fas-search" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-button [hidden]="!downloadAllCoursesEnabled || !courses || courses.length < 2 || downloadAllCoursesLoading"
|
||||
(click)="prefetchCourses()" [attr.aria-label]="'core.courses.downloadcourses' | translate">
|
||||
<ion-icon [name]="downloadAllCoursesIcon" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-spinner [hidden]="!downloadAllCoursesEnabled || !courses || courses.length < 2 ||
|
||||
downloadAllCoursesBadge != '' || !downloadAllCoursesLoading"
|
||||
[attr.aria-label]="'core.loading' | translate"></ion-spinner>
|
||||
<ion-badge [hidden]="!downloadAllCoursesEnabled || !courses || courses.length < 2 || !downloadAllCoursesLoading ||
|
||||
downloadAllCoursesBadge == '' || !downloadAllCoursesLoading"
|
||||
role="progressbar" [attr.aria-valuemax]="downloadAllCoursesTotal"
|
||||
[attr.aria-valuenow]="downloadAllCoursesCount" [attr.aria-valuetext]="downloadAllCoursesBadgeA11yText">
|
||||
{{downloadAllCoursesBadge}}
|
||||
</ion-badge>
|
||||
</core-navbar-buttons>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-refresher slot="fixed" [disabled]="!coursesLoaded" (ionRefresh)="refreshCourses($event.target)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
|
||||
<core-loading [hideUntil]="coursesLoaded">
|
||||
<ion-searchbar #searchbar *ngIf="courses && courses.length > 5" [(ngModel)]="filter" (ionInput)="filterChanged($event)"
|
||||
(ionCancel)="filterChanged()" [placeholder]="'core.courses.filtermycourses' | translate">
|
||||
</ion-searchbar>
|
||||
<ion-grid class="ion-no-padding safe-area-padding">
|
||||
<ion-row class="ion-no-padding">
|
||||
<ion-col *ngFor="let course of filteredCourses" class="ion-no-padding"
|
||||
size="12" size-sm="6" size-md="6" size-lg="4" size-xl="4">
|
||||
<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="fas-graduation-cap"
|
||||
[message]="'core.courses.nocourses' | translate">
|
||||
<p *ngIf="searchEnabled">{{ 'core.courses.searchcoursesadvice' | translate }}</p>
|
||||
</core-empty-box>
|
||||
</core-loading>
|
||||
</ion-content>
|
|
@ -1,40 +0,0 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// 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 { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { CoreSharedModule } from '@/core/shared.module';
|
||||
import { CoreCoursesMyCoursesPage } from './my-courses';
|
||||
import { CoreCoursesComponentsModule } from '../../components/components.module';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: CoreCoursesMyCoursesPage,
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes),
|
||||
CoreSharedModule,
|
||||
CoreCoursesComponentsModule,
|
||||
],
|
||||
declarations: [
|
||||
CoreCoursesMyCoursesPage,
|
||||
],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class CoreCoursesMyCoursesPageModule { }
|
|
@ -1,219 +0,0 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// 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 { IonSearchbar, IonRefresher } from '@ionic/angular';
|
||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import {
|
||||
CoreCoursesProvider,
|
||||
CoreCourses,
|
||||
} from '../../services/courses';
|
||||
import { CoreCoursesHelper, CoreEnrolledCourseDataWithExtraInfoAndOptions } from '../../services/courses-helper';
|
||||
import { CoreCourseHelper } from '@features/course/services/course-helper';
|
||||
import { CoreConstants } from '@/core/constants';
|
||||
import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { Translate } from '@singletons';
|
||||
|
||||
/**
|
||||
* Page that displays the list of courses the user is enrolled in.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'page-core-courses-my-courses',
|
||||
templateUrl: 'my-courses.html',
|
||||
})
|
||||
export class CoreCoursesMyCoursesPage implements OnInit, OnDestroy {
|
||||
|
||||
@ViewChild(IonSearchbar) searchbar!: IonSearchbar;
|
||||
|
||||
courses: CoreEnrolledCourseDataWithExtraInfoAndOptions[] = [];
|
||||
filteredCourses: CoreEnrolledCourseDataWithExtraInfoAndOptions[] = [];
|
||||
searchEnabled = false;
|
||||
filter = '';
|
||||
showFilter = false;
|
||||
coursesLoaded = false;
|
||||
downloadAllCoursesIcon = CoreConstants.ICON_NOT_DOWNLOADED;
|
||||
downloadAllCoursesLoading = false;
|
||||
downloadAllCoursesBadge = '';
|
||||
downloadAllCoursesEnabled = false;
|
||||
downloadAllCoursesCount?: number;
|
||||
downloadAllCoursesTotal?: number;
|
||||
downloadAllCoursesBadgeA11yText = '';
|
||||
|
||||
protected myCoursesObserver: CoreEventObserver;
|
||||
protected siteUpdatedObserver: CoreEventObserver;
|
||||
protected isDestroyed = false;
|
||||
protected courseIds = '';
|
||||
|
||||
constructor() {
|
||||
// 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.fetchCourses();
|
||||
}
|
||||
},
|
||||
|
||||
CoreSites.getCurrentSiteId(),
|
||||
);
|
||||
|
||||
// Refresh the enabled flags if site is updated.
|
||||
this.siteUpdatedObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
|
||||
this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite();
|
||||
this.downloadAllCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
|
||||
}, CoreSites.getCurrentSiteId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite();
|
||||
this.downloadAllCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
|
||||
|
||||
this.fetchCourses().finally(() => {
|
||||
this.coursesLoaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the user courses.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async fetchCourses(): Promise<void> {
|
||||
try {
|
||||
const courses: CoreEnrolledCourseDataWithExtraInfoAndOptions[] = await CoreCourses.getUserCourses();
|
||||
const courseIds = courses.map((course) => course.id);
|
||||
|
||||
this.courseIds = courseIds.join(',');
|
||||
|
||||
await CoreCoursesHelper.loadCoursesExtraInfo(courses);
|
||||
|
||||
const options = await CoreCourses.getCoursesAdminAndNavOptions(courseIds);
|
||||
courses.forEach((course) => {
|
||||
course.navOptions = options.navOptions[course.id];
|
||||
course.admOptions = options.admOptions[course.id];
|
||||
});
|
||||
|
||||
this.courses = courses;
|
||||
this.filteredCourses = this.courses;
|
||||
this.filter = '';
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcourses', true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the courses.
|
||||
*
|
||||
* @param refresher Refresher.
|
||||
*/
|
||||
refreshCourses(refresher: IonRefresher): void {
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
promises.push(CoreCourses.invalidateUserCourses());
|
||||
promises.push(CoreCourseOptionsDelegate.clearAndInvalidateCoursesOptions());
|
||||
if (this.courseIds) {
|
||||
promises.push(CoreCourses.invalidateCoursesByField('ids', this.courseIds));
|
||||
}
|
||||
|
||||
Promise.all(promises).finally(() => {
|
||||
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 Received Event.
|
||||
*/
|
||||
filterChanged(event?: Event): void {
|
||||
const target = <HTMLInputElement>event?.target || null;
|
||||
const newValue = target ? String(target.value).trim().toLowerCase() : null;
|
||||
if (!newValue || !this.courses) {
|
||||
this.filteredCourses = this.courses;
|
||||
} else {
|
||||
// Use displayname if available, or fullname if not.
|
||||
if (this.courses.length > 0 && typeof this.courses[0].displayname != 'undefined') {
|
||||
this.filteredCourses = this.courses.filter((course) => course.displayname!.toLowerCase().indexOf(newValue) > -1);
|
||||
} else {
|
||||
this.filteredCourses = this.courses.filter((course) => course.fullname.toLowerCase().indexOf(newValue) > -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefetch all the courses.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
async prefetchCourses(): Promise<void> {
|
||||
this.downloadAllCoursesLoading = true;
|
||||
|
||||
try {
|
||||
await CoreCourseHelper.confirmAndPrefetchCourses(this.courses, { onProgress: (progress) => {
|
||||
this.downloadAllCoursesBadge = progress.count + ' / ' + progress.total;
|
||||
this.downloadAllCoursesBadgeA11yText =
|
||||
Translate.instant('core.course.downloadcoursesprogressdescription', progress);
|
||||
this.downloadAllCoursesCount = progress.count;
|
||||
this.downloadAllCoursesTotal = progress.total;
|
||||
} });
|
||||
} catch (error) {
|
||||
if (!this.isDestroyed) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true);
|
||||
}
|
||||
}
|
||||
|
||||
this.downloadAllCoursesBadge = '';
|
||||
this.downloadAllCoursesLoading = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to search courses.
|
||||
*/
|
||||
openSearch(): void {
|
||||
CoreNavigator.navigateToSitePath('courses/search');
|
||||
}
|
||||
|
||||
/**
|
||||
* Page destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.isDestroyed = true;
|
||||
this.myCoursesObserver?.off();
|
||||
this.siteUpdatedObserver?.off();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button [text]="'core.back' | translate"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<h1>{{ 'core.courses.searchcourses' | translate }}</h1>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<core-search-box (onSubmit)="search($event)" (onClear)="clearSearch()"
|
||||
[placeholder]="'core.courses.search' | translate" [searchLabel]="'core.courses.search' | translate" autoFocus="true"
|
||||
searchArea="CoreCoursesSearch"></core-search-box>
|
||||
|
||||
<ng-container *ngIf="total > 0">
|
||||
<ion-item-divider>
|
||||
<ion-label><h2>{{ 'core.courses.totalcoursesearchresults' | translate:{$a: total} }}</h2></ion-label>
|
||||
</ion-item-divider>
|
||||
<core-courses-course-list-item *ngFor="let course of courses" [course]="course"></core-courses-course-list-item>
|
||||
<core-infinite-loading [enabled]="canLoadMore" (action)="loadMoreResults($event)" [error]="loadMoreError">
|
||||
</core-infinite-loading>
|
||||
</ng-container>
|
||||
<core-empty-box *ngIf="total == 0" icon="search" [message]="'core.courses.nosearchresults' | translate"></core-empty-box>
|
||||
</ion-content>
|
|
@ -1,100 +0,0 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// 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 } from '@angular/core';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreCourseBasicSearchedData, CoreCourses } from '../../services/courses';
|
||||
|
||||
/**
|
||||
* Page that allows searching for courses.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'page-core-courses-search',
|
||||
templateUrl: 'search.html',
|
||||
})
|
||||
export class CoreCoursesSearchPage {
|
||||
|
||||
total = 0;
|
||||
courses: CoreCourseBasicSearchedData[] = [];
|
||||
canLoadMore = false;
|
||||
loadMoreError = false;
|
||||
|
||||
protected page = 0;
|
||||
protected currentSearch = '';
|
||||
|
||||
/**
|
||||
* Search a new text.
|
||||
*
|
||||
* @param text The text to search.
|
||||
*/
|
||||
async search(text: string): Promise<void> {
|
||||
this.currentSearch = text;
|
||||
this.courses = [];
|
||||
this.page = 0;
|
||||
this.total = 0;
|
||||
|
||||
const modal = await CoreDomUtils.showModalLoading('core.searching', true);
|
||||
this.searchCourses().finally(() => {
|
||||
modal.dismiss();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear search box.
|
||||
*/
|
||||
clearSearch(): void {
|
||||
this.currentSearch = '';
|
||||
this.courses = [];
|
||||
this.page = 0;
|
||||
this.total = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load more results.
|
||||
*
|
||||
* @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading.
|
||||
*/
|
||||
loadMoreResults(infiniteComplete?: () => void ): void {
|
||||
this.searchCourses().finally(() => {
|
||||
infiniteComplete && infiniteComplete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Search courses or load the next page of current search.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async searchCourses(): Promise<void> {
|
||||
this.loadMoreError = false;
|
||||
|
||||
try {
|
||||
const response = await CoreCourses.search(this.currentSearch, this.page);
|
||||
|
||||
if (this.page === 0) {
|
||||
this.courses = response.courses;
|
||||
} else {
|
||||
this.courses = this.courses.concat(response.courses);
|
||||
}
|
||||
this.total = response.total;
|
||||
|
||||
this.page++;
|
||||
this.canLoadMore = this.courses.length < this.total;
|
||||
} catch (error) {
|
||||
this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading.
|
||||
CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorsearching', true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -13,13 +13,13 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@services/sites';
|
||||
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { CoreStatusWithWarningsWSResponse, CoreWarningsWSResponse, CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws';
|
||||
import { CoreEvents } from '@singletons/events';
|
||||
import { CoreWSError } from '@classes/errors/wserror';
|
||||
import { CoreCourseWithImageAndColor } from './courses-helper';
|
||||
|
||||
const ROOT_CACHE_KEY = 'mmCourses:';
|
||||
|
||||
|
@ -62,12 +62,9 @@ export class CoreCoursesProvider {
|
|||
static readonly STATE_HIDDEN = 'hidden';
|
||||
static readonly STATE_FAVOURITE = 'favourite';
|
||||
|
||||
protected logger: CoreLogger;
|
||||
protected userCoursesIds: { [id: number]: boolean } = {}; // Use an object to make it faster to search.
|
||||
|
||||
constructor() {
|
||||
this.logger = CoreLogger.getInstance('CoreCoursesProvider');
|
||||
}
|
||||
protected downloadOptionsEnabled = false;
|
||||
|
||||
/**
|
||||
* Whether current site supports getting course options.
|
||||
|
@ -1121,6 +1118,7 @@ export class CoreCoursesProvider {
|
|||
* @param text Text to search.
|
||||
* @param page Page to get.
|
||||
* @param perPage Number of courses per page. Defaults to CoreCoursesProvider.SEARCH_PER_PAGE.
|
||||
* @param limitToEnrolled Limit to enrolled courses.
|
||||
* @param siteId Site ID. If not defined, use current site.
|
||||
* @return Promise resolved with the courses and the total of matches.
|
||||
*/
|
||||
|
@ -1128,6 +1126,7 @@ export class CoreCoursesProvider {
|
|||
text: string,
|
||||
page: number = 0,
|
||||
perPage: number = CoreCoursesProvider.SEARCH_PER_PAGE,
|
||||
limitToEnrolled: boolean = false,
|
||||
siteId?: string,
|
||||
): Promise<{ total: number; courses: CoreCourseBasicSearchedData[] }> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
@ -1136,6 +1135,7 @@ export class CoreCoursesProvider {
|
|||
criteriavalue: text,
|
||||
page: page,
|
||||
perpage: perPage,
|
||||
limittoenrolled: limitToEnrolled,
|
||||
};
|
||||
const preSets: CoreSiteWSPreSets = {
|
||||
getFromCache: false,
|
||||
|
@ -1216,6 +1216,29 @@ export class CoreCoursesProvider {
|
|||
return site.write('core_course_set_favourite_courses', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get download options enabled option.
|
||||
*
|
||||
* @return True if enabled, false otherwise.
|
||||
*/
|
||||
getCourseDownloadOptionsEnabled(): boolean {
|
||||
return this.downloadOptionsEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set trigger and save the download option.
|
||||
*
|
||||
* @param enable True to enable, false to disable.
|
||||
*/
|
||||
setCourseDownloadOptionsEnabled(enable: boolean): void {
|
||||
if (this.downloadOptionsEnabled == enable) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.downloadOptionsEnabled = enable;
|
||||
CoreEvents.trigger(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED, { enabled: enable });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const CoreCourses = makeSingleton(CoreCoursesProvider);
|
||||
|
@ -1358,6 +1381,14 @@ export type CoreCourseSearchedData = CoreCourseBasicSearchedData & {
|
|||
courseformatoptions?: CoreCourseFormatOption[]; // Additional options for particular course format.
|
||||
};
|
||||
|
||||
/**
|
||||
* Course to render as list item.
|
||||
*/
|
||||
export type CoreCourseListItem = CoreCourseSearchedData & CoreCourseWithImageAndColor & {
|
||||
completionusertracked?: boolean; // If the user is completion tracked.
|
||||
progress?: number | null; // Progress percentage.
|
||||
};
|
||||
|
||||
export type CoreCourseGetCoursesData = CoreEnrolledCourseBasicData & {
|
||||
categoryid: number; // Category id.
|
||||
categorysortorder?: number; // Sort order into the category.
|
||||
|
|
|
@ -107,7 +107,7 @@ export class CoreCoursesDashboardProvider {
|
|||
}
|
||||
|
||||
/**
|
||||
* Check if Site Home is disabled in a certain site.
|
||||
* Check if Dashboard is disabled in a certain site.
|
||||
*
|
||||
* @param site Site. If not defined, use current site.
|
||||
* @return Whether it's disabled.
|
||||
|
|
|
@ -18,7 +18,7 @@ import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base
|
|||
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { CoreCoursesMyCoursesHomeHandlerService } from './my-courses-home';
|
||||
import { CoreCoursesMyCoursesMainMenuHandlerService } from './my-courses-mainmenu';
|
||||
|
||||
/**
|
||||
* Handler to treat links to course index (list of courses).
|
||||
|
@ -31,25 +31,22 @@ export class CoreCoursesIndexLinkHandlerService extends CoreContentLinksHandlerB
|
|||
pattern = /\/course\/?(index\.php.*)?$/;
|
||||
|
||||
/**
|
||||
* Get the list of actions for a link (url).
|
||||
*
|
||||
* @param siteIds List of sites the URL belongs to.
|
||||
* @param url The URL to treat.
|
||||
* @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
|
||||
* @return List of (or promise resolved with list of) actions.
|
||||
* @inheritdoc
|
||||
*/
|
||||
getActions(siteIds: string[], url: string, params: Params): CoreContentLinksAction[] {
|
||||
return [{
|
||||
action: (siteId): void => {
|
||||
let pageName = CoreCoursesMyCoursesHomeHandlerService.PAGE_NAME;
|
||||
let pageName = CoreCoursesMyCoursesMainMenuHandlerService.PAGE_NAME;
|
||||
const pageParams: Params = {};
|
||||
|
||||
if (params.categoryid) {
|
||||
pageName += '/categories/' + params.categoryid;
|
||||
} else {
|
||||
pageName += '/all';
|
||||
pageName += '/list';
|
||||
pageParams.mode = 'all';
|
||||
}
|
||||
|
||||
CoreNavigator.navigateToSitePath(pageName, { siteId });
|
||||
CoreNavigator.navigateToSitePath(pageName, { params: pageParams, siteId });
|
||||
},
|
||||
}];
|
||||
}
|
||||
|
|
|
@ -13,39 +13,29 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreMainMenuHomeHandler, CoreMainMenuHomeHandlerToDisplay } from '@features/mainmenu/services/home-delegate';
|
||||
import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '@features/mainmenu/services/mainmenu-delegate';
|
||||
import { CoreSiteHomeHomeHandler } from '@features/sitehome/services/handlers/sitehome-home';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { CoreCourses } from '../courses';
|
||||
import { CoreDashboardHomeHandler } from './dashboard-home';
|
||||
|
||||
/**
|
||||
* Handler to add my courses into home page.
|
||||
* Handler to add my courses into main menu.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CoreCoursesMyCoursesHomeHandlerService implements CoreMainMenuHomeHandler {
|
||||
export class CoreCoursesMyCoursesMainMenuHandlerService implements CoreMainMenuHandler {
|
||||
|
||||
static readonly PAGE_NAME = 'courses';
|
||||
|
||||
name = 'CoreCoursesMyCourses';
|
||||
priority = 900;
|
||||
priority = 850;
|
||||
|
||||
/**
|
||||
* Check if the handler is enabled on a site level.
|
||||
*
|
||||
* @return Whether or not the handler is enabled on a site level.
|
||||
* @inheritdoc
|
||||
*/
|
||||
isEnabled(): Promise<boolean> {
|
||||
return this.isEnabledForSite();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the handler is enabled on a certain site.
|
||||
*
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Whether or not the handler is enabled on a site level.
|
||||
*/
|
||||
async isEnabledForSite(siteId?: string): Promise<boolean> {
|
||||
async isEnabled(): Promise<boolean> {
|
||||
const siteId = CoreSites.getCurrentSiteId();
|
||||
const disabled = await CoreCourses.isMyCoursesDisabled(siteId);
|
||||
|
||||
if (disabled) {
|
||||
|
@ -59,20 +49,17 @@ export class CoreCoursesMyCoursesHomeHandlerService implements CoreMainMenuHomeH
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the data needed to render the handler.
|
||||
*
|
||||
* @return Data needed to render the handler.
|
||||
* @inheritdoc
|
||||
*/
|
||||
getDisplayData(): CoreMainMenuHomeHandlerToDisplay {
|
||||
getDisplayData(): CoreMainMenuHandlerData {
|
||||
return {
|
||||
title: 'core.courses.mycourses',
|
||||
page: CoreCoursesMyCoursesHomeHandlerService.PAGE_NAME,
|
||||
page: CoreCoursesMyCoursesMainMenuHandlerService.PAGE_NAME,
|
||||
class: 'core-courses-my-courses-handler',
|
||||
icon: 'fas-graduation-cap',
|
||||
selectPriority: 900,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const CoreCoursesMyCoursesHomeHandler = makeSingleton(CoreCoursesMyCoursesHomeHandlerService);
|
||||
export const CoreCoursesMyCoursesHomeHandler = makeSingleton(CoreCoursesMyCoursesMainMenuHandlerService);
|
|
@ -3,12 +3,12 @@
|
|||
<ion-icon name="fas-search" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||
</ion-button>
|
||||
<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>
|
||||
<core-context-menu-item *ngIf="(downloadCourseEnabled || downloadCoursesEnabled)" [priority]="500"
|
||||
[content]="'addon.storagemanager.managestorage' | translate"
|
||||
(action)="manageCoursesStorage()" iconAction="fas-archive"></core-context-menu-item>
|
||||
<core-context-menu-item [priority]="1000" *ngIf="displayEnableDownload"
|
||||
[content]="'core.settings.showdownloadoptions' | translate" (action)="switchDownload()"
|
||||
iconAction="toggle" [(toggle)]="downloadEnabled"></core-context-menu-item>
|
||||
<core-context-menu-item [priority]="500"
|
||||
[content]="'addon.storagemanager.managestorage' | translate"
|
||||
(action)="manageCoursesStorage()" iconAction="fas-archive"></core-context-menu-item>
|
||||
</core-context-menu>
|
||||
</core-navbar-buttons>
|
||||
<ion-content>
|
||||
|
|
|
@ -50,31 +50,32 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy {
|
|||
siteHomeId = 1;
|
||||
currentSite!: CoreSite;
|
||||
searchEnabled = false;
|
||||
displayEnableDownload = false;
|
||||
downloadEnabled = false;
|
||||
downloadCourseEnabled = false;
|
||||
downloadCoursesEnabled = false;
|
||||
downloadEnabledIcon = 'far-square';
|
||||
newsForumModule?: NewsForum;
|
||||
|
||||
protected updateSiteObserver?: CoreEventObserver;
|
||||
|
||||
/**
|
||||
* Page being initialized.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite();
|
||||
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
|
||||
this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
|
||||
protected updateSiteObserver: CoreEventObserver;
|
||||
protected downloadEnabledObserver: CoreEventObserver;
|
||||
|
||||
constructor() {
|
||||
// Refresh the enabled flags if site is updated.
|
||||
this.updateSiteObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
|
||||
this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite();
|
||||
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
|
||||
this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
|
||||
|
||||
this.switchDownload(this.downloadEnabled && this.downloadCourseEnabled && this.downloadCoursesEnabled);
|
||||
this.displayEnableDownload = !CoreSites.getRequiredCurrentSite().isOfflineDisabled();
|
||||
}, CoreSites.getCurrentSiteId());
|
||||
|
||||
this.downloadEnabledObserver = CoreEvents.on(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED, (data) => {
|
||||
this.downloadEnabled = data.enabled;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite();
|
||||
|
||||
this.currentSite = CoreSites.getRequiredCurrentSite();
|
||||
this.siteHomeId = CoreSites.getCurrentSiteHomeId();
|
||||
|
||||
|
@ -84,6 +85,9 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy {
|
|||
CoreCourseHelper.openModule(module, this.siteHomeId, undefined, modParams);
|
||||
}
|
||||
|
||||
this.displayEnableDownload = !CoreSites.getRequiredCurrentSite().isOfflineDisabled();
|
||||
this.downloadEnabled = CoreCourses.getCourseDownloadOptionsEnabled();
|
||||
|
||||
this.loadContent().finally(() => {
|
||||
this.dataLoaded = true;
|
||||
});
|
||||
|
@ -190,21 +194,10 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
/**
|
||||
* Toggle download enabled.
|
||||
* Switch download enabled.
|
||||
*/
|
||||
toggleDownload(): void {
|
||||
this.switchDownload(!this.downloadEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to switch download enabled.
|
||||
*
|
||||
* @param enable If enable or disable.
|
||||
*/
|
||||
protected switchDownload(enable: boolean): void {
|
||||
this.downloadEnabled = (this.downloadCourseEnabled || this.downloadCoursesEnabled) && enable;
|
||||
this.downloadEnabledIcon = this.downloadEnabled ? 'far-check-square' : 'far-square';
|
||||
CoreEvents.trigger(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED, { enabled: this.downloadEnabled });
|
||||
switchDownload(): void {
|
||||
CoreCourses.setCourseDownloadOptionsEnabled(this.downloadEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -218,21 +211,21 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy {
|
|||
* Go to search courses.
|
||||
*/
|
||||
openSearch(): void {
|
||||
CoreNavigator.navigateToSitePath('courses/search');
|
||||
CoreNavigator.navigateToSitePath('courses/list', { params : { mode: 'search' } });
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to available courses.
|
||||
*/
|
||||
openAvailableCourses(): void {
|
||||
CoreNavigator.navigateToSitePath('courses/all');
|
||||
CoreNavigator.navigateToSitePath('courses/list', { params : { mode: 'all' } });
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to my courses.
|
||||
*/
|
||||
openMyCourses(): void {
|
||||
CoreNavigator.navigateToSitePath('courses/my');
|
||||
CoreNavigator.navigateToSitePath('courses/list', { params : { mode: 'my' } });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -246,7 +239,8 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy {
|
|||
* Component being destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.updateSiteObserver?.off();
|
||||
this.updateSiteObserver.off();
|
||||
this.downloadEnabledObserver.off();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -169,20 +169,12 @@ export class CoreSiteHomeProvider {
|
|||
// Get number of news items to show.
|
||||
add = !!CoreSites.getCurrentSite()?.getStoredConfig('newsitems');
|
||||
break;
|
||||
case FrontPageItemNames['LIST_OF_CATEGORIES']:
|
||||
case FrontPageItemNames['COMBO_LIST']:
|
||||
itemNumber = FrontPageItemNames['LIST_OF_CATEGORIES']; // Do not break here.
|
||||
case FrontPageItemNames['LIST_OF_CATEGORIES']:
|
||||
case FrontPageItemNames['LIST_OF_COURSE']:
|
||||
add = true;
|
||||
if (itemNumber == FrontPageItemNames['COMBO_LIST']) {
|
||||
itemNumber = FrontPageItemNames['LIST_OF_CATEGORIES'];
|
||||
}
|
||||
break;
|
||||
case FrontPageItemNames['ENROLLED_COURSES']:
|
||||
if (!CoreCourses.isMyCoursesDisabledInSite()) {
|
||||
const courses = await CoreCourses.getUserCourses();
|
||||
|
||||
add = courses.length > 0;
|
||||
}
|
||||
add = true;
|
||||
break;
|
||||
case FrontPageItemNames['COURSE_SEARCH_BOX']:
|
||||
add = !CoreCourses.isSearchCoursesDisabledInSite();
|
||||
|
|
|
@ -124,7 +124,7 @@ export class CoreSitePluginsCallWSBaseDirective implements OnInit, OnDestroy {
|
|||
invalidate(): Promise<void> {
|
||||
const params = this.getParamsForWS();
|
||||
|
||||
return CoreSitePlugins.instance.invalidateCallWS(this.name, params, this.preSets);
|
||||
return CoreSitePlugins.invalidateCallWS(this.name, params, this.preSets);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue