MOBILE-4660 course: Adapt course downloads to new data structure
parent
d384752113
commit
d1856f5fff
|
@ -70,7 +70,7 @@ export class AddonBlockActivityModulesComponent extends CoreBlockBaseComponent i
|
||||||
let modFullNames: Record<string, string> = {};
|
let modFullNames: Record<string, string> = {};
|
||||||
const brandedIcons: Record<string, boolean|undefined> = {};
|
const brandedIcons: Record<string, boolean|undefined> = {};
|
||||||
|
|
||||||
const modules = CoreCourseHelper.getSectionsModules(sections, {
|
const modules = CoreCourse.getSectionsModules(sections, {
|
||||||
ignoreSection: section => !CoreCourseHelper.canUserViewSection(section),
|
ignoreSection: section => !CoreCourseHelper.canUserViewSection(section),
|
||||||
ignoreModule: module => !CoreCourseHelper.canUserViewModule(module) || !CoreCourse.moduleHasView(module),
|
ignoreModule: module => !CoreCourseHelper.canUserViewModule(module) || !CoreCourse.moduleHasView(module),
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
// (C) Copyright 2015 Moodle Pty Ltd.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
export const ADDON_MOD_SUBSECTION_COMPONENT = 'mmaModSubsection';
|
|
|
@ -1,98 +0,0 @@
|
||||||
// (C) Copyright 2015 Moodle Pty Ltd.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { CoreCourseResourcePrefetchHandlerBase } from '@features/course/classes/resource-prefetch-handler';
|
|
||||||
import { CoreCourse, CoreCourseAnyModuleData, CoreCourseWSSection } from '@features/course/services/course';
|
|
||||||
import { makeSingleton } from '@singletons';
|
|
||||||
import { ADDON_MOD_SUBSECTION_COMPONENT } from '../../constants';
|
|
||||||
import { CoreCourseHelper, CoreCourseModuleData } from '@features/course/services/course-helper';
|
|
||||||
import { CoreFileSizeSum } from '@services/plugin-file-delegate';
|
|
||||||
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
|
|
||||||
import { CoreSites } from '@services/sites';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handler to prefetch subsections.
|
|
||||||
*/
|
|
||||||
@Injectable({ providedIn: 'root' })
|
|
||||||
export class AddonModSubsectionPrefetchHandlerService extends CoreCourseResourcePrefetchHandlerBase {
|
|
||||||
|
|
||||||
name = 'AddonModSubsection';
|
|
||||||
modName = 'subsection';
|
|
||||||
component = ADDON_MOD_SUBSECTION_COMPONENT;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
protected async performDownloadOrPrefetch(
|
|
||||||
siteId: string,
|
|
||||||
module: CoreCourseModuleData,
|
|
||||||
courseId: number,
|
|
||||||
): Promise<void> {
|
|
||||||
const section = await this.getSection(module, courseId, siteId);
|
|
||||||
if (!section) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await CoreCourseHelper.prefetchSections([section], courseId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
async getDownloadSize(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreFileSizeSum> {
|
|
||||||
const section = await this.getSection(module, courseId);
|
|
||||||
if (!section) {
|
|
||||||
return { size: 0, total: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
return await CoreCourseModulePrefetchDelegate.getDownloadSize(section.modules, courseId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
async getDownloadedSize(module: CoreCourseAnyModuleData, courseId: number): Promise<number> {
|
|
||||||
const section = await this.getSection(module, courseId);
|
|
||||||
if (!section) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return CoreCourseHelper.getModulesDownloadedSize(section.modules, courseId);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the section of a module.
|
|
||||||
*
|
|
||||||
* @param module Module.
|
|
||||||
* @param courseId Course ID.
|
|
||||||
* @param siteId Site ID. If not defined, current site.
|
|
||||||
* @returns Promise resolved with the section if found.
|
|
||||||
*/
|
|
||||||
protected async getSection(
|
|
||||||
module: CoreCourseAnyModuleData,
|
|
||||||
courseId: number,
|
|
||||||
siteId?: string,
|
|
||||||
): Promise<CoreCourseWSSection | undefined> {
|
|
||||||
siteId = siteId ?? CoreSites.getCurrentSiteId();
|
|
||||||
|
|
||||||
const sections = await CoreCourse.getSections(courseId, false, true, undefined, siteId);
|
|
||||||
|
|
||||||
return sections.find((section) =>
|
|
||||||
section.component === 'mod_subsection' && section.itemid === module.instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
export const AddonModSubsectionPrefetchHandler = makeSingleton(AddonModSubsectionPrefetchHandlerService);
|
|
|
@ -15,8 +15,6 @@
|
||||||
import { APP_INITIALIZER, NgModule } from '@angular/core';
|
import { APP_INITIALIZER, NgModule } from '@angular/core';
|
||||||
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
|
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
|
||||||
import { AddonModSubsectionIndexLinkHandler } from './services/handlers/index-link';
|
import { AddonModSubsectionIndexLinkHandler } from './services/handlers/index-link';
|
||||||
import { AddonModSubsectionPrefetchHandler } from './services/handlers/prefetch';
|
|
||||||
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
providers: [
|
providers: [
|
||||||
|
@ -25,7 +23,6 @@ import { CoreCourseModulePrefetchDelegate } from '@features/course/services/modu
|
||||||
multi: true,
|
multi: true,
|
||||||
useValue: () => {
|
useValue: () => {
|
||||||
CoreContentLinksDelegate.registerHandler(AddonModSubsectionIndexLinkHandler.instance);
|
CoreContentLinksDelegate.registerHandler(AddonModSubsectionIndexLinkHandler.instance);
|
||||||
CoreCourseModulePrefetchDelegate.registerHandler(AddonModSubsectionPrefetchHandler.instance);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
|
|
||||||
<ion-card class="wholecourse">
|
<ion-card class="wholecourse">
|
||||||
<ion-card-header>
|
<ion-card-header>
|
||||||
<ion-card-title>
|
<ion-card-title>
|
||||||
|
@ -57,7 +56,7 @@
|
||||||
|
|
||||||
|
|
||||||
<ng-template #sectionCard let-section="section">
|
<ng-template #sectionCard let-section="section">
|
||||||
<ion-card class="section" *ngIf="section.modules.length > 0" [id]="'addons-course-storage-'+section.id">
|
<ion-card class="section" *ngIf="section.contents.length > 0" [id]="'addons-course-storage-'+section.id">
|
||||||
<ion-accordion [value]="section.id" toggleIconSlot="start">
|
<ion-accordion [value]="section.id" toggleIconSlot="start">
|
||||||
<ion-item [detail]="false" slot="header" class="card-header">
|
<ion-item [detail]="false" slot="header" class="card-header">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
|
@ -102,45 +101,46 @@
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-card-content slot="content">
|
<ion-card-content slot="content">
|
||||||
<ng-container *ngIf="section.expanded">
|
<ng-container *ngIf="section.expanded">
|
||||||
<ng-container *ngFor="let module of section.modules">
|
<ng-container *ngFor="let modOrSubsection of section.contents">
|
||||||
@if (module.subSection) {
|
@if (!isModule(modOrSubsection)) {
|
||||||
<ng-container *ngTemplateOutlet="sectionCard; context: { section: module.subSection }" />
|
<ng-container *ngTemplateOutlet="sectionCard; context: { section: modOrSubsection }" />
|
||||||
} @else {
|
} @else {
|
||||||
<ion-item class="core-course-storage-activity"
|
<ion-item class="core-course-storage-activity"
|
||||||
*ngIf="downloadEnabled || (!module.calculatingSize && module.totalSize > 0)">
|
*ngIf="downloadEnabled || (!modOrSubsection.calculatingSize && modOrSubsection.totalSize > 0)">
|
||||||
<core-mod-icon slot="start" *ngIf="module.handlerData.icon" [modicon]="module.handlerData.icon"
|
<core-mod-icon slot="start" *ngIf="modOrSubsection.handlerData.icon"
|
||||||
[modname]="module.modname" [componentId]="module.instance" [fallbackTranslation]="module.modplural"
|
[modicon]="modOrSubsection.handlerData.icon" [modname]="modOrSubsection.modname"
|
||||||
[isBranded]="module.branded" />
|
[componentId]="modOrSubsection.instance" [fallbackTranslation]="modOrSubsection.modplural"
|
||||||
|
[isBranded]="modOrSubsection.branded" />
|
||||||
<ion-label class="ion-text-wrap">
|
<ion-label class="ion-text-wrap">
|
||||||
<p class="item-heading {{module.handlerData!.class}} addon-storagemanager-module-size">
|
<p class="item-heading {{modOrSubsection.handlerData!.class}} addon-storagemanager-module-size">
|
||||||
<core-format-text [text]="module.handlerData.title" [courseId]="module.course" contextLevel="module"
|
<core-format-text [text]="modOrSubsection.handlerData.title" [courseId]="modOrSubsection.course"
|
||||||
[contextInstanceId]="module.id" [adaptImg]="false" />
|
contextLevel="module" [contextInstanceId]="modOrSubsection.id" [adaptImg]="false" />
|
||||||
</p>
|
</p>
|
||||||
<ion-badge [color]="module.downloadStatus === statusDownloaded ? 'success' : 'light'"
|
<ion-badge [color]="modOrSubsection.downloadStatus === statusDownloaded ? 'success' : 'light'"
|
||||||
*ngIf="!module.calculatingSize && module.totalSize > 0">
|
*ngIf="!modOrSubsection.calculatingSize && modOrSubsection.totalSize > 0">
|
||||||
<ion-icon name="fam-cloud-done" *ngIf="module.downloadStatus === statusDownloaded"
|
<ion-icon name="fam-cloud-done" *ngIf="modOrSubsection.downloadStatus === statusDownloaded"
|
||||||
[attr.aria-label]="'core.downloaded' | translate" />{{ module.totalSize |
|
[attr.aria-label]="'core.downloaded' | translate" />{{ modOrSubsection.totalSize |
|
||||||
coreBytesToSize }}
|
coreBytesToSize }}
|
||||||
</ion-badge>
|
</ion-badge>
|
||||||
<ion-badge color="light" *ngIf="module.calculatingSize ||
|
<ion-badge color="light" *ngIf="modOrSubsection.calculatingSize ||
|
||||||
(section.isDownloading && module.downloadStatus === statusDownloaded)">
|
(section.isDownloading && modOrSubsection.downloadStatus === statusDownloaded)">
|
||||||
{{ 'core.calculating' | translate }}
|
{{ 'core.calculating' | translate }}
|
||||||
</ion-badge>
|
</ion-badge>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
|
|
||||||
<div class="storage-buttons" slot="end">
|
<div class="storage-buttons" slot="end">
|
||||||
<core-download-refresh *ngIf="downloadEnabled && module.handlerData?.showDownloadButton &&
|
<core-download-refresh *ngIf="downloadEnabled && modOrSubsection.handlerData?.showDownloadButton &&
|
||||||
module.downloadStatus !== statusDownloaded" [status]="module.downloadStatus" [enabled]="true"
|
modOrSubsection.downloadStatus !== statusDownloaded" [status]="modOrSubsection.downloadStatus" [enabled]="true"
|
||||||
[canTrustDownload]="true" [loading]="module.spinner || module.handlerData.spinner"
|
[canTrustDownload]="true" [loading]="modOrSubsection.spinner || modOrSubsection.handlerData.spinner"
|
||||||
(action)="prefetchModule(module)"
|
(action)="prefetchModule(modOrSubsection)"
|
||||||
[statusesTranslatable]="{notdownloaded: 'addon.storagemanager.downloaddatafrom' }"
|
[statusesTranslatable]="{notdownloaded: 'addon.storagemanager.downloaddatafrom' }"
|
||||||
[statusSubject]="module.name" />
|
[statusSubject]="modOrSubsection.name" />
|
||||||
<ion-button fill="clear" (click)="deleteForModule($event, module)"
|
<ion-button fill="clear" (click)="deleteForModule($event, modOrSubsection)"
|
||||||
*ngIf="!module.calculatingSize && module.totalSize > 0" color="danger">
|
*ngIf="!modOrSubsection.calculatingSize && modOrSubsection.totalSize > 0" color="danger">
|
||||||
<ion-icon name="fas-trash" slot="icon-only" [attr.aria-label]="
|
<ion-icon name="fas-trash" slot="icon-only" [attr.aria-label]="
|
||||||
'addon.storagemanager.deletedatafrom' | translate: { name: module.name }" />
|
'addon.storagemanager.deletedatafrom' | translate: { name: modOrSubsection.name }" />
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<p *ngIf="!downloadEnabled || !module.handlerData?.showDownloadButton" class="sr-only">
|
<p *ngIf="!downloadEnabled || !modOrSubsection.handlerData?.showDownloadButton" class="sr-only">
|
||||||
{{ 'core.notdownloadable' | translate }}
|
{{ 'core.notdownloadable' | translate }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -14,10 +14,11 @@
|
||||||
|
|
||||||
import { CoreConstants, DownloadStatus } from '@/core/constants';
|
import { CoreConstants, DownloadStatus } from '@/core/constants';
|
||||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnDestroy, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { CoreCourse, CoreCourseProvider } from '@features/course/services/course';
|
import { CoreCourse, CoreCourseProvider, sectionContentIsModule } from '@features/course/services/course';
|
||||||
import {
|
import {
|
||||||
CoreCourseHelper,
|
CoreCourseHelper,
|
||||||
CoreCourseModuleData,
|
CoreCourseModuleData,
|
||||||
|
CoreCourseSection,
|
||||||
CoreCourseSectionWithStatus,
|
CoreCourseSectionWithStatus,
|
||||||
CorePrefetchStatusInfo,
|
CorePrefetchStatusInfo,
|
||||||
} from '@features/course/services/course-helper';
|
} from '@features/course/services/course-helper';
|
||||||
|
@ -31,9 +32,8 @@ import { CoreNavigator } from '@services/navigator';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { Translate } from '@singletons';
|
import { Translate } from '@singletons';
|
||||||
import { CoreArray } from '@singletons/array';
|
|
||||||
import { CoreDom } from '@singletons/dom';
|
import { CoreDom } from '@singletons/dom';
|
||||||
import { CoreEventObserver, CoreEvents, CoreEventSectionStatusChangedData } from '@singletons/events';
|
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
|
||||||
|
@ -66,6 +66,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
};
|
};
|
||||||
|
|
||||||
statusDownloaded = DownloadStatus.DOWNLOADED;
|
statusDownloaded = DownloadStatus.DOWNLOADED;
|
||||||
|
isModule = sectionContentIsModule;
|
||||||
|
|
||||||
protected siteUpdatedObserver?: CoreEventObserver;
|
protected siteUpdatedObserver?: CoreEventObserver;
|
||||||
protected courseStatusObserver?: CoreEventObserver;
|
protected courseStatusObserver?: CoreEventObserver;
|
||||||
|
@ -116,30 +117,8 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
const sections = (await CoreCourse.getSections(this.courseId, false, true))
|
const sections = (await CoreCourse.getSections(this.courseId, false, true))
|
||||||
.filter((section) => !CoreCourseHelper.isSectionStealth(section));
|
.filter((section) => !CoreCourseHelper.isSectionStealth(section));
|
||||||
|
|
||||||
const sectionsToRender = (await CoreCourseHelper.addHandlerDataForModules(sections, this.courseId)).sections
|
this.sections = (await CoreCourseHelper.addHandlerDataForModules(sections, this.courseId)).sections
|
||||||
.map(section => ({
|
.map(section => this.formatSection(section));
|
||||||
...section,
|
|
||||||
totalSize: 0,
|
|
||||||
calculatingSize: false,
|
|
||||||
expanded: section.id === initialSectionId,
|
|
||||||
modules: section.modules.map(module => ({
|
|
||||||
...module,
|
|
||||||
totalSize: 0,
|
|
||||||
calculatingSize: false,
|
|
||||||
})),
|
|
||||||
}));
|
|
||||||
|
|
||||||
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.loaded = true;
|
||||||
|
|
||||||
|
@ -165,6 +144,33 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a section.
|
||||||
|
*
|
||||||
|
* @param section Section to format.
|
||||||
|
* @param expanded Whether section should be expanded.
|
||||||
|
* @returns Formatted section,
|
||||||
|
*/
|
||||||
|
protected formatSection(section: CoreCourseSection, expanded = false): AddonStorageManagerCourseSection {
|
||||||
|
return {
|
||||||
|
...section,
|
||||||
|
totalSize: 0,
|
||||||
|
calculatingSize: true,
|
||||||
|
expanded: expanded,
|
||||||
|
contents: section.contents.map(modOrSubsection => {
|
||||||
|
if (sectionContentIsModule(modOrSubsection)) {
|
||||||
|
return {
|
||||||
|
...modOrSubsection,
|
||||||
|
totalSize: 0,
|
||||||
|
calculatingSize: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.formatSection(modOrSubsection, expanded);
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Init course prefetch information.
|
* Init course prefetch information.
|
||||||
*/
|
*/
|
||||||
|
@ -221,12 +227,12 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the affected section.
|
// Get the affected section.
|
||||||
const sectionFinder = CoreCourseHelper.findSectionWithSubsection(this.sections, data.sectionId);
|
const { section } = CoreCourseHelper.findSection(this.sections, { id: data.sectionId });
|
||||||
if (!sectionFinder?.section) {
|
if (!section) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const section = sectionFinder.section;
|
// @todo: Handle parents too? It seems the SECTION_STATUS_CHANGED event is never triggered.
|
||||||
|
|
||||||
// Check if the affected section is being downloaded.
|
// 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.
|
// If so, we don't update section status because it'll already be updated when the download finishes.
|
||||||
|
@ -247,30 +253,9 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
);
|
);
|
||||||
|
|
||||||
this.moduleStatusObserver = CoreEvents.on(CoreEvents.PACKAGE_STATUS_CHANGED, (data) => {
|
this.moduleStatusObserver = CoreEvents.on(CoreEvents.PACKAGE_STATUS_CHANGED, (data) => {
|
||||||
let moduleFound: AddonStorageManagerModule | undefined;
|
const modules = CoreCourse.getSectionsModules(this.sections);
|
||||||
|
const moduleFound = modules.find(module => module.id === data.componentId && module.prefetchHandler &&
|
||||||
this.sections.some((section) =>
|
data.component === module.prefetchHandler?.component);
|
||||||
section.modules.some((module) => {
|
|
||||||
if (module.id === data.componentId &&
|
|
||||||
module.prefetchHandler &&
|
|
||||||
data.component === module.prefetchHandler?.component) {
|
|
||||||
moduleFound = module;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} else if (module.subSection) {
|
|
||||||
return module.subSection.modules.some((module) => {
|
|
||||||
if (module.id === data.componentId &&
|
|
||||||
module.prefetchHandler &&
|
|
||||||
data.component === module.prefetchHandler?.component) {
|
|
||||||
moduleFound = module;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (!moduleFound) {
|
if (!moduleFound) {
|
||||||
return;
|
return;
|
||||||
|
@ -278,13 +263,6 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
// Call determineModuleStatus to get the right status to display.
|
// Call determineModuleStatus to get the right status to display.
|
||||||
const status = CoreCourseModulePrefetchDelegate.determineModuleStatus(moduleFound, data.status);
|
const status = CoreCourseModulePrefetchDelegate.determineModuleStatus(moduleFound, data.status);
|
||||||
if (moduleFound.subSection) {
|
|
||||||
const data: CoreEventSectionStatusChangedData = {
|
|
||||||
sectionId: moduleFound.subSection.id,
|
|
||||||
courseId: this.courseId,
|
|
||||||
};
|
|
||||||
CoreEvents.trigger(CoreEvents.SECTION_STATUS_CHANGED, data, CoreSites.getCurrentSiteId());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the status.
|
// Update the status.
|
||||||
this.updateModuleStatus(moduleFound, status);
|
this.updateModuleStatus(moduleFound, status);
|
||||||
|
@ -307,22 +285,15 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
* @param sections Modules.
|
* @param sections Modules.
|
||||||
*/
|
*/
|
||||||
protected async updateSizes(sections: AddonStorageManagerCourseSection[]): Promise<void> {
|
protected async updateSizes(sections: AddonStorageManagerCourseSection[]): Promise<void> {
|
||||||
sections = CoreArray.unique(sections, 'id');
|
|
||||||
|
|
||||||
this.calculatingSize = true;
|
this.calculatingSize = true;
|
||||||
sections.forEach((section) => {
|
CoreCourseHelper.flattenSections(sections).forEach((section) => {
|
||||||
section.calculatingSize = true;
|
section.calculatingSize = true;
|
||||||
section.modules.map((module) => {
|
|
||||||
if (module.subSection) {
|
|
||||||
module.subSection.calculatingSize = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
|
|
||||||
// Update only affected module sections.
|
// Update only affected module sections.
|
||||||
const modules = this.getAllModulesList(sections);
|
const modules = CoreCourse.getSectionsModules(sections);
|
||||||
await Promise.all(modules.map(async (module) => {
|
await Promise.all(modules.map(async (module) => {
|
||||||
await this.calculateModuleSize(module);
|
await this.calculateModuleSize(module);
|
||||||
}));
|
}));
|
||||||
|
@ -333,13 +304,12 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
|
|
||||||
section.modules.forEach((module) => {
|
section.contents.forEach((modOrSubsection) => {
|
||||||
if (module.subSection) {
|
if (!sectionContentIsModule(modOrSubsection)) {
|
||||||
updateSectionSize(module.subSection);
|
updateSectionSize(modOrSubsection);
|
||||||
module.totalSize = module.subSection.totalSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
section.totalSize += module.totalSize ?? 0;
|
section.totalSize += modOrSubsection.totalSize ?? 0;
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
});
|
});
|
||||||
section.calculatingSize = false;
|
section.calculatingSize = false;
|
||||||
|
@ -390,7 +360,8 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const modules = this.getAllModulesList(this.sections).filter((module) => module.totalSize && module.totalSize > 0);
|
const modules = CoreCourse.getSectionsModules(this.sections)
|
||||||
|
.filter((module) => module.totalSize && module.totalSize > 0);
|
||||||
|
|
||||||
await this.deleteModules(modules);
|
await this.deleteModules(modules);
|
||||||
}
|
}
|
||||||
|
@ -420,22 +391,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const modules: AddonStorageManagerModule[] = [];
|
const modules = CoreCourse.getSectionsModules([section]).filter((module) => module.totalSize && module.totalSize > 0);
|
||||||
section.modules.forEach((module) => {
|
|
||||||
if (module.subSection) {
|
|
||||||
module.subSection.modules.forEach((module) => {
|
|
||||||
if (module.totalSize && module.totalSize > 0) {
|
|
||||||
modules.push(module);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (module.totalSize && module.totalSize > 0) {
|
|
||||||
modules.push(module);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.deleteModules(modules);
|
await this.deleteModules(modules);
|
||||||
}
|
}
|
||||||
|
@ -481,16 +437,17 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
protected async deleteModules(modules: AddonStorageManagerModule[]): Promise<void> {
|
protected async deleteModules(modules: AddonStorageManagerModule[]): Promise<void> {
|
||||||
const modal = await CoreLoadings.show('core.deleting', true);
|
const modal = await CoreLoadings.show('core.deleting', true);
|
||||||
|
|
||||||
const sections: AddonStorageManagerCourseSection[] = [];
|
const sections = new Set<AddonStorageManagerCourseSection>();
|
||||||
const promises = modules.map(async (module) => {
|
const promises = modules.map(async (module) => {
|
||||||
// Remove the files.
|
// Remove the files.
|
||||||
await CoreCourseHelper.removeModuleStoredData(module, this.courseId);
|
await CoreCourseHelper.removeModuleStoredData(module, this.courseId);
|
||||||
|
|
||||||
module.totalSize = 0;
|
module.totalSize = 0;
|
||||||
|
|
||||||
const sectionFinder = CoreCourseHelper.findSectionWithSubsection(this.sections, module.section);
|
const { section, parents } = CoreCourseHelper.findSection(this.sections, { id: module.section });
|
||||||
if (sectionFinder?.section) {
|
const rootSection = parents[0] ?? section;
|
||||||
sections.push(sectionFinder?.section);
|
if (rootSection && !sections.has(rootSection)) {
|
||||||
|
sections.add(rootSection);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -501,7 +458,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
} finally {
|
} finally {
|
||||||
modal.dismiss();
|
modal.dismiss();
|
||||||
|
|
||||||
await this.updateSizes(sections);
|
await this.updateSizes(Array.from(sections));
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
}
|
}
|
||||||
|
@ -583,9 +540,10 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
} finally {
|
} finally {
|
||||||
module.spinner = false;
|
module.spinner = false;
|
||||||
|
|
||||||
const sectionFinder = CoreCourseHelper.findSectionWithSubsection(this.sections, module.section);
|
const { section, parents } = CoreCourseHelper.findSection(this.sections, { id: module.section });
|
||||||
if (sectionFinder?.section) {
|
const rootSection = parents[0] ?? section;
|
||||||
await this.updateSizes([sectionFinder?.section]);
|
if (rootSection) {
|
||||||
|
await this.updateSizes([rootSection]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -614,18 +572,20 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
* @param section Section to check.
|
* @param section Section to check.
|
||||||
*/
|
*/
|
||||||
protected async calculateModulesStatusOnSection(section: AddonStorageManagerCourseSection): Promise<void> {
|
protected async calculateModulesStatusOnSection(section: AddonStorageManagerCourseSection): Promise<void> {
|
||||||
await Promise.all(section.modules.map(async (module) => {
|
await Promise.all(section.contents.map(async (modOrSubsection) => {
|
||||||
if (module.handlerData?.showDownloadButton) {
|
if (!sectionContentIsModule(modOrSubsection)) {
|
||||||
module.spinner = true;
|
await this.calculateModulesStatusOnSection(modOrSubsection);
|
||||||
// Listen for changes on this module status, even if download isn't enabled.
|
|
||||||
module.prefetchHandler = CoreCourseModulePrefetchDelegate.getPrefetchHandlerFor(module.modname);
|
|
||||||
const status = await CoreCourseModulePrefetchDelegate.getModuleStatus(module, this.courseId);
|
|
||||||
|
|
||||||
this.updateModuleStatus(module, status);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (module.subSection) {
|
if (modOrSubsection.handlerData?.showDownloadButton) {
|
||||||
await this.calculateModulesStatusOnSection(module.subSection);
|
modOrSubsection.spinner = true;
|
||||||
|
// Listen for changes on this module status, even if download isn't enabled.
|
||||||
|
modOrSubsection.prefetchHandler = CoreCourseModulePrefetchDelegate.getPrefetchHandlerFor(modOrSubsection.modname);
|
||||||
|
const status = await CoreCourseModulePrefetchDelegate.getModuleStatus(modOrSubsection, this.courseId);
|
||||||
|
|
||||||
|
this.updateModuleStatus(modOrSubsection, status);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -714,29 +674,6 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all modules list.
|
|
||||||
*
|
|
||||||
* @param sections Sections to get the modules from.
|
|
||||||
* @returns All modules list.
|
|
||||||
*/
|
|
||||||
protected getAllModulesList(sections: AddonStorageManagerCourseSection[]): AddonStorageManagerModule[] {
|
|
||||||
const modules: AddonStorageManagerModule[] = [];
|
|
||||||
sections.forEach((section) => {
|
|
||||||
section.modules.forEach((module) => {
|
|
||||||
modules.push(module);
|
|
||||||
|
|
||||||
if (module.subSection) {
|
|
||||||
module.subSection.modules.forEach((module) => {
|
|
||||||
modules.push(module);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return modules;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the size of the modules.
|
* Calculate the size of the modules.
|
||||||
*
|
*
|
||||||
|
@ -770,19 +707,16 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
*/
|
*/
|
||||||
accordionGroupChange(event?: AccordionGroupChangeEventDetail): void {
|
accordionGroupChange(event?: AccordionGroupChangeEventDetail): void {
|
||||||
const sectionIds = event?.value as string[] ?? this.accordionMultipleValue;
|
const sectionIds = event?.value as string[] ?? this.accordionMultipleValue;
|
||||||
this.sections.forEach((section) => {
|
const allSections = CoreCourseHelper.flattenSections(this.sections);
|
||||||
|
allSections.forEach((section) => {
|
||||||
section.expanded = false;
|
section.expanded = false;
|
||||||
section.modules.forEach((section) => {
|
|
||||||
if (section.subSection) {
|
|
||||||
section.subSection.expanded = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
sectionIds.forEach((sectionId) => {
|
sectionIds.forEach((sectionId) => {
|
||||||
const sectionToExpand = CoreCourseHelper.findSectionById(this.sections, Number(sectionId));
|
const section = allSections.find((section) => section.id === Number(sectionId));
|
||||||
if (sectionToExpand) {
|
|
||||||
sectionToExpand.expanded = true;
|
if (section) {
|
||||||
|
section.expanded = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -796,15 +730,10 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
this.moduleStatusObserver?.off();
|
this.moduleStatusObserver?.off();
|
||||||
this.siteUpdatedObserver?.off();
|
this.siteUpdatedObserver?.off();
|
||||||
|
|
||||||
this.sections.forEach((section) => {
|
CoreCourse.getSectionsModules(this.sections).forEach((module) => {
|
||||||
section.modules.forEach((module) => {
|
module.handlerData?.onDestroy?.();
|
||||||
module.subSection?.modules.forEach((module) => {
|
|
||||||
module.handlerData?.onDestroy?.();
|
|
||||||
});
|
|
||||||
|
|
||||||
module.handlerData?.onDestroy?.();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.isDestroyed = true;
|
this.isDestroyed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -813,9 +742,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
*
|
*
|
||||||
* @param sections Sections to calculate their status.
|
* @param sections Sections to calculate their status.
|
||||||
*/
|
*/
|
||||||
protected async calculateSectionsStatus(
|
protected async calculateSectionsStatus(sections: AddonStorageManagerCourseSection[]): Promise<void> {
|
||||||
sections: AddonStorageManagerCourseSection[],
|
|
||||||
): Promise<void> {
|
|
||||||
if (!sections) {
|
if (!sections) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -829,12 +756,6 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
section.isCalculating = true;
|
section.isCalculating = true;
|
||||||
await this.calculateModulesStatusOnSection(section);
|
await this.calculateModulesStatusOnSection(section);
|
||||||
await CoreCourseHelper.calculateSectionStatus(section, this.courseId, false, false);
|
await CoreCourseHelper.calculateSectionStatus(section, this.courseId, false, false);
|
||||||
|
|
||||||
await Promise.all(section.modules.map(async (module) => {
|
|
||||||
if (module.subSection) {
|
|
||||||
return CoreCourseHelper.calculateSectionStatus(module.subSection, this.courseId, false, false);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
} finally {
|
} finally {
|
||||||
section.isCalculating = false;
|
section.isCalculating = false;
|
||||||
}
|
}
|
||||||
|
@ -843,11 +764,11 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AddonStorageManagerCourseSection = Omit<CoreCourseSectionWithStatus, 'modules'> & {
|
type AddonStorageManagerCourseSection = Omit<CoreCourseSectionWithStatus, 'contents'> & {
|
||||||
totalSize: number;
|
totalSize: number;
|
||||||
calculatingSize: boolean;
|
calculatingSize: boolean;
|
||||||
expanded: boolean;
|
expanded: boolean;
|
||||||
modules: AddonStorageManagerModule[];
|
contents: (AddonStorageManagerCourseSection | AddonStorageManagerModule)[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type AddonStorageManagerModule = CoreCourseModuleData & {
|
type AddonStorageManagerModule = CoreCourseModuleData & {
|
||||||
|
@ -856,5 +777,4 @@ type AddonStorageManagerModule = CoreCourseModuleData & {
|
||||||
prefetchHandler?: CoreCourseModulePrefetchHandler;
|
prefetchHandler?: CoreCourseModulePrefetchHandler;
|
||||||
spinner?: boolean;
|
spinner?: boolean;
|
||||||
downloadStatus?: DownloadStatus;
|
downloadStatus?: DownloadStatus;
|
||||||
subSection?: AddonStorageManagerCourseSection;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -239,7 +239,7 @@ export class AddonStorageManagerCoursesStoragePage implements OnInit, OnDestroy
|
||||||
*/
|
*/
|
||||||
private async calculateDownloadedCourseSize(courseId: number): Promise<number> {
|
private async calculateDownloadedCourseSize(courseId: number): Promise<number> {
|
||||||
const sections = await CoreCourse.getSections(courseId);
|
const sections = await CoreCourse.getSections(courseId);
|
||||||
const modules = CoreCourseHelper.getSectionsModules(sections);
|
const modules = CoreCourse.getSectionsModules(sections);
|
||||||
|
|
||||||
return CoreCourseHelper.getModulesDownloadedSize(modules, courseId);
|
return CoreCourseHelper.getModulesDownloadedSize(modules, courseId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -374,7 +374,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
section = lastModuleSection || section;
|
section = lastModuleSection || section;
|
||||||
moduleId = lastModuleSection ? this.lastModuleViewed.cmId : undefined;
|
moduleId = lastModuleSection ? this.lastModuleViewed.cmId : undefined;
|
||||||
} else {
|
} else {
|
||||||
const modules = CoreCourseHelper.getSectionsModules([currentSectionData.section]);
|
const modules = CoreCourse.getSectionsModules([currentSectionData.section]);
|
||||||
if (modules.some(module => module.id === this.lastModuleViewed?.cmId)) {
|
if (modules.some(module => module.id === this.lastModuleViewed?.cmId)) {
|
||||||
// Last module viewed is inside the highlighted section.
|
// Last module viewed is inside the highlighted section.
|
||||||
moduleId = this.lastModuleViewed.cmId;
|
moduleId = this.lastModuleViewed.cmId;
|
||||||
|
@ -665,7 +665,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sectionModules = CoreCourseHelper.getSectionsModules([this.sections[this.lastShownSectionIndex]]);
|
const sectionModules = CoreCourse.getSectionsModules([this.sections[this.lastShownSectionIndex]]);
|
||||||
|
|
||||||
modulesLoaded += sectionModules.reduce((total, module) =>
|
modulesLoaded += sectionModules.reduce((total, module) =>
|
||||||
!CoreCourseHelper.isModuleStealth(module, this.sections[this.lastShownSectionIndex]) ? total + 1 : total, 0);
|
!CoreCourseHelper.isModuleStealth(module, this.sections[this.lastShownSectionIndex]) ? total + 1 : total, 0);
|
||||||
|
@ -817,9 +817,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
});
|
});
|
||||||
|
|
||||||
sectionIds?.forEach((sectionId) => {
|
sectionIds?.forEach((sectionId) => {
|
||||||
const sId = Number(sectionId);
|
const section = allSections.find((section) => section.id === Number(sectionId));
|
||||||
const section = allSections.find((section) => section.id === sId);
|
|
||||||
|
|
||||||
if (section) {
|
if (section) {
|
||||||
section.expanded = true;
|
section.expanded = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,7 +102,7 @@ export class CoreCourseModuleNavigationComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
const sections = await CoreCourse.getSections(this.courseId, false, true, preSets);
|
const sections = await CoreCourse.getSections(this.courseId, false, true, preSets);
|
||||||
|
|
||||||
const modules = await CoreCourseHelper.getSectionsModules(sections, {
|
const modules = await CoreCourse.getSectionsModules(sections, {
|
||||||
ignoreSection: (section) => !this.isSectionAvailable(section),
|
ignoreSection: (section) => !this.isSectionAvailable(section),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@ export class CoreCourseModuleNavigationComponent implements OnInit, OnDestroy {
|
||||||
if (checkNext) {
|
if (checkNext) {
|
||||||
// Find next Module.
|
// Find next Module.
|
||||||
this.nextModule = undefined;
|
this.nextModule = undefined;
|
||||||
for (let i = currentModuleIndex + 1; i < modules.length && this.nextModule == undefined; i++) {
|
for (let i = currentModuleIndex + 1; i < modules.length && this.nextModule === undefined; i++) {
|
||||||
const module = modules[i];
|
const module = modules[i];
|
||||||
if (this.isModuleAvailable(module)) {
|
if (this.isModuleAvailable(module)) {
|
||||||
this.nextModule = module;
|
this.nextModule = module;
|
||||||
|
@ -126,7 +126,7 @@ export class CoreCourseModuleNavigationComponent implements OnInit, OnDestroy {
|
||||||
if (checkPrevious) {
|
if (checkPrevious) {
|
||||||
// Find previous Module.
|
// Find previous Module.
|
||||||
this.previousModule = undefined;
|
this.previousModule = undefined;
|
||||||
for (let i = currentModuleIndex - 1; i >= 0 && this.previousModule == undefined; i--) {
|
for (let i = currentModuleIndex - 1; i >= 0 && this.previousModule === undefined; i--) {
|
||||||
const module = modules[i];
|
const module = modules[i];
|
||||||
if (this.isModuleAvailable(module)) {
|
if (this.isModuleAvailable(module)) {
|
||||||
this.previousModule = module;
|
this.previousModule = module;
|
||||||
|
|
|
@ -27,6 +27,7 @@ import {
|
||||||
import {
|
import {
|
||||||
CoreCourseHelper,
|
CoreCourseHelper,
|
||||||
CoreCourseModuleCompletionData,
|
CoreCourseModuleCompletionData,
|
||||||
|
CoreCourseModuleData,
|
||||||
CoreCourseSection,
|
CoreCourseSection,
|
||||||
} 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';
|
||||||
|
@ -215,10 +216,11 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy, CoreRefreshCon
|
||||||
protected async loadSections(refresh?: boolean): Promise<void> {
|
protected async loadSections(refresh?: boolean): Promise<void> {
|
||||||
// Get all the sections.
|
// Get all the sections.
|
||||||
const sections = await CoreCourse.getSections(this.course.id, false, true);
|
const sections = await CoreCourse.getSections(this.course.id, false, true);
|
||||||
|
let modules: CoreCourseModuleData[] | undefined;
|
||||||
|
|
||||||
if (refresh) {
|
if (refresh) {
|
||||||
// Invalidate the recently downloaded module list. To ensure info can be prefetched.
|
// Invalidate the recently downloaded module list. To ensure info can be prefetched.
|
||||||
const modules = CoreCourseHelper.getSectionsModules(sections);
|
modules = CoreCourse.getSectionsModules(sections);
|
||||||
|
|
||||||
await CoreCourseModulePrefetchDelegate.invalidateModules(modules, this.course.id);
|
await CoreCourseModulePrefetchDelegate.invalidateModules(modules, this.course.id);
|
||||||
}
|
}
|
||||||
|
@ -227,7 +229,9 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy, CoreRefreshCon
|
||||||
|
|
||||||
// Get the completion status.
|
// Get the completion status.
|
||||||
if (CoreCoursesHelper.isCompletionEnabledInCourse(this.course)) {
|
if (CoreCoursesHelper.isCompletionEnabledInCourse(this.course)) {
|
||||||
const modules = CoreCourseHelper.getSectionsModules(sections);
|
if (!modules) {
|
||||||
|
modules = CoreCourse.getSectionsModules(sections);
|
||||||
|
}
|
||||||
|
|
||||||
if (modules[0]?.completion !== undefined) {
|
if (modules[0]?.completion !== undefined) {
|
||||||
// The module already has completion (3.6 onwards). Load the offline completion.
|
// The module already has completion (3.6 onwards). Load the offline completion.
|
||||||
|
@ -334,7 +338,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy, CoreRefreshCon
|
||||||
|
|
||||||
if (this.sections) {
|
if (this.sections) {
|
||||||
// If the completion value is not used, the page won't be reloaded, so update the progress bar.
|
// If the completion value is not used, the page won't be reloaded, so update the progress bar.
|
||||||
const completionModules = CoreCourseHelper.getSectionsModules(this.sections)
|
const completionModules = CoreCourse.getSectionsModules(this.sections)
|
||||||
.map((module) => module.completion && module.completion > 0 ? 1 : module.completion)
|
.map((module) => module.completion && module.completion > 0 ? 1 : module.completion)
|
||||||
.reduce((accumulator, currentValue) => (accumulator || 0) + (currentValue || 0), 0);
|
.reduce((accumulator, currentValue) => (accumulator || 0) + (currentValue || 0), 0);
|
||||||
|
|
||||||
|
|
|
@ -175,7 +175,7 @@ export class CoreCourseListModTypePage implements OnInit {
|
||||||
while (this.lastShownSectionIndex < this.sections.length - 1 && modulesLoaded < CoreCourseListModTypePage.PAGE_LENGTH) {
|
while (this.lastShownSectionIndex < this.sections.length - 1 && modulesLoaded < CoreCourseListModTypePage.PAGE_LENGTH) {
|
||||||
this.lastShownSectionIndex++;
|
this.lastShownSectionIndex++;
|
||||||
|
|
||||||
const sectionModules = CoreCourseHelper.getSectionsModules([this.sections[this.lastShownSectionIndex]]);
|
const sectionModules = CoreCourse.getSectionsModules([this.sections[this.lastShownSectionIndex]]);
|
||||||
modulesLoaded += sectionModules.length;
|
modulesLoaded += sectionModules.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -78,7 +78,6 @@ import { CoreEnrolAction, CoreEnrolDelegate } from '@features/enrol/services/enr
|
||||||
import { LazyRoutesModule } from '@/app/app-routing.module';
|
import { LazyRoutesModule } from '@/app/app-routing.module';
|
||||||
import { CoreModals } from '@services/modals';
|
import { CoreModals } from '@services/modals';
|
||||||
import { CoreLoadings } from '@services/loadings';
|
import { CoreLoadings } from '@services/loadings';
|
||||||
import { ArrayElement } from '@/core/utils/types';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prefetch info of a module.
|
* Prefetch info of a module.
|
||||||
|
@ -288,11 +287,11 @@ export class CoreCourseHelperProvider {
|
||||||
throw new CoreError('Invalid section');
|
throw new CoreError('Invalid section');
|
||||||
}
|
}
|
||||||
|
|
||||||
const sectionWithStatus = <CoreCourseSectionWithStatus> section;
|
// Get the status of this section based on their modules.
|
||||||
|
const { modules, subsections } = CoreCourse.classifyContents(section.contents);
|
||||||
|
|
||||||
// Get the status of this section.
|
|
||||||
const statusData = await CoreCourseModulePrefetchDelegate.getModulesStatus(
|
const statusData = await CoreCourseModulePrefetchDelegate.getModulesStatus(
|
||||||
section.contents,
|
modules,
|
||||||
courseId,
|
courseId,
|
||||||
section.id,
|
section.id,
|
||||||
refresh,
|
refresh,
|
||||||
|
@ -300,12 +299,20 @@ export class CoreCourseHelperProvider {
|
||||||
checkUpdates,
|
checkUpdates,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Now calculate status of subsections, and add them to the status data. Each subsection counts as 1 item in the section.
|
||||||
|
await Promise.all(subsections.map(async (subsection) => {
|
||||||
|
const subsectionStatus = await this.calculateSectionStatus(subsection, courseId, refresh, checkUpdates);
|
||||||
|
statusData.total++;
|
||||||
|
statusData.status = CoreFilepool.determinePackagesStatus(statusData.status, subsectionStatus.statusData.status);
|
||||||
|
}));
|
||||||
|
|
||||||
// Check if it's being downloaded.
|
// Check if it's being downloaded.
|
||||||
const downloadId = this.getSectionDownloadId(section);
|
const downloadId = this.getSectionDownloadId(section);
|
||||||
if (CoreCourseModulePrefetchDelegate.isBeingDownloaded(downloadId)) {
|
if (CoreCourseModulePrefetchDelegate.isBeingDownloaded(downloadId)) {
|
||||||
statusData.status = DownloadStatus.DOWNLOADING;
|
statusData.status = DownloadStatus.DOWNLOADING;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sectionWithStatus = <CoreCourseSectionWithStatus> section;
|
||||||
sectionWithStatus.downloadStatus = statusData.status;
|
sectionWithStatus.downloadStatus = statusData.status;
|
||||||
|
|
||||||
// Set this section data.
|
// Set this section data.
|
||||||
|
@ -404,42 +411,28 @@ export class CoreCourseHelperProvider {
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
|
||||||
const promises = courses.map(async (course) => {
|
const promises = courses.map(async (course) => {
|
||||||
const subPromises: Promise<void>[] = [];
|
|
||||||
let sections: CoreCourseWSSection[];
|
|
||||||
let handlers: CoreCourseOptionsHandlerToDisplay[] = [];
|
|
||||||
let menuHandlers: CoreCourseOptionsMenuHandlerToDisplay[] = [];
|
|
||||||
let success = true;
|
let success = true;
|
||||||
|
|
||||||
// Get the sections and the handlers.
|
// Get the sections and the handlers.
|
||||||
subPromises.push(CoreCourse.getSections(course.id, false, true).then((courseSections) => {
|
const [sections, handlers, menuHandlers] = await Promise.all([
|
||||||
sections = courseSections;
|
CoreCourse.getSections(course.id, false, true),
|
||||||
|
CoreCourseOptionsDelegate.getHandlersToDisplay(course, false),
|
||||||
|
CoreCourseOptionsDelegate.getMenuHandlersToDisplay(course, false),
|
||||||
|
]);
|
||||||
|
|
||||||
return;
|
try {
|
||||||
}));
|
await this.prefetchCourse(course, sections, handlers, menuHandlers, siteId);
|
||||||
|
} catch (error) {
|
||||||
|
success = false;
|
||||||
|
|
||||||
subPromises.push(CoreCourseOptionsDelegate.getHandlersToDisplay(course, false).then((cHandlers) => {
|
throw error;
|
||||||
handlers = cHandlers;
|
} finally {
|
||||||
|
|
||||||
return;
|
|
||||||
}));
|
|
||||||
subPromises.push(CoreCourseOptionsDelegate.getMenuHandlersToDisplay(course, false).then((mHandlers) => {
|
|
||||||
menuHandlers = mHandlers;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}));
|
|
||||||
|
|
||||||
return Promise.all(subPromises).then(() => this.prefetchCourse(course, sections, handlers, menuHandlers, siteId))
|
|
||||||
.catch((error) => {
|
|
||||||
success = false;
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}).finally(() => {
|
|
||||||
// Course downloaded or failed, notify the progress.
|
// Course downloaded or failed, notify the progress.
|
||||||
count++;
|
count++;
|
||||||
if (options.onProgress) {
|
if (options.onProgress) {
|
||||||
options.onProgress({ count: count, total: total, courseId: course.id, success: success });
|
options.onProgress({ count: count, total: total, courseId: course.id, success: success });
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (options.onProgress) {
|
if (options.onProgress) {
|
||||||
|
@ -469,20 +462,31 @@ export class CoreCourseHelperProvider {
|
||||||
total: true,
|
total: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
await Promise.all(sections.map(async (section) => {
|
const getSectionSize = async (section: CoreCourseWSSection): Promise<CoreFileSizeSum> => {
|
||||||
if (section.id === CoreCourseProvider.ALL_SECTIONS_ID) {
|
if (section.id === CoreCourseProvider.ALL_SECTIONS_ID) {
|
||||||
return;
|
return { size: 0, total: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
const sectionSize = await CoreCourseModulePrefetchDelegate.getDownloadSize(section.modules, courseId);
|
const { modules, subsections } = CoreCourse.classifyContents(section.contents);
|
||||||
|
|
||||||
sizeSum.total = sizeSum.total && sectionSize.total;
|
const [modulesSize, subsectionsSizes] = await Promise.all([
|
||||||
sizeSum.size += sectionSize.size;
|
CoreCourseModulePrefetchDelegate.getDownloadSize(modules, courseId),
|
||||||
|
Promise.all(subsections.map((modOrSubsection) => getSectionSize(modOrSubsection))),
|
||||||
|
]);
|
||||||
|
|
||||||
// Check if the section has embedded files in the description.
|
// Check if the section has embedded files in the description.
|
||||||
if (!hasEmbeddedFiles && CoreFilepool.extractDownloadableFilesFromHtml(section.summary).length > 0) {
|
if (!hasEmbeddedFiles && CoreFilepool.extractDownloadableFilesFromHtml(section.summary).length > 0) {
|
||||||
hasEmbeddedFiles = true;
|
hasEmbeddedFiles = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return subsectionsSizes.concat(modulesSize).reduce((sizeSum, contentSize) => ({
|
||||||
|
size: sizeSum.size + contentSize.size,
|
||||||
|
total: sizeSum.total && contentSize.total,
|
||||||
|
}), { size: 0, total: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
await Promise.all(sections.map(async (section) => {
|
||||||
|
await getSectionSize(section);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (hasEmbeddedFiles) {
|
if (hasEmbeddedFiles) {
|
||||||
|
@ -1586,7 +1590,7 @@ export class CoreCourseHelperProvider {
|
||||||
// Prefetch other data needed to render the course.
|
// Prefetch other data needed to render the course.
|
||||||
promises.push(CoreCourses.getCoursesByField('id', course.id));
|
promises.push(CoreCourses.getCoursesByField('id', course.id));
|
||||||
|
|
||||||
const modules = this.getSectionsModules(sections);
|
const modules = CoreCourse.getSectionsModules(sections);
|
||||||
if (!modules.length || modules[0].completion === undefined) {
|
if (!modules.length || modules[0].completion === undefined) {
|
||||||
promises.push(CoreCourse.getActivitiesCompletionStatus(course.id));
|
promises.push(CoreCourse.getActivitiesCompletionStatus(course.id));
|
||||||
}
|
}
|
||||||
|
@ -1646,7 +1650,7 @@ export class CoreCourseHelperProvider {
|
||||||
* @param updateAllSections Update all sections status
|
* @param updateAllSections Update all sections status
|
||||||
*/
|
*/
|
||||||
async prefetchSections(
|
async prefetchSections(
|
||||||
sections: (CoreCourseSectionWithStatus & CoreCourseSectionWithSubsections)[],
|
sections: CoreCourseSectionWithStatus[],
|
||||||
courseId: number,
|
courseId: number,
|
||||||
updateAllSections = false,
|
updateAllSections = false,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
@ -1736,18 +1740,27 @@ export class CoreCourseHelperProvider {
|
||||||
* @returns Promise resolved when the section is prefetched.
|
* @returns Promise resolved when the section is prefetched.
|
||||||
*/
|
*/
|
||||||
protected async syncModulesAndPrefetchSection(section: CoreCourseSectionWithStatus, courseId: number): Promise<void> {
|
protected async syncModulesAndPrefetchSection(section: CoreCourseSectionWithStatus, courseId: number): Promise<void> {
|
||||||
// Sync the modules first.
|
const { modules, subsections } = CoreCourse.classifyContents(section.contents);
|
||||||
await CoreCourseModulePrefetchDelegate.syncModules(section.contents, courseId);
|
|
||||||
|
|
||||||
// Validate the section needs to be downloaded and calculate amount of modules that need to be downloaded.
|
const syncAndPrefetchModules = async () => {
|
||||||
const result = await CoreCourseModulePrefetchDelegate.getModulesStatus(section.contents, courseId, section.id);
|
// Sync the modules first.
|
||||||
|
await CoreCourseModulePrefetchDelegate.syncModules(modules, courseId);
|
||||||
|
|
||||||
if (result.status === DownloadStatus.DOWNLOADED || result.status === DownloadStatus.NOT_DOWNLOADABLE) {
|
// Validate the section needs to be downloaded and calculate amount of modules that need to be downloaded.
|
||||||
// Section is downloaded or not downloadable, nothing to do.
|
const result = await CoreCourseModulePrefetchDelegate.getModulesStatus(modules, courseId, section.id);
|
||||||
return ;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.prefetchSingleSection(section, result, courseId);
|
if (result.status === DownloadStatus.DOWNLOADED || result.status === DownloadStatus.NOT_DOWNLOADABLE) {
|
||||||
|
// Section is downloaded or not downloadable, nothing to do.
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.prefetchSingleSection(section, result, courseId);
|
||||||
|
};
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
syncAndPrefetchModules(),
|
||||||
|
Promise.all(subsections.map(subsection => this.prefetchSingleSectionIfNeeded(subsection, courseId))),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1860,7 +1873,7 @@ export class CoreCourseHelperProvider {
|
||||||
async deleteCourseFiles(courseId: number): Promise<void> {
|
async deleteCourseFiles(courseId: number): Promise<void> {
|
||||||
const siteId = CoreSites.getCurrentSiteId();
|
const siteId = CoreSites.getCurrentSiteId();
|
||||||
const sections = await CoreCourse.getSections(courseId);
|
const sections = await CoreCourse.getSections(courseId);
|
||||||
const modules = this.getSectionsModules(sections);
|
const modules = CoreCourse.getSectionsModules(sections);
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
...modules.map((module) => this.removeModuleStoredData(module, courseId)),
|
...modules.map((module) => this.removeModuleStoredData(module, courseId)),
|
||||||
|
@ -2116,44 +2129,6 @@ export class CoreCourseHelperProvider {
|
||||||
return { section: foundSection, parents: parents.reverse() };
|
return { section: foundSection, parents: parents.reverse() };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a list of sections, returns the list of modules in the sections.
|
|
||||||
* The modules are ordered in the order of appearance in the course.
|
|
||||||
*
|
|
||||||
* @param sections Sections.
|
|
||||||
* @param options Other options.
|
|
||||||
* @returns Modules.
|
|
||||||
*/
|
|
||||||
getSectionsModules<
|
|
||||||
Section extends CoreCourseWSSection,
|
|
||||||
Module = Extract<ArrayElement<Section['contents']>, CoreCourseModuleData>
|
|
||||||
>(
|
|
||||||
sections: Section[],
|
|
||||||
options: CoreCourseGetSectionsModulesOptions<Section, Module> = {},
|
|
||||||
): Module[] {
|
|
||||||
let modules: Module[] = [];
|
|
||||||
|
|
||||||
sections.forEach((section) => {
|
|
||||||
if (options.ignoreSection && options.ignoreSection(section)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.contents.forEach((modOrSubsection) => {
|
|
||||||
if (sectionContentIsModule(modOrSubsection)) {
|
|
||||||
if (options.ignoreModule && options.ignoreModule(modOrSubsection as Module)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
modules.push(modOrSubsection as Module);
|
|
||||||
} else {
|
|
||||||
modules = modules.concat(this.getSectionsModules([modOrSubsection], options));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return modules;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a list of sections, returns the list of sections and subsections.
|
* Given a list of sections, returns the list of sections and subsections.
|
||||||
*
|
*
|
||||||
|
@ -2284,11 +2259,3 @@ export type CoreCourseGuestAccessInfo = {
|
||||||
*/
|
*/
|
||||||
passwordRequired?: boolean;
|
passwordRequired?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Options for get sections modules.
|
|
||||||
*/
|
|
||||||
export type CoreCourseGetSectionsModulesOptions<Section, Module> = {
|
|
||||||
ignoreSection?: (section: Section) => boolean; // Function to filter sections. Return true to ignore it, false to use it.
|
|
||||||
ignoreModule?: (module: Module) => boolean; // Function to filter module. Return true to ignore it, false to use it.
|
|
||||||
};
|
|
||||||
|
|
|
@ -64,6 +64,7 @@ import { CoreSiteWSPreSets, WSObservable } from '@classes/sites/authenticated-si
|
||||||
import { CoreLoadings } from '@services/loadings';
|
import { CoreLoadings } from '@services/loadings';
|
||||||
import { CoreArray } from '@singletons/array';
|
import { CoreArray } from '@singletons/array';
|
||||||
import { CoreText } from '@singletons/text';
|
import { CoreText } from '@singletons/text';
|
||||||
|
import { ArrayElement } from '@/core/utils/types';
|
||||||
|
|
||||||
const ROOT_CACHE_KEY = 'mmCourse:';
|
const ROOT_CACHE_KEY = 'mmCourse:';
|
||||||
|
|
||||||
|
@ -1074,13 +1075,40 @@ export class CoreCourseProvider {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a list of sections, returns the list of modules in the sections.
|
* Given a list of sections, returns the list of modules in the sections.
|
||||||
|
* The modules are ordered in the order of appearance in the course.
|
||||||
*
|
*
|
||||||
* @param sections Sections.
|
* @param sections Sections.
|
||||||
|
* @param options Other options.
|
||||||
* @returns Modules.
|
* @returns Modules.
|
||||||
* @deprecated since 4.5. Use CoreCourseHelper.getSectionsModules instead.
|
|
||||||
*/
|
*/
|
||||||
getSectionsModules(sections: CoreCourseWSSection[]): CoreCourseModuleData[] {
|
getSectionsModules<
|
||||||
return CoreCourseHelper.getSectionsModules(sections);
|
Section extends CoreCourseWSSection,
|
||||||
|
Module = Extract<ArrayElement<Section['contents']>, CoreCourseModuleData>
|
||||||
|
>(
|
||||||
|
sections: Section[],
|
||||||
|
options: CoreCourseGetSectionsModulesOptions<Section, Module> = {},
|
||||||
|
): Module[] {
|
||||||
|
let modules: Module[] = [];
|
||||||
|
|
||||||
|
sections.forEach((section) => {
|
||||||
|
if (options.ignoreSection && options.ignoreSection(section)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.contents.forEach((modOrSubsection) => {
|
||||||
|
if (sectionContentIsModule(modOrSubsection)) {
|
||||||
|
if (options.ignoreModule && options.ignoreModule(modOrSubsection as Module)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
modules.push(modOrSubsection as Module);
|
||||||
|
} else {
|
||||||
|
modules = modules.concat(this.getSectionsModules([modOrSubsection], options));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return modules;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1635,6 +1663,31 @@ export class CoreCourseProvider {
|
||||||
return CoreDomUtils.removeElementFromHtml(availabilityInfo, 'li[data-action="showmore"]');
|
return CoreDomUtils.removeElementFromHtml(availabilityInfo, 'li[data-action="showmore"]');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given section contents, classify them into modules and sections.
|
||||||
|
*
|
||||||
|
* @param contents Contents.
|
||||||
|
* @returns Classified contents.
|
||||||
|
*/
|
||||||
|
classifyContents<
|
||||||
|
Contents extends CoreCourseModuleOrSection,
|
||||||
|
Module = Extract<Contents, CoreCourseModuleData>,
|
||||||
|
Section = Extract<Contents, CoreCourseWSSection>,
|
||||||
|
>(contents: Contents[]): { modules: Module[]; subsections: Section[] } {
|
||||||
|
const modules: Module[] = [];
|
||||||
|
const subsections: Section[] = [];
|
||||||
|
|
||||||
|
contents.forEach((content) => {
|
||||||
|
if (sectionContentIsModule(content)) {
|
||||||
|
modules.push(content as Module);
|
||||||
|
} else {
|
||||||
|
subsections.push(content as unknown as Section);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { modules, subsections };
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CoreCourse = makeSingleton(CoreCourseProvider);
|
export const CoreCourse = makeSingleton(CoreCourseProvider);
|
||||||
|
@ -2069,3 +2122,11 @@ export type CoreCourseGetSectionsOptions = CoreSitesCommonWSOptions & {
|
||||||
includeStealthModules?: boolean; // Defaults to true.
|
includeStealthModules?: boolean; // Defaults to true.
|
||||||
preSets?: CoreSiteWSPreSets;
|
preSets?: CoreSiteWSPreSets;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for get sections modules.
|
||||||
|
*/
|
||||||
|
export type CoreCourseGetSectionsModulesOptions<Section, Module> = {
|
||||||
|
ignoreSection?: (section: Section) => boolean; // Function to filter sections. Return true to ignore it, false to use it.
|
||||||
|
ignoreModule?: (module: Module) => boolean; // Function to filter module. Return true to ignore it, false to use it.
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in New Issue