commit
1ffe48c915
|
@ -1551,6 +1551,7 @@
|
||||||
"core.courses.selfenrolment": "local_moodlemobileapp",
|
"core.courses.selfenrolment": "local_moodlemobileapp",
|
||||||
"core.courses.sendpaymentbutton": "enrol_paypal",
|
"core.courses.sendpaymentbutton": "enrol_paypal",
|
||||||
"core.courses.show": "block_myoverview",
|
"core.courses.show": "block_myoverview",
|
||||||
|
"core.courses.showonlyenrolled": "local_moodlemobileapp",
|
||||||
"core.courses.therearecourses": "moodle",
|
"core.courses.therearecourses": "moodle",
|
||||||
"core.courses.totalcoursesearchresults": "local_moodlemobileapp",
|
"core.courses.totalcoursesearchresults": "local_moodlemobileapp",
|
||||||
"core.currentdevice": "local_moodlemobileapp",
|
"core.currentdevice": "local_moodlemobileapp",
|
||||||
|
|
|
@ -170,7 +170,7 @@ export class AddonBlogEntriesPage implements OnInit {
|
||||||
entry.contextInstanceId = entry.userid;
|
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) => {
|
return CoreUser.getProfile(entry.userid, entry.courseid, true).then((user) => {
|
||||||
entry.user = user;
|
entry.user = user;
|
||||||
|
|
|
@ -44,8 +44,8 @@
|
||||||
'addon.messages.muteconversation') | translate" (action)="changeMute($event)" [closeOnClick]="false"
|
'addon.messages.muteconversation') | translate" (action)="changeMute($event)" [closeOnClick]="false"
|
||||||
[iconAction]="muteIcon"></core-context-menu-item>
|
[iconAction]="muteIcon"></core-context-menu-item>
|
||||||
<core-context-menu-item [hidden]="!canDelete || !messages || !messages.length" [priority]="400"
|
<core-context-menu-item [hidden]="!canDelete || !messages || !messages.length" [priority]="400"
|
||||||
[content]="'addon.messages.showdeletemessages' | translate" (action)="toggleDelete()"
|
[content]="'addon.messages.showdeletemessages' | translate"
|
||||||
[iconAction]="(showDelete ? 'far-check-square' : 'far-square')"></core-context-menu-item>
|
iconAction="toggle" [(toggle)]="showDelete"></core-context-menu-item>
|
||||||
<core-context-menu-item [hidden]="!groupMessagingEnabled || !conversationId || isGroup || !messages || !messages.length"
|
<core-context-menu-item [hidden]="!groupMessagingEnabled || !conversationId || isGroup || !messages || !messages.length"
|
||||||
[priority]="200" [content]="'addon.messages.deleteconversation' | translate" (action)="deleteConversation($event)"
|
[priority]="200" [content]="'addon.messages.deleteconversation' | translate" (action)="deleteConversation($event)"
|
||||||
[closeOnClick]="false" [iconAction]="deleteIcon"></core-context-menu-item>
|
[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;
|
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.
|
* View info. If it's an individual conversation, go to the user profile.
|
||||||
* If it's a group conversation, view info about the group.
|
* If it's a group conversation, view info about the group.
|
||||||
|
|
|
@ -212,12 +212,12 @@
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<!-- Button to start/continue. -->
|
<!-- 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 }}
|
{{ buttonText | translate }}
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
|
||||||
<!-- Button to open in browser if it cannot be attempted in the app. -->
|
<!-- 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
|
unsupportedRules.length || behaviourSupported === false)" expand="block" [href]="externalUrl" core-link
|
||||||
[showBrowserWarning]="false">
|
[showBrowserWarning]="false">
|
||||||
{{ 'core.openinbrowser' | translate }}
|
{{ 'core.openinbrowser' | translate }}
|
||||||
|
|
|
@ -118,7 +118,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy {
|
||||||
try {
|
try {
|
||||||
await this.setStartTime(this.currentSco.id);
|
await this.setStartTime(this.currentSco.id);
|
||||||
} catch (error) {
|
} 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 {
|
try {
|
||||||
AddonModScormHelper.convertAttemptToOffline(this.scorm, this.attempt);
|
AddonModScormHelper.convertAttemptToOffline(this.scorm, this.attempt);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
CoreDomUtils.instance.showErrorModalDefault(error, 'core.error', true);
|
CoreDomUtils.showErrorModalDefault(error, 'core.error', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.refreshToc();
|
this.refreshToc();
|
||||||
|
@ -303,7 +303,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy {
|
||||||
this.userData = data;
|
this.userData = data;
|
||||||
this.accessInfo = accessInfo;
|
this.accessInfo = accessInfo;
|
||||||
} catch (error) {
|
} 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);
|
await AddonModScorm.saveTracks(sco.id, this.attempt, tracks, this.scorm, true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
CoreDomUtils.instance.showErrorModalDefault(error, 'core.error', true);
|
CoreDomUtils.showErrorModalDefault(error, 'core.error', true);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
// Refresh TOC, some prerequisites might have changed.
|
// Refresh TOC, some prerequisites might have changed.
|
||||||
|
@ -510,7 +510,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
await this.fetchToc();
|
await this.fetchToc();
|
||||||
} catch (error) {
|
} 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 { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate';
|
||||||
import { CoreMainMenuRoutingModule } from '@features/mainmenu/mainmenu-routing.module';
|
import { CoreMainMenuRoutingModule } from '@features/mainmenu/mainmenu-routing.module';
|
||||||
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-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 { AddonStorageManagerCourseMenuHandler } from './services/handlers/course-menu';
|
||||||
|
import { AddonStorageManagerSettingsHandler } from './services/handlers/settings';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
|
@ -30,6 +33,7 @@ const routes: Routes = [
|
||||||
imports: [
|
imports: [
|
||||||
CoreMainMenuTabRoutingModule.forChild(routes),
|
CoreMainMenuTabRoutingModule.forChild(routes),
|
||||||
CoreMainMenuRoutingModule.forChild({ children: routes }),
|
CoreMainMenuRoutingModule.forChild({ children: routes }),
|
||||||
|
CoreSitePreferencesRoutingModule.forChild(routes),
|
||||||
],
|
],
|
||||||
exports: [CoreMainMenuRoutingModule],
|
exports: [CoreMainMenuRoutingModule],
|
||||||
providers: [
|
providers: [
|
||||||
|
@ -38,6 +42,7 @@ const routes: Routes = [
|
||||||
multi: true,
|
multi: true,
|
||||||
useValue: () => {
|
useValue: () => {
|
||||||
CoreCourseOptionsDelegate.registerHandler(AddonStorageManagerCourseMenuHandler.instance);
|
CoreCourseOptionsDelegate.registerHandler(AddonStorageManagerCourseMenuHandler.instance);
|
||||||
|
CoreSettingsDelegate.registerHandler(AddonStorageManagerSettingsHandler.instance);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -119,7 +119,7 @@ export class AppComponent implements OnInit, AfterViewInit {
|
||||||
});
|
});
|
||||||
CoreUtils.closeInAppBrowser();
|
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.
|
// Check if the URL has a custom URL scheme. In Android they need to be opened manually.
|
||||||
const urlScheme = CoreUrlUtils.getUrlProtocol(url);
|
const urlScheme = CoreUrlUtils.getUrlProtocol(url);
|
||||||
if (urlScheme && urlScheme !== 'file' && urlScheme !== 'cdvfile') {
|
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() 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.
|
@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 "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 no icon or spinner is selected, no action or link will work.
|
||||||
// If href but no iconAction is provided arrow-right will be used.
|
// If href but no iconAction is provided arrow-right will be used.
|
||||||
@Input() iconSlash?: boolean; // Display a red slash over the icon.
|
@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() badgeA11yText?: string; // Description for the badge, if needed.
|
||||||
@Input() hidden?: boolean; // Whether the item should be hidden.
|
@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() 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() 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() 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 hasAction = false;
|
||||||
protected destroyed = 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.
|
* Component destroyed.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -55,6 +55,12 @@ export class CoreContextMenuPopoverComponent {
|
||||||
* @return Return true if success, false if error.
|
* @return Return true if success, false if error.
|
||||||
*/
|
*/
|
||||||
itemClicked(event: Event, item: CoreContextMenuItemComponent): boolean {
|
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) {
|
if (!!item.action && item.action.observers.length > 0) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
|
@ -11,11 +11,16 @@
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<p class="item-heading"><core-format-text [clean]="true" [text]="item.content" [filter]="false"></core-format-text></p>
|
<p class="item-heading"><core-format-text [clean]="true" [text]="item.content" [filter]="false"></core-format-text></p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-icon *ngIf="(item.href || item.action) && item.iconAction && item.iconAction != 'spinner'" [name]="item.iconAction"
|
<ng-container *ngIf="(item.href || item.action) && item.iconAction">
|
||||||
[class.icon-slash]="item.iconSlash" slot="end" aria-hidden="true">
|
<ion-icon *ngIf="item.iconAction != 'spinner' && item.iconAction != 'toggle'" [name]="item.iconAction"
|
||||||
</ion-icon>
|
[class.icon-slash]="item.iconSlash" slot="end" aria-hidden="true">
|
||||||
<ion-spinner *ngIf="(item.href || item.action) && item.iconAction == 'spinner'" slot="end"
|
</ion-icon>
|
||||||
[attr.aria-label]="'core.loading' | translate"></ion-spinner>
|
<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">
|
<ion-badge class="{{item.badgeClass}}" slot="end" *ngIf="item.badge">
|
||||||
<span [attr.ara-hidden]="!!item.badgeA11yText">{{item.badge}}</span>
|
<span [attr.ara-hidden]="!!item.badgeA11yText">{{item.badge}}</span>
|
||||||
<span class="sr-only" *ngIf="item.badgeA11yText">
|
<span class="sr-only" *ngIf="item.badgeA11yText">
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<core-navbar-buttons slot="end">
|
<core-navbar-buttons slot="end">
|
||||||
<core-context-menu>
|
<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()">
|
[content]="'core.settings.showdownloadoptions' | translate" (action)="toggleDownload()">
|
||||||
</core-context-menu-item>
|
</core-context-menu-item>
|
||||||
<core-context-menu-item [hidden]="!downloadCourseEnabled" [priority]="1900"
|
<core-context-menu-item [hidden]="!downloadCourseEnabled" [priority]="1900"
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { IonContent, IonRefresher } from '@ionic/angular';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { CoreCourses, CoreCourseAnyCourseData } from '@features/courses/services/courses';
|
import { CoreCourses, CoreCourseAnyCourseData, CoreCoursesProvider } from '@features/courses/services/courses';
|
||||||
import {
|
import {
|
||||||
CoreCourse,
|
CoreCourse,
|
||||||
CoreCourseCompletionActivityStatus,
|
CoreCourseCompletionActivityStatus,
|
||||||
|
@ -64,7 +64,6 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
||||||
courseMenuHandlers: CoreCourseOptionsMenuHandlerToDisplay[] = [];
|
courseMenuHandlers: CoreCourseOptionsMenuHandlerToDisplay[] = [];
|
||||||
dataLoaded = false;
|
dataLoaded = false;
|
||||||
downloadEnabled = false;
|
downloadEnabled = false;
|
||||||
downloadEnabledIcon = 'far-square'; // Disabled by default.
|
|
||||||
downloadCourseEnabled = false;
|
downloadCourseEnabled = false;
|
||||||
moduleId?: number;
|
moduleId?: number;
|
||||||
displayEnableDownload = false;
|
displayEnableDownload = false;
|
||||||
|
@ -79,14 +78,34 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
||||||
protected formatOptions?: Record<string, unknown>;
|
protected formatOptions?: Record<string, unknown>;
|
||||||
protected completionObserver?: CoreEventObserver;
|
protected completionObserver?: CoreEventObserver;
|
||||||
protected courseStatusObserver?: CoreEventObserver;
|
protected courseStatusObserver?: CoreEventObserver;
|
||||||
|
protected siteUpdatedObserver?: CoreEventObserver;
|
||||||
|
protected downloadEnabledObserver?: CoreEventObserver;
|
||||||
protected syncObserver?: CoreEventObserver;
|
protected syncObserver?: CoreEventObserver;
|
||||||
protected isDestroyed = false;
|
protected isDestroyed = false;
|
||||||
protected modulesHaveCompletion = false;
|
protected modulesHaveCompletion = false;
|
||||||
protected isGuest?: boolean;
|
protected isGuest?: boolean;
|
||||||
protected debouncedUpdateCachedCompletion?: () => void; // Update the cached completion after a certain time.
|
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> {
|
async ngOnInit(): Promise<void> {
|
||||||
|
|
||||||
|
@ -104,10 +123,12 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
||||||
this.moduleId = CoreNavigator.getRouteNumberParam('moduleId');
|
this.moduleId = CoreNavigator.getRouteNumberParam('moduleId');
|
||||||
this.isGuest = CoreNavigator.getRouteBooleanParam('isGuest');
|
this.isGuest = CoreNavigator.getRouteBooleanParam('isGuest');
|
||||||
|
|
||||||
this.displayEnableDownload = !CoreSites.getCurrentSite()?.isOfflineDisabled() &&
|
this.displayEnableDownload = !CoreSites.getRequiredCurrentSite().isOfflineDisabled() &&
|
||||||
CoreCourseFormatDelegate.displayEnableDownload(this.course);
|
CoreCourseFormatDelegate.displayEnableDownload(this.course);
|
||||||
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
|
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
|
||||||
|
|
||||||
|
this.downloadEnabled = this.displayEnableDownload && CoreCourses.getCourseDownloadOptionsEnabled();
|
||||||
|
|
||||||
this.debouncedUpdateCachedCompletion = CoreUtils.debounce(() => {
|
this.debouncedUpdateCachedCompletion = CoreUtils.debounce(() => {
|
||||||
if (this.modulesHaveCompletion) {
|
if (this.modulesHaveCompletion) {
|
||||||
CoreUtils.ignoreErrors(CoreCourse.getSections(this.course.id, false, true));
|
CoreUtils.ignoreErrors(CoreCourse.getSections(this.course.id, false, true));
|
||||||
|
@ -138,7 +159,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
protected async initListeners(): Promise<void> {
|
protected async initListeners(): Promise<void> {
|
||||||
if (this.downloadCourseEnabled) {
|
if (this.downloadCourseEnabled && !this.courseStatusObserver) {
|
||||||
// Listen for changes in course status.
|
// Listen for changes in course status.
|
||||||
this.courseStatusObserver = CoreEvents.on(CoreEvents.COURSE_STATUS_CHANGED, (data) => {
|
this.courseStatusObserver = CoreEvents.on(CoreEvents.COURSE_STATUS_CHANGED, (data) => {
|
||||||
if (data.courseId == this.course.id || data.courseId == CoreCourseProvider.ALL_COURSES_CLEARED) {
|
if (data.courseId == this.course.id || data.courseId == CoreCourseProvider.ALL_COURSES_CLEARED) {
|
||||||
|
@ -153,26 +174,30 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.completionObserver = CoreEvents.on(
|
if (!this.completionObserver) {
|
||||||
CoreEvents.COMPLETION_MODULE_VIEWED,
|
this.completionObserver = CoreEvents.on(
|
||||||
(data) => {
|
CoreEvents.COMPLETION_MODULE_VIEWED,
|
||||||
if (data && data.courseId == this.course.id) {
|
(data) => {
|
||||||
this.refreshAfterCompletionChange(true);
|
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) => {
|
this.refreshAfterCompletionChange(false);
|
||||||
if (!data || data.courseId != this.course.id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
* Toggle download enabled.
|
||||||
*/
|
*/
|
||||||
toggleDownload(): void {
|
toggleDownload(): void {
|
||||||
this.downloadEnabled = !this.downloadEnabled;
|
CoreCourses.setCourseDownloadOptionsEnabled(this.downloadEnabled);
|
||||||
this.downloadEnabledIcon = this.downloadEnabled ? 'far-check-square' : 'far-square';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -517,6 +541,8 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
||||||
this.completionObserver?.off();
|
this.completionObserver?.off();
|
||||||
this.courseStatusObserver?.off();
|
this.courseStatusObserver?.off();
|
||||||
this.syncObserver?.off();
|
this.syncObserver?.off();
|
||||||
|
this.siteUpdatedObserver?.off();
|
||||||
|
this.downloadEnabledObserver?.off();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -6,6 +6,10 @@
|
||||||
<img [src]="course.courseImage" core-external-content alt=""/>
|
<img [src]="course.courseImage" core-external-content alt=""/>
|
||||||
</ion-avatar>
|
</ion-avatar>
|
||||||
<ion-label>
|
<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)"
|
<p *ngIf="course.categoryname || (course.displayname && course.shortname && course.fullname != course.displayname)"
|
||||||
class="core-course-additional-info">
|
class="core-course-additional-info">
|
||||||
<span *ngIf="course.categoryname" class="core-course-category">
|
<span *ngIf="course.categoryname" class="core-course-category">
|
||||||
|
@ -19,10 +23,6 @@
|
||||||
</core-format-text>
|
</core-format-text>
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</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">
|
<p *ngIf="isEnrolled && course.progress! >= 0 && course.completionusertracked !== false">
|
||||||
<core-progress-bar [progress]="course.progress" a11yText="core.courses.aria:courseprogress"></core-progress-bar>
|
<core-progress-bar [progress]="course.progress" a11yText="core.courses.aria:courseprogress"></core-progress-bar>
|
||||||
</p>
|
</p>
|
||||||
|
@ -34,4 +34,14 @@
|
||||||
slot="end">
|
slot="end">
|
||||||
</ion-icon>
|
</ion-icon>
|
||||||
</ng-container>
|
</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>
|
</ion-item>
|
||||||
|
|
|
@ -12,11 +12,15 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnChanges, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { CoreCourseHelper } from '@features/course/services/course-helper';
|
import { CoreCourseProvider, CoreCourse } from '@features/course/services/course';
|
||||||
|
import { CoreCourseHelper, CorePrefetchStatusInfo } from '@features/course/services/course-helper';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
import { CoreCourses, CoreCourseSearchedData } from '../../services/courses';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreCoursesHelper, CoreCourseWithImageAndColor } from '../../services/courses-helper';
|
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.
|
* 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',
|
templateUrl: 'core-courses-course-list-item.html',
|
||||||
styleUrls: ['course-list-item.scss'],
|
styleUrls: ['course-list-item.scss'],
|
||||||
})
|
})
|
||||||
export class CoreCoursesCourseListItemComponent implements OnInit {
|
export class CoreCoursesCourseListItemComponent implements OnInit, OnDestroy, OnChanges {
|
||||||
|
|
||||||
@Input() course!: CoreCourseSearchedData & CoreCourseWithImageAndColor & {
|
@Input() course!: CoreCourseListItem; // The course to render.
|
||||||
completionusertracked?: boolean; // If the user is completion tracked.
|
|
||||||
progress?: number | null; // Progress percentage.
|
@Input() showDownload = false; // If true, will show download button.
|
||||||
}; // The course to render.
|
|
||||||
|
|
||||||
icons: CoreCoursesEnrolmentIcons[] = [];
|
icons: CoreCoursesEnrolmentIcons[] = [];
|
||||||
isEnrolled = false;
|
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> {
|
async ngOnInit(): Promise<void> {
|
||||||
CoreCoursesHelper.loadCourseColorAndImage(this.course);
|
CoreCoursesHelper.loadCourseColorAndImage(this.course);
|
||||||
|
|
||||||
// Check if the user is enrolled in the course.
|
this.isEnrolled = this.course.progress !== undefined;
|
||||||
try {
|
|
||||||
const course = await CoreCourses.getUserCourse(this.course.id);
|
|
||||||
this.course.progress = course.progress;
|
|
||||||
this.course.completionusertracked = course.completionusertracked;
|
|
||||||
|
|
||||||
this.isEnrolled = true;
|
if (!this.isEnrolled) {
|
||||||
} catch {
|
try {
|
||||||
this.isEnrolled = false;
|
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.icons = [];
|
||||||
|
|
||||||
this.course.enrollmentmethods.forEach((instance) => {
|
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.
|
* 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() 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.
|
@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 = {
|
prefetchCourseData: CorePrefetchStatusInfo = {
|
||||||
icon: '',
|
icon: '',
|
||||||
statusTranslatable: 'core.loading',
|
statusTranslatable: 'core.loading',
|
||||||
|
@ -64,6 +62,7 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy, On
|
||||||
progress = -1;
|
progress = -1;
|
||||||
completionUserTracked: boolean | undefined = false;
|
completionUserTracked: boolean | undefined = false;
|
||||||
|
|
||||||
|
protected courseStatus = CoreConstants.NOT_DOWNLOADED;
|
||||||
protected isDestroyed = false;
|
protected isDestroyed = false;
|
||||||
protected courseStatusObserver?: CoreEventObserver;
|
protected courseStatusObserver?: CoreEventObserver;
|
||||||
protected siteUpdatedObserver?: CoreEventObserver;
|
protected siteUpdatedObserver?: CoreEventObserver;
|
||||||
|
@ -109,7 +108,7 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy, On
|
||||||
* Initialize prefetch course.
|
* Initialize prefetch course.
|
||||||
*/
|
*/
|
||||||
async initPrefetchCourse(): Promise<void> {
|
async initPrefetchCourse(): Promise<void> {
|
||||||
if (typeof this.courseStatusObserver != 'undefined') {
|
if (this.courseStatusObserver !== undefined) {
|
||||||
// Already initialized.
|
// Already initialized.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { RouterModule, Routes } from '@angular/router';
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
redirectTo: 'my',
|
redirectTo: 'list',
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -33,22 +33,10 @@ const routes: Routes = [
|
||||||
.then(m => m.CoreCoursesCategoriesPageModule),
|
.then(m => m.CoreCoursesCategoriesPageModule),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'all',
|
path: 'list',
|
||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
import('./pages/available-courses/available-courses.module')
|
import('./pages/list/list.module')
|
||||||
.then(m => m.CoreCoursesAvailableCoursesPageModule),
|
.then(m => m.CoreCoursesListPageModule),
|
||||||
},
|
|
||||||
{
|
|
||||||
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),
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -15,9 +15,12 @@
|
||||||
import { APP_INITIALIZER, NgModule, Type } from '@angular/core';
|
import { APP_INITIALIZER, NgModule, Type } from '@angular/core';
|
||||||
import { Routes } from '@angular/router';
|
import { Routes } from '@angular/router';
|
||||||
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
|
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 { CoreMainMenuHomeRoutingModule } from '@features/mainmenu/pages/home/home-routing.module';
|
||||||
import { CoreMainMenuHomeDelegate } from '@features/mainmenu/services/home-delegate';
|
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 { CorePushNotificationsDelegate } from '@features/pushnotifications/services/push-delegate';
|
||||||
import { CoreCoursesProvider } from './services/courses';
|
import { CoreCoursesProvider } from './services/courses';
|
||||||
import { CoreCoursesHelperProvider } from './services/courses-helper';
|
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 { CoreDashboardHomeHandler, CoreDashboardHomeHandlerService } from './services/handlers/dashboard-home';
|
||||||
import { CoreCoursesDashboardLinkHandler } from './services/handlers/dashboard-link';
|
import { CoreCoursesDashboardLinkHandler } from './services/handlers/dashboard-link';
|
||||||
import { CoreCoursesEnrolPushClickHandler } from './services/handlers/enrol-push-click';
|
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';
|
import { CoreCoursesRequestPushClickHandler } from './services/handlers/request-push-click';
|
||||||
|
|
||||||
export const CORE_COURSES_SERVICES: Type<unknown>[] = [
|
export const CORE_COURSES_SERVICES: Type<unknown>[] = [
|
||||||
|
@ -42,10 +48,6 @@ const mainMenuHomeChildrenRoutes: Routes = [
|
||||||
path: CoreDashboardHomeHandlerService.PAGE_NAME,
|
path: CoreDashboardHomeHandlerService.PAGE_NAME,
|
||||||
loadChildren: () => import('./pages/dashboard/dashboard.module').then(m => m.CoreCoursesDashboardPageModule),
|
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 = [
|
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({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CoreMainMenuHomeRoutingModule.forChild({
|
CoreMainMenuHomeRoutingModule.forChild({
|
||||||
children: mainMenuHomeChildrenRoutes,
|
children: mainMenuHomeChildrenRoutes,
|
||||||
siblings: mainMenuHomeSiblingRoutes,
|
siblings: mainMenuHomeSiblingRoutes,
|
||||||
}),
|
}),
|
||||||
|
CoreMainMenuRoutingModule.forChild({ children: mainMenuTabRoutes }),
|
||||||
|
CoreMainMenuTabRoutingModule.forChild(mainMenuTabRoutes),
|
||||||
],
|
],
|
||||||
|
exports: [CoreMainMenuRoutingModule],
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
provide: APP_INITIALIZER,
|
provide: APP_INITIALIZER,
|
||||||
multi: true,
|
multi: true,
|
||||||
useValue: () => {
|
useValue: () => {
|
||||||
CoreMainMenuHomeDelegate.registerHandler(CoreDashboardHomeHandler.instance);
|
CoreMainMenuHomeDelegate.registerHandler(CoreDashboardHomeHandler.instance);
|
||||||
CoreMainMenuHomeDelegate.registerHandler(CoreCoursesMyCoursesHomeHandler.instance);
|
CoreMainMenuDelegate.registerHandler(CoreCoursesMyCoursesHomeHandler.instance);
|
||||||
CoreContentLinksDelegate.registerHandler(CoreCoursesCourseLinkHandler.instance);
|
CoreContentLinksDelegate.registerHandler(CoreCoursesCourseLinkHandler.instance);
|
||||||
CoreContentLinksDelegate.registerHandler(CoreCoursesIndexLinkHandler.instance);
|
CoreContentLinksDelegate.registerHandler(CoreCoursesIndexLinkHandler.instance);
|
||||||
CoreContentLinksDelegate.registerHandler(CoreCoursesDashboardLinkHandler.instance);
|
CoreContentLinksDelegate.registerHandler(CoreCoursesDashboardLinkHandler.instance);
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
"selfenrolment": "Self enrolment",
|
"selfenrolment": "Self enrolment",
|
||||||
"sendpaymentbutton": "Send payment via PayPal",
|
"sendpaymentbutton": "Send payment via PayPal",
|
||||||
"show": "Restore to view",
|
"show": "Restore to view",
|
||||||
|
"showonlyenrolled": "Show only my courses",
|
||||||
"therearecourses": "There are {{$a}} courses",
|
"therearecourses": "There are {{$a}} courses",
|
||||||
"totalcoursesearchresults": "Total courses: {{$a}}"
|
"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 [text]="title" contextLevel="coursecat" [contextInstanceId]="currentCategory && currentCategory!.id">
|
||||||
</core-format-text>
|
</core-format-text>
|
||||||
</h1>
|
</h1>
|
||||||
|
<ion-buttons slot="end">
|
||||||
|
<core-context-menu>
|
||||||
|
<core-context-menu-item *ngIf="downloadCourseEnabled || downloadCoursesEnabled" [priority]="1000"
|
||||||
|
[content]="'core.settings.showdownloadoptions' | translate" (action)="toggleDownload()"
|
||||||
|
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-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content>
|
<ion-content>
|
||||||
|
@ -17,22 +27,18 @@
|
||||||
<ion-item *ngIf="currentCategory" class="ion-text-wrap">
|
<ion-item *ngIf="currentCategory" class="ion-text-wrap">
|
||||||
<ion-icon name="fas-folder" slot="start" [attr.aria-label]="'core.category' | translate"></ion-icon>
|
<ion-icon name="fas-folder" slot="start" [attr.aria-label]="'core.category' | translate"></ion-icon>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>
|
<p class="item-heading">
|
||||||
<core-format-text [text]="currentCategory!.name" contextLevel="coursecat"
|
<core-format-text [text]="currentCategory.name" contextLevel="coursecat"
|
||||||
[contextInstanceId]="currentCategory!.id"></core-format-text>
|
[contextInstanceId]="currentCategory.id"></core-format-text>
|
||||||
</h2>
|
</p>
|
||||||
</ion-label>
|
<p *ngIf="currentCategory.description">
|
||||||
</ion-item>
|
<core-format-text [text]="currentCategory.description" maxHeight="60" contextLevel="coursecat"
|
||||||
<ion-item class="ion-text-wrap" *ngIf="currentCategory && currentCategory!.description">
|
[contextInstanceId]="currentCategory.id"></core-format-text>
|
||||||
<ion-label>
|
</p>
|
||||||
<h2>
|
|
||||||
<core-format-text [text]="currentCategory!.description" maxHeight="60" contextLevel="coursecat"
|
|
||||||
[contextInstanceId]="currentCategory!.id"></core-format-text>
|
|
||||||
</h2>
|
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<div *ngIf="categories.length > 0">
|
<ng-container *ngIf="categories.length > 0">
|
||||||
<ion-item-divider>
|
<ion-item-divider>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>{{ 'core.courses.categories' | translate }}</h2>
|
<h2>{{ 'core.courses.categories' | translate }}</h2>
|
||||||
|
@ -48,22 +54,24 @@
|
||||||
</core-format-text>
|
</core-format-text>
|
||||||
</h2>
|
</h2>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-badge slot="end" *ngIf="category.coursecount > 0" color="light">
|
<ion-badge slot="end" *ngIf="!showOnlyEnrolled && category.coursecount > 0" color="light">
|
||||||
<span aria-hidden="true">{{ category.coursecount }}</span>
|
<span aria-hidden="true">{{ category.coursecount }}</span>
|
||||||
<span class="sr-only">{{ 'core.courses.therearecourses' | translate:{ $a: category.coursecount } }}</span>
|
<span class="sr-only">{{ 'core.courses.therearecourses' | translate:{ $a: category.coursecount } }}</span>
|
||||||
</ion-badge>
|
</ion-badge>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</ng-container>
|
||||||
|
|
||||||
<div *ngIf="courses.length > 0">
|
<ng-container *ngIf="courses.length > 0">
|
||||||
<ion-item-divider>
|
<ion-item-divider>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>{{ 'core.courses.courses' | translate }}</h2>
|
<h2 *ngIf="!showOnlyEnrolled">{{ 'core.courses.courses' | translate }}</h2>
|
||||||
|
<h2 *ngIf="showOnlyEnrolled">{{ 'core.courses.mycourses' | translate }}</h2>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item-divider>
|
</ion-item-divider>
|
||||||
<core-courses-course-list-item *ngFor="let course of courses" [course]="course"></core-courses-course-list-item>
|
<core-courses-course-list-item *ngFor="let course of courses" [course]="course" [showDownload]="downloadEnabled">
|
||||||
</div>
|
</core-courses-course-list-item>
|
||||||
|
</ng-container>
|
||||||
<core-empty-box *ngIf="!categories.length && !courses.length" icon="fas-graduation-cap"
|
<core-empty-box *ngIf="!categories.length && !courses.length" icon="fas-graduation-cap"
|
||||||
[message]="'core.courses.nocoursesyet' | translate">
|
[message]="'core.courses.nocoursesyet' | translate">
|
||||||
</core-empty-box>
|
</core-empty-box>
|
||||||
|
|
|
@ -12,14 +12,15 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { IonRefresher } from '@ionic/angular';
|
import { IonRefresher } from '@ionic/angular';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { CoreCategoryData, CoreCourses, CoreCourseSearchedData } from '../../services/courses';
|
import { CoreCategoryData, CoreCourseListItem, CoreCourses, CoreCoursesProvider } from '../../services/courses';
|
||||||
import { Translate } from '@singletons';
|
import { Translate } from '@singletons';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays a list of categories and the courses in the current category if any.
|
* Page that displays a list of categories and the courses in the current category if any.
|
||||||
|
@ -28,25 +29,69 @@ import { CoreNavigator } from '@services/navigator';
|
||||||
selector: 'page-core-courses-categories',
|
selector: 'page-core-courses-categories',
|
||||||
templateUrl: 'categories.html',
|
templateUrl: 'categories.html',
|
||||||
})
|
})
|
||||||
export class CoreCoursesCategoriesPage implements OnInit {
|
export class CoreCoursesCategoriesPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
title: string;
|
title: string;
|
||||||
currentCategory?: CoreCategoryData;
|
currentCategory?: CoreCategoryData;
|
||||||
categories: CoreCategoryData[] = [];
|
categories: CoreCategoryData[] = [];
|
||||||
courses: CoreCourseSearchedData[] = [];
|
courses: CoreCourseListItem[] = [];
|
||||||
categoriesLoaded = false;
|
categoriesLoaded = false;
|
||||||
|
|
||||||
|
showOnlyEnrolled = false;
|
||||||
|
|
||||||
|
downloadEnabled = false;
|
||||||
|
downloadCourseEnabled = false;
|
||||||
|
downloadCoursesEnabled = false;
|
||||||
|
|
||||||
|
protected categoryCourses: CoreCourseListItem[] = [];
|
||||||
|
protected currentSiteId: string;
|
||||||
protected categoryId = 0;
|
protected categoryId = 0;
|
||||||
|
protected myCoursesObserver: CoreEventObserver;
|
||||||
|
protected siteUpdatedObserver: CoreEventObserver;
|
||||||
|
protected downloadEnabledObserver: CoreEventObserver;
|
||||||
|
protected isDestroyed = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.title = Translate.instant('core.courses.categories');
|
this.title = Translate.instant('core.courses.categories');
|
||||||
|
this.currentSiteId = CoreSites.getRequiredCurrentSite().getId();
|
||||||
|
|
||||||
|
// Update list if user enrols in a course.
|
||||||
|
this.myCoursesObserver = CoreEvents.on(
|
||||||
|
CoreCoursesProvider.EVENT_MY_COURSES_UPDATED,
|
||||||
|
(data) => {
|
||||||
|
if (data.action == CoreCoursesProvider.ACTION_ENROL) {
|
||||||
|
this.fetchCategories();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
this.currentSiteId,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Refresh the enabled flags if site is updated.
|
||||||
|
this.siteUpdatedObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
|
||||||
|
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
|
||||||
|
this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
|
||||||
|
|
||||||
|
this.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 {
|
ngOnInit(): void {
|
||||||
this.categoryId = CoreNavigator.getRouteNumberParam('id') || 0;
|
this.categoryId = CoreNavigator.getRouteNumberParam('id') || 0;
|
||||||
|
this.showOnlyEnrolled = CoreNavigator.getRouteBooleanParam('enrolled') || this.showOnlyEnrolled;
|
||||||
|
|
||||||
|
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
|
||||||
|
this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
|
||||||
|
|
||||||
|
this.downloadEnabled =
|
||||||
|
(this.downloadCourseEnabled || this.downloadCoursesEnabled) && CoreCourses.getCourseDownloadOptionsEnabled();
|
||||||
|
|
||||||
this.fetchCategories().finally(() => {
|
this.fetchCategories().finally(() => {
|
||||||
this.categoriesLoaded = true;
|
this.categoriesLoaded = true;
|
||||||
|
@ -87,13 +132,14 @@ export class CoreCoursesCategoriesPage implements OnInit {
|
||||||
this.title = this.currentCategory.name;
|
this.title = this.currentCategory.name;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.courses = await CoreCourses.getCoursesByField('category', this.categoryId);
|
this.categoryCourses = await CoreCourses.getCoursesByField('category', this.categoryId);
|
||||||
|
await this.filterEnrolled();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcourses', true);
|
!this.isDestroyed && CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcourses', true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcategories', true);
|
!this.isDestroyed && CoreDomUtils.showErrorModalDefault(error, 'core.courses.errorloadcategories', true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,7 +154,7 @@ export class CoreCoursesCategoriesPage implements OnInit {
|
||||||
promises.push(CoreCourses.invalidateUserCourses());
|
promises.push(CoreCourses.invalidateUserCourses());
|
||||||
promises.push(CoreCourses.invalidateCategories(this.categoryId, true));
|
promises.push(CoreCourses.invalidateCategories(this.categoryId, true));
|
||||||
promises.push(CoreCourses.invalidateCoursesByField('category', this.categoryId));
|
promises.push(CoreCourses.invalidateCoursesByField('category', this.categoryId));
|
||||||
promises.push(CoreSites.getCurrentSite()!.invalidateConfig());
|
promises.push(CoreSites.getRequiredCurrentSite().invalidateConfig());
|
||||||
|
|
||||||
Promise.all(promises).finally(() => {
|
Promise.all(promises).finally(() => {
|
||||||
this.fetchCategories().finally(() => {
|
this.fetchCategories().finally(() => {
|
||||||
|
@ -123,7 +169,53 @@ export class CoreCoursesCategoriesPage implements OnInit {
|
||||||
* @param categoryId Category Id.
|
* @param categoryId Category Id.
|
||||||
*/
|
*/
|
||||||
openCategory(categoryId: number): void {
|
openCategory(categoryId: number): void {
|
||||||
CoreNavigator.navigateToSitePath('courses/categories/' + categoryId);
|
CoreNavigator.navigateToSitePath(
|
||||||
|
'courses/categories/' + categoryId,
|
||||||
|
{ params: {
|
||||||
|
enrolled: this.showOnlyEnrolled,
|
||||||
|
} },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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>
|
</ion-button>
|
||||||
<core-context-menu>
|
<core-context-menu>
|
||||||
<core-context-menu-item *ngIf="(downloadCourseEnabled || downloadCoursesEnabled)" [priority]="1000"
|
<core-context-menu-item *ngIf="(downloadCourseEnabled || downloadCoursesEnabled)" [priority]="1000"
|
||||||
[content]="'core.settings.showdownloadoptions' | translate" (action)="toggleDownload()"
|
[content]="'core.settings.showdownloadoptions' | translate" (action)="switchDownload()"
|
||||||
[iconAction]="downloadEnabledIcon"></core-context-menu-item>
|
iconAction="toggle" [(toggle)]="downloadEnabled"></core-context-menu-item>
|
||||||
<core-context-menu-item *ngIf="(downloadCourseEnabled || downloadCoursesEnabled)" [priority]="500"
|
<core-context-menu-item [priority]="500"
|
||||||
[content]="'addon.storagemanager.managestorage' | translate"
|
[content]="'addon.storagemanager.managestorage' | translate"
|
||||||
(action)="manageCoursesStorage()" iconAction="fas-archive"></core-context-menu-item>
|
(action)="manageCoursesStorage()" iconAction="fas-archive"></core-context-menu-item>
|
||||||
</core-context-menu>
|
</core-context-menu>
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
<ion-content>
|
<ion-content>
|
||||||
|
|
|
@ -40,30 +40,39 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy {
|
||||||
downloadEnabled = false;
|
downloadEnabled = false;
|
||||||
downloadCourseEnabled = false;
|
downloadCourseEnabled = false;
|
||||||
downloadCoursesEnabled = false;
|
downloadCoursesEnabled = false;
|
||||||
downloadEnabledIcon = 'far-square';
|
|
||||||
userId?: number;
|
userId?: number;
|
||||||
blocks: Partial<CoreCourseBlock>[] = [];
|
blocks: Partial<CoreCourseBlock>[] = [];
|
||||||
loaded = false;
|
loaded = false;
|
||||||
|
|
||||||
protected updateSiteObserver?: CoreEventObserver;
|
protected updateSiteObserver: CoreEventObserver;
|
||||||
|
protected downloadEnabledObserver: CoreEventObserver;
|
||||||
/**
|
|
||||||
* Initialize the component.
|
|
||||||
*/
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite();
|
|
||||||
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
|
|
||||||
this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
|
|
||||||
|
|
||||||
|
constructor() {
|
||||||
// Refresh the enabled flags if site is updated.
|
// Refresh the enabled flags if site is updated.
|
||||||
this.updateSiteObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
|
this.updateSiteObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
|
||||||
this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite();
|
this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite();
|
||||||
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
|
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
|
||||||
this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
|
this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
|
||||||
|
|
||||||
this.switchDownload(this.downloadEnabled && this.downloadCourseEnabled && this.downloadCoursesEnabled);
|
this.downloadEnabled = (this.downloadCourseEnabled || this.downloadCoursesEnabled) && this.downloadEnabled;
|
||||||
}, CoreSites.getCurrentSiteId());
|
}, 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();
|
this.loadContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,21 +148,10 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle download enabled.
|
* Switch download enabled.
|
||||||
*/
|
*/
|
||||||
toggleDownload(): void {
|
switchDownload(): void {
|
||||||
this.switchDownload(!this.downloadEnabled);
|
CoreCourses.setCourseDownloadOptionsEnabled(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 });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -167,14 +165,15 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy {
|
||||||
* Go to search courses.
|
* Go to search courses.
|
||||||
*/
|
*/
|
||||||
async openSearch(): Promise<void> {
|
async openSearch(): Promise<void> {
|
||||||
CoreNavigator.navigateToSitePath('/courses/search');
|
CoreNavigator.navigateToSitePath('/courses/list', { params : { mode: 'search' } });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component being destroyed.
|
* Component being destroyed.
|
||||||
*/
|
*/
|
||||||
ngOnDestroy(): void {
|
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 { CoreCoursesComponentsModule } from '../../components/components.module';
|
||||||
import { CoreSearchComponentsModule } from '@features/search/components/components.module';
|
import { CoreSearchComponentsModule } from '@features/search/components/components.module';
|
||||||
|
|
||||||
import { CoreCoursesSearchPage } from './search';
|
import { CoreCoursesListPage } from './list';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: CoreCoursesSearchPage,
|
component: CoreCoursesListPage,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -36,8 +36,8 @@ const routes: Routes = [
|
||||||
CoreSearchComponentsModule,
|
CoreSearchComponentsModule,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
CoreCoursesSearchPage,
|
CoreCoursesListPage,
|
||||||
],
|
],
|
||||||
exports: [RouterModule],
|
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.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { CoreLogger } from '@singletons/logger';
|
|
||||||
import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@services/sites';
|
import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@services/sites';
|
||||||
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
|
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
import { CoreStatusWithWarningsWSResponse, CoreWarningsWSResponse, CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws';
|
import { CoreStatusWithWarningsWSResponse, CoreWarningsWSResponse, CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws';
|
||||||
import { CoreEvents } from '@singletons/events';
|
import { CoreEvents } from '@singletons/events';
|
||||||
import { CoreWSError } from '@classes/errors/wserror';
|
import { CoreWSError } from '@classes/errors/wserror';
|
||||||
|
import { CoreCourseWithImageAndColor } from './courses-helper';
|
||||||
|
|
||||||
const ROOT_CACHE_KEY = 'mmCourses:';
|
const ROOT_CACHE_KEY = 'mmCourses:';
|
||||||
|
|
||||||
|
@ -62,12 +62,9 @@ export class CoreCoursesProvider {
|
||||||
static readonly STATE_HIDDEN = 'hidden';
|
static readonly STATE_HIDDEN = 'hidden';
|
||||||
static readonly STATE_FAVOURITE = 'favourite';
|
static readonly STATE_FAVOURITE = 'favourite';
|
||||||
|
|
||||||
protected logger: CoreLogger;
|
|
||||||
protected userCoursesIds: { [id: number]: boolean } = {}; // Use an object to make it faster to search.
|
protected userCoursesIds: { [id: number]: boolean } = {}; // Use an object to make it faster to search.
|
||||||
|
|
||||||
constructor() {
|
protected downloadOptionsEnabled = false;
|
||||||
this.logger = CoreLogger.getInstance('CoreCoursesProvider');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether current site supports getting course options.
|
* Whether current site supports getting course options.
|
||||||
|
@ -1121,6 +1118,7 @@ export class CoreCoursesProvider {
|
||||||
* @param text Text to search.
|
* @param text Text to search.
|
||||||
* @param page Page to get.
|
* @param page Page to get.
|
||||||
* @param perPage Number of courses per page. Defaults to CoreCoursesProvider.SEARCH_PER_PAGE.
|
* @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.
|
* @param siteId Site ID. If not defined, use current site.
|
||||||
* @return Promise resolved with the courses and the total of matches.
|
* @return Promise resolved with the courses and the total of matches.
|
||||||
*/
|
*/
|
||||||
|
@ -1128,6 +1126,7 @@ export class CoreCoursesProvider {
|
||||||
text: string,
|
text: string,
|
||||||
page: number = 0,
|
page: number = 0,
|
||||||
perPage: number = CoreCoursesProvider.SEARCH_PER_PAGE,
|
perPage: number = CoreCoursesProvider.SEARCH_PER_PAGE,
|
||||||
|
limitToEnrolled: boolean = false,
|
||||||
siteId?: string,
|
siteId?: string,
|
||||||
): Promise<{ total: number; courses: CoreCourseBasicSearchedData[] }> {
|
): Promise<{ total: number; courses: CoreCourseBasicSearchedData[] }> {
|
||||||
const site = await CoreSites.getSite(siteId);
|
const site = await CoreSites.getSite(siteId);
|
||||||
|
@ -1136,6 +1135,7 @@ export class CoreCoursesProvider {
|
||||||
criteriavalue: text,
|
criteriavalue: text,
|
||||||
page: page,
|
page: page,
|
||||||
perpage: perPage,
|
perpage: perPage,
|
||||||
|
limittoenrolled: limitToEnrolled,
|
||||||
};
|
};
|
||||||
const preSets: CoreSiteWSPreSets = {
|
const preSets: CoreSiteWSPreSets = {
|
||||||
getFromCache: false,
|
getFromCache: false,
|
||||||
|
@ -1216,6 +1216,29 @@ export class CoreCoursesProvider {
|
||||||
return site.write('core_course_set_favourite_courses', params);
|
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);
|
export const CoreCourses = makeSingleton(CoreCoursesProvider);
|
||||||
|
@ -1358,6 +1381,14 @@ export type CoreCourseSearchedData = CoreCourseBasicSearchedData & {
|
||||||
courseformatoptions?: CoreCourseFormatOption[]; // Additional options for particular course format.
|
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 & {
|
export type CoreCourseGetCoursesData = CoreEnrolledCourseBasicData & {
|
||||||
categoryid: number; // Category id.
|
categoryid: number; // Category id.
|
||||||
categorysortorder?: number; // Sort order into the category.
|
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.
|
* @param site Site. If not defined, use current site.
|
||||||
* @return Whether it's disabled.
|
* @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 { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
import { makeSingleton } from '@singletons';
|
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).
|
* Handler to treat links to course index (list of courses).
|
||||||
|
@ -31,25 +31,22 @@ export class CoreCoursesIndexLinkHandlerService extends CoreContentLinksHandlerB
|
||||||
pattern = /\/course\/?(index\.php.*)?$/;
|
pattern = /\/course\/?(index\.php.*)?$/;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the list of actions for a link (url).
|
* @inheritdoc
|
||||||
*
|
|
||||||
* @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.
|
|
||||||
*/
|
*/
|
||||||
getActions(siteIds: string[], url: string, params: Params): CoreContentLinksAction[] {
|
getActions(siteIds: string[], url: string, params: Params): CoreContentLinksAction[] {
|
||||||
return [{
|
return [{
|
||||||
action: (siteId): void => {
|
action: (siteId): void => {
|
||||||
let pageName = CoreCoursesMyCoursesHomeHandlerService.PAGE_NAME;
|
let pageName = CoreCoursesMyCoursesMainMenuHandlerService.PAGE_NAME;
|
||||||
|
const pageParams: Params = {};
|
||||||
|
|
||||||
if (params.categoryid) {
|
if (params.categoryid) {
|
||||||
pageName += '/categories/' + params.categoryid;
|
pageName += '/categories/' + params.categoryid;
|
||||||
} else {
|
} 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.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
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 { CoreSiteHomeHomeHandler } from '@features/sitehome/services/handlers/sitehome-home';
|
||||||
|
import { CoreSites } from '@services/sites';
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
import { CoreCourses } from '../courses';
|
import { CoreCourses } from '../courses';
|
||||||
import { CoreDashboardHomeHandler } from './dashboard-home';
|
import { CoreDashboardHomeHandler } from './dashboard-home';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler to add my courses into home page.
|
* Handler to add my courses into main menu.
|
||||||
*/
|
*/
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class CoreCoursesMyCoursesHomeHandlerService implements CoreMainMenuHomeHandler {
|
export class CoreCoursesMyCoursesMainMenuHandlerService implements CoreMainMenuHandler {
|
||||||
|
|
||||||
static readonly PAGE_NAME = 'courses';
|
static readonly PAGE_NAME = 'courses';
|
||||||
|
|
||||||
name = 'CoreCoursesMyCourses';
|
name = 'CoreCoursesMyCourses';
|
||||||
priority = 900;
|
priority = 850;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the handler is enabled on a site level.
|
* @inheritdoc
|
||||||
*
|
|
||||||
* @return Whether or not the handler is enabled on a site level.
|
|
||||||
*/
|
*/
|
||||||
isEnabled(): Promise<boolean> {
|
async isEnabled(): Promise<boolean> {
|
||||||
return this.isEnabledForSite();
|
const siteId = CoreSites.getCurrentSiteId();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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> {
|
|
||||||
const disabled = await CoreCourses.isMyCoursesDisabled(siteId);
|
const disabled = await CoreCourses.isMyCoursesDisabled(siteId);
|
||||||
|
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
|
@ -59,20 +49,17 @@ export class CoreCoursesMyCoursesHomeHandlerService implements CoreMainMenuHomeH
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the data needed to render the handler.
|
* @inheritdoc
|
||||||
*
|
|
||||||
* @return Data needed to render the handler.
|
|
||||||
*/
|
*/
|
||||||
getDisplayData(): CoreMainMenuHomeHandlerToDisplay {
|
getDisplayData(): CoreMainMenuHandlerData {
|
||||||
return {
|
return {
|
||||||
title: 'core.courses.mycourses',
|
title: 'core.courses.mycourses',
|
||||||
page: CoreCoursesMyCoursesHomeHandlerService.PAGE_NAME,
|
page: CoreCoursesMyCoursesMainMenuHandlerService.PAGE_NAME,
|
||||||
class: 'core-courses-my-courses-handler',
|
class: 'core-courses-my-courses-handler',
|
||||||
icon: 'fas-graduation-cap',
|
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-icon name="fas-search" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<core-context-menu>
|
<core-context-menu>
|
||||||
<core-context-menu-item *ngIf="(downloadCourseEnabled || downloadCoursesEnabled)" [priority]="1000"
|
<core-context-menu-item [priority]="1000" *ngIf="displayEnableDownload"
|
||||||
[content]="'core.settings.showdownloadoptions' | translate" (action)="toggleDownload()"
|
[content]="'core.settings.showdownloadoptions' | translate" (action)="switchDownload()"
|
||||||
[iconAction]="downloadEnabledIcon"></core-context-menu-item>
|
iconAction="toggle" [(toggle)]="downloadEnabled"></core-context-menu-item>
|
||||||
<core-context-menu-item *ngIf="(downloadCourseEnabled || downloadCoursesEnabled)" [priority]="500"
|
<core-context-menu-item [priority]="500"
|
||||||
[content]="'addon.storagemanager.managestorage' | translate"
|
[content]="'addon.storagemanager.managestorage' | translate"
|
||||||
(action)="manageCoursesStorage()" iconAction="fas-archive"></core-context-menu-item>
|
(action)="manageCoursesStorage()" iconAction="fas-archive"></core-context-menu-item>
|
||||||
</core-context-menu>
|
</core-context-menu>
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
<ion-content>
|
<ion-content>
|
||||||
|
|
|
@ -50,31 +50,32 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy {
|
||||||
siteHomeId = 1;
|
siteHomeId = 1;
|
||||||
currentSite!: CoreSite;
|
currentSite!: CoreSite;
|
||||||
searchEnabled = false;
|
searchEnabled = false;
|
||||||
|
displayEnableDownload = false;
|
||||||
downloadEnabled = false;
|
downloadEnabled = false;
|
||||||
downloadCourseEnabled = false;
|
|
||||||
downloadCoursesEnabled = false;
|
|
||||||
downloadEnabledIcon = 'far-square';
|
|
||||||
newsForumModule?: NewsForum;
|
newsForumModule?: NewsForum;
|
||||||
|
|
||||||
protected updateSiteObserver?: CoreEventObserver;
|
protected updateSiteObserver: CoreEventObserver;
|
||||||
|
protected downloadEnabledObserver: CoreEventObserver;
|
||||||
/**
|
|
||||||
* Page being initialized.
|
|
||||||
*/
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite();
|
|
||||||
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
|
|
||||||
this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
|
|
||||||
|
|
||||||
|
constructor() {
|
||||||
// Refresh the enabled flags if site is updated.
|
// Refresh the enabled flags if site is updated.
|
||||||
this.updateSiteObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
|
this.updateSiteObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
|
||||||
this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite();
|
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());
|
}, 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.currentSite = CoreSites.getRequiredCurrentSite();
|
||||||
this.siteHomeId = CoreSites.getCurrentSiteHomeId();
|
this.siteHomeId = CoreSites.getCurrentSiteHomeId();
|
||||||
|
|
||||||
|
@ -84,6 +85,9 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy {
|
||||||
CoreCourseHelper.openModule(module, this.siteHomeId, undefined, modParams);
|
CoreCourseHelper.openModule(module, this.siteHomeId, undefined, modParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.displayEnableDownload = !CoreSites.getRequiredCurrentSite().isOfflineDisabled();
|
||||||
|
this.downloadEnabled = CoreCourses.getCourseDownloadOptionsEnabled();
|
||||||
|
|
||||||
this.loadContent().finally(() => {
|
this.loadContent().finally(() => {
|
||||||
this.dataLoaded = true;
|
this.dataLoaded = true;
|
||||||
});
|
});
|
||||||
|
@ -190,21 +194,10 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle download enabled.
|
* Switch download enabled.
|
||||||
*/
|
*/
|
||||||
toggleDownload(): void {
|
switchDownload(): void {
|
||||||
this.switchDownload(!this.downloadEnabled);
|
CoreCourses.setCourseDownloadOptionsEnabled(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 });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -218,21 +211,21 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy {
|
||||||
* Go to search courses.
|
* Go to search courses.
|
||||||
*/
|
*/
|
||||||
openSearch(): void {
|
openSearch(): void {
|
||||||
CoreNavigator.navigateToSitePath('courses/search');
|
CoreNavigator.navigateToSitePath('courses/list', { params : { mode: 'search' } });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Go to available courses.
|
* Go to available courses.
|
||||||
*/
|
*/
|
||||||
openAvailableCourses(): void {
|
openAvailableCourses(): void {
|
||||||
CoreNavigator.navigateToSitePath('courses/all');
|
CoreNavigator.navigateToSitePath('courses/list', { params : { mode: 'all' } });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Go to my courses.
|
* Go to my courses.
|
||||||
*/
|
*/
|
||||||
openMyCourses(): void {
|
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.
|
* Component being destroyed.
|
||||||
*/
|
*/
|
||||||
ngOnDestroy(): void {
|
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.
|
// Get number of news items to show.
|
||||||
add = !!CoreSites.getCurrentSite()?.getStoredConfig('newsitems');
|
add = !!CoreSites.getCurrentSite()?.getStoredConfig('newsitems');
|
||||||
break;
|
break;
|
||||||
case FrontPageItemNames['LIST_OF_CATEGORIES']:
|
|
||||||
case FrontPageItemNames['COMBO_LIST']:
|
case FrontPageItemNames['COMBO_LIST']:
|
||||||
|
itemNumber = FrontPageItemNames['LIST_OF_CATEGORIES']; // Do not break here.
|
||||||
|
case FrontPageItemNames['LIST_OF_CATEGORIES']:
|
||||||
case FrontPageItemNames['LIST_OF_COURSE']:
|
case FrontPageItemNames['LIST_OF_COURSE']:
|
||||||
add = true;
|
|
||||||
if (itemNumber == FrontPageItemNames['COMBO_LIST']) {
|
|
||||||
itemNumber = FrontPageItemNames['LIST_OF_CATEGORIES'];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case FrontPageItemNames['ENROLLED_COURSES']:
|
case FrontPageItemNames['ENROLLED_COURSES']:
|
||||||
if (!CoreCourses.isMyCoursesDisabledInSite()) {
|
add = true;
|
||||||
const courses = await CoreCourses.getUserCourses();
|
|
||||||
|
|
||||||
add = courses.length > 0;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case FrontPageItemNames['COURSE_SEARCH_BOX']:
|
case FrontPageItemNames['COURSE_SEARCH_BOX']:
|
||||||
add = !CoreCourses.isSearchCoursesDisabledInSite();
|
add = !CoreCourses.isSearchCoursesDisabledInSite();
|
||||||
|
|
|
@ -124,7 +124,7 @@ export class CoreSitePluginsCallWSBaseDirective implements OnInit, OnDestroy {
|
||||||
invalidate(): Promise<void> {
|
invalidate(): Promise<void> {
|
||||||
const params = this.getParamsForWS();
|
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