commit
ee8c9de101
|
@ -1106,6 +1106,7 @@
|
||||||
"addon.storagemanager.deletecourses": "local_moodlemobileapp",
|
"addon.storagemanager.deletecourses": "local_moodlemobileapp",
|
||||||
"addon.storagemanager.deletedatafrom": "local_moodlemobileapp",
|
"addon.storagemanager.deletedatafrom": "local_moodlemobileapp",
|
||||||
"addon.storagemanager.info": "local_moodlemobileapp",
|
"addon.storagemanager.info": "local_moodlemobileapp",
|
||||||
|
"addon.storagemanager.managecoursestorage": "local_moodlemobileapp",
|
||||||
"addon.storagemanager.managestorage": "local_moodlemobileapp",
|
"addon.storagemanager.managestorage": "local_moodlemobileapp",
|
||||||
"addon.storagemanager.storageused": "local_moodlemobileapp",
|
"addon.storagemanager.storageused": "local_moodlemobileapp",
|
||||||
"assets.countries.AD": "countries",
|
"assets.countries.AD": "countries",
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<core-course-module *ngFor="let module of mainMenuBlock.modules" [module]="module" [courseId]="siteHomeId"
|
<core-course-module *ngFor="let module of mainMenuBlock.modules" [module]="module" [section]="mainMenuBlock"></core-course-module>
|
||||||
[downloadEnabled]="downloadEnabled" [section]="mainMenuBlock"></core-course-module>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</core-loading>
|
</core-loading>
|
||||||
|
|
|
@ -91,7 +91,7 @@ export class AddonBlockSiteMainMenuComponent extends CoreBlockBaseComponent impl
|
||||||
const items = config.frontpageloggedin.split(',');
|
const items = config.frontpageloggedin.split(',');
|
||||||
const hasNewsItem = items.find((item) => parseInt(item, 10) == FrontPageItemNames['NEWS_ITEMS']);
|
const hasNewsItem = items.find((item) => parseInt(item, 10) == FrontPageItemNames['NEWS_ITEMS']);
|
||||||
|
|
||||||
const result = CoreCourseHelper.addHandlerDataForModules(
|
const result = await CoreCourseHelper.addHandlerDataForModules(
|
||||||
[mainMenuBlock],
|
[mainMenuBlock],
|
||||||
this.siteHomeId,
|
this.siteHomeId,
|
||||||
undefined,
|
undefined,
|
||||||
|
|
|
@ -4,5 +4,6 @@
|
||||||
"deletedatafrom": "Offload data from {{name}}",
|
"deletedatafrom": "Offload data from {{name}}",
|
||||||
"info": "Files stored on your device make the app work faster and enable the app to be used offline. You can safely offload files if you need to free up storage space.",
|
"info": "Files stored on your device make the app work faster and enable the app to be used offline. You can safely offload files if you need to free up storage space.",
|
||||||
"managestorage": "Manage storage",
|
"managestorage": "Manage storage",
|
||||||
|
"managecoursestorage": "Manage course storage",
|
||||||
"storageused": "File storage used:"
|
"storageused": "File storage used:"
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<ion-back-button [text]="'core.back' | translate"></ion-back-button>
|
<ion-back-button [text]="'core.back' | translate"></ion-back-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
<ion-title>
|
<ion-title>
|
||||||
<h1>{{ 'addon.storagemanager.managestorage' | translate }}</h1>
|
<h1>{{ 'addon.storagemanager.managecoursestorage' | translate }}</h1>
|
||||||
</ion-title>
|
</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
@ -12,54 +12,90 @@
|
||||||
<core-loading [hideUntil]="loaded">
|
<core-loading [hideUntil]="loaded">
|
||||||
<ion-card class="wholecourse">
|
<ion-card class="wholecourse">
|
||||||
<ion-card-header>
|
<ion-card-header>
|
||||||
<ion-card-title *ngIf="course.displayname">{{ course.displayname }}</ion-card-title>
|
<ion-card-title>{{ title }}</ion-card-title>
|
||||||
<ion-card-title *ngIf="!course.displayname">{{ course.fullname }}</ion-card-title>
|
|
||||||
<p class="ion-text-wrap">{{ 'addon.storagemanager.info' | translate }}</p>
|
<p class="ion-text-wrap">{{ 'addon.storagemanager.info' | translate }}</p>
|
||||||
<ion-item class="size ion-text-wrap ion-no-padding" lines="none">
|
<ion-item class="size ion-text-wrap ion-no-padding" lines="none">
|
||||||
<ion-icon name="fas-archive" slot="start" aria-hidden="true"></ion-icon>
|
<ion-icon name="fas-archive" slot="start" aria-hidden="true"></ion-icon>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<p class="item-heading ion-text-wrap">{{ 'addon.storagemanager.storageused' | translate }}</p>
|
<p class="item-heading ion-text-wrap">{{ 'addon.storagemanager.storageused' | translate }}</p>
|
||||||
|
<ion-badge color="light">{{ totalSize | coreBytesToSize }}</ion-badge>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<p slot="end" class="ion-text-end">{{ totalSize | coreBytesToSize }}</p>
|
|
||||||
<ion-button slot="end" (click)="deleteForCourse()" [disabled]="totalSize == 0">
|
<ion-button slot="end" (click)="deleteForCourse()" [disabled]="totalSize == 0">
|
||||||
<ion-icon name="fas-trash" slot="icon-only" [attr.aria-label]="'addon.storagemanager.deletecourse' | translate">
|
<ion-icon name="fas-trash" slot="icon-only" [attr.aria-label]="'addon.storagemanager.deletecourse' | translate">
|
||||||
</ion-icon>
|
</ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
<ion-button *ngIf="downloadCourseEnabled" (click)="prefetchCourse()" expand="block">
|
||||||
|
<ion-icon *ngIf="!prefetchCourseData.loading" [name]="prefetchCourseData.icon" slot="start"></ion-icon>
|
||||||
|
<ion-spinner *ngIf="prefetchCourseData.loading" slot="start"></ion-spinner>
|
||||||
|
{{ prefetchCourseData.statusTranslatable | translate }}
|
||||||
|
</ion-button>
|
||||||
</ion-card-header>
|
</ion-card-header>
|
||||||
</ion-card>
|
</ion-card>
|
||||||
<ng-container *ngFor="let section of sections">
|
<ng-container *ngFor="let section of sections">
|
||||||
<ion-card *ngIf="section.totalSize! > 0" class="section">
|
<ion-card class="section" *ngIf="section.modules.length > 0">
|
||||||
<ion-card-header>
|
<ion-card-header>
|
||||||
<ion-item class="ion-no-padding">
|
<ion-item class="ion-no-padding">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<p class="item-heading ion-text-wrap">{{ section.name }}</p>
|
<p class="item-heading ion-text-wrap">{{ section.name }}</p>
|
||||||
|
<ion-badge color="light" *ngIf="section.totalSize > 0">
|
||||||
|
{{ section.totalSize | coreBytesToSize }}
|
||||||
|
</ion-badge>
|
||||||
|
<!-- Download progress. -->
|
||||||
|
<p *ngIf="downloadEnabled && section.isDownloading">
|
||||||
|
<core-progress-bar [progress]="section.total == 0 ? -1 : section.count / section.total">
|
||||||
|
</core-progress-bar>
|
||||||
|
</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<p slot="end" class="ion-text-end">{{ section.totalSize | coreBytesToSize }}</p>
|
<div class="storage-buttons" slot="end" *ngIf="section.totalSize > 0 || downloadEnabled">
|
||||||
<ion-button slot="end" (click)="deleteForSection(section)">
|
<ion-button (click)="deleteForSection(section)" *ngIf="section.totalSize > 0">
|
||||||
<ion-icon name="fas-trash" slot="icon-only"
|
<ion-icon name="fas-trash" slot="icon-only"
|
||||||
[attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate: { name: section.name }">
|
[attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate: { name: section.name }">
|
||||||
</ion-icon>
|
</ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
<div *ngIf="downloadEnabled" slot="end" class="core-button-spinner">
|
||||||
|
<core-download-refresh *ngIf="!section.isDownloading" [status]="section.downloadStatus" [enabled]="true"
|
||||||
|
(action)="prefecthSection(section)" [loading]="section.isDownloading || section.isCalculating"
|
||||||
|
[canTrustDownload]="true" size="small">
|
||||||
|
</core-download-refresh>
|
||||||
|
|
||||||
|
<ion-badge class="core-course-download-section-progress"
|
||||||
|
*ngIf="section.isDownloading && section.count < section.total" role="progressbar"
|
||||||
|
[attr.aria-valuemax]="section.total" [attr.aria-valuenow]="section.count"
|
||||||
|
[attr.aria-valuetext]="'core.course.downloadsectionprogressdescription' | translate:section">
|
||||||
|
{{section.count}} / {{section.total}}
|
||||||
|
</ion-badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-card-header>
|
</ion-card-header>
|
||||||
<ion-card-content>
|
<ion-card-content>
|
||||||
<ng-container *ngFor="let module of section.modules">
|
<ng-container *ngFor="let module of section.modules">
|
||||||
<ion-item class="ion-no-padding" *ngIf="module.totalSize! > 0">
|
<ion-item class="ion-no-padding"
|
||||||
<core-mod-icon slot="start" *ngIf="module.handlerData!.icon" [modicon]="module.handlerData!.icon"
|
*ngIf="(downloadEnabled && module.handlerData?.showDownloadButton) || module.totalSize > 0">
|
||||||
|
<core-mod-icon slot="start" *ngIf="module.handlerData.icon" [modicon]="module.handlerData.icon"
|
||||||
[modname]="module.modname" [componentId]="module.instance">
|
[modname]="module.modname" [componentId]="module.instance">
|
||||||
</core-mod-icon>
|
</core-mod-icon>
|
||||||
<ion-label class="ion-text-wrap">
|
<ion-label class="ion-text-wrap">
|
||||||
<h3 class="{{module.handlerData!.class}} addon-storagemanager-module-size">
|
<h3 class="{{module.handlerData!.class}} addon-storagemanager-module-size">
|
||||||
{{ module.name }}
|
{{ module.name }}
|
||||||
</h3>
|
</h3>
|
||||||
|
<ion-badge color="light" *ngIf="module.totalSize > 0">
|
||||||
|
{{ module.totalSize | coreBytesToSize }}
|
||||||
|
</ion-badge>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<p slot="end" class="ion-text-end">{{ module.totalSize | coreBytesToSize }}</p>
|
|
||||||
<ion-button fill="clear" slot="end" (click)="deleteForModule(module)">
|
<div class="storage-buttons" slot="end">
|
||||||
<ion-icon name="fas-trash" slot="icon-only"
|
<ion-button fill="clear" (click)="deleteForModule(module, section)" *ngIf="module.totalSize > 0">
|
||||||
[attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate: { name: module.name }">
|
<ion-icon name="fas-trash" slot="icon-only"
|
||||||
</ion-icon>
|
[attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate: { name: module.name }">
|
||||||
</ion-button>
|
</ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
<core-download-refresh *ngIf="downloadEnabled && module.handlerData?.showDownloadButton"
|
||||||
|
[status]="module.downloadStatus" [enabled]="true" [canTrustDownload]="true" size="small"
|
||||||
|
[loading]="module.spinner || module.handlerData.spinner" (action)="prefetchModule(module, section)">
|
||||||
|
</core-download-refresh>
|
||||||
|
</div>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ion-card-content>
|
</ion-card-content>
|
||||||
|
|
|
@ -9,3 +9,12 @@
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.storage-buttons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-item {
|
||||||
|
--inner-padding-end: 0px;
|
||||||
|
}
|
||||||
|
|
|
@ -13,37 +13,77 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { CoreConstants } from '@/core/constants';
|
import { CoreConstants } from '@/core/constants';
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { CoreCourse } from '@features/course/services/course';
|
import { CoreCourse, CoreCourseProvider } from '@features/course/services/course';
|
||||||
import { CoreCourseHelper, CoreCourseModuleData, CoreCourseSection } from '@features/course/services/course-helper';
|
import {
|
||||||
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
|
CoreCourseHelper,
|
||||||
import { CoreEnrolledCourseData } from '@features/courses/services/courses';
|
CoreCourseModuleData,
|
||||||
|
CoreCourseSectionWithStatus,
|
||||||
|
CorePrefetchStatusInfo,
|
||||||
|
} from '@features/course/services/course-helper';
|
||||||
|
import {
|
||||||
|
CoreCourseModulePrefetchDelegate,
|
||||||
|
CoreCourseModulePrefetchHandler } from '@features/course/services/module-prefetch-delegate';
|
||||||
|
import { CoreCourses, CoreEnrolledCourseData } from '@features/courses/services/courses';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { Translate } from '@singletons';
|
import { Translate } from '@singletons';
|
||||||
|
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays the amount of file storage used by each activity on the course, and allows
|
* Page that displays the amount of file storage used by each activity on the course, and allows
|
||||||
* the user to delete these files.
|
* the user to prefecth and delete this data.
|
||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-addon-storagemanager-course-storage',
|
selector: 'page-addon-storagemanager-course-storage',
|
||||||
templateUrl: 'course-storage.html',
|
templateUrl: 'course-storage.html',
|
||||||
styleUrls: ['course-storage.scss'],
|
styleUrls: ['course-storage.scss'],
|
||||||
})
|
})
|
||||||
export class AddonStorageManagerCourseStoragePage implements OnInit {
|
export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
course!: CoreEnrolledCourseData;
|
course?: CoreEnrolledCourseData;
|
||||||
|
courseId!: number;
|
||||||
|
title = '';
|
||||||
loaded = false;
|
loaded = false;
|
||||||
sections: AddonStorageManagerCourseSection[] = [];
|
sections: AddonStorageManagerCourseSection[] = [];
|
||||||
totalSize = 0;
|
totalSize = 0;
|
||||||
|
|
||||||
|
downloadEnabled = false;
|
||||||
|
downloadCourseEnabled = false;
|
||||||
|
|
||||||
|
prefetchCourseData: CorePrefetchStatusInfo = {
|
||||||
|
icon: CoreConstants.ICON_LOADING,
|
||||||
|
statusTranslatable: 'core.course.downloadcourse',
|
||||||
|
status: '',
|
||||||
|
loading: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
protected siteUpdatedObserver?: CoreEventObserver;
|
||||||
|
protected courseStatusObserver?: CoreEventObserver;
|
||||||
|
protected sectionStatusObserver?: CoreEventObserver;
|
||||||
|
protected moduleStatusObserver?: CoreEventObserver;
|
||||||
|
protected isDestroyed = false;
|
||||||
|
protected isGuest = false;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// Refresh the enabled flags if site is updated.
|
||||||
|
this.siteUpdatedObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
|
||||||
|
this.downloadCourseEnabled = !!this.course && !CoreCourses.isDownloadCourseDisabledInSite();
|
||||||
|
this.downloadEnabled = !CoreSites.getRequiredCurrentSite().isOfflineDisabled();
|
||||||
|
|
||||||
|
this.initCoursePrefetch();
|
||||||
|
this.initModulePrefetch();
|
||||||
|
}, CoreSites.getCurrentSiteId());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View loaded.
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
this.course = CoreNavigator.getRequiredRouteParam<CoreEnrolledCourseData>('course');
|
this.courseId = CoreNavigator.getRequiredRouteParam('courseId');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
CoreDomUtils.showErrorModal(error);
|
CoreDomUtils.showErrorModal(error);
|
||||||
|
|
||||||
|
@ -52,16 +92,160 @@ export class AddonStorageManagerCourseStoragePage implements OnInit {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sections = await CoreCourse.getSections(this.course.id, false, true);
|
this.course = CoreNavigator.getRouteParam<CoreEnrolledCourseData>('course');
|
||||||
this.sections = CoreCourseHelper.addHandlerDataForModules(sections, this.course.id).sections;
|
this.title = this.course?.displayname ?? this.course?.fullname ?? '';
|
||||||
|
if (!this.title && this.courseId == CoreSites.getCurrentSiteHomeId()) {
|
||||||
|
this.title = Translate.instant('core.sitehome.sitehome');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isGuest = !!CoreNavigator.getRouteBooleanParam('isGuest');
|
||||||
|
|
||||||
|
this.downloadCourseEnabled = !!this.course && !CoreCourses.isDownloadCourseDisabledInSite();
|
||||||
|
this.downloadEnabled = !CoreSites.getRequiredCurrentSite().isOfflineDisabled();
|
||||||
|
|
||||||
|
const sections = await CoreCourse.getSections(this.courseId, false, true);
|
||||||
|
this.sections = (await CoreCourseHelper.addHandlerDataForModules(sections, this.courseId)).sections
|
||||||
|
.map((section) => ({ ...section, totalSize: 0 }));
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
this.loadSizes(),
|
||||||
|
this.initCoursePrefetch(),
|
||||||
|
this.initModulePrefetch(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init course prefetch information.
|
||||||
|
*
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected async initCoursePrefetch(): Promise<void> {
|
||||||
|
if (!this.downloadCourseEnabled || this.courseStatusObserver) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for changes in course status.
|
||||||
|
this.courseStatusObserver = CoreEvents.on(CoreEvents.COURSE_STATUS_CHANGED, (data) => {
|
||||||
|
if (data.courseId == this.courseId || data.courseId == CoreCourseProvider.ALL_COURSES_CLEARED) {
|
||||||
|
this.updateCourseStatus(data.status);
|
||||||
|
}
|
||||||
|
}, CoreSites.getCurrentSiteId());
|
||||||
|
|
||||||
|
// Determine the course prefetch status.
|
||||||
|
await this.determineCoursePrefetchIcon();
|
||||||
|
|
||||||
|
if (this.prefetchCourseData.icon != CoreConstants.ICON_LOADING) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Course is being downloaded. Get the download promise.
|
||||||
|
const promise = CoreCourseHelper.getCourseDownloadPromise(this.courseId);
|
||||||
|
if (promise) {
|
||||||
|
// There is a download promise. Show an error if it fails.
|
||||||
|
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.
|
||||||
|
const status = await CoreCourse.setCoursePreviousStatus(this.courseId);
|
||||||
|
|
||||||
|
this.updateCourseStatus(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init module prefetch information.
|
||||||
|
*
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected async initModulePrefetch(): Promise<void> {
|
||||||
|
if (!this.downloadEnabled || this.sectionStatusObserver) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for section status changes.
|
||||||
|
this.sectionStatusObserver = CoreEvents.on(
|
||||||
|
CoreEvents.SECTION_STATUS_CHANGED,
|
||||||
|
async (data) => {
|
||||||
|
if (!this.downloadEnabled || !this.sections.length || !data.sectionId || data.courseId != this.courseId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the affected section is being downloaded.
|
||||||
|
// If so, we don't update section status because it'll already be updated when the download finishes.
|
||||||
|
const downloadId = CoreCourseHelper.getSectionDownloadId({ id: data.sectionId });
|
||||||
|
if (CoreCourseModulePrefetchDelegate.isBeingDownloaded(downloadId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the affected section.
|
||||||
|
const section = this.sections.find(section => section.id == data.sectionId);
|
||||||
|
if (!section) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recalculate the status.
|
||||||
|
await CoreCourseHelper.calculateSectionStatus(section, this.courseId, false);
|
||||||
|
|
||||||
|
if (section.isDownloading && !CoreCourseModulePrefetchDelegate.isBeingDownloaded(downloadId)) {
|
||||||
|
// All the modules are now downloading, set a download all promise.
|
||||||
|
this.prefecthSection(section);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
CoreSites.getCurrentSiteId(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// The download status of a section might have been changed from within a module page.
|
||||||
|
CoreCourseHelper.calculateSectionsStatus(this.sections, this.courseId, false, false);
|
||||||
|
|
||||||
|
this.sections.forEach((section) => {
|
||||||
|
section.modules.forEach((module) => {
|
||||||
|
if (module.handlerData?.showDownloadButton) {
|
||||||
|
|
||||||
|
module.spinner = true;
|
||||||
|
// Listen for changes on this module status, even if download isn't enabled.
|
||||||
|
module.prefetchHandler = CoreCourseModulePrefetchDelegate.getPrefetchHandlerFor(module.modname);
|
||||||
|
this.calculateModuleStatus(module);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.moduleStatusObserver = CoreEvents.on(CoreEvents.PACKAGE_STATUS_CHANGED, (data) => {
|
||||||
|
let module: AddonStorageManagerModule | undefined;
|
||||||
|
|
||||||
|
this.sections.some((section) => {
|
||||||
|
module = section.modules.find((module) =>
|
||||||
|
module.id == data.componentId && module.prefetchHandler && data.component == module.prefetchHandler?.component);
|
||||||
|
|
||||||
|
return !!module;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!module) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call determineModuleStatus to get the right status to display.
|
||||||
|
const status = CoreCourseModulePrefetchDelegate.determineModuleStatus(module, data.status);
|
||||||
|
|
||||||
|
// Update the status.
|
||||||
|
this.updateModuleStatus(module, status);
|
||||||
|
}, CoreSites.getCurrentSiteId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init section, course and modules sizes.
|
||||||
|
*/
|
||||||
|
protected async loadSizes(): Promise<void> {
|
||||||
this.totalSize = 0;
|
this.totalSize = 0;
|
||||||
|
|
||||||
const promises: Promise<void>[] = [];
|
const promises: Promise<void>[] = [];
|
||||||
this.sections.forEach((section) => {
|
this.sections.forEach((section) => {
|
||||||
section.totalSize = 0;
|
section.totalSize = 0;
|
||||||
section.modules.forEach((module) => {
|
section.modules.forEach((module) => {
|
||||||
module.parentSection = section;
|
|
||||||
module.totalSize = 0;
|
module.totalSize = 0;
|
||||||
|
|
||||||
// Note: This function only gets the size for modules which are downloadable.
|
// Note: This function only gets the size for modules which are downloadable.
|
||||||
|
@ -71,11 +255,11 @@ export class AddonStorageManagerCourseStoragePage implements OnInit {
|
||||||
// But these aren't necessarily consistent, for example mod_frog vs mmaModFrog.
|
// But these aren't necessarily consistent, for example mod_frog vs mmaModFrog.
|
||||||
// There is nothing enforcing correct values.
|
// There is nothing enforcing correct values.
|
||||||
// Most modules which have large files are downloadable, so I think this is sufficient.
|
// Most modules which have large files are downloadable, so I think this is sufficient.
|
||||||
const promise = CoreCourseModulePrefetchDelegate.getModuleStoredSize(module, this.course.id).then((size) => {
|
const promise = CoreCourseModulePrefetchDelegate.getModuleStoredSize(module, this.courseId).then((size) => {
|
||||||
// There are some cases where the return from this is not a valid number.
|
// There are some cases where the return from this is not a valid number.
|
||||||
if (!isNaN(size)) {
|
if (!isNaN(size)) {
|
||||||
module.totalSize = Number(size);
|
module.totalSize = Number(size);
|
||||||
section.totalSize! += size;
|
section.totalSize += size;
|
||||||
this.totalSize += size;
|
this.totalSize += size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,8 +270,8 @@ export class AddonStorageManagerCourseStoragePage implements OnInit {
|
||||||
});
|
});
|
||||||
|
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
this.loaded = true;
|
|
||||||
|
|
||||||
|
// Mark course as not downloaded if course size is 0.
|
||||||
if (this.totalSize == 0) {
|
if (this.totalSize == 0) {
|
||||||
this.markCourseAsNotDownloaded();
|
this.markCourseAsNotDownloaded();
|
||||||
}
|
}
|
||||||
|
@ -146,15 +330,16 @@ export class AddonStorageManagerCourseStoragePage implements OnInit {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.deleteModules(modules);
|
this.deleteModules(modules, section);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The user has requested a delete for a module's data
|
* The user has requested a delete for a module's data
|
||||||
*
|
*
|
||||||
* @param module Module details
|
* @param module Module details
|
||||||
|
* @param section Section the module belongs to.
|
||||||
*/
|
*/
|
||||||
async deleteForModule(module: AddonStorageManagerModule): Promise<void> {
|
async deleteForModule(module: AddonStorageManagerModule, section: AddonStorageManagerCourseSection): Promise<void> {
|
||||||
if (module.totalSize === 0) {
|
if (module.totalSize === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -169,25 +354,29 @@ export class AddonStorageManagerCourseStoragePage implements OnInit {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.deleteModules([module]);
|
this.deleteModules([module], section);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes the specified modules, showing the loading overlay while it happens.
|
* Deletes the specified modules, showing the loading overlay while it happens.
|
||||||
*
|
*
|
||||||
* @param modules Modules to delete
|
* @param modules Modules to delete
|
||||||
|
* @param section Section the modules belong to.
|
||||||
* @return Promise<void> Once deleting has finished
|
* @return Promise<void> Once deleting has finished
|
||||||
*/
|
*/
|
||||||
protected async deleteModules(modules: AddonStorageManagerModule[]): Promise<void> {
|
protected async deleteModules(modules: AddonStorageManagerModule[], section?: AddonStorageManagerCourseSection): Promise<void> {
|
||||||
const modal = await CoreDomUtils.showModalLoading();
|
const modal = await CoreDomUtils.showModalLoading();
|
||||||
|
|
||||||
const promises: Promise<void>[] = [];
|
const promises: Promise<void>[] = [];
|
||||||
modules.forEach((module) => {
|
modules.forEach((module) => {
|
||||||
// Remove the files.
|
// Remove the files.
|
||||||
const promise = CoreCourseHelper.removeModuleStoredData(module, this.course.id).then(() => {
|
const promise = CoreCourseHelper.removeModuleStoredData(module, this.courseId).then(() => {
|
||||||
|
const moduleSize = module.totalSize || 0;
|
||||||
// When the files and cache are removed, update the size.
|
// When the files and cache are removed, update the size.
|
||||||
module.parentSection!.totalSize! -= module.totalSize!;
|
if (section) {
|
||||||
this.totalSize -= module.totalSize!;
|
section.totalSize -= moduleSize;
|
||||||
|
}
|
||||||
|
this.totalSize -= moduleSize;
|
||||||
module.totalSize = 0;
|
module.totalSize = 0;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -203,13 +392,8 @@ export class AddonStorageManagerCourseStoragePage implements OnInit {
|
||||||
} finally {
|
} finally {
|
||||||
modal.dismiss();
|
modal.dismiss();
|
||||||
|
|
||||||
// @TODO This is a workaround that should be more specific solving MOBILE-3305.
|
await this.loadSizes();
|
||||||
// Also should take into account all modules are not downloaded.
|
CoreCourseHelper.calculateSectionsStatus(this.sections, this.courseId, false, false);
|
||||||
|
|
||||||
// Mark course as not downloaded if course size is 0.
|
|
||||||
if (this.totalSize == 0) {
|
|
||||||
this.markCourseAsNotDownloaded();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,17 +405,201 @@ export class AddonStorageManagerCourseStoragePage implements OnInit {
|
||||||
// Also should take into account all modules are not downloaded.
|
// Also should take into account all modules are not downloaded.
|
||||||
// Check after MOBILE-3188 is integrated.
|
// Check after MOBILE-3188 is integrated.
|
||||||
|
|
||||||
CoreCourse.setCourseStatus(this.course.id, CoreConstants.NOT_DOWNLOADED);
|
CoreCourse.setCourseStatus(this.courseId, CoreConstants.NOT_DOWNLOADED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the status of sections.
|
||||||
|
*
|
||||||
|
* @param refresh If refresh or not.
|
||||||
|
*/
|
||||||
|
protected calculateSectionsStatus(refresh?: boolean): void {
|
||||||
|
if (!this.sections) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CoreUtils.ignoreErrors(CoreCourseHelper.calculateSectionsStatus(this.sections, this.courseId, refresh));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirm and prefetch a section. If the section is "all sections", prefetch all the sections.
|
||||||
|
*
|
||||||
|
* @param section Section to download.
|
||||||
|
* @param refresh Refresh clicked (not used).
|
||||||
|
*/
|
||||||
|
async prefecthSection(section: AddonStorageManagerCourseSection): Promise<void> {
|
||||||
|
section.isCalculating = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await CoreCourseHelper.confirmDownloadSizeSection(this.courseId, section, this.sections);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await CoreCourseHelper.prefetchSection(section, this.courseId, this.sections);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
if (!this.isDestroyed) {
|
||||||
|
CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingsection', true);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
await this.loadSizes();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// User cancelled or there was an error calculating the size.
|
||||||
|
if (!this.isDestroyed && error) {
|
||||||
|
CoreDomUtils.showErrorModal(error);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
section.isCalculating = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download the module.
|
||||||
|
*
|
||||||
|
* @param module Module to prefetch.
|
||||||
|
* @param refresh Whether it's refreshing.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async prefetchModule(
|
||||||
|
module: AddonStorageManagerModule,
|
||||||
|
refresh = false,
|
||||||
|
): Promise<void> {
|
||||||
|
if (!module.prefetchHandler) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show spinner since this operation might take a while.
|
||||||
|
module.spinner = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get download size to ask for confirm if it's high.
|
||||||
|
const size = await module.prefetchHandler.getDownloadSize(module, module.course, true);
|
||||||
|
|
||||||
|
await CoreCourseHelper.prefetchModule(module.prefetchHandler, module, size, module.course, refresh);
|
||||||
|
|
||||||
|
CoreCourseHelper.calculateSectionsStatus(this.sections, this.courseId, false, false);
|
||||||
|
} catch (error) {
|
||||||
|
if (!this.isDestroyed) {
|
||||||
|
CoreDomUtils.showErrorModalDefault(error, 'core.errordownloading', true);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
module.spinner = false;
|
||||||
|
|
||||||
|
await this.loadSizes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show download buttons according to module status.
|
||||||
|
*
|
||||||
|
* @param module Module to update.
|
||||||
|
* @param status Module status.
|
||||||
|
*/
|
||||||
|
protected updateModuleStatus(module: AddonStorageManagerModule, status: string): void {
|
||||||
|
if (!status) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.spinner = false;
|
||||||
|
module.downloadStatus = status;
|
||||||
|
|
||||||
|
module.handlerData?.updateStatus?.(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate and show module status.
|
||||||
|
*
|
||||||
|
* @param module Module to update.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected async calculateModuleStatus(module: AddonStorageManagerModule): Promise<void> {
|
||||||
|
if (!module) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = await CoreCourseModulePrefetchDelegate.getModuleStatus(module, this.courseId);
|
||||||
|
|
||||||
|
this.updateModuleStatus(module, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the prefetch icon of the course.
|
||||||
|
*
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected async determineCoursePrefetchIcon(): Promise<void> {
|
||||||
|
this.prefetchCourseData = await CoreCourseHelper.getCourseStatusIconAndTitle(this.courseId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 whole course.
|
||||||
|
*/
|
||||||
|
async prefetchCourse(): Promise<void> {
|
||||||
|
if (!this.course) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await CoreCourseHelper.confirmAndPrefetchCourse(
|
||||||
|
this.prefetchCourseData,
|
||||||
|
this.course,
|
||||||
|
{
|
||||||
|
sections: this.sections,
|
||||||
|
isGuest: this.isGuest,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
if (this.isDestroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.courseStatusObserver?.off();
|
||||||
|
this.sectionStatusObserver?.off();
|
||||||
|
this.moduleStatusObserver?.off();
|
||||||
|
this.siteUpdatedObserver?.off();
|
||||||
|
|
||||||
|
this.sections.forEach((section) => {
|
||||||
|
section.modules.forEach((module) => {
|
||||||
|
module.handlerData?.onDestroy?.();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.isDestroyed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AddonStorageManagerCourseSection = Omit<CoreCourseSection, 'modules'> & {
|
type AddonStorageManagerCourseSection = Omit<CoreCourseSectionWithStatus, 'modules'> & {
|
||||||
totalSize?: number;
|
totalSize: number;
|
||||||
modules: AddonStorageManagerModule[];
|
modules: AddonStorageManagerModule[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type AddonStorageManagerModule = CoreCourseModuleData & {
|
type AddonStorageManagerModule = CoreCourseModuleData & {
|
||||||
parentSection?: AddonStorageManagerCourseSection;
|
|
||||||
totalSize?: number;
|
totalSize?: number;
|
||||||
|
prefetchHandler?: CoreCourseModulePrefetchHandler;
|
||||||
|
spinner?: boolean;
|
||||||
|
downloadStatus?: string;
|
||||||
};
|
};
|
||||||
|
|
|
@ -49,7 +49,7 @@ export class AddonStorageManagerCourseMenuHandlerService implements CoreCourseOp
|
||||||
): CoreCourseOptionsMenuHandlerData {
|
): CoreCourseOptionsMenuHandlerData {
|
||||||
return {
|
return {
|
||||||
icon: 'fas-archive',
|
icon: 'fas-archive',
|
||||||
title: 'addon.storagemanager.managestorage',
|
title: 'addon.storagemanager.managecoursestorage',
|
||||||
page: 'storage/' + course.id,
|
page: 'storage/' + course.id,
|
||||||
class: 'addon-storagemanager-coursemenu-handler',
|
class: 'addon-storagemanager-coursemenu-handler',
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,3 +7,5 @@
|
||||||
{{ 'core.percentagenumber' | translate: {$a: text} }}
|
{{ 'core.percentagenumber' | translate: {$a: text} }}
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
<ion-progress-bar *ngIf="progress < 0" type="indeterminate"></ion-progress-bar>
|
||||||
|
|
|
@ -35,4 +35,11 @@
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ion-progress-bar {
|
||||||
|
--progress-background: var(--color);
|
||||||
|
height: var(--height);
|
||||||
|
margin-top: calc((var(--line-height) - var(--height)) /2);
|
||||||
|
margin-bottom: calc((var(--line-height) - var(--height)) /2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ import { DomSanitizer, Translate } from '@singletons';
|
||||||
})
|
})
|
||||||
export class CoreProgressBarComponent implements OnChanges {
|
export class CoreProgressBarComponent implements OnChanges {
|
||||||
|
|
||||||
@Input() progress!: number | string; // Percentage from 0 to 100.
|
@Input() progress!: number | string; // Percentage from 0 to 100. Negative number will show an indeterminate progress bar.
|
||||||
@Input() text?: string; // Percentage in text to be shown at the right. If not defined, progress will be used.
|
@Input() text?: string; // Percentage in text to be shown at the right. If not defined, progress will be used.
|
||||||
@Input() a11yText?: string; // Accessibility text to read before the percentage.
|
@Input() a11yText?: string; // Accessibility text to read before the percentage.
|
||||||
@Input() ariaDescribedBy?: string; // ID of the element that described the progress, if any.
|
@Input() ariaDescribedBy?: string; // ID of the element that described the progress, if any.
|
||||||
|
|
|
@ -27,7 +27,6 @@ import { CoreBlockSideBlocksComponent } from '../side-blocks/side-blocks';
|
||||||
export class CoreBlockSideBlocksButtonComponent {
|
export class CoreBlockSideBlocksButtonComponent {
|
||||||
|
|
||||||
@Input() courseId!: number;
|
@Input() courseId!: number;
|
||||||
@Input() downloadEnabled = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open side blocks.
|
* Open side blocks.
|
||||||
|
@ -37,7 +36,6 @@ export class CoreBlockSideBlocksButtonComponent {
|
||||||
component: CoreBlockSideBlocksComponent,
|
component: CoreBlockSideBlocksComponent,
|
||||||
componentProps: {
|
componentProps: {
|
||||||
courseId: this.courseId,
|
courseId: this.courseId,
|
||||||
downloadEnabled: this.downloadEnabled,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,7 @@
|
||||||
<core-loading [hideUntil]="loaded">
|
<core-loading [hideUntil]="loaded">
|
||||||
<ion-list *ngIf="blocks.length > 0">
|
<ion-list *ngIf="blocks.length > 0">
|
||||||
<ng-container *ngFor="let block of blocks">
|
<ng-container *ngFor="let block of blocks">
|
||||||
<core-block *ngIf="block.visible" [block]="block" contextLevel="course" [instanceId]="courseId"
|
<core-block *ngIf="block.visible" [block]="block" contextLevel="course" [instanceId]="courseId"></core-block>
|
||||||
[extraData]="{'downloadEnabled': downloadEnabled}"></core-block>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,6 @@ import { CoreCoursesDashboard } from '@features/courses/services/dashboard';
|
||||||
export class CoreBlockSideBlocksComponent implements OnInit {
|
export class CoreBlockSideBlocksComponent implements OnInit {
|
||||||
|
|
||||||
@Input() courseId?: number;
|
@Input() courseId?: number;
|
||||||
@Input() downloadEnabled = false;
|
|
||||||
|
|
||||||
@ViewChildren(CoreBlockComponent) blocksComponents?: QueryList<CoreBlockComponent>;
|
@ViewChildren(CoreBlockComponent) blocksComponents?: QueryList<CoreBlockComponent>;
|
||||||
|
|
||||||
|
|
|
@ -11,10 +11,8 @@
|
||||||
<core-loading [hideUntil]="loaded">
|
<core-loading [hideUntil]="loaded">
|
||||||
<!-- Section selector. -->
|
<!-- Section selector. -->
|
||||||
<core-dynamic-component [component]="sectionSelectorComponent" [data]="data">
|
<core-dynamic-component [component]="sectionSelectorComponent" [data]="data">
|
||||||
|
|
||||||
<div *ngIf="displaySectionSelector && sections && hasSeveralSections"
|
<div *ngIf="displaySectionSelector && sections && hasSeveralSections"
|
||||||
class="ion-text-wrap ion-justify-content-between ion-align-items-center core-button-selector-row"
|
class="ion-text-wrap ion-justify-content-between ion-align-items-center core-button-selector-row">
|
||||||
[class.core-section-download]="downloadEnabled">
|
|
||||||
<core-combobox [modalOptions]="sectionSelectorModalOptions" interface="modal" listboxId="core-course-section-button"
|
<core-combobox [modalOptions]="sectionSelectorModalOptions" interface="modal" listboxId="core-course-section-button"
|
||||||
icon="fas-folder" [label]="'core.course.section' | translate"
|
icon="fas-folder" [label]="'core.course.section' | translate"
|
||||||
[selection]="selectedSection ? selectedSection.name : 'core.course.sections' | translate"
|
[selection]="selectedSection ? selectedSection.name : 'core.course.sections' | translate"
|
||||||
|
@ -26,16 +24,14 @@
|
||||||
<ng-container *ngIf="!selectedSection">{{ 'core.course.sections' | translate }}</ng-container>
|
<ng-container *ngIf="!selectedSection">{{ 'core.course.sections' | translate }}</ng-container>
|
||||||
</span>
|
</span>
|
||||||
</core-combobox>
|
</core-combobox>
|
||||||
<!-- Section download. -->
|
|
||||||
<ng-container *ngTemplateOutlet="sectionDownloadTemplate; context: {section: selectedSection}"></ng-container>
|
|
||||||
</div>
|
</div>
|
||||||
</core-dynamic-component>
|
</core-dynamic-component>
|
||||||
|
|
||||||
<!-- Course summary. By default we only display the course progress. -->
|
<!-- Course summary. By default we only display the course progress. -->
|
||||||
<core-dynamic-component [component]="courseSummaryComponent" [data]="data">
|
<core-dynamic-component [component]="courseSummaryComponent" [data]="data">
|
||||||
<ion-list lines="none" class="core-format-progress-list" *ngIf="imageThumb || (selectedSection?.id == allSectionsId && progress !== undefined) ||
|
<ion-list *ngIf="imageThumb || (selectedSection?.id == allSectionsId && progress !== undefined) ||
|
||||||
(selectedSection && selectedSection.id != allSectionsId &&
|
(selectedSection && selectedSection.id != allSectionsId &&
|
||||||
(selectedSection.availabilityinfo || selectedSection.visible === 0))">
|
(selectedSection.availabilityinfo || selectedSection.visible === 0))" lines="none" class="core-format-progress-list">
|
||||||
<div *ngIf="imageThumb" class="core-course-thumb">
|
<div *ngIf="imageThumb" class="core-course-thumb">
|
||||||
<img [src]="imageThumb" core-external-content alt="" />
|
<img [src]="imageThumb" core-external-content alt="" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -103,8 +99,7 @@
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
|
|
||||||
<core-block-side-blocks-button *ngIf="course && displayBlocks && hasBlocks" [courseId]="course.id"
|
<core-block-side-blocks-button *ngIf="course && displayBlocks && hasBlocks" [courseId]="course.id">
|
||||||
[downloadEnabled]="downloadEnabled">
|
|
||||||
</core-block-side-blocks-button>
|
</core-block-side-blocks-button>
|
||||||
</core-loading>
|
</core-loading>
|
||||||
</core-dynamic-component>
|
</core-dynamic-component>
|
||||||
|
@ -114,7 +109,7 @@
|
||||||
<section *ngIf="!section.hiddenbynumsections && section.id != allSectionsId && section.id != stealthModulesSectionId">
|
<section *ngIf="!section.hiddenbynumsections && section.id != allSectionsId && section.id != stealthModulesSectionId">
|
||||||
<!-- Title is only displayed when viewing all sections. -->
|
<!-- Title is only displayed when viewing all sections. -->
|
||||||
<ion-item-divider *ngIf="selectedSection?.id == allSectionsId && section.name" class="ion-text-wrap" color="light"
|
<ion-item-divider *ngIf="selectedSection?.id == allSectionsId && section.name" class="ion-text-wrap" color="light"
|
||||||
[class.core-section-download]="downloadEnabled" [class.item-dimmed]="section.visible === 0 || section.uservisible === false">
|
[class.item-dimmed]="section.visible === 0 || section.uservisible === false">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>
|
<h2>
|
||||||
<core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course?.id">
|
<core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course?.id">
|
||||||
|
@ -133,8 +128,6 @@
|
||||||
</ion-badge>
|
</ion-badge>
|
||||||
</p>
|
</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<!-- Section download. -->
|
|
||||||
<ng-container *ngTemplateOutlet="sectionDownloadTemplate; context: {section: section}"></ng-container>
|
|
||||||
</ion-item-divider>
|
</ion-item-divider>
|
||||||
|
|
||||||
<ion-item class="ion-text-wrap" *ngIf="section.summary">
|
<ion-item class="ion-text-wrap" *ngIf="section.summary">
|
||||||
|
@ -145,28 +138,10 @@
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ng-container *ngFor="let module of section.modules">
|
<ng-container *ngFor="let module of section.modules">
|
||||||
<core-course-module *ngIf="module.visibleoncoursepage !== 0" [module]="module" [courseId]="course?.id"
|
<core-course-module *ngIf="module.visibleoncoursepage !== 0" [module]="module" [section]="section"
|
||||||
[downloadEnabled]="downloadEnabled" [section]="section" (completionChanged)="onCompletionChange($event)"
|
(completionChanged)="onCompletionChange($event)" [showActivityDates]="course?.showactivitydates"
|
||||||
(statusChanged)="onModuleStatusChange()" [showActivityDates]="course?.showactivitydates"
|
|
||||||
[showCompletionConditions]="course?.showcompletionconditions">
|
[showCompletionConditions]="course?.showcompletionconditions">
|
||||||
</core-course-module>
|
</core-course-module>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</section>
|
</section>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<!-- Template to render a section download button/progress. -->
|
|
||||||
<ng-template #sectionDownloadTemplate let-section="section">
|
|
||||||
<div *ngIf="section && downloadEnabled" slot="end" class="core-button-spinner">
|
|
||||||
<!-- Download progress. -->
|
|
||||||
<ion-badge class="core-course-download-section-progress"
|
|
||||||
*ngIf="section.isDownloading && section.total > 0 && section.count < section.total" role="progressbar"
|
|
||||||
[attr.aria-valuemax]="section.total" [attr.aria-valuenow]="section.count"
|
|
||||||
[attr.aria-valuetext]="'core.course.downloadsectionprogressdescription' | translate:section">
|
|
||||||
{{section.count}} / {{section.total}}
|
|
||||||
</ion-badge>
|
|
||||||
|
|
||||||
<core-download-refresh [status]="section.downloadStatus" [enabled]="downloadEnabled" (action)="prefetch(section)"
|
|
||||||
[loading]="section.isDownloading || section.isCalculating" [canTrustDownload]="true" size="small">
|
|
||||||
</core-download-refresh>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
|
@ -55,21 +55,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.core-section-download {
|
|
||||||
core-combobox {
|
|
||||||
max-width: calc(100% - 64px);
|
|
||||||
}
|
|
||||||
.core-button-spinner {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
@include margin-horizontal(10px);
|
|
||||||
|
|
||||||
ion-badge.core-course-download-courses-progress {
|
|
||||||
@include margin(null, 12px, null, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.core-course-section-nav-buttons {
|
.core-course-section-nav-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|
|
@ -27,7 +27,6 @@ import {
|
||||||
ElementRef,
|
ElementRef,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { ModalOptions } from '@ionic/core';
|
import { ModalOptions } from '@ionic/core';
|
||||||
import { CoreSites } from '@services/sites';
|
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-component';
|
import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-component';
|
||||||
import { CoreCourseAnyCourseData } from '@features/courses/services/courses';
|
import { CoreCourseAnyCourseData } from '@features/courses/services/courses';
|
||||||
|
@ -37,17 +36,14 @@ import {
|
||||||
CoreCourseProvider,
|
CoreCourseProvider,
|
||||||
} from '@features/course/services/course';
|
} from '@features/course/services/course';
|
||||||
import {
|
import {
|
||||||
CoreCourseHelper,
|
|
||||||
CoreCourseModuleData,
|
CoreCourseModuleData,
|
||||||
CoreCourseModuleCompletionData,
|
CoreCourseModuleCompletionData,
|
||||||
CoreCourseSection,
|
CoreCourseSection,
|
||||||
CoreCourseSectionWithStatus,
|
|
||||||
} from '@features/course/services/course-helper';
|
} from '@features/course/services/course-helper';
|
||||||
import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate';
|
import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate';
|
||||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
import { IonContent, IonRefresher } from '@ionic/angular';
|
import { IonContent, IonRefresher } from '@ionic/angular';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
|
|
||||||
import { CoreCourseSectionSelectorComponent } from '../section-selector/section-selector';
|
import { CoreCourseSectionSelectorComponent } from '../section-selector/section-selector';
|
||||||
import { CoreBlockHelper } from '@features/block/services/block-helper';
|
import { CoreBlockHelper } from '@features/block/services/block-helper';
|
||||||
|
|
||||||
|
@ -71,8 +67,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
static readonly LOAD_MORE_ACTIVITIES = 20; // How many activities should load each time showMoreActivities is called.
|
static readonly LOAD_MORE_ACTIVITIES = 20; // How many activities should load each time showMoreActivities is called.
|
||||||
|
|
||||||
@Input() course?: CoreCourseAnyCourseData; // The course to render.
|
@Input() course?: CoreCourseAnyCourseData; // The course to render.
|
||||||
@Input() sections?: CoreCourseSectionWithStatus[]; // List of course sections. The status will be calculated in this component.
|
@Input() sections?: CoreCourseSection[]; // List of course sections.
|
||||||
@Input() downloadEnabled?: boolean; // Whether the download of sections and modules is enabled.
|
|
||||||
@Input() initialSectionId?: number; // The section to load first (by ID).
|
@Input() initialSectionId?: number; // The section to load first (by ID).
|
||||||
@Input() initialSectionNumber?: number; // The section to load first (by number).
|
@Input() initialSectionNumber?: number; // The section to load first (by number).
|
||||||
@Input() moduleId?: number; // The module ID to scroll to. Must be inside the initial selected section.
|
@Input() moduleId?: number; // The module ID to scroll to. Must be inside the initial selected section.
|
||||||
|
@ -108,7 +103,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
componentProps: {},
|
componentProps: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
protected sectionStatusObserver?: CoreEventObserver;
|
|
||||||
protected selectTabObserver?: CoreEventObserver;
|
protected selectTabObserver?: CoreEventObserver;
|
||||||
protected lastCourseFormat?: string;
|
protected lastCourseFormat?: string;
|
||||||
protected sectionSelectorExpanded = false;
|
protected sectionSelectorExpanded = false;
|
||||||
|
@ -125,38 +119,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
* Component being initialized.
|
* Component being initialized.
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
// Listen for section status changes.
|
|
||||||
this.sectionStatusObserver = CoreEvents.on(
|
|
||||||
CoreEvents.SECTION_STATUS_CHANGED,
|
|
||||||
async (data) => {
|
|
||||||
if (!this.downloadEnabled || !this.sections?.length || !data.sectionId || data.courseId != this.course?.id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the affected section is being downloaded.
|
|
||||||
// If so, we don't update section status because it'll already be updated when the download finishes.
|
|
||||||
const downloadId = CoreCourseHelper.getSectionDownloadId({ id: data.sectionId });
|
|
||||||
if (CoreCourseModulePrefetchDelegate.isBeingDownloaded(downloadId)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the affected section.
|
|
||||||
const section = this.sections.find(section => section.id == data.sectionId);
|
|
||||||
if (!section) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recalculate the status.
|
|
||||||
await CoreCourseHelper.calculateSectionStatus(section, this.course.id, false);
|
|
||||||
|
|
||||||
if (section.isDownloading && !CoreCourseModulePrefetchDelegate.isBeingDownloaded(downloadId)) {
|
|
||||||
// All the modules are now downloading, set a download all promise.
|
|
||||||
this.prefetch(section);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
CoreSites.getCurrentSiteId(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Listen for select course tab events to select the right section if needed.
|
// Listen for select course tab events to select the right section if needed.
|
||||||
this.selectTabObserver = CoreEvents.on(CoreEvents.SELECT_COURSE_TAB, (data) => {
|
this.selectTabObserver = CoreEvents.on(CoreEvents.SELECT_COURSE_TAB, (data) => {
|
||||||
if (data.name) {
|
if (data.name) {
|
||||||
|
@ -205,10 +167,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
this.sectionSelectorModalOptions.componentProps!.sections = this.sections;
|
this.sectionSelectorModalOptions.componentProps!.sections = this.sections;
|
||||||
this.treatSections(this.sections);
|
this.treatSections(this.sections);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.downloadEnabled && (changes.downloadEnabled || changes.sections)) {
|
|
||||||
this.calculateSectionsStatus(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -219,7 +177,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
this.data.sections = this.sections;
|
this.data.sections = this.sections;
|
||||||
this.data.initialSectionId = this.initialSectionId;
|
this.data.initialSectionId = this.initialSectionId;
|
||||||
this.data.initialSectionNumber = this.initialSectionNumber;
|
this.data.initialSectionNumber = this.initialSectionNumber;
|
||||||
this.data.downloadEnabled = this.downloadEnabled;
|
|
||||||
this.data.moduleId = this.moduleId;
|
this.data.moduleId = this.moduleId;
|
||||||
this.data.completionChanged = this.completionChanged;
|
this.data.completionChanged = this.completionChanged;
|
||||||
}
|
}
|
||||||
|
@ -394,9 +351,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
this.canLoadMore = false;
|
this.canLoadMore = false;
|
||||||
this.showSectionId = 0;
|
this.showSectionId = 0;
|
||||||
this.showMoreActivities();
|
this.showMoreActivities();
|
||||||
if (this.downloadEnabled) {
|
|
||||||
this.calculateSectionsStatus(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.moduleId && previousValue === undefined) {
|
if (this.moduleId && previousValue === undefined) {
|
||||||
|
@ -432,62 +386,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
return section1 && section2 ? section1.id === section2.id : section1 === section2;
|
return section1 && section2 ? section1.id === section2.id : section1 === section2;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate the status of sections.
|
|
||||||
*
|
|
||||||
* @param refresh If refresh or not.
|
|
||||||
*/
|
|
||||||
protected calculateSectionsStatus(refresh?: boolean): void {
|
|
||||||
if (!this.sections || !this.course) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CoreUtils.ignoreErrors(CoreCourseHelper.calculateSectionsStatus(this.sections, this.course.id, refresh));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Confirm and prefetch a section. If the section is "all sections", prefetch all the sections.
|
|
||||||
*
|
|
||||||
* @param section Section to download.
|
|
||||||
* @param refresh Refresh clicked (not used).
|
|
||||||
*/
|
|
||||||
async prefetch(section: CoreCourseSectionWithStatus): Promise<void> {
|
|
||||||
section.isCalculating = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await CoreCourseHelper.confirmDownloadSizeSection(this.course!.id, section, this.sections);
|
|
||||||
|
|
||||||
await this.prefetchSection(section, true);
|
|
||||||
} catch (error) {
|
|
||||||
// User cancelled or there was an error calculating the size.
|
|
||||||
if (error) {
|
|
||||||
CoreDomUtils.showErrorModal(error);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
section.isCalculating = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prefetch a section.
|
|
||||||
*
|
|
||||||
* @param section The section to download.
|
|
||||||
* @param manual Whether the prefetch was started manually or it was automatically started because all modules
|
|
||||||
* are being downloaded.
|
|
||||||
*/
|
|
||||||
protected async prefetchSection(section: CoreCourseSectionWithStatus, manual?: boolean): Promise<void> {
|
|
||||||
try {
|
|
||||||
await CoreCourseHelper.prefetchSection(section, this.course!.id, this.sections);
|
|
||||||
} catch (error) {
|
|
||||||
// Don't show error message if it's an automatic download.
|
|
||||||
if (!manual) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingsection', true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refresh the data.
|
* Refresh the data.
|
||||||
*
|
*
|
||||||
|
@ -580,7 +478,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
* Component destroyed.
|
* Component destroyed.
|
||||||
*/
|
*/
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.sectionStatusObserver && this.sectionStatusObserver.off();
|
|
||||||
this.selectTabObserver && this.selectTabObserver.off();
|
this.selectTabObserver && this.selectTabObserver.off();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -591,17 +488,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
this.dynamicComponents?.forEach((component) => {
|
this.dynamicComponents?.forEach((component) => {
|
||||||
component.callComponentFunction('ionViewDidEnter');
|
component.callComponentFunction('ionViewDidEnter');
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!this.downloadEnabled || !this.course || !this.sections) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The download status of a section might have been changed from within a module page.
|
|
||||||
if (this.selectedSection && this.selectedSection.id !== CoreCourseProvider.ALL_SECTIONS_ID) {
|
|
||||||
CoreCourseHelper.calculateSectionStatus(this.selectedSection, this.course.id, false, false);
|
|
||||||
} else {
|
|
||||||
CoreCourseHelper.calculateSectionsStatus(this.sections, this.course.id, false, false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -653,17 +539,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
this.updateProgress();
|
this.updateProgress();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Recalculate the download status of each section, in response to a module being downloaded.
|
|
||||||
*/
|
|
||||||
onModuleStatusChange(): void {
|
|
||||||
if (!this.downloadEnabled || !this.sections || !this.course) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CoreCourseHelper.calculateSectionsStatus(this.sections, this.course.id, false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update course progress.
|
* Update course progress.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<ion-label class="core-module-title">
|
<ion-label class="core-module-title">
|
||||||
<p class="item-heading">
|
<p class="item-heading">
|
||||||
<core-format-text [text]="module.handlerData.title" contextLevel="module" [contextInstanceId]="module.id"
|
<core-format-text [text]="module.handlerData.title" contextLevel="module" [contextInstanceId]="module.id"
|
||||||
[courseId]="courseId" [attr.aria-label]="module.handlerData.a11yTitle + ', ' + modNameTranslated">
|
[courseId]="module.course" [attr.aria-label]="module.handlerData.a11yTitle + ', ' + modNameTranslated">
|
||||||
</core-format-text>
|
</core-format-text>
|
||||||
</p>
|
</p>
|
||||||
<ion-badge *ngIf="module.handlerData.extraBadge" [color]="module.handlerData.extraBadgeColor"
|
<ion-badge *ngIf="module.handlerData.extraBadge" [color]="module.handlerData.extraBadgeColor"
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
<div class="core-module-availabilityinfo" *ngIf="module.availabilityinfo">
|
<div class="core-module-availabilityinfo" *ngIf="module.availabilityinfo">
|
||||||
<ion-badge class="ion-text-wrap">{{ 'core.restricted' | translate }}</ion-badge>
|
<ion-badge class="ion-text-wrap">{{ 'core.restricted' | translate }}</ion-badge>
|
||||||
<core-format-text [text]="module.availabilityinfo" contextLevel="module" [contextInstanceId]="module.id"
|
<core-format-text [text]="module.availabilityinfo" contextLevel="module" [contextInstanceId]="module.id"
|
||||||
[courseId]="courseId" class="ion-text-wrap">
|
[courseId]="module.course" class="ion-text-wrap">
|
||||||
</core-format-text>
|
</core-format-text>
|
||||||
</div>
|
</div>
|
||||||
<ion-badge *ngIf="module.completiondata?.offline" color="warning" class="ion-text-wrap">
|
<ion-badge *ngIf="module.completiondata?.offline" color="warning" class="ion-text-wrap">
|
||||||
|
@ -48,13 +48,9 @@
|
||||||
</core-course-module-completion-legacy>
|
</core-course-module-completion-legacy>
|
||||||
|
|
||||||
<div class="core-module-buttons-more">
|
<div class="core-module-buttons-more">
|
||||||
<core-download-refresh [status]="downloadStatus" [enabled]="downloadEnabled" [canTrustDownload]="true"
|
|
||||||
[loading]="spinner || module.handlerData.spinner" (action)="download($event)" size="small">
|
|
||||||
</core-download-refresh>
|
|
||||||
|
|
||||||
<!-- Buttons defined by the module handler. -->
|
<!-- Buttons defined by the module handler. -->
|
||||||
<ion-button fill="clear" *ngFor="let button of module.handlerData.buttons" color="dark" size="small"
|
<ion-button fill="clear" *ngFor="let button of module.handlerData.buttons" color="dark" size="small"
|
||||||
[hidden]="button.hidden || spinner || module.handlerData.spinner" class="core-animate-show-hide"
|
[hidden]="button.hidden || module.handlerData.spinner" class="core-animate-show-hide"
|
||||||
(click)="buttonClicked($event, button)" [attr.aria-label]="button.label | translate:{$a: module.handlerData.title}">
|
(click)="buttonClicked($event, button)" [attr.aria-label]="button.label | translate:{$a: module.handlerData.title}">
|
||||||
<ion-icon [name]="button.icon" slot="icon-only" aria-hidden="true"></ion-icon>
|
<ion-icon [name]="button.icon" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
@ -81,7 +77,7 @@
|
||||||
</core-course-module-completion>
|
</core-course-module-completion>
|
||||||
|
|
||||||
<core-format-text class="core-module-description" *ngIf="module.description" [maxHeight]="80" [text]="module.description"
|
<core-format-text class="core-module-description" *ngIf="module.description" [maxHeight]="80" [text]="module.description"
|
||||||
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
|
contextLevel="module" [contextInstanceId]="module.id" [courseId]="module.course">
|
||||||
</core-format-text>
|
</core-format-text>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
|
@ -15,20 +15,13 @@
|
||||||
import { Component, Input, Output, EventEmitter, OnInit, OnDestroy } from '@angular/core';
|
import { Component, Input, Output, EventEmitter, OnInit, OnDestroy } from '@angular/core';
|
||||||
|
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
|
||||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
|
||||||
import {
|
import {
|
||||||
CoreCourseHelper,
|
|
||||||
CoreCourseModuleData,
|
CoreCourseModuleData,
|
||||||
CoreCourseModuleCompletionData,
|
CoreCourseModuleCompletionData,
|
||||||
CoreCourseSection,
|
CoreCourseSection,
|
||||||
} from '@features/course/services/course-helper';
|
} from '@features/course/services/course-helper';
|
||||||
import { CoreCourse } from '@features/course/services/course';
|
import { CoreCourse } from '@features/course/services/course';
|
||||||
import { CoreCourseModuleDelegate, CoreCourseModuleHandlerButton } from '@features/course/services/module-delegate';
|
import { CoreCourseModuleDelegate, CoreCourseModuleHandlerButton } from '@features/course/services/module-delegate';
|
||||||
import {
|
|
||||||
CoreCourseModulePrefetchDelegate,
|
|
||||||
CoreCourseModulePrefetchHandler,
|
|
||||||
} from '@features/course/services/module-prefetch-delegate';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component to display a module entry in a list of modules.
|
* Component to display a module entry in a list of modules.
|
||||||
|
@ -45,47 +38,20 @@ import {
|
||||||
export class CoreCourseModuleComponent implements OnInit, OnDestroy {
|
export class CoreCourseModuleComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
@Input() module!: CoreCourseModuleData; // The module to render.
|
@Input() module!: CoreCourseModuleData; // The module to render.
|
||||||
@Input() courseId?: number; // The course the module belongs to.
|
|
||||||
@Input() section?: CoreCourseSection; // The section the module belongs to.
|
@Input() section?: CoreCourseSection; // The section the module belongs to.
|
||||||
@Input() showActivityDates = false; // Whether to show activity dates.
|
@Input() showActivityDates = false; // Whether to show activity dates.
|
||||||
@Input() showCompletionConditions = false; // Whether to show activity completion conditions.
|
@Input() showCompletionConditions = false; // Whether to show activity completion conditions.
|
||||||
// eslint-disable-next-line @angular-eslint/no-input-rename
|
|
||||||
@Input('downloadEnabled') set enabled(value: boolean) {
|
|
||||||
this.downloadEnabled = value;
|
|
||||||
|
|
||||||
if (!this.module.handlerData?.showDownloadButton || !this.downloadEnabled || this.statusCalculated) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// First time that the download is enabled. Initialize the data.
|
|
||||||
this.statusCalculated = true;
|
|
||||||
this.spinner = true; // Show spinner while calculating the status.
|
|
||||||
|
|
||||||
// Get current status to decide which icon should be shown.
|
|
||||||
this.calculateAndShowStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Output() completionChanged = new EventEmitter<CoreCourseModuleCompletionData>(); // Notify when module completion changes.
|
@Output() completionChanged = new EventEmitter<CoreCourseModuleCompletionData>(); // Notify when module completion changes.
|
||||||
@Output() statusChanged = new EventEmitter<CoreCourseModuleStatusChangedData>(); // Notify when the download status changes.
|
|
||||||
|
|
||||||
downloadStatus?: string;
|
|
||||||
spinner?: boolean; // Whether to display a loading spinner.
|
|
||||||
downloadEnabled?: boolean; // Whether the download of sections and modules is enabled.
|
|
||||||
modNameTranslated = '';
|
modNameTranslated = '';
|
||||||
hasInfo = false;
|
hasInfo = false;
|
||||||
showLegacyCompletion = false; // Whether to show module completion in the old format.
|
showLegacyCompletion = false; // Whether to show module completion in the old format.
|
||||||
showManualCompletion = false; // Whether to show manual completion when completion conditions are disabled.
|
showManualCompletion = false; // Whether to show manual completion when completion conditions are disabled.
|
||||||
|
|
||||||
protected prefetchHandler?: CoreCourseModulePrefetchHandler;
|
|
||||||
protected statusObserver?: CoreEventObserver;
|
|
||||||
protected statusCalculated = false;
|
|
||||||
protected isDestroyed = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component being initialized.
|
* Component being initialized.
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.courseId = this.courseId || this.module.course;
|
|
||||||
this.modNameTranslated = CoreCourse.translateModuleName(this.module.modname) || '';
|
this.modNameTranslated = CoreCourse.translateModuleName(this.module.modname) || '';
|
||||||
this.showLegacyCompletion = !CoreSites.getCurrentSite()?.isVersionGreaterEqualThan('3.11');
|
this.showLegacyCompletion = !CoreSites.getCurrentSite()?.isVersionGreaterEqualThan('3.11');
|
||||||
this.checkShowManualCompletion();
|
this.checkShowManualCompletion();
|
||||||
|
@ -103,29 +69,6 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy {
|
||||||
(this.showCompletionConditions && this.module.completiondata.isautomatic))
|
(this.showCompletionConditions && this.module.completiondata.isautomatic))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.module.handlerData.showDownloadButton) {
|
|
||||||
// Listen for changes on this module status, even if download isn't enabled.
|
|
||||||
this.prefetchHandler = CoreCourseModulePrefetchDelegate.getPrefetchHandlerFor(this.module.modname);
|
|
||||||
|
|
||||||
this.statusObserver = CoreEvents.on(CoreEvents.PACKAGE_STATUS_CHANGED, (data) => {
|
|
||||||
if (!this.module || data.componentId != this.module.id || !this.prefetchHandler ||
|
|
||||||
data.component != this.prefetchHandler.component) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call determineModuleStatus to get the right status to display.
|
|
||||||
const status = CoreCourseModulePrefetchDelegate.determineModuleStatus(this.module, data.status);
|
|
||||||
|
|
||||||
if (this.downloadEnabled) {
|
|
||||||
// Download is enabled, show the status.
|
|
||||||
this.showStatus(status);
|
|
||||||
} else if (this.module.handlerData?.updateStatus) {
|
|
||||||
// Download isn't enabled but the handler defines a updateStatus function, call it anyway.
|
|
||||||
this.module.handlerData.updateStatus(status);
|
|
||||||
}
|
|
||||||
}, CoreSites.getCurrentSiteId());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -143,7 +86,7 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy {
|
||||||
*/
|
*/
|
||||||
moduleClicked(event: Event): void {
|
moduleClicked(event: Event): void {
|
||||||
if (this.module.uservisible !== false && this.module.handlerData?.action) {
|
if (this.module.uservisible !== false && this.module.handlerData?.action) {
|
||||||
this.module.handlerData.action(event, this.module, this.courseId!);
|
this.module.handlerData.action(event, this.module, this.module.course);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,91 +104,14 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
button.action(event, this.module, this.courseId!);
|
button.action(event, this.module, this.module.course);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Download the module.
|
|
||||||
*
|
|
||||||
* @param refresh Whether it's refreshing.
|
|
||||||
* @return Promise resolved when done.
|
|
||||||
*/
|
|
||||||
async download(refresh: boolean): Promise<void> {
|
|
||||||
if (!this.prefetchHandler || !this.module) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show spinner since this operation might take a while.
|
|
||||||
this.spinner = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Get download size to ask for confirm if it's high.
|
|
||||||
const size = await this.prefetchHandler.getDownloadSize(this.module, this.module.course, true);
|
|
||||||
|
|
||||||
await CoreCourseHelper.prefetchModule(this.prefetchHandler, this.module, size, this.module.course, refresh);
|
|
||||||
|
|
||||||
const eventData = {
|
|
||||||
sectionId: this.section?.id,
|
|
||||||
moduleId: this.module.id,
|
|
||||||
courseId: this.module.course,
|
|
||||||
};
|
|
||||||
this.statusChanged.emit(eventData);
|
|
||||||
} catch (error) {
|
|
||||||
// Error, hide spinner.
|
|
||||||
this.spinner = false;
|
|
||||||
if (!this.isDestroyed) {
|
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'core.errordownloading', true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show download buttons according to module status.
|
|
||||||
*
|
|
||||||
* @param status Module status.
|
|
||||||
*/
|
|
||||||
protected showStatus(status: string): void {
|
|
||||||
if (!status) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.spinner = false;
|
|
||||||
this.downloadStatus = status;
|
|
||||||
|
|
||||||
this.module.handlerData?.updateStatus?.(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate and show module status.
|
|
||||||
*
|
|
||||||
* @return Promise resolved when done.
|
|
||||||
*/
|
|
||||||
protected async calculateAndShowStatus(): Promise<void> {
|
|
||||||
if (!this.module || !this.courseId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const status = await CoreCourseModulePrefetchDelegate.getModuleStatus(this.module, this.courseId);
|
|
||||||
|
|
||||||
this.showStatus(status);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component destroyed.
|
* Component destroyed.
|
||||||
*/
|
*/
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
// this.statusObserver?.off();
|
|
||||||
this.module.handlerData?.onDestroy?.();
|
this.module.handlerData?.onDestroy?.();
|
||||||
this.isDestroyed = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Data sent to the status changed output.
|
|
||||||
*/
|
|
||||||
export type CoreCourseModuleStatusChangedData = {
|
|
||||||
moduleId: number;
|
|
||||||
courseId: number;
|
|
||||||
sectionId?: number;
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<core-dynamic-component [component]="componentClass" [data]="data"></core-dynamic-component>
|
<core-dynamic-component [component]="componentClass" [data]="data"></core-dynamic-component>
|
||||||
|
|
||||||
<core-block-side-blocks-button *ngIf="course && hasBlocks" [courseId]="course.id" [downloadEnabled]="downloadEnabled">
|
<core-block-side-blocks-button *ngIf="course && hasBlocks" [courseId]="course.id">
|
||||||
</core-block-side-blocks-button>
|
</core-block-side-blocks-button>
|
||||||
|
|
|
@ -36,7 +36,6 @@ export class CoreCourseFormatSingleActivityComponent implements OnChanges {
|
||||||
|
|
||||||
@Input() course?: CoreCourseAnyCourseData; // The course to render.
|
@Input() course?: CoreCourseAnyCourseData; // The course to render.
|
||||||
@Input() sections?: CoreCourseSectionWithStatus[]; // List of course sections.
|
@Input() sections?: CoreCourseSectionWithStatus[]; // List of course sections.
|
||||||
@Input() downloadEnabled?: boolean; // Whether the download of sections and modules is enabled.
|
|
||||||
@Input() initialSectionId?: number; // The section to load first (by ID).
|
@Input() initialSectionId?: number; // The section to load first (by ID).
|
||||||
@Input() initialSectionNumber?: number; // The section to load first (by number).
|
@Input() initialSectionNumber?: number; // The section to load first (by number).
|
||||||
@Input() moduleId?: number; // The module ID to scroll to. Must be inside the initial selected section.
|
@Input() moduleId?: number; // The module ID to scroll to. Must be inside the initial selected section.
|
||||||
|
|
|
@ -68,13 +68,6 @@ export class CoreCourseFormatSingleActivityHandlerService implements CoreCourseF
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
displayEnableDownload(): boolean {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,12 +1,5 @@
|
||||||
<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="toggle" [(toggle)]="downloadEnabled"
|
|
||||||
[content]="'core.settings.showdownloadoptions' | translate" (action)="toggleDownload()">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item [hidden]="!downloadCourseEnabled" [priority]="1900"
|
|
||||||
[content]="prefetchCourseData.statusTranslatable | translate" (action)="prefetchCourse()" [iconAction]="prefetchCourseData.icon"
|
|
||||||
[closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item [priority]="1800" [content]="'core.course.coursesummary' | translate" (action)="openCourseSummary()"
|
<core-context-menu-item [priority]="1800" [content]="'core.course.coursesummary' | translate" (action)="openCourseSummary()"
|
||||||
iconAction="fas-graduation-cap">
|
iconAction="fas-graduation-cap">
|
||||||
</core-context-menu-item>
|
</core-context-menu-item>
|
||||||
|
@ -22,8 +15,7 @@
|
||||||
|
|
||||||
<core-loading [hideUntil]="dataLoaded">
|
<core-loading [hideUntil]="dataLoaded">
|
||||||
<core-course-format [course]="course" [sections]="sections" [initialSectionId]="sectionId" [initialSectionNumber]="sectionNumber"
|
<core-course-format [course]="course" [sections]="sections" [initialSectionId]="sectionId" [initialSectionNumber]="sectionNumber"
|
||||||
[downloadEnabled]="downloadEnabled" [moduleId]="moduleId" (completionChanged)="onCompletionChange($event)"
|
[moduleId]="moduleId" (completionChanged)="onCompletionChange($event)" class="core-course-format-{{course.format}}">
|
||||||
class="core-course-format-{{course.format}}">
|
|
||||||
</core-course-format>
|
</core-course-format>
|
||||||
</core-loading>
|
</core-loading>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
|
@ -15,20 +15,17 @@
|
||||||
import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core';
|
import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core';
|
||||||
import { IonContent, IonRefresher } from '@ionic/angular';
|
import { IonContent, IonRefresher } from '@ionic/angular';
|
||||||
|
|
||||||
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, CoreCoursesProvider } from '@features/courses/services/courses';
|
import { CoreCourses, CoreCourseAnyCourseData } from '@features/courses/services/courses';
|
||||||
import {
|
import {
|
||||||
CoreCourse,
|
CoreCourse,
|
||||||
CoreCourseCompletionActivityStatus,
|
CoreCourseCompletionActivityStatus,
|
||||||
CoreCourseProvider,
|
|
||||||
} from '@features/course/services/course';
|
} from '@features/course/services/course';
|
||||||
import {
|
import {
|
||||||
CoreCourseHelper,
|
CoreCourseHelper,
|
||||||
CoreCourseModuleCompletionData,
|
CoreCourseModuleCompletionData,
|
||||||
CoreCourseSection,
|
CoreCourseSection,
|
||||||
CorePrefetchStatusInfo,
|
|
||||||
} from '@features/course/services/course-helper';
|
} from '@features/course/services/course-helper';
|
||||||
import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate';
|
import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate';
|
||||||
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
|
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
|
||||||
|
@ -43,7 +40,6 @@ import {
|
||||||
CoreEventObserver,
|
CoreEventObserver,
|
||||||
} from '@singletons/events';
|
} from '@singletons/events';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
import { CoreConstants } from '@/core/constants';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays the contents of a course.
|
* Page that displays the contents of a course.
|
||||||
|
@ -63,47 +59,19 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
||||||
sectionNumber?: number;
|
sectionNumber?: number;
|
||||||
courseMenuHandlers: CoreCourseOptionsMenuHandlerToDisplay[] = [];
|
courseMenuHandlers: CoreCourseOptionsMenuHandlerToDisplay[] = [];
|
||||||
dataLoaded = false;
|
dataLoaded = false;
|
||||||
downloadEnabled = false;
|
|
||||||
downloadCourseEnabled = false;
|
downloadCourseEnabled = false;
|
||||||
moduleId?: number;
|
moduleId?: number;
|
||||||
displayEnableDownload = false;
|
displayEnableDownload = false;
|
||||||
displayRefresher = false;
|
displayRefresher = false;
|
||||||
prefetchCourseData: CorePrefetchStatusInfo = {
|
|
||||||
icon: CoreConstants.ICON_LOADING,
|
|
||||||
statusTranslatable: 'core.course.downloadcourse',
|
|
||||||
status: '',
|
|
||||||
loading: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
protected formatOptions?: Record<string, unknown>;
|
protected formatOptions?: Record<string, unknown>;
|
||||||
protected completionObserver?: CoreEventObserver;
|
protected completionObserver?: 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 = false;
|
||||||
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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
|
@ -121,13 +89,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
||||||
this.sectionId = CoreNavigator.getRouteNumberParam('sectionId');
|
this.sectionId = CoreNavigator.getRouteNumberParam('sectionId');
|
||||||
this.sectionNumber = CoreNavigator.getRouteNumberParam('sectionNumber');
|
this.sectionNumber = CoreNavigator.getRouteNumberParam('sectionNumber');
|
||||||
this.moduleId = CoreNavigator.getRouteNumberParam('moduleId');
|
this.moduleId = CoreNavigator.getRouteNumberParam('moduleId');
|
||||||
this.isGuest = CoreNavigator.getRouteBooleanParam('isGuest');
|
this.isGuest = !!CoreNavigator.getRouteBooleanParam('isGuest');
|
||||||
|
|
||||||
this.displayEnableDownload = !CoreSites.getRequiredCurrentSite().isOfflineDisabled() &&
|
|
||||||
CoreCourseFormatDelegate.displayEnableDownload(this.course);
|
|
||||||
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
|
|
||||||
|
|
||||||
this.downloadEnabled = this.displayEnableDownload && CoreCourses.getCourseDownloadOptionsEnabled();
|
|
||||||
|
|
||||||
this.debouncedUpdateCachedCompletion = CoreUtils.debounce(() => {
|
this.debouncedUpdateCachedCompletion = CoreUtils.debounce(() => {
|
||||||
if (this.modulesHaveCompletion) {
|
if (this.modulesHaveCompletion) {
|
||||||
|
@ -149,8 +111,6 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
||||||
await this.loadData(false, true);
|
await this.loadData(false, true);
|
||||||
|
|
||||||
this.dataLoaded = true;
|
this.dataLoaded = true;
|
||||||
|
|
||||||
this.initPrefetch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -159,15 +119,6 @@ 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 && !this.courseStatusObserver) {
|
|
||||||
// Listen for changes in course status.
|
|
||||||
this.courseStatusObserver = CoreEvents.on(CoreEvents.COURSE_STATUS_CHANGED, (data) => {
|
|
||||||
if (data.courseId == this.course.id || data.courseId == CoreCourseProvider.ALL_COURSES_CLEARED) {
|
|
||||||
this.updateCourseStatus(data.status);
|
|
||||||
}
|
|
||||||
}, CoreSites.getCurrentSiteId());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the course format requires the view to be refreshed when completion changes.
|
// Check if the course format requires the view to be refreshed when completion changes.
|
||||||
const shouldRefresh = await CoreCourseFormatDelegate.shouldRefreshWhenCompletionChanges(this.course);
|
const shouldRefresh = await CoreCourseFormatDelegate.shouldRefreshWhenCompletionChanges(this.course);
|
||||||
if (!shouldRefresh) {
|
if (!shouldRefresh) {
|
||||||
|
@ -200,41 +151,6 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Init prefetch data if needed.
|
|
||||||
*
|
|
||||||
* @return Promise resolved when done.
|
|
||||||
*/
|
|
||||||
protected async initPrefetch(): Promise<void> {
|
|
||||||
if (!this.downloadCourseEnabled) {
|
|
||||||
// Cannot download the whole course, stop.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine the course prefetch status.
|
|
||||||
await this.determineCoursePrefetchIcon();
|
|
||||||
|
|
||||||
if (this.prefetchCourseData.icon != CoreConstants.ICON_LOADING) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Course is being downloaded. Get the download promise.
|
|
||||||
const promise = CoreCourseHelper.getCourseDownloadPromise(this.course.id);
|
|
||||||
if (promise) {
|
|
||||||
// There is a download promise. Show an error if it fails.
|
|
||||||
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.
|
|
||||||
const status = await CoreCourse.setCoursePreviousStatus(this.course.id);
|
|
||||||
|
|
||||||
this.updateCourseStatus(status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch and load all the data required for the view.
|
* Fetch and load all the data required for the view.
|
||||||
*
|
*
|
||||||
|
@ -314,7 +230,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add handlers
|
// Add handlers
|
||||||
const result = CoreCourseHelper.addHandlerDataForModules(
|
const result = await CoreCourseHelper.addHandlerDataForModules(
|
||||||
sections,
|
sections,
|
||||||
this.course.id,
|
this.course.id,
|
||||||
completionStatus,
|
completionStatus,
|
||||||
|
@ -463,59 +379,6 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines the prefetch icon of the course.
|
|
||||||
*
|
|
||||||
* @return Promise resolved when done.
|
|
||||||
*/
|
|
||||||
protected async determineCoursePrefetchIcon(): Promise<void> {
|
|
||||||
this.prefetchCourseData = await CoreCourseHelper.getCourseStatusIconAndTitle(this.course.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prefetch the whole course.
|
|
||||||
*/
|
|
||||||
async prefetchCourse(): Promise<void> {
|
|
||||||
try {
|
|
||||||
await CoreCourseHelper.confirmAndPrefetchCourse(
|
|
||||||
this.prefetchCourseData,
|
|
||||||
this.course,
|
|
||||||
{
|
|
||||||
sections: this.sections,
|
|
||||||
menuHandlers: this.courseMenuHandlers,
|
|
||||||
isGuest: this.isGuest,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
if (this.isDestroyed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle download enabled.
|
|
||||||
*/
|
|
||||||
toggleDownload(): void {
|
|
||||||
CoreCourses.setCourseDownloadOptionsEnabled(this.downloadEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open the course summary
|
* Open the course summary
|
||||||
*/
|
*/
|
||||||
|
@ -542,10 +405,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.isDestroyed = true;
|
this.isDestroyed = true;
|
||||||
this.completionObserver?.off();
|
this.completionObserver?.off();
|
||||||
this.courseStatusObserver?.off();
|
|
||||||
this.syncObserver?.off();
|
this.syncObserver?.off();
|
||||||
this.siteUpdatedObserver?.off();
|
|
||||||
this.downloadEnabledObserver?.off();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -19,8 +19,7 @@
|
||||||
<ion-list>
|
<ion-list>
|
||||||
<ng-container *ngFor="let section of sections">
|
<ng-container *ngFor="let section of sections">
|
||||||
<ng-container *ngFor="let module of section.modules">
|
<ng-container *ngFor="let module of section.modules">
|
||||||
<core-course-module *ngIf="module.visibleoncoursepage !== 0" [module]="module" [section]="section" [courseId]="courseId"
|
<core-course-module *ngIf="module.visibleoncoursepage !== 0" [module]="module" [section]="section">
|
||||||
[downloadEnabled]="downloadEnabled">
|
|
||||||
</core-course-module>
|
</core-course-module>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
|
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
import { CoreSites } from '@services/sites';
|
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreCourse } from '@features/course/services/course';
|
import { CoreCourse } from '@features/course/services/course';
|
||||||
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
|
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
|
||||||
|
@ -36,7 +35,6 @@ export class CoreCourseListModTypePage implements OnInit {
|
||||||
sections: CoreCourseSection[] = [];
|
sections: CoreCourseSection[] = [];
|
||||||
title = '';
|
title = '';
|
||||||
loaded = false;
|
loaded = false;
|
||||||
downloadEnabled = false;
|
|
||||||
courseId?: number;
|
courseId?: number;
|
||||||
|
|
||||||
protected modName?: string;
|
protected modName?: string;
|
||||||
|
@ -49,7 +47,6 @@ export class CoreCourseListModTypePage implements OnInit {
|
||||||
this.title = CoreNavigator.getRouteParam('title') || '';
|
this.title = CoreNavigator.getRouteParam('title') || '';
|
||||||
this.courseId = CoreNavigator.getRouteNumberParam('courseId');
|
this.courseId = CoreNavigator.getRouteNumberParam('courseId');
|
||||||
this.modName = CoreNavigator.getRouteParam('modName');
|
this.modName = CoreNavigator.getRouteParam('modName');
|
||||||
this.downloadEnabled = !CoreSites.getCurrentSite()?.isOfflineDisabled();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.fetchData();
|
await this.fetchData();
|
||||||
|
@ -105,7 +102,7 @@ export class CoreCourseListModTypePage implements OnInit {
|
||||||
return section.modules.length > 0;
|
return section.modules.length > 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = CoreCourseHelper.addHandlerDataForModules(sections, this.courseId);
|
const result = await CoreCourseHelper.addHandlerDataForModules(sections, this.courseId);
|
||||||
|
|
||||||
this.sections = result.sections;
|
this.sections = result.sections;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -168,58 +168,60 @@ export class CoreCourseHelperProvider {
|
||||||
* @param forCoursePage Whether the data will be used to render the course page.
|
* @param forCoursePage Whether the data will be used to render the course page.
|
||||||
* @return Whether the sections have content.
|
* @return Whether the sections have content.
|
||||||
*/
|
*/
|
||||||
addHandlerDataForModules(
|
async addHandlerDataForModules(
|
||||||
sections: CoreCourseWSSection[],
|
sections: CoreCourseWSSection[],
|
||||||
courseId: number,
|
courseId: number,
|
||||||
completionStatus?: Record<string, CoreCourseCompletionActivityStatus>,
|
completionStatus?: Record<string, CoreCourseCompletionActivityStatus>,
|
||||||
courseName?: string,
|
courseName?: string,
|
||||||
forCoursePage = false,
|
forCoursePage = false,
|
||||||
): { hasContent: boolean; sections: CoreCourseSection[] } {
|
): Promise<{ hasContent: boolean; sections: CoreCourseSection[] }> {
|
||||||
|
|
||||||
let hasContent = false;
|
let hasContent = false;
|
||||||
|
|
||||||
const formattedSections = sections.map<CoreCourseSection>((courseSection) => {
|
const formattedSections = await Promise.all(
|
||||||
const section = {
|
sections.map<Promise<CoreCourseSection>>(async (courseSection) => {
|
||||||
...courseSection,
|
const section = {
|
||||||
hasContent: this.sectionHasContent(courseSection),
|
...courseSection,
|
||||||
};
|
hasContent: this.sectionHasContent(courseSection),
|
||||||
|
};
|
||||||
|
|
||||||
if (!section.hasContent) {
|
if (!section.hasContent) {
|
||||||
return section;
|
return section;
|
||||||
}
|
|
||||||
|
|
||||||
hasContent = true;
|
|
||||||
|
|
||||||
section.modules.forEach(async (module) => {
|
|
||||||
module.handlerData = await CoreCourseModuleDelegate.getModuleDataFor(
|
|
||||||
module.modname,
|
|
||||||
module,
|
|
||||||
courseId,
|
|
||||||
section.id,
|
|
||||||
forCoursePage,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!module.completiondata && completionStatus && completionStatus[module.id] !== undefined) {
|
|
||||||
// Should not happen on > 3.6. Check if activity has completions and if it's marked.
|
|
||||||
const activityStatus = completionStatus[module.id];
|
|
||||||
|
|
||||||
module.completiondata = {
|
|
||||||
state: activityStatus.state,
|
|
||||||
timecompleted: activityStatus.timecompleted,
|
|
||||||
overrideby: activityStatus.overrideby || 0,
|
|
||||||
valueused: activityStatus.valueused,
|
|
||||||
tracking: activityStatus.tracking,
|
|
||||||
courseId,
|
|
||||||
cmid: module.id,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the module is stealth.
|
hasContent = true;
|
||||||
module.isStealth = module.visibleoncoursepage === 0 || (!!module.visible && !section.visible);
|
|
||||||
});
|
|
||||||
|
|
||||||
return section;
|
await Promise.all(section.modules.map(async (module) => {
|
||||||
});
|
module.handlerData = await CoreCourseModuleDelegate.getModuleDataFor(
|
||||||
|
module.modname,
|
||||||
|
module,
|
||||||
|
courseId,
|
||||||
|
section.id,
|
||||||
|
forCoursePage,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!module.completiondata && completionStatus && completionStatus[module.id] !== undefined) {
|
||||||
|
// Should not happen on > 3.6. Check if activity has completions and if it's marked.
|
||||||
|
const activityStatus = completionStatus[module.id];
|
||||||
|
|
||||||
|
module.completiondata = {
|
||||||
|
state: activityStatus.state,
|
||||||
|
timecompleted: activityStatus.timecompleted,
|
||||||
|
overrideby: activityStatus.overrideby || 0,
|
||||||
|
valueused: activityStatus.valueused,
|
||||||
|
tracking: activityStatus.tracking,
|
||||||
|
courseId,
|
||||||
|
cmid: module.id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the module is stealth.
|
||||||
|
module.isStealth = module.visibleoncoursepage === 0 || (!!module.visible && !section.visible);
|
||||||
|
}));
|
||||||
|
|
||||||
|
return section;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
return { hasContent, sections: formattedSections };
|
return { hasContent, sections: formattedSections };
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,8 +58,9 @@ export interface CoreCourseFormatHandler extends CoreDelegateHandler {
|
||||||
displayBlocks?(course: CoreCourseAnyCourseData): boolean;
|
displayBlocks?(course: CoreCourseAnyCourseData): boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the option to enable section/module download should be displayed. Defaults to true.
|
* Whether the option to enable section/module download should be displayed.
|
||||||
*
|
*
|
||||||
|
* @deprecated on 4.0 Not used anymore because prefetch has been moved to storage manager.
|
||||||
* @param course The course to check.
|
* @param course The course to check.
|
||||||
* @return Whether the option to enable section/module download should be displayed.
|
* @return Whether the option to enable section/module download should be displayed.
|
||||||
*/
|
*/
|
||||||
|
@ -204,16 +205,6 @@ export class CoreCourseFormatDelegateService extends CoreDelegate<CoreCourseForm
|
||||||
return !!this.executeFunctionOnEnabled<boolean>(course.format || '', 'displayBlocks', [course]);
|
return !!this.executeFunctionOnEnabled<boolean>(course.format || '', 'displayBlocks', [course]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the option to enable section/module download should be displayed. Defaults to true.
|
|
||||||
*
|
|
||||||
* @param course The course to check.
|
|
||||||
* @return Whether the option to enable section/module download should be displayed
|
|
||||||
*/
|
|
||||||
displayEnableDownload(course: CoreCourseAnyCourseData): boolean {
|
|
||||||
return !!this.executeFunctionOnEnabled<boolean>(course.format || '', 'displayEnableDownload', [course]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the course refresher should be displayed. If it returns false, a refresher must be included in the course format,
|
* Whether the course refresher should be displayed. If it returns false, a refresher must be included in the course format,
|
||||||
* and the doRefresh method of CoreCourseSectionPage must be called on refresh. Defaults to true.
|
* and the doRefresh method of CoreCourseSectionPage must be called on refresh. Defaults to true.
|
||||||
|
|
|
@ -78,17 +78,6 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the option to enable section/module download should be displayed. Defaults to true.
|
|
||||||
*
|
|
||||||
* @param course The course to check.
|
|
||||||
* @return Whether the option to enable section/module download should be displayed
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
displayEnableDownload(course: CoreCourseAnyCourseData): boolean {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the default section selector should be displayed. Defaults to true.
|
* Whether the default section selector should be displayed. Defaults to true.
|
||||||
*
|
*
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
|
|
||||||
<core-block-side-blocks-button *ngIf="hasSideBlocks" [downloadEnabled]="downloadEnabled"></core-block-side-blocks-button>
|
<core-block-side-blocks-button *ngIf="hasSideBlocks"></core-block-side-blocks-button>
|
||||||
|
|
||||||
<core-empty-box *ngIf="blocks.length == 0" icon="fas-cubes" [message]="'core.course.nocontentavailable' | translate">
|
<core-empty-box *ngIf="blocks.length == 0" icon="fas-cubes" [message]="'core.course.nocontentavailable' | translate">
|
||||||
</core-empty-box>
|
</core-empty-box>
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
<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 [priority]="1000" *ngIf="displayEnableDownload" [content]="'core.settings.showdownloadoptions' | translate"
|
|
||||||
(action)="switchDownload()" iconAction="toggle" [(toggle)]="downloadEnabled"></core-context-menu-item>
|
|
||||||
<core-context-menu-item [priority]="500" [content]="'addon.storagemanager.managestorage' | translate"
|
<core-context-menu-item [priority]="500" [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-item [priority]="400" [content]="'addon.storagemanager.managecoursestorage' | translate"
|
||||||
|
(action)="manageCourseStorage()" iconAction="fas-archive"></core-context-menu-item>
|
||||||
</core-context-menu>
|
</core-context-menu>
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
<ion-content>
|
<ion-content>
|
||||||
|
@ -24,8 +24,7 @@
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<core-course-module *ngFor="let module of section.modules" [module]="module" [courseId]="siteHomeId"
|
<core-course-module *ngFor="let module of section.modules" [module]="module" [section]="section"></core-course-module>
|
||||||
[downloadEnabled]="downloadEnabled" [section]="section"></core-course-module>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<!-- Site home items: news, categories, courses, etc. -->
|
<!-- Site home items: news, categories, courses, etc. -->
|
||||||
|
@ -52,7 +51,7 @@
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
<core-block-side-blocks-button *ngIf="hasBlocks" [courseId]="siteHomeId" [downloadEnabled]="downloadEnabled">
|
<core-block-side-blocks-button *ngIf="hasBlocks" [courseId]="siteHomeId">
|
||||||
</core-block-side-blocks-button>
|
</core-block-side-blocks-button>
|
||||||
|
|
||||||
<core-empty-box *ngIf="!hasContent" icon="fas-box-open" [message]="'core.course.nocontentavailable' | translate">
|
<core-empty-box *ngIf="!hasContent" icon="fas-box-open" [message]="'core.course.nocontentavailable' | translate">
|
||||||
|
@ -71,8 +70,7 @@
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #news>
|
<ng-template #news>
|
||||||
<core-course-module class="core-sitehome-news" *ngIf="newsForumModule" [module]="newsForumModule" [courseId]="siteHomeId"
|
<core-course-module class="core-sitehome-news" *ngIf="newsForumModule" [module]="newsForumModule">
|
||||||
[downloadEnabled]="downloadEnabled">
|
|
||||||
</core-course-module>
|
</core-course-module>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ import { CoreCourse, CoreCourseWSSection } from '@features/course/services/cours
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreSiteHome } from '@features/sitehome/services/sitehome';
|
import { CoreSiteHome } from '@features/sitehome/services/sitehome';
|
||||||
import { CoreCourses, CoreCoursesProvider } from '@features//courses/services/courses';
|
import { CoreCourses } from '@features//courses/services/courses';
|
||||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
import { CoreCourseHelper, CoreCourseModuleData } from '@features/course/services/course-helper';
|
import { CoreCourseHelper, CoreCourseModuleData } from '@features/course/services/course-helper';
|
||||||
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
|
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
|
||||||
|
@ -50,24 +50,15 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy {
|
||||||
siteHomeId = 1;
|
siteHomeId = 1;
|
||||||
currentSite!: CoreSite;
|
currentSite!: CoreSite;
|
||||||
searchEnabled = false;
|
searchEnabled = false;
|
||||||
displayEnableDownload = false;
|
|
||||||
downloadEnabled = false;
|
|
||||||
newsForumModule?: CoreCourseModuleData;
|
newsForumModule?: CoreCourseModuleData;
|
||||||
|
|
||||||
protected updateSiteObserver: CoreEventObserver;
|
protected updateSiteObserver: CoreEventObserver;
|
||||||
protected downloadEnabledObserver: CoreEventObserver;
|
|
||||||
|
|
||||||
constructor() {
|
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.displayEnableDownload = !CoreSites.getRequiredCurrentSite().isOfflineDisabled();
|
|
||||||
}, CoreSites.getCurrentSiteId());
|
}, CoreSites.getCurrentSiteId());
|
||||||
|
|
||||||
this.downloadEnabledObserver = CoreEvents.on(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED, (data) => {
|
|
||||||
this.downloadEnabled = data.enabled;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -85,9 +76,6 @@ 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;
|
||||||
});
|
});
|
||||||
|
@ -129,13 +117,15 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy {
|
||||||
// Check "Include a topic section" setting from numsections.
|
// Check "Include a topic section" setting from numsections.
|
||||||
this.section = config.numsections ? sections.find((section) => section.section == 1) : undefined;
|
this.section = config.numsections ? sections.find((section) => section.section == 1) : undefined;
|
||||||
if (this.section) {
|
if (this.section) {
|
||||||
const result = CoreCourseHelper.addHandlerDataForModules(
|
const result = await CoreCourseHelper.addHandlerDataForModules(
|
||||||
[this.section],
|
[this.section],
|
||||||
this.siteHomeId,
|
this.siteHomeId,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.section.hasContent = result.hasContent;
|
||||||
this.hasContent = result.hasContent || this.hasContent;
|
this.hasContent = result.hasContent || this.hasContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,13 +174,6 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Switch download enabled.
|
|
||||||
*/
|
|
||||||
switchDownload(): void {
|
|
||||||
CoreCourses.setCourseDownloadOptionsEnabled(this.downloadEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open page to manage courses storage.
|
* Open page to manage courses storage.
|
||||||
*/
|
*/
|
||||||
|
@ -198,6 +181,13 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy {
|
||||||
CoreNavigator.navigateToSitePath('/storage');
|
CoreNavigator.navigateToSitePath('/storage');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open page to manage course storage.
|
||||||
|
*/
|
||||||
|
manageCourseStorage(): void {
|
||||||
|
CoreNavigator.navigateToSitePath('/storage/' + this.siteHomeId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Go to search courses.
|
* Go to search courses.
|
||||||
*/
|
*/
|
||||||
|
@ -231,7 +221,6 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy {
|
||||||
*/
|
*/
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.updateSiteObserver.off();
|
this.updateSiteObserver.off();
|
||||||
this.downloadEnabledObserver.off();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,13 +35,6 @@ export class CoreSitePluginsCourseFormatHandler extends CoreSitePluginsBaseHandl
|
||||||
return this.handlerSchema.canviewallsections ?? true;
|
return this.handlerSchema.canviewallsections ?? true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
displayEnableDownload(): boolean {
|
|
||||||
return this.handlerSchema.displayenabledownload ?? true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -213,6 +213,10 @@ ion-button.button-outline {
|
||||||
--background: var(--contrast-background);
|
--background: var(--contrast-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ion-button ion-spinner {
|
||||||
|
--color: inherit !important;
|
||||||
|
}
|
||||||
|
|
||||||
@each $color-name, $value in $colors {
|
@each $color-name, $value in $colors {
|
||||||
.text-#{$color-name},
|
.text-#{$color-name},
|
||||||
p.text-#{$color-name} {
|
p.text-#{$color-name} {
|
||||||
|
|
|
@ -9,6 +9,7 @@ information provided here is intended especially for developers.
|
||||||
- CoreCourseModulePrefetchDelegate.getPrefetchHandlerFor now admits module name instead of full module object.
|
- CoreCourseModulePrefetchDelegate.getPrefetchHandlerFor now admits module name instead of full module object.
|
||||||
- CoreCourse.getModuleBasicInfoByInstance and CoreCourse.getModuleBasicInfo have been modified to accept an "options" parameter instead of only siteId.
|
- CoreCourse.getModuleBasicInfoByInstance and CoreCourse.getModuleBasicInfo have been modified to accept an "options" parameter instead of only siteId.
|
||||||
- The function CoreFilepool.isFileDownloadingByUrl now returns Promise<boolean> instead of relying on resolve/reject.
|
- The function CoreFilepool.isFileDownloadingByUrl now returns Promise<boolean> instead of relying on resolve/reject.
|
||||||
|
- downloadEnabled input has been removed from CoreBlockSideBlocksComponent, CoreCourseFormatComponent, CoreCourseFormatSingleActivityComponent and CoreCourseModuleComponent.
|
||||||
|
|
||||||
=== 3.9.5 ===
|
=== 3.9.5 ===
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue