747 lines
25 KiB
TypeScript
747 lines
25 KiB
TypeScript
// (C) Copyright 2015 Moodle Pty Ltd.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
import { CoreConstants } from '@/core/constants';
|
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnDestroy, OnInit } from '@angular/core';
|
|
import { CoreCourse, CoreCourseProvider } from '@features/course/services/course';
|
|
import {
|
|
CoreCourseHelper,
|
|
CoreCourseModuleData,
|
|
CoreCourseSectionWithStatus,
|
|
CorePrefetchStatusInfo,
|
|
} from '@features/course/services/course-helper';
|
|
import {
|
|
CoreCourseModulePrefetchDelegate,
|
|
CoreCourseModulePrefetchHandler } from '@features/course/services/module-prefetch-delegate';
|
|
import { CoreCourses } from '@features/courses/services/courses';
|
|
import { CoreNavigator } from '@services/navigator';
|
|
import { CoreSites } from '@services/sites';
|
|
import { CoreDomUtils } from '@services/utils/dom';
|
|
import { CoreUtils } from '@services/utils/utils';
|
|
import { Translate } from '@singletons';
|
|
import { CoreDom } from '@singletons/dom';
|
|
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
|
|
|
/**
|
|
* Page that displays the amount of file storage used by each activity on the course, and allows
|
|
* the user to prefecth and delete this data.
|
|
*/
|
|
@Component({
|
|
selector: 'page-addon-storagemanager-course-storage',
|
|
templateUrl: 'course-storage.html',
|
|
styleUrls: ['course-storage.scss'],
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
})
|
|
export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
|
|
|
courseId!: number;
|
|
title = '';
|
|
loaded = false;
|
|
sections: AddonStorageManagerCourseSection[] = [];
|
|
totalSize = 0;
|
|
calculatingSize = true;
|
|
|
|
downloadEnabled = false;
|
|
downloadCourseEnabled = false;
|
|
|
|
prefetchCourseData: CorePrefetchStatusInfo = {
|
|
icon: CoreConstants.ICON_LOADING,
|
|
statusTranslatable: 'core.course.downloadcourse',
|
|
status: '',
|
|
loading: true,
|
|
};
|
|
|
|
statusDownloaded = CoreConstants.DOWNLOADED;
|
|
|
|
protected initialSectionId?: number;
|
|
protected siteUpdatedObserver?: CoreEventObserver;
|
|
protected courseStatusObserver?: CoreEventObserver;
|
|
protected sectionStatusObserver?: CoreEventObserver;
|
|
protected moduleStatusObserver?: CoreEventObserver;
|
|
protected isDestroyed = false;
|
|
protected isGuest = false;
|
|
|
|
constructor(protected elementRef: ElementRef, protected changeDetectorRef: ChangeDetectorRef) {
|
|
// Refresh the enabled flags if site is updated.
|
|
this.siteUpdatedObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
|
|
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
|
|
this.downloadEnabled = !CoreSites.getRequiredCurrentSite().isOfflineDisabled();
|
|
|
|
this.initCoursePrefetch();
|
|
this.initModulePrefetch();
|
|
this.changeDetectorRef.markForCheck();
|
|
}, CoreSites.getCurrentSiteId());
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
async ngOnInit(): Promise<void> {
|
|
try {
|
|
this.courseId = CoreNavigator.getRequiredRouteParam('courseId');
|
|
} catch (error) {
|
|
CoreDomUtils.showErrorModal(error);
|
|
|
|
CoreNavigator.back();
|
|
|
|
return;
|
|
}
|
|
|
|
this.title = CoreNavigator.getRouteParam<string>('title') || '';
|
|
if (!this.title && this.courseId == CoreSites.getCurrentSiteHomeId()) {
|
|
this.title = Translate.instant('core.sitehome.sitehome');
|
|
}
|
|
|
|
this.isGuest = !!CoreNavigator.getRouteBooleanParam('isGuest');
|
|
this.initialSectionId = CoreNavigator.getRouteNumberParam('sectionId');
|
|
|
|
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
|
|
this.downloadEnabled = !CoreSites.getRequiredCurrentSite().isOfflineDisabled();
|
|
|
|
const sections = (await CoreCourse.getSections(this.courseId, false, true))
|
|
.filter((section) => !CoreCourseHelper.isSectionStealth(section));
|
|
this.sections = (await CoreCourseHelper.addHandlerDataForModules(sections, this.courseId)).sections
|
|
.map(section => ({
|
|
...section,
|
|
totalSize: 0,
|
|
calculatingSize: true,
|
|
expanded: section.id === this.initialSectionId,
|
|
modules: section.modules.map(module => ({
|
|
...module,
|
|
calculatingSize: true,
|
|
})),
|
|
}));
|
|
|
|
this.loaded = true;
|
|
|
|
CoreDom.scrollToElement(
|
|
this.elementRef.nativeElement,
|
|
'.core-course-storage-section-expanded',
|
|
{ addYAxis: -10 },
|
|
);
|
|
|
|
await Promise.all([
|
|
this.initSizes(),
|
|
this.initCoursePrefetch(),
|
|
this.initModulePrefetch(),
|
|
]);
|
|
this.changeDetectorRef.markForCheck();
|
|
}
|
|
|
|
/**
|
|
* Init course prefetch information.
|
|
*
|
|
* @returns 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.
|
|
*
|
|
* @returns 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 initSizes(): Promise<void> {
|
|
await Promise.all(this.sections.map(async (section) => {
|
|
await Promise.all(section.modules.map(async (module) => {
|
|
// Note: This function only gets the size for modules which are downloadable.
|
|
// For other modules it always returns 0, even if they have downloaded some files.
|
|
// However there is no 100% reliable way to actually track the files in this case.
|
|
// You can maybe guess it based on the component and componentid.
|
|
// But these aren't necessarily consistent, for example mod_frog vs mmaModFrog.
|
|
// There is nothing enforcing correct values.
|
|
// Most modules which have large files are downloadable, so I think this is sufficient.
|
|
const size = await CoreCourseModulePrefetchDelegate.getModuleStoredSize(module, this.courseId);
|
|
|
|
// There are some cases where the return from this is not a valid number.
|
|
if (!isNaN(size)) {
|
|
module.totalSize = Number(size);
|
|
section.totalSize += size;
|
|
this.totalSize += size;
|
|
}
|
|
|
|
module.calculatingSize = false;
|
|
}));
|
|
|
|
section.calculatingSize = false;
|
|
}));
|
|
|
|
this.calculatingSize = false;
|
|
|
|
// Mark course as not downloaded if course size is 0.
|
|
if (this.totalSize == 0) {
|
|
this.markCourseAsNotDownloaded();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update the sizes of some modules.
|
|
*
|
|
* @param modules Modules.
|
|
* @param section Section the modules belong to.
|
|
* @returns Promise resolved when done.
|
|
*/
|
|
protected async updateModulesSizes(
|
|
modules: AddonStorageManagerModule[],
|
|
section?: AddonStorageManagerCourseSection,
|
|
): Promise<void> {
|
|
this.calculatingSize = true;
|
|
|
|
await Promise.all(modules.map(async (module) => {
|
|
if (module.calculatingSize) {
|
|
return;
|
|
}
|
|
|
|
module.calculatingSize = true;
|
|
this.changeDetectorRef.markForCheck();
|
|
|
|
if (!section) {
|
|
section = this.sections.find((section) => section.modules.some((mod) => mod.id === module.id));
|
|
if (section) {
|
|
section.calculatingSize = true;
|
|
this.changeDetectorRef.markForCheck();
|
|
}
|
|
}
|
|
|
|
try {
|
|
const size = await CoreCourseModulePrefetchDelegate.getModuleStoredSize(module, this.courseId);
|
|
|
|
const diff = (isNaN(size) ? 0 : size) - (module.totalSize ?? 0);
|
|
|
|
module.totalSize = Number(size);
|
|
this.totalSize += diff;
|
|
if (section) {
|
|
section.totalSize += diff;
|
|
}
|
|
} catch {
|
|
// Ignore errors, it shouldn't happen.
|
|
} finally {
|
|
module.calculatingSize = false;
|
|
this.changeDetectorRef.markForCheck();
|
|
}
|
|
}));
|
|
|
|
this.calculatingSize = false;
|
|
if (section) {
|
|
section.calculatingSize = false;
|
|
}
|
|
this.changeDetectorRef.markForCheck();
|
|
}
|
|
|
|
/**
|
|
* The user has requested a delete for the whole course data.
|
|
*
|
|
* (This works by deleting data for each module on the course that has data.)
|
|
*
|
|
* @param event Event object.
|
|
*/
|
|
async deleteForCourse(event: Event): Promise<void> {
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
|
|
try {
|
|
await CoreDomUtils.showDeleteConfirm(
|
|
'addon.storagemanager.confirmdeletedatafrom',
|
|
{ name: this.title },
|
|
);
|
|
} catch (error) {
|
|
if (!CoreDomUtils.isCanceledError(error)) {
|
|
throw error;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
const modules: AddonStorageManagerModule[] = [];
|
|
this.sections.forEach((section) => {
|
|
section.modules.forEach((module) => {
|
|
if (module.totalSize && module.totalSize > 0) {
|
|
modules.push(module);
|
|
}
|
|
});
|
|
});
|
|
|
|
this.deleteModules(modules);
|
|
}
|
|
|
|
/**
|
|
* The user has requested a delete for a section's data.
|
|
*
|
|
* (This works by deleting data for each module in the section that has data.)
|
|
*
|
|
* @param event Event object.
|
|
* @param section Section object with information about section and modules
|
|
*/
|
|
async deleteForSection(event: Event, section: AddonStorageManagerCourseSection): Promise<void> {
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
|
|
try {
|
|
await CoreDomUtils.showDeleteConfirm(
|
|
'addon.storagemanager.confirmdeletedatafrom',
|
|
{ name: section.name },
|
|
);
|
|
} catch (error) {
|
|
if (!CoreDomUtils.isCanceledError(error)) {
|
|
throw error;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
const modules: AddonStorageManagerModule[] = [];
|
|
section.modules.forEach((module) => {
|
|
if (module.totalSize && module.totalSize > 0) {
|
|
modules.push(module);
|
|
}
|
|
});
|
|
|
|
this.deleteModules(modules, section);
|
|
}
|
|
|
|
/**
|
|
* The user has requested a delete for a module's data
|
|
*
|
|
* @param event Event object.
|
|
* @param module Module details
|
|
* @param section Section the module belongs to.
|
|
*/
|
|
async deleteForModule(
|
|
event: Event,
|
|
module: AddonStorageManagerModule,
|
|
section: AddonStorageManagerCourseSection,
|
|
): Promise<void> {
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
|
|
if (module.totalSize === 0) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await CoreDomUtils.showDeleteConfirm(
|
|
'addon.storagemanager.confirmdeletedatafrom',
|
|
{ name: module.name },
|
|
);
|
|
} catch (error) {
|
|
if (!CoreDomUtils.isCanceledError(error)) {
|
|
throw error;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
this.deleteModules([module], section);
|
|
}
|
|
|
|
/**
|
|
* Deletes the specified modules, showing the loading overlay while it happens.
|
|
*
|
|
* @param modules Modules to delete
|
|
* @param section Section the modules belong to.
|
|
* @returns Promise<void> Once deleting has finished
|
|
*/
|
|
protected async deleteModules(modules: AddonStorageManagerModule[], section?: AddonStorageManagerCourseSection): Promise<void> {
|
|
const modal = await CoreDomUtils.showModalLoading('core.deleting', true);
|
|
|
|
const promises: Promise<void>[] = [];
|
|
modules.forEach((module) => {
|
|
// Remove the files.
|
|
const promise = CoreCourseHelper.removeModuleStoredData(module, this.courseId).then(() => {
|
|
const moduleSize = module.totalSize || 0;
|
|
// When the files and cache are removed, update the size.
|
|
if (section) {
|
|
section.totalSize -= moduleSize;
|
|
}
|
|
this.totalSize -= moduleSize;
|
|
module.totalSize = 0;
|
|
|
|
return;
|
|
});
|
|
|
|
promises.push(promise);
|
|
});
|
|
|
|
try {
|
|
await Promise.all(promises);
|
|
} catch (error) {
|
|
CoreDomUtils.showErrorModalDefault(error, Translate.instant('core.errordeletefile'));
|
|
} finally {
|
|
modal.dismiss();
|
|
|
|
await this.updateModulesSizes(modules, section);
|
|
CoreCourseHelper.calculateSectionsStatus(this.sections, this.courseId, false, false);
|
|
|
|
// For delete all, reset all section sizes so icons are updated.
|
|
if (this.totalSize == 0) {
|
|
this.sections.map((section) => {
|
|
section.calculatingSize = true;
|
|
section.totalSize = 0;
|
|
section.calculatingSize = false;
|
|
});
|
|
}
|
|
this.changeDetectorRef.markForCheck();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Mark course as not downloaded.
|
|
*/
|
|
protected markCourseAsNotDownloaded(): void {
|
|
// @TODO In order to correctly check the status of the course we should check all module statuses.
|
|
// We are currently marking as not downloaded if size is 0 but we should take into account that
|
|
// resources without files can be downloaded and cached.
|
|
|
|
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.
|
|
*/
|
|
async prefecthSection(section: AddonStorageManagerCourseSection): Promise<void> {
|
|
section.isCalculating = true;
|
|
this.changeDetectorRef.markForCheck();
|
|
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.updateModulesSizes(section.modules, section);
|
|
this.changeDetectorRef.markForCheck();
|
|
}
|
|
} catch (error) {
|
|
// User cancelled or there was an error calculating the size.
|
|
if (!this.isDestroyed && error) {
|
|
CoreDomUtils.showErrorModal(error);
|
|
this.changeDetectorRef.markForCheck();
|
|
|
|
return;
|
|
}
|
|
} finally {
|
|
section.isCalculating = false;
|
|
this.changeDetectorRef.markForCheck();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Download the module.
|
|
*
|
|
* @param module Module to prefetch.
|
|
* @param refresh Whether it's refreshing.
|
|
* @returns 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.updateModulesSizes([module]);
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
this.changeDetectorRef.markForCheck();
|
|
}
|
|
|
|
/**
|
|
* Calculate and show module status.
|
|
*
|
|
* @param module Module to update.
|
|
* @returns 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.
|
|
*
|
|
* @returns 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;
|
|
this.changeDetectorRef.markForCheck();
|
|
}
|
|
|
|
/**
|
|
* Prefetch the whole course.
|
|
*
|
|
* @param event Event object.
|
|
*/
|
|
async prefetchCourse(event: Event): Promise<void> {
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
|
|
const courses = await CoreCourses.getUserCourses(true);
|
|
let course = courses.find((course) => course.id == this.courseId);
|
|
if (!course) {
|
|
course = await CoreCourses.getCourse(this.courseId);
|
|
}
|
|
if (!course) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
this.changeDetectorRef.markForCheck();
|
|
await CoreCourseHelper.confirmAndPrefetchCourse(
|
|
this.prefetchCourseData,
|
|
course,
|
|
{
|
|
sections: this.sections,
|
|
isGuest: this.isGuest,
|
|
},
|
|
);
|
|
this.changeDetectorRef.markForCheck();
|
|
} catch (error) {
|
|
if (this.isDestroyed) {
|
|
return;
|
|
}
|
|
|
|
CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Toggle expand status.
|
|
*
|
|
* @param event Event object.
|
|
* @param section Section to expand / collapse.
|
|
*/
|
|
toggleExpand(event: Event, section: AddonStorageManagerCourseSection): void {
|
|
section.expanded = !section.expanded;
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
}
|
|
|
|
/**
|
|
* @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<CoreCourseSectionWithStatus, 'modules'> & {
|
|
totalSize: number;
|
|
calculatingSize: boolean;
|
|
expanded: boolean;
|
|
modules: AddonStorageManagerModule[];
|
|
};
|
|
|
|
type AddonStorageManagerModule = CoreCourseModuleData & {
|
|
totalSize?: number;
|
|
calculatingSize: boolean;
|
|
prefetchHandler?: CoreCourseModulePrefetchHandler;
|
|
spinner?: boolean;
|
|
downloadStatus?: string;
|
|
};
|