forked from CIT/Vmeda.Online
		
	MOBILE-4660 storagemanager: Manage subsections on storage manager
This commit is contained in:
		
							parent
							
								
									a169d9301a
								
							
						
					
					
						commit
						a2c6a5b578
					
				@ -46,9 +46,18 @@
 | 
			
		||||
            </ion-card-header>
 | 
			
		||||
        </ion-card>
 | 
			
		||||
 | 
			
		||||
        <ion-accordion-group [multiple]="true" (ionChange)="accordionGroupChange($event.detail)" #accordionGroup>
 | 
			
		||||
        <ion-accordion-group [multiple]="true" (ionChange)="accordionGroupChange($event.detail)" [value]="accordionMultipleValue">
 | 
			
		||||
            <ng-container *ngFor="let section of sections">
 | 
			
		||||
                <ion-card class="section" *ngIf="section.modules.length > 0">
 | 
			
		||||
                <ng-container *ngTemplateOutlet="sectionCard; context: { section }" />
 | 
			
		||||
            </ng-container>
 | 
			
		||||
        </ion-accordion-group>
 | 
			
		||||
 | 
			
		||||
    </core-loading>
 | 
			
		||||
</ion-content>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<ng-template #sectionCard let-section="section">
 | 
			
		||||
    <ion-card class="section" *ngIf="section.modules.length > 0" [id]="'addons-course-storage-'+section.id">
 | 
			
		||||
        <ion-accordion [value]="section.id" toggleIconSlot="start">
 | 
			
		||||
            <ion-item [detail]="false" slot="header" class="card-header">
 | 
			
		||||
                <ion-label>
 | 
			
		||||
@ -69,8 +78,7 @@
 | 
			
		||||
                        <core-progress-bar [progress]="section.total === 0 ? -1 : (section.count / section.total) * 100" />
 | 
			
		||||
                    </p>
 | 
			
		||||
                </ion-label>
 | 
			
		||||
                            <div class="storage-buttons" slot="end"
 | 
			
		||||
                                *ngIf="(!section.calculatingSize && section.totalSize > 0) || downloadEnabled">
 | 
			
		||||
                <div class="storage-buttons" slot="end" *ngIf="(!section.calculatingSize && section.totalSize > 0) || downloadEnabled">
 | 
			
		||||
                    <div *ngIf="downloadEnabled" slot="end" class="core-button-spinner">
 | 
			
		||||
                        <core-download-refresh *ngIf="!section.isDownloading && section.downloadStatus !== statusDownloaded"
 | 
			
		||||
                            [status]="section.downloadStatus" [enabled]="true" (action)="prefecthSection(section)"
 | 
			
		||||
@ -85,8 +93,8 @@
 | 
			
		||||
                            {{section.count}} / {{section.total}}
 | 
			
		||||
                        </ion-badge>
 | 
			
		||||
                    </div>
 | 
			
		||||
                                <ion-button (click)="deleteForSection($event, section)"
 | 
			
		||||
                                    *ngIf="!section.calculatingSize && section.totalSize > 0" color="danger" fill="clear">
 | 
			
		||||
                    <ion-button (click)="deleteForSection($event, section)" *ngIf="!section.calculatingSize && section.totalSize > 0"
 | 
			
		||||
                        color="danger" fill="clear">
 | 
			
		||||
                        <ion-icon name="fas-trash" slot="icon-only"
 | 
			
		||||
                            [attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate: { name: section.name }" />
 | 
			
		||||
                    </ion-button>
 | 
			
		||||
@ -95,15 +103,18 @@
 | 
			
		||||
            <ion-card-content slot="content">
 | 
			
		||||
                <ng-container *ngIf="section.expanded">
 | 
			
		||||
                    <ng-container *ngFor="let module of section.modules">
 | 
			
		||||
                        @if (module.subSection) {
 | 
			
		||||
                        <ng-container *ngTemplateOutlet="sectionCard; context: { section: module.subSection }" />
 | 
			
		||||
                        } @else {
 | 
			
		||||
                        <ion-item class="core-course-storage-activity"
 | 
			
		||||
                            *ngIf="downloadEnabled || (!module.calculatingSize && module.totalSize > 0)">
 | 
			
		||||
                            <core-mod-icon slot="start" *ngIf="module.handlerData.icon" [modicon]="module.handlerData.icon"
 | 
			
		||||
                                            [modname]="module.modname" [componentId]="module.instance"
 | 
			
		||||
                                            [fallbackTranslation]="module.modplural" [isBranded]="module.branded" />
 | 
			
		||||
                                [modname]="module.modname" [componentId]="module.instance" [fallbackTranslation]="module.modplural"
 | 
			
		||||
                                [isBranded]="module.branded" />
 | 
			
		||||
                            <ion-label class="ion-text-wrap">
 | 
			
		||||
                                <p class="item-heading {{module.handlerData!.class}} addon-storagemanager-module-size">
 | 
			
		||||
                                                <core-format-text [text]="module.handlerData.title" [courseId]="module.course"
 | 
			
		||||
                                                    contextLevel="module" [contextInstanceId]="module.id" [adaptImg]="false" />
 | 
			
		||||
                                    <core-format-text [text]="module.handlerData.title" [courseId]="module.course" contextLevel="module"
 | 
			
		||||
                                        [contextInstanceId]="module.id" [adaptImg]="false" />
 | 
			
		||||
                                </p>
 | 
			
		||||
                                <ion-badge [color]="module.downloadStatus === statusDownloaded ? 'success' : 'light'"
 | 
			
		||||
                                    *ngIf="!module.calculatingSize && module.totalSize > 0">
 | 
			
		||||
@ -124,7 +135,7 @@
 | 
			
		||||
                                    (action)="prefetchModule(module)"
 | 
			
		||||
                                    [statusesTranslatable]="{notdownloaded: 'addon.storagemanager.downloaddatafrom' }"
 | 
			
		||||
                                    [statusSubject]="module.name" />
 | 
			
		||||
                                            <ion-button fill="clear" (click)="deleteForModule($event, module, section)"
 | 
			
		||||
                                <ion-button fill="clear" (click)="deleteForModule($event, module)"
 | 
			
		||||
                                    *ngIf="!module.calculatingSize && module.totalSize > 0" color="danger">
 | 
			
		||||
                                    <ion-icon name="fas-trash" slot="icon-only" [attr.aria-label]="
 | 
			
		||||
                                        'addon.storagemanager.deletedatafrom' | translate: { name: module.name }" />
 | 
			
		||||
@ -134,13 +145,10 @@
 | 
			
		||||
                                </p>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </ion-item>
 | 
			
		||||
                        }
 | 
			
		||||
                    </ng-container>
 | 
			
		||||
                </ng-container>
 | 
			
		||||
            </ion-card-content>
 | 
			
		||||
        </ion-accordion>
 | 
			
		||||
    </ion-card>
 | 
			
		||||
            </ng-container>
 | 
			
		||||
        </ion-accordion-group>
 | 
			
		||||
 | 
			
		||||
    </core-loading>
 | 
			
		||||
</ion-content>
 | 
			
		||||
</ng-template>
 | 
			
		||||
 | 
			
		||||
@ -17,11 +17,6 @@
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .accordion-expanded {
 | 
			
		||||
            ion-item.card-header {
 | 
			
		||||
                --border-width: 0 0 1px 0;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        ion-card-content {
 | 
			
		||||
            padding: 0;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,7 @@
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { CoreConstants, DownloadStatus } from '@/core/constants';
 | 
			
		||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core';
 | 
			
		||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnDestroy, OnInit } from '@angular/core';
 | 
			
		||||
import { CoreCourse, CoreCourseProvider } from '@features/course/services/course';
 | 
			
		||||
import {
 | 
			
		||||
    CoreCourseHelper,
 | 
			
		||||
@ -25,7 +25,7 @@ import {
 | 
			
		||||
    CoreCourseModulePrefetchDelegate,
 | 
			
		||||
    CoreCourseModulePrefetchHandler } from '@features/course/services/module-prefetch-delegate';
 | 
			
		||||
import { CoreCourseAnyCourseData, CoreCourses } from '@features/courses/services/courses';
 | 
			
		||||
import { AccordionGroupChangeEventDetail, IonAccordionGroup } from '@ionic/angular';
 | 
			
		||||
import { AccordionGroupChangeEventDetail } from '@ionic/angular';
 | 
			
		||||
import { CoreLoadings } from '@services/loadings';
 | 
			
		||||
import { CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
@ -47,14 +47,13 @@ import { CoreEventObserver, CoreEvents } from '@singletons/events';
 | 
			
		||||
})
 | 
			
		||||
export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
    @ViewChild('accordionGroup', { static: true }) accordionGroup!: IonAccordionGroup;
 | 
			
		||||
 | 
			
		||||
    courseId!: number;
 | 
			
		||||
    title = '';
 | 
			
		||||
    loaded = false;
 | 
			
		||||
    sections: AddonStorageManagerCourseSection[] = [];
 | 
			
		||||
    totalSize = 0;
 | 
			
		||||
    calculatingSize = true;
 | 
			
		||||
    accordionMultipleValue: string[] = [];
 | 
			
		||||
 | 
			
		||||
    downloadEnabled = false;
 | 
			
		||||
    downloadCourseEnabled = false;
 | 
			
		||||
@ -116,7 +115,8 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
        const sections = (await CoreCourse.getSections(this.courseId, false, true))
 | 
			
		||||
            .filter((section) => !CoreCourseHelper.isSectionStealth(section));
 | 
			
		||||
        this.sections = (await CoreCourseHelper.addHandlerDataForModules(sections, this.courseId)).sections
 | 
			
		||||
 | 
			
		||||
        const sectionsToRender = (await CoreCourseHelper.addHandlerDataForModules(sections, this.courseId)).sections
 | 
			
		||||
            .map(section => ({
 | 
			
		||||
                ...section,
 | 
			
		||||
                totalSize: 0,
 | 
			
		||||
@ -128,15 +128,29 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
 | 
			
		||||
                })),
 | 
			
		||||
            }));
 | 
			
		||||
 | 
			
		||||
        const subSections = sectionsToRender.filter((section) => section.component === 'mod_subsection');
 | 
			
		||||
 | 
			
		||||
        this.sections = sectionsToRender.filter((section) => section.component !== 'mod_subsection');
 | 
			
		||||
        this.sections.forEach((section) => {
 | 
			
		||||
            section.modules.forEach((module) => {
 | 
			
		||||
                if (module.modname === 'subsection') {
 | 
			
		||||
                    module.subSection = subSections.find((section) =>
 | 
			
		||||
                        section.component === 'mod_subsection' && section.itemid === module.instance);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.loaded = true;
 | 
			
		||||
 | 
			
		||||
        this.accordionGroup.value = String(initialSectionId);
 | 
			
		||||
        if (initialSectionId !== undefined) {
 | 
			
		||||
            this.accordionMultipleValue.push(initialSectionId.toString());
 | 
			
		||||
 | 
			
		||||
            CoreDom.scrollToElement(
 | 
			
		||||
                this.elementRef.nativeElement,
 | 
			
		||||
            '.accordion-expanded',
 | 
			
		||||
                `#addons-course-storage-${initialSectionId}`,
 | 
			
		||||
                { addYAxis: -10 },
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await Promise.all([
 | 
			
		||||
            this.initSizes(),
 | 
			
		||||
@ -158,7 +172,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
        // 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) {
 | 
			
		||||
            if (data.courseId === this.courseId || data.courseId === CoreCourseProvider.ALL_COURSES_CLEARED) {
 | 
			
		||||
                this.updateCourseStatus(data.status);
 | 
			
		||||
            }
 | 
			
		||||
        }, CoreSites.getCurrentSiteId());
 | 
			
		||||
@ -213,17 +227,20 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Get the affected section.
 | 
			
		||||
                const section = this.sections.find(section => section.id == data.sectionId);
 | 
			
		||||
                if (!section) {
 | 
			
		||||
                const sectionFinder = CoreCourseHelper.findSectionWithSubsection(this.sections, data.sectionId);
 | 
			
		||||
                if (!sectionFinder?.section) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Recalculate the status.
 | 
			
		||||
                await CoreCourseHelper.calculateSectionStatus(section, this.courseId, false);
 | 
			
		||||
                await CoreCourseHelper.calculateSectionStatus(sectionFinder.section, this.courseId, false);
 | 
			
		||||
                if (sectionFinder.subSection) {
 | 
			
		||||
                    await CoreCourseHelper.calculateSectionStatus(sectionFinder.subSection, this.courseId, false);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (section.isDownloading && !CoreCourseModulePrefetchDelegate.isBeingDownloaded(downloadId)) {
 | 
			
		||||
                if (sectionFinder.section.isDownloading && !CoreCourseModulePrefetchDelegate.isBeingDownloaded(downloadId)) {
 | 
			
		||||
                    // All the modules are now downloading, set a download all promise.
 | 
			
		||||
                    this.prefecthSection(section);
 | 
			
		||||
                    this.prefecthSection(sectionFinder.section);
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            CoreSites.getCurrentSiteId(),
 | 
			
		||||
@ -233,36 +250,46 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
 | 
			
		||||
        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.calculateModulesStatusOnSection(section);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.moduleStatusObserver = CoreEvents.on(CoreEvents.PACKAGE_STATUS_CHANGED, (data) => {
 | 
			
		||||
            let module: AddonStorageManagerModule | undefined;
 | 
			
		||||
            let moduleFound: AddonStorageManagerModule | undefined;
 | 
			
		||||
 | 
			
		||||
            this.sections.some((section) => {
 | 
			
		||||
                module = section.modules.find((module) =>
 | 
			
		||||
                    module.id == data.componentId && module.prefetchHandler && data.component == module.prefetchHandler?.component);
 | 
			
		||||
            this.sections.some((section) =>
 | 
			
		||||
                section.modules.some((module) => {
 | 
			
		||||
                    if (module.subSection) {
 | 
			
		||||
                        return module.subSection.modules.some((module) => {
 | 
			
		||||
                            if (module.id === data.componentId &&
 | 
			
		||||
                                module.prefetchHandler &&
 | 
			
		||||
                                data.component === module.prefetchHandler?.component) {
 | 
			
		||||
                                moduleFound = module;
 | 
			
		||||
 | 
			
		||||
                return !!module;
 | 
			
		||||
                                return true;
 | 
			
		||||
                            }
 | 
			
		||||
                        });
 | 
			
		||||
                    } else {
 | 
			
		||||
                        if (module.id === data.componentId &&
 | 
			
		||||
                            module.prefetchHandler &&
 | 
			
		||||
                            data.component === module.prefetchHandler?.component) {
 | 
			
		||||
                            moduleFound = module;
 | 
			
		||||
 | 
			
		||||
            if (!module) {
 | 
			
		||||
                            return true;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    return false;
 | 
			
		||||
            }));
 | 
			
		||||
 | 
			
		||||
            if (!moduleFound) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Call determineModuleStatus to get the right status to display.
 | 
			
		||||
            const status = CoreCourseModulePrefetchDelegate.determineModuleStatus(module, data.status);
 | 
			
		||||
            const status = CoreCourseModulePrefetchDelegate.determineModuleStatus(moduleFound, data.status);
 | 
			
		||||
 | 
			
		||||
            // Update the status.
 | 
			
		||||
            this.updateModuleStatus(module, status);
 | 
			
		||||
            this.updateModuleStatus(moduleFound, status);
 | 
			
		||||
        }, CoreSites.getCurrentSiteId());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -270,50 +297,24 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
 | 
			
		||||
     * 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;
 | 
			
		||||
        const modules = this.getAllModulesList();
 | 
			
		||||
        await Promise.all(modules.map(async (module) => {
 | 
			
		||||
            await this.calculateModuleSize(module);
 | 
			
		||||
        }));
 | 
			
		||||
 | 
			
		||||
            section.calculatingSize = false;
 | 
			
		||||
        }));
 | 
			
		||||
 | 
			
		||||
        this.calculatingSize = false;
 | 
			
		||||
 | 
			
		||||
        // Mark course as not downloaded if course size is 0.
 | 
			
		||||
        if (this.totalSize == 0) {
 | 
			
		||||
            this.markCourseAsNotDownloaded();
 | 
			
		||||
        }
 | 
			
		||||
        await this.updateModulesSizes(modules);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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> {
 | 
			
		||||
    protected async updateModulesSizes(modules: AddonStorageManagerModule[]): Promise<void> {
 | 
			
		||||
        this.calculatingSize = true;
 | 
			
		||||
        let section: AddonStorageManagerCourseSection | undefined;
 | 
			
		||||
        let subSection: AddonStorageManagerCourseSection | undefined;
 | 
			
		||||
 | 
			
		||||
        await Promise.all(modules.map(async (module) => {
 | 
			
		||||
            if (module.calculatingSize) {
 | 
			
		||||
@ -321,38 +322,59 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            module.calculatingSize = true;
 | 
			
		||||
            this.changeDetectorRef.markForCheck();
 | 
			
		||||
 | 
			
		||||
            if (!section) {
 | 
			
		||||
                section = this.sections.find((section) => section.modules.some((mod) => mod.id === module.id));
 | 
			
		||||
            const sectionFinder = CoreCourseHelper.findSectionWithSubsection(this.sections, module.section);
 | 
			
		||||
            section = sectionFinder?.section;
 | 
			
		||||
            if (section) {
 | 
			
		||||
                section.calculatingSize = true;
 | 
			
		||||
 | 
			
		||||
                subSection = sectionFinder?.subSection;
 | 
			
		||||
                if (subSection) {
 | 
			
		||||
                    subSection.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();
 | 
			
		||||
            }
 | 
			
		||||
            await this.calculateModuleSize(module);
 | 
			
		||||
        }));
 | 
			
		||||
 | 
			
		||||
        this.calculatingSize = false;
 | 
			
		||||
        if (section) {
 | 
			
		||||
            section.calculatingSize = false;
 | 
			
		||||
        // Update section and total sizes.
 | 
			
		||||
        this.totalSize = 0;
 | 
			
		||||
        this.sections.forEach((section) => {
 | 
			
		||||
            section.totalSize = 0;
 | 
			
		||||
            section.modules.forEach((module) => {
 | 
			
		||||
                if (module.subSection) {
 | 
			
		||||
                    const subSection = module.subSection;
 | 
			
		||||
 | 
			
		||||
                    subSection.totalSize = 0;
 | 
			
		||||
                    subSection.modules.forEach((module) => {
 | 
			
		||||
                        if (module.totalSize && module.totalSize > 0) {
 | 
			
		||||
                            subSection.totalSize += module.totalSize;
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                    subSection.calculatingSize = false;
 | 
			
		||||
 | 
			
		||||
                    section.totalSize += module.subSection.totalSize;
 | 
			
		||||
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (module.totalSize && module.totalSize > 0) {
 | 
			
		||||
                    section.totalSize += module.totalSize;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            section.calculatingSize = false;
 | 
			
		||||
            this.totalSize += section.totalSize;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.calculatingSize = false;
 | 
			
		||||
 | 
			
		||||
        // Mark course as not downloaded if course size is 0.
 | 
			
		||||
        if (this.totalSize === 0) {
 | 
			
		||||
            this.markCourseAsNotDownloaded();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.changeDetectorRef.markForCheck();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -380,16 +402,9 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const modules: AddonStorageManagerModule[] = [];
 | 
			
		||||
        this.sections.forEach((section) => {
 | 
			
		||||
            section.modules.forEach((module) => {
 | 
			
		||||
                if (module.totalSize && module.totalSize > 0) {
 | 
			
		||||
                    modules.push(module);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
        const modules = this.getAllModulesList().filter((module) => module.totalSize && module.totalSize > 0);
 | 
			
		||||
 | 
			
		||||
        this.deleteModules(modules);
 | 
			
		||||
        await this.deleteModules(modules);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -419,12 +434,22 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
        const modules: AddonStorageManagerModule[] = [];
 | 
			
		||||
        section.modules.forEach((module) => {
 | 
			
		||||
            if (module.subSection) {
 | 
			
		||||
                module.subSection.modules.forEach((module) => {
 | 
			
		||||
                    if (module.totalSize && module.totalSize > 0) {
 | 
			
		||||
                        modules.push(module);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
        this.deleteModules(modules, section);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (module.totalSize && module.totalSize > 0) {
 | 
			
		||||
                modules.push(module);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        await this.deleteModules(modules);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -432,12 +457,10 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
 | 
			
		||||
     *
 | 
			
		||||
     * @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();
 | 
			
		||||
@ -459,35 +482,23 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.deleteModules([module], section);
 | 
			
		||||
        await this.deleteModules([module]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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> {
 | 
			
		||||
    protected async deleteModules(modules: AddonStorageManagerModule[]): Promise<void> {
 | 
			
		||||
        const modal = await CoreLoadings.show('core.deleting', true);
 | 
			
		||||
 | 
			
		||||
        const promises: Promise<void>[] = [];
 | 
			
		||||
        modules.forEach((module) => {
 | 
			
		||||
        const promises = modules.map(async (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;
 | 
			
		||||
            await CoreCourseHelper.removeModuleStoredData(module, this.courseId);
 | 
			
		||||
 | 
			
		||||
            module.totalSize = 0;
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            promises.push(promise);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
@ -497,13 +508,9 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
 | 
			
		||||
        } finally {
 | 
			
		||||
            modal.dismiss();
 | 
			
		||||
 | 
			
		||||
            await this.updateModulesSizes(modules, section);
 | 
			
		||||
            await this.updateModulesSizes(modules);
 | 
			
		||||
            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.totalSize = 0);
 | 
			
		||||
            }
 | 
			
		||||
            this.changeDetectorRef.markForCheck();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -551,8 +558,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
 | 
			
		||||
                    CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingsection', true);
 | 
			
		||||
                }
 | 
			
		||||
            } finally {
 | 
			
		||||
                await this.updateModulesSizes(section.modules, section);
 | 
			
		||||
                this.changeDetectorRef.markForCheck();
 | 
			
		||||
                await this.updateModulesSizes(section.modules);
 | 
			
		||||
            }
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            // User cancelled or there was an error calculating the size.
 | 
			
		||||
@ -602,7 +608,6 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
 | 
			
		||||
            module.spinner = false;
 | 
			
		||||
 | 
			
		||||
            await this.updateModulesSizes([module]);
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -624,6 +629,24 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
 | 
			
		||||
        this.changeDetectorRef.markForCheck();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Calculate all modules status on a section.
 | 
			
		||||
     *
 | 
			
		||||
     * @param section Section to check.
 | 
			
		||||
     */
 | 
			
		||||
    protected async calculateModulesStatusOnSection(section: AddonStorageManagerCourseSection): Promise<void> {
 | 
			
		||||
        await Promise.all(section.modules.map(async (module) => {
 | 
			
		||||
            if (module.subSection) {
 | 
			
		||||
                await this.calculateModulesStatusOnSection(module.subSection);
 | 
			
		||||
            } else 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);
 | 
			
		||||
                await this.calculateModuleStatus(module);
 | 
			
		||||
            }
 | 
			
		||||
        }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Calculate and show module status.
 | 
			
		||||
     *
 | 
			
		||||
@ -664,6 +687,12 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
 | 
			
		||||
        this.changeDetectorRef.markForCheck();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the course object.
 | 
			
		||||
     *
 | 
			
		||||
     * @param courseId Course ID.
 | 
			
		||||
     * @returns Promise resolved with the course object if found.
 | 
			
		||||
     */
 | 
			
		||||
    protected async getCourse(courseId: number): Promise<CoreCourseAnyCourseData | undefined> {
 | 
			
		||||
        try {
 | 
			
		||||
            // Check if user is enrolled. If enrolled, no guest access.
 | 
			
		||||
@ -709,8 +738,9 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
 | 
			
		||||
                    isGuest: this.isGuest,
 | 
			
		||||
                },
 | 
			
		||||
            );
 | 
			
		||||
            await Promise.all(this.sections.map(section => this.updateModulesSizes(section.modules, section)));
 | 
			
		||||
            this.changeDetectorRef.markForCheck();
 | 
			
		||||
 | 
			
		||||
            const modules = this.getAllModulesList();
 | 
			
		||||
            await this.updateModulesSizes(modules);
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            if (this.isDestroyed) {
 | 
			
		||||
                return;
 | 
			
		||||
@ -720,19 +750,76 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get all modules list.
 | 
			
		||||
     *
 | 
			
		||||
     * @returns All modules list.
 | 
			
		||||
     */
 | 
			
		||||
    protected getAllModulesList(): AddonStorageManagerModule[] {
 | 
			
		||||
        const modules: AddonStorageManagerModule[] = [];
 | 
			
		||||
        this.sections.forEach((section) => {
 | 
			
		||||
            section.modules.forEach((module) => {
 | 
			
		||||
                if (module.subSection) {
 | 
			
		||||
                    module.subSection.modules.forEach((module) => {
 | 
			
		||||
                        modules.push(module);
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                modules.push(module);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return modules;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Calculate the size of a module.
 | 
			
		||||
     *
 | 
			
		||||
     * @param module Module to calculate.
 | 
			
		||||
     */
 | 
			
		||||
    protected async calculateModuleSize(module: AddonStorageManagerModule): Promise<void> {
 | 
			
		||||
        module.calculatingSize = true;
 | 
			
		||||
 | 
			
		||||
        // 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 CoreUtils.ignoreErrors(CoreCourseModulePrefetchDelegate.getModuleStoredSize(module, this.courseId));
 | 
			
		||||
 | 
			
		||||
        if (size !== undefined) {
 | 
			
		||||
            // There are some cases where the return from this is not a valid number.
 | 
			
		||||
            module.totalSize = !isNaN(size) ? Number(size) : 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.changeDetectorRef.markForCheck();
 | 
			
		||||
        module.calculatingSize = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Toggle expand status.
 | 
			
		||||
     *
 | 
			
		||||
     * @param event Event object.
 | 
			
		||||
     */
 | 
			
		||||
    accordionGroupChange(event: AccordionGroupChangeEventDetail): void {
 | 
			
		||||
        const sectionIds = event.value as string[] | [];
 | 
			
		||||
        this.sections.forEach((section) => {
 | 
			
		||||
            section.expanded = false;
 | 
			
		||||
            section.modules.forEach((section) => {
 | 
			
		||||
                if (section.subSection) {
 | 
			
		||||
                    section.subSection.expanded = false;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        event.value.forEach((sectionId) => {
 | 
			
		||||
            const section = this.sections.find((section) => section.id === Number(sectionId));
 | 
			
		||||
            if (section) {
 | 
			
		||||
                section.expanded = true;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        sectionIds.forEach((sectionId) => {
 | 
			
		||||
            const sectionToExpand = CoreCourseHelper.findSectionById(this.sections, Number(sectionId));
 | 
			
		||||
            if (sectionToExpand) {
 | 
			
		||||
                sectionToExpand.expanded = true;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
@ -748,6 +835,10 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
        this.sections.forEach((section) => {
 | 
			
		||||
            section.modules.forEach((module) => {
 | 
			
		||||
                module.subSection?.modules.forEach((module) => {
 | 
			
		||||
                    module.handlerData?.onDestroy?.();
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                module.handlerData?.onDestroy?.();
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
@ -769,4 +860,5 @@ type AddonStorageManagerModule = CoreCourseModuleData & {
 | 
			
		||||
    prefetchHandler?: CoreCourseModulePrefetchHandler;
 | 
			
		||||
    spinner?: boolean;
 | 
			
		||||
    downloadStatus?: DownloadStatus;
 | 
			
		||||
    subSection?: AddonStorageManagerCourseSection;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1600,7 +1600,7 @@ export class CoreCourseHelperProvider {
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @returns Promise resolved when the download finishes.
 | 
			
		||||
     */
 | 
			
		||||
    async prefetchCourse(
 | 
			
		||||
    protected async prefetchCourse(
 | 
			
		||||
        course: CoreCourseAnyCourseData,
 | 
			
		||||
        sections: CoreCourseWSSection[],
 | 
			
		||||
        courseHandlers: CoreCourseOptionsHandlerToDisplay[],
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user