MOBILE-3659 course: Add utility classes

main
Dani Palou 2021-01-25 07:42:55 +01:00
parent 310ee19d26
commit 8e8793fff2
10 changed files with 1496 additions and 10 deletions

View File

@ -0,0 +1,193 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { CoreConstants } from '@/core/constants';
import { CoreNetworkError } from '@classes/errors/network-error';
import { CoreFilterHelper } from '@features/filter/services/filter-helper';
import { CoreApp } from '@services/app';
import { CoreFilepool } from '@services/filepool';
import { CoreSites } from '@services/sites';
import { CoreCourse, CoreCourseWSModule } from '../services/course';
import { CoreCourseModulePrefetchHandlerBase } from './module-prefetch-handler';
/**
* Base prefetch handler to be registered in CoreCourseModulePrefetchDelegate. It is useful to minimize the amount of
* functions that handlers need to implement. It also provides some helper features like preventing a module to be
* downloaded twice at the same time.
*
* If your handler inherits from this service, you just need to override the functions that you want to change.
*
* This class should be used for ACTIVITIES. You must override the prefetch function, and it's recommended to call
* prefetchPackage in there since it handles the package status.
*/
export class CoreCourseActivityPrefetchHandlerBase extends CoreCourseModulePrefetchHandlerBase {
/**
* Download the module.
*
* @param module The module object returned by WS.
* @param courseId Course ID.
* @param dirPath Path of the directory where to store all the content files.
* @return Promise resolved when all content is downloaded.
*/
download(module: CoreCourseWSModule, courseId: number, dirPath?: string): Promise<void> {
// Same implementation for download and prefetch.
return this.prefetch(module, courseId, false, dirPath);
}
/**
* Prefetch a module.
*
* @param module Module.
* @param courseId Course ID the module belongs to.
* @param single True if we're downloading a single module, false if we're downloading a whole section.
* @param dirPath Path of the directory where to store all the content files.
* @return Promise resolved when done.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async prefetch(module: CoreCourseWSModule, courseId?: number, single?: boolean, dirPath?: string): Promise<void> {
// To be overridden. It should call prefetchPackage
return;
}
/**
* Prefetch the module, setting package status at start and finish.
*
* Example usage from a child instance:
* return this.prefetchPackage(module, courseId, single, this.prefetchModule.bind(this, otherParam), siteId);
*
* Then the function "prefetchModule" will receive params:
* prefetchModule(module, courseId, single, siteId, someParam, anotherParam)
*
* @param module Module.
* @param courseId Course ID the module belongs to.
* @param downloadFn Function to perform the prefetch. Please check the documentation of prefetchFunction.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the module has been downloaded. Data returned is not reliable.
*/
async prefetchPackage(
module: CoreCourseWSModule,
courseId: number,
downloadFunction: () => Promise<string>,
siteId?: string,
): Promise<void> {
siteId = siteId || CoreSites.instance.getCurrentSiteId();
if (!CoreApp.instance.isOnline()) {
// Cannot prefetch in offline.
throw new CoreNetworkError();
}
if (this.isDownloading(module.id, siteId)) {
// There's already a download ongoing for this module, return the promise.
return this.getOngoingDownload(module.id, siteId);
}
const prefetchPromise = this.changeStatusAndPrefetch(module, courseId, downloadFunction, siteId);
return this.addOngoingDownload(module.id, prefetchPromise, siteId);
}
protected async changeStatusAndPrefetch(
module: CoreCourseWSModule,
courseId: number,
downloadFunction: () => Promise<string>,
siteId?: string,
): Promise<void> {
try {
await this.setDownloading(module.id, siteId);
// Package marked as downloading, get module info to be able to handle links. Get module filters too.
await Promise.all([
CoreCourse.instance.getModuleBasicInfo(module.id, siteId),
CoreCourse.instance.getModule(module.id, courseId, undefined, false, true, siteId),
CoreFilterHelper.instance.getFilters('module', module.id, { courseId }),
]);
// Call the download function.
let extra = await downloadFunction();
// Only accept string types.
if (typeof extra != 'string') {
extra = '';
}
// Prefetch finished, mark as downloaded.
await this.setDownloaded(module.id, siteId, extra);
} catch (error) {
// Error prefetching, go back to previous status and reject the promise.
return this.setPreviousStatus(module.id, siteId);
throw error;
}
}
/**
* Mark the module as downloaded.
*
* @param id Unique identifier per component.
* @param siteId Site ID. If not defined, current site.
* @param extra Extra data to store.
* @return Promise resolved when done.
*/
setDownloaded(id: number, siteId?: string, extra?: string): Promise<void> {
siteId = siteId || CoreSites.instance.getCurrentSiteId();
return CoreFilepool.instance.storePackageStatus(siteId, CoreConstants.DOWNLOADED, this.component, id, extra);
}
/**
* Mark the module as downloading.
*
* @param id Unique identifier per component.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when done.
*/
setDownloading(id: number, siteId?: string): Promise<void> {
siteId = siteId || CoreSites.instance.getCurrentSiteId();
return CoreFilepool.instance.storePackageStatus(siteId, CoreConstants.DOWNLOADING, this.component, id);
}
/**
* Set previous status and return a rejected promise.
*
* @param id Unique identifier per component.
* @param siteId Site ID. If not defined, current site.
* @return Rejected promise.
*/
async setPreviousStatus(id: number, siteId?: string): Promise<void> {
siteId = siteId || CoreSites.instance.getCurrentSiteId();
await CoreFilepool.instance.setPackagePreviousStatus(siteId, this.component, id);
}
/**
* Set previous status and return a rejected promise.
*
* @param id Unique identifier per component.
* @param error Error to throw.
* @param siteId Site ID. If not defined, current site.
* @return Rejected promise.
* @deprecated since 3.9.5. Use setPreviousStatus instead.
*/
async setPreviousStatusAndReject(id: number, error?: Error, siteId?: string): Promise<never> {
siteId = siteId || CoreSites.instance.getCurrentSiteId();
await CoreFilepool.instance.setPackagePreviousStatus(siteId, this.component, id);
throw error;
}
}

View File

@ -0,0 +1,57 @@
// (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 { CoreSyncBaseProvider } from '@classes/base-sync';
import { CoreCourseWSModule } from '../services/course';
import { CoreCourseModulePrefetchDelegate } from '../services/module-prefetch-delegate';
import { CoreCourseModulePrefetchHandlerBase } from './module-prefetch-handler';
/**
* Base class to create activity sync providers. It provides some common functions.
*/
export class CoreCourseActivitySyncBaseProvider extends CoreSyncBaseProvider {
/**
* Conveniece function to prefetch data after an update.
*
* @param module Module.
* @param courseId Course ID.
* @param preventDownloadRegex If regex matches, don't download the data. Defaults to check files.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when done.
*/
async prefetchAfterUpdate(
prefetchHandler: CoreCourseModulePrefetchHandlerBase,
module: CoreCourseWSModule,
courseId: number,
preventDownloadRegex?: RegExp,
siteId?: string,
): Promise<void> {
// Get the module updates to check if the data was updated or not.
const result = await CoreCourseModulePrefetchDelegate.instance.getModuleUpdates(module, courseId, true, siteId);
if (!result?.updates.length) {
return;
}
// Only prefetch if files haven't changed, to prevent downloading too much data automatically.
const regex = preventDownloadRegex || /^.*files$/;
const shouldDownload = !result.updates.find((entry) => entry.name.match(regex));
if (shouldDownload) {
return prefetchHandler.download(module, courseId);
}
}
}

View File

@ -0,0 +1,269 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, Inject, Input, OnDestroy, OnInit, Optional } from '@angular/core';
import { IonContent } from '@ionic/angular';
import { CoreCourseModuleMainResourceComponent } from './main-resource-component';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { Network, NgZone } from '@singletons';
import { Subscription } from 'rxjs';
import { CoreApp } from '@services/app';
import { CoreCourse } from '../services/course';
import { CoreUtils } from '@services/utils/utils';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreWSExternalWarning } from '@services/ws';
import { CoreCourseContentsPage } from '../pages/contents/contents';
/**
* Template class to easily create CoreCourseModuleMainComponent of activities.
*/
@Component({
template: '',
})
export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy {
@Input() group?: number; // Group ID the component belongs to.
moduleName?: string; // Raw module name to be translated. It will be translated on init.
// Data for context menu.
syncIcon?: string; // Sync icon.
hasOffline?: boolean; // If it has offline data to be synced.
isOnline?: boolean; // If the app is online or not.
protected syncObserver?: CoreEventObserver; // It will observe the sync auto event.
protected onlineSubscription: Subscription; // It will observe the status of the network connection.
protected syncEventName?: string; // Auto sync event name.
constructor(
@Optional() @Inject('') loggerName: string = 'CoreCourseModuleMainResourceComponent',
protected content?: IonContent,
courseContentsPage?: CoreCourseContentsPage,
) {
super(loggerName, courseContentsPage);
// Refresh online status when changes.
this.onlineSubscription = Network.instance.onChange().subscribe(() => {
// Execute the callback in the Angular zone, so change detection doesn't stop working.
NgZone.instance.run(() => {
this.isOnline = CoreApp.instance.isOnline();
});
});
}
/**
* Component being initialized.
*/
async ngOnInit(): Promise<void> {
super.ngOnInit();
this.hasOffline = false;
this.syncIcon = 'spinner';
this.moduleName = CoreCourse.instance.translateModuleName(this.moduleName || '');
if (this.syncEventName) {
// Refresh data if this discussion is synchronized automatically.
this.syncObserver = CoreEvents.on(this.syncEventName, (data) => {
this.autoSyncEventReceived(data);
}, this.siteId);
}
}
/**
* Compares sync event data with current data to check if refresh content is needed.
*
* @param syncEventData Data received on sync observer.
* @return True if refresh is needed, false otherwise.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
protected isRefreshSyncNeeded(syncEventData: unknown): boolean {
return false;
}
/**
* An autosync event has been received, check if refresh is needed and update the view.
*
* @param syncEventData Data receiven on sync observer.
*/
protected autoSyncEventReceived(syncEventData: unknown): void {
if (this.isRefreshSyncNeeded(syncEventData)) {
// Refresh the data.
this.showLoadingAndRefresh(false);
}
}
/**
* Perform the refresh content function.
*
* @param sync If the refresh needs syncing.
* @param showErrors Wether to show errors to the user or hide them.
* @return Resolved when done.
*/
protected async refreshContent(sync: boolean = false, showErrors: boolean = false): Promise<void> {
if (!this.module) {
// This can happen if course format changes from single activity to weekly/topics.
return;
}
this.refreshIcon = 'spinner';
this.syncIcon = 'spinner';
try {
await CoreUtils.instance.ignoreErrors(this.invalidateContent());
await this.loadContent(true, sync, showErrors);
} finally {
this.refreshIcon = 'fas-redo';
this.syncIcon = 'fas-sync';
}
}
/**
* Show loading and perform the load content function.
*
* @param sync If the fetch needs syncing.
* @param showErrors Wether to show errors to the user or hide them.
* @return Resolved when done.
*/
protected async showLoadingAndFetch(sync: boolean = false, showErrors: boolean = false): Promise<void> {
this.refreshIcon = 'spinner';
this.syncIcon = 'spinner';
this.loaded = false;
this.content?.scrollToTop();
try {
await this.loadContent(false, sync, showErrors);
} finally {
this.refreshIcon = 'fas-redo';
this.syncIcon = 'fas-sync';
}
}
/**
* Show loading and perform the refresh content function.
*
* @param sync If the refresh needs syncing.
* @param showErrors Wether to show errors to the user or hide them.
* @return Resolved when done.
*/
protected showLoadingAndRefresh(sync: boolean = false, showErrors: boolean = false): Promise<void> {
this.refreshIcon = 'spinner';
this.syncIcon = 'spinner';
this.loaded = false;
this.content?.scrollToTop();
return this.refreshContent(sync, showErrors);
}
/**
* Download the component contents.
*
* @param refresh Whether we're refreshing data.
* @param sync If the refresh needs syncing.
* @param showErrors Wether to show errors to the user or hide them.
* @return Promise resolved when done.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> {
return;
}
/**
* Loads the component contents and shows the corresponding error.
*
* @param refresh Whether we're refreshing data.
* @param sync If the refresh needs syncing.
* @param showErrors Wether to show errors to the user or hide them.
* @return Promise resolved when done.
*/
protected async loadContent(refresh?: boolean, sync: boolean = false, showErrors: boolean = false): Promise<void> {
this.isOnline = CoreApp.instance.isOnline();
if (!this.module) {
// This can happen if course format changes from single activity to weekly/topics.
return;
}
try {
await this.fetchContent(refresh, sync, showErrors);
} catch (error) {
if (!refresh) {
// Some call failed, retry without using cache since it might be a new activity.
return await this.refreshContent(sync);
}
CoreDomUtils.instance.showErrorModalDefault(error, this.fetchContentDefaultError, true);
} finally {
this.loaded = true;
this.refreshIcon = 'fas-redo';
this.syncIcon = 'fas-sync';
}
}
/**
* Performs the sync of the activity.
*
* @return Promise resolved when done.
*/
protected async sync(): Promise<unknown> {
return {};
}
/**
* Checks if sync has succeed from result sync data.
*
* @param result Data returned on the sync function.
* @return If suceed or not.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
protected hasSyncSucceed(result: unknown): boolean {
return true;
}
/**
* Tries to synchronize the activity.
*
* @param showErrors If show errors to the user of hide them.
* @return Promise resolved with true if sync succeed, or false if failed.
*/
protected async syncActivity(showErrors: boolean = false): Promise<boolean> {
try {
const result = <{warnings?: CoreWSExternalWarning[]}> await this.sync();
if (result?.warnings?.length) {
CoreDomUtils.instance.showErrorModal(result.warnings[0]);
}
return this.hasSyncSucceed(result);
} catch (error) {
if (showErrors) {
CoreDomUtils.instance.showErrorModalDefault(error, 'core.errorsync', true);
}
return false;
}
}
/**
* Component being destroyed.
*/
ngOnDestroy(): void {
super.ngOnDestroy();
this.onlineSubscription?.unsubscribe();
this.syncObserver?.off();
}
}

View File

@ -0,0 +1,412 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { CoreConstants } from '@/core/constants';
import { OnInit, OnDestroy, Input, Output, EventEmitter, Component, Optional, Inject } from '@angular/core';
import { IonRefresher } from '@ionic/angular';
import { CoreApp } from '@services/app';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreTextErrorObject, CoreTextUtils } from '@services/utils/text';
import { CoreUtils } from '@services/utils/utils';
import { Translate } from '@singletons';
import { CoreEventObserver, CoreEventPackageStatusChanged, CoreEvents } from '@singletons/events';
import { CoreLogger } from '@singletons/logger';
import { CoreCourseContentsPage } from '../pages/contents/contents';
import { CoreCourse } from '../services/course';
import { CoreCourseHelper, CoreCourseModule } from '../services/course-helper';
import { CoreCourseModuleDelegate, CoreCourseModuleMainComponent } from '../services/module-delegate';
import { CoreCourseModulePrefetchDelegate } from '../services/module-prefetch-delegate';
/**
* Result of a resource download.
*/
export type CoreCourseResourceDownloadResult = {
failed?: boolean; // Whether the download has failed.
error?: string | CoreTextErrorObject; // The error in case it failed.
};
/**
* Template class to easily create CoreCourseModuleMainComponent of resources (or activities without syncing).
*/
@Component({
template: '',
})
export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, CoreCourseModuleMainComponent {
@Input() module?: CoreCourseModule; // The module of the component.
@Input() courseId?: number; // Course ID the component belongs to.
@Output() dataRetrieved = new EventEmitter<unknown>(); // Called to notify changes the index page from the main component.
loaded = false; // If the component has been loaded.
component?: string; // Component name.
componentId?: number; // Component ID.
blog?: boolean; // If blog is avalaible.
// Data for context menu.
externalUrl?: string; // External URL to open in browser.
description?: string; // Module description.
refreshIcon = 'spinner'; // Refresh icon, normally spinner or refresh.
prefetchStatusIcon?: string; // Used when calling fillContextMenu.
prefetchStatus?: string; // Used when calling fillContextMenu.
prefetchText?: string; // Used when calling fillContextMenu.
size?: string; // Used when calling fillContextMenu.
isDestroyed?: boolean; // Whether the component is destroyed, used when calling fillContextMenu.
contextMenuStatusObserver?: CoreEventObserver; // Observer of package status, used when calling fillContextMenu.
contextFileStatusObserver?: CoreEventObserver; // Observer of file status, used when calling fillContextMenu.
protected fetchContentDefaultError = 'core.course.errorgetmodule'; // Default error to show when loading contents.
protected isCurrentView?: boolean; // Whether the component is in the current view.
protected siteId?: string; // Current Site ID.
protected statusObserver?: CoreEventObserver; // Observer of package status. Only if setStatusListener is called.
protected currentStatus?: string; // The current status of the module. Only if setStatusListener is called.
protected logger: CoreLogger;
constructor(
@Optional() @Inject('') loggerName: string = 'CoreCourseModuleMainResourceComponent',
protected courseContentsPage?: CoreCourseContentsPage,
) {
this.logger = CoreLogger.getInstance(loggerName);
}
/**
* Component being initialized.
*/
async ngOnInit(): Promise<void> {
this.siteId = CoreSites.instance.getCurrentSiteId();
this.description = this.module?.description;
this.componentId = this.module?.id;
this.externalUrl = this.module?.url;
this.courseId = this.courseId || this.module?.course;
// @todo this.blog = await this.blogProvider.isPluginEnabled();
}
/**
* Refresh the data.
*
* @param refresher Refresher.
* @param done Function to call when done.
* @param showErrors If show errors to the user of hide them.
* @return Promise resolved when done.
*/
async doRefresh(refresher?: CustomEvent<IonRefresher>, done?: () => void, showErrors: boolean = false): Promise<void> {
if (!this.loaded || !this.module) {
return;
}
// If it's a single activity course and the refresher is displayed within the component,
// call doRefresh on the section page to refresh the course data.
if (this.courseContentsPage && !CoreCourseModuleDelegate.instance.displayRefresherInSingleActivity(this.module.modname)) {
await CoreUtils.instance.ignoreErrors(this.courseContentsPage.doRefresh());
}
await CoreUtils.instance.ignoreErrors(this.refreshContent(true, showErrors));
refresher?.detail.complete();
done && done();
}
/**
* Perform the refresh content function.
*
* @param sync If the refresh needs syncing.
* @param showErrors Wether to show errors to the user or hide them.
* @return Resolved when done.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
protected async refreshContent(sync: boolean = false, showErrors: boolean = false): Promise<void> {
if (!this.module) {
// This can happen if course format changes from single activity to weekly/topics.
return;
}
this.refreshIcon = 'spinner';
try {
await CoreUtils.instance.ignoreErrors(this.invalidateContent());
await this.loadContent(true);
} finally {
this.refreshIcon = 'fas-redo';
}
}
/**
* Perform the invalidate content function.
*
* @return Resolved when done.
*/
protected async invalidateContent(): Promise<void> {
return;
}
/**
* Download the component contents.
*
* @param refresh Whether we're refreshing data.
* @return Promise resolved when done.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
protected async fetchContent(refresh?: boolean): Promise<void> {
return;
}
/**
* Loads the component contents and shows the corresponding error.
*
* @param refresh Whether we're refreshing data.
* @return Promise resolved when done.
*/
protected async loadContent(refresh?: boolean): Promise<void> {
if (!this.module) {
// This can happen if course format changes from single activity to weekly/topics.
return;
}
try {
await this.fetchContent(refresh);
} catch (error) {
CoreDomUtils.instance.showErrorModalDefault(error, this.fetchContentDefaultError, true);
} finally {
this.loaded = true;
this.refreshIcon = 'fas-redo';
}
}
/**
* Fill the context menu options
*/
protected fillContextMenu(refresh: boolean = false): void {
if (!this.module) {
return;
}
// All data obtained, now fill the context menu.
CoreCourseHelper.instance.fillContextMenu(this, this.module, this.courseId!, refresh, this.component);
}
/**
* Check if the module is prefetched or being prefetched. To make it faster, just use the data calculated by fillContextMenu.
* This means that you need to call fillContextMenu to make this work.
*/
protected isPrefetched(): boolean {
return this.prefetchStatus != CoreConstants.NOT_DOWNLOADABLE && this.prefetchStatus != CoreConstants.NOT_DOWNLOADED;
}
/**
* Expand the description.
*/
expandDescription(): void {
CoreTextUtils.instance.viewText(Translate.instance.instant('core.description'), this.description!, {
component: this.component,
componentId: this.module?.id,
filter: true,
contextLevel: 'module',
instanceId: this.module?.id,
courseId: this.courseId,
});
}
/**
* Go to blog posts.
*/
async gotoBlog(): Promise<void> {
// @todo return this.linkHelper.goInSite(this.navCtrl, 'AddonBlogEntriesPage', { cmId: this.module.id });
}
/**
* Prefetch the module.
*
* @param done Function to call when done.
*/
prefetch(done?: () => void): void {
if (!this.module) {
return;
}
CoreCourseHelper.instance.contextMenuPrefetch(this, this.module, this.courseId!, done);
}
/**
* Confirm and remove downloaded files.
*
* @param done Function to call when done.
*/
removeFiles(done?: () => void): void {
if (!this.module) {
return;
}
if (this.prefetchStatus == CoreConstants.DOWNLOADING) {
CoreDomUtils.instance.showAlertTranslated(undefined, 'core.course.cannotdeletewhiledownloading');
return;
}
CoreCourseHelper.instance.confirmAndRemoveFiles(this.module, this.courseId!, done);
}
/**
* Get message about an error occurred while downloading files.
*
* @param error The specific error.
* @param multiLine Whether to put each message in a different paragraph or in a single line.
*/
protected getErrorDownloadingSomeFilesMessage(error: string | CoreTextErrorObject, multiLine?: boolean): string {
if (multiLine) {
return CoreTextUtils.instance.buildSeveralParagraphsMessage([
Translate.instance.instant('core.errordownloadingsomefiles'),
error,
]);
} else {
error = CoreTextUtils.instance.getErrorMessageFromError(error) || error;
return Translate.instance.instant('core.errordownloadingsomefiles') + (error ? ' ' + error : '');
}
}
/**
* Show an error occurred while downloading files.
*
* @param error The specific error.
*/
protected showErrorDownloadingSomeFiles(error: string | CoreTextErrorObject): void {
CoreDomUtils.instance.showErrorModal(this.getErrorDownloadingSomeFilesMessage(error, true));
}
/**
* Displays some data based on the current status.
*
* @param status The current status.
* @param previousStatus The previous status. If not defined, there is no previous status.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
protected showStatus(status: string, previousStatus?: string): void {
// To be overridden.
}
/**
* Watch for changes on the status.
*
* @return Promise resolved when done.
*/
protected async setStatusListener(): Promise<void> {
if (typeof this.statusObserver != 'undefined' || !this.module) {
return;
}
// Listen for changes on this module status.
this.statusObserver = CoreEvents.on<CoreEventPackageStatusChanged>(CoreEvents.PACKAGE_STATUS_CHANGED, (data) => {
if (!this.module || data.componentId != this.module.id || data.component != this.component) {
return;
}
// The status has changed, update it.
const previousStatus = this.currentStatus;
this.currentStatus = data.status;
this.showStatus(this.currentStatus, previousStatus);
}, this.siteId);
// Also, get the current status.
const status = await CoreCourseModulePrefetchDelegate.instance.getModuleStatus(this.module, this.courseId!);
this.currentStatus = status;
this.showStatus(status);
}
/**
* Download a resource if needed.
* If the download call fails the promise won't be rejected, but the error will be included in the returned object.
* If module.contents cannot be loaded then the Promise will be rejected.
*
* @param refresh Whether we're refreshing data.
* @return Promise resolved when done.
*/
protected async downloadResourceIfNeeded(
refresh?: boolean,
contentsAlreadyLoaded?: boolean,
): Promise<CoreCourseResourceDownloadResult> {
const result: CoreCourseResourceDownloadResult = {
failed: false,
};
if (!this.module) {
return result;
}
// Get module status to determine if it needs to be downloaded.
await this.setStatusListener();
if (this.currentStatus != CoreConstants.DOWNLOADED) {
// Download content. This function also loads module contents if needed.
try {
await CoreCourseModulePrefetchDelegate.instance.downloadModule(this.module, this.courseId!);
// If we reach here it means the download process already loaded the contents, no need to do it again.
contentsAlreadyLoaded = true;
} catch (error) {
// Mark download as failed but go on since the main files could have been downloaded.
result.failed = true;
result.error = error;
}
}
if (!this.module.contents.length || (refresh && !contentsAlreadyLoaded)) {
// Try to load the contents.
const ignoreCache = refresh && CoreApp.instance.isOnline();
try {
await CoreCourse.instance.loadModuleContents(this.module, this.courseId, undefined, false, ignoreCache);
} catch (error) {
// Error loading contents. If we ignored cache, try to get the cached value.
if (ignoreCache && !this.module.contents) {
await CoreCourse.instance.loadModuleContents(this.module, this.courseId);
} else if (!this.module.contents) {
// Not able to load contents, throw the error.
throw error;
}
}
}
return result;
}
/**
* Component being destroyed.
*/
ngOnDestroy(): void {
this.isDestroyed = true;
this.contextMenuStatusObserver?.off();
this.contextFileStatusObserver?.off();
this.statusObserver?.off();
}
/**
* User entered the page that contains the component. This function should be called by the page that contains this component.
*/
ionViewDidEnter(): void {
this.isCurrentView = true;
}
/**
* User left the page that contains the component. This function should be called by the page that contains this component.
*/
ionViewDidLeave(): void {
this.isCurrentView = false;
}
}

View File

@ -0,0 +1,344 @@
// (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 { CoreFilepool } from '@services/filepool';
import { CoreFileSizeSum, CorePluginFileDelegate } from '@services/plugin-file-delegate';
import { CoreSites } from '@services/sites';
import { CoreWSExternalFile } from '@services/ws';
import { CoreCourse, CoreCourseModuleContentFile, CoreCourseWSModule } from '../services/course';
import { CoreCourseModulePrefetchHandler } from '../services/module-prefetch-delegate';
/**
* Base prefetch handler to be registered in CoreCourseModulePrefetchDelegate. Prefetch handlers should inherit either
* from CoreCourseModuleActivityPrefetchHandlerBase or CoreCourseModuleResourcePrefetchHandlerBase, depending on whether
* they are an activity or a resource. It's not recommended to inherit from this class directly.
*/
export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePrefetchHandler {
/**
* Name of the handler.
*/
name = 'CoreCourseModulePrefetchHandler';
/**
* Name of the module. It should match the "modname" of the module returned in core_course_get_contents.
*/
modName = 'default';
/**
* The handler's component.
*/
component = 'core_module';
/**
* The RegExp to check updates. If a module has an update whose name matches this RegExp, the module will be marked
* as outdated. This RegExp is ignored if hasUpdates function is defined.
*/
updatesNames = /^.*files$/;
/**
* If true, this module will be ignored when determining the status of a list of modules. The module will
* still be downloaded when downloading the section/course, it only affects whether the button should be displayed.
*/
skipListStatus = false;
/**
* List of download promises to prevent downloading the module twice at the same time.
*/
protected downloadPromises: { [s: string]: { [s: string]: Promise<void> } } = {};
/**
* Add an ongoing download to the downloadPromises list. When the promise finishes it will be removed.
*
* @param id Unique identifier per component.
* @param promise Promise to add.
* @param siteId Site ID. If not defined, current site.
* @return Promise of the current download.
*/
async addOngoingDownload(id: number, promise: Promise<void>, siteId?: string): Promise<void> {
siteId = siteId || CoreSites.instance.getCurrentSiteId();
const uniqueId = this.getUniqueId(id);
if (!this.downloadPromises[siteId]) {
this.downloadPromises[siteId] = {};
}
this.downloadPromises[siteId][uniqueId] = promise;
try {
return await this.downloadPromises[siteId][uniqueId];
} finally {
delete this.downloadPromises[siteId][uniqueId];
}
}
/**
* Download the module.
*
* @param module The module object returned by WS.
* @param courseId Course ID.
* @param dirPath Path of the directory where to store all the content files.
* @return Promise resolved when all content is downloaded.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async download(module: CoreCourseWSModule, courseId: number, dirPath?: string): Promise<void> {
// To be overridden.
return;
}
/**
* Returns a list of content files that can be downloaded.
*
* @param module The module object returned by WS.
* @return List of files.
*/
getContentDownloadableFiles(module: CoreCourseWSModule): CoreCourseModuleContentFile[] {
if (!module.contents?.length) {
return [];
}
return module.contents.filter((content) => this.isFileDownloadable(content));
}
/**
* Get the download size of a module.
*
* @param module Module.
* @param courseId Course ID the module belongs to.
* @param single True if we're downloading a single module, false if we're downloading a whole section.
* @return Promise resolved with the size.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async getDownloadSize(module: CoreCourseWSModule, courseId: number, single?: boolean): Promise<CoreFileSizeSum> {
try {
const files = await this.getFiles(module, courseId);
return await CorePluginFileDelegate.instance.getFilesDownloadSize(files);
} catch {
return { size: -1, total: false };
}
}
/**
* Get the downloaded size of a module. If not defined, we'll use getFiles to calculate it (it can be slow).
*
* @param module Module.
* @param courseId Course ID the module belongs to.
* @return Size, or promise resolved with the size.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async getDownloadedSize(module: CoreCourseWSModule, courseId: number): Promise<number> {
const siteId = CoreSites.instance.getCurrentSiteId();
return CoreFilepool.instance.getFilesSizeByComponent(siteId, this.component, module.id);
}
/**
* Get list of files. If not defined, we'll assume they're in module.contents.
*
* @param module Module.
* @param courseId Course ID the module belongs to.
* @param single True if we're downloading a single module, false if we're downloading a whole section.
* @return Promise resolved with the list of files.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async getFiles(module: CoreCourseWSModule, courseId: number, single?: boolean): Promise<CoreWSExternalFile[]> {
// To be overridden.
return [];
}
/**
* Returns module intro files.
*
* @param module The module object returned by WS.
* @param courseId Course ID.
* @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down).
* @return Promise resolved with list of intro files.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async getIntroFiles(module: CoreCourseWSModule, courseId: number, ignoreCache?: boolean): Promise<CoreWSExternalFile[]> {
return this.getIntroFilesFromInstance(module);
}
/**
* Returns module intro files from instance.
*
* @param module The module object returned by WS.
* @param instance The instance to get the intro files (book, assign, ...). If not defined, module will be used.
* @return List of intro files.
*/
getIntroFilesFromInstance(module: CoreCourseWSModule, instance?: ModuleInstance): CoreWSExternalFile[] {
if (instance) {
if (typeof instance.introfiles != 'undefined') {
return instance.introfiles;
} else if (instance.intro) {
return CoreFilepool.instance.extractDownloadableFilesFromHtmlAsFakeFileObjects(instance.intro);
}
}
if (module.description) {
return CoreFilepool.instance.extractDownloadableFilesFromHtmlAsFakeFileObjects(module.description);
}
return [];
}
/**
* If there's an ongoing download for a certain identifier return it.
*
* @param id Unique identifier per component.
* @param siteId Site ID. If not defined, current site.
* @return Promise of the current download.
*/
async getOngoingDownload(id: number, siteId?: string): Promise<void> {
siteId = siteId || CoreSites.instance.getCurrentSiteId();
if (this.isDownloading(id, siteId)) {
// There's already a download ongoing, return the promise.
return this.downloadPromises[siteId][this.getUniqueId(id)];
}
}
/**
* Create unique identifier using component and id.
*
* @param id Unique ID inside component.
* @return Unique ID.
*/
getUniqueId(id: number): string {
return this.component + '#' + id;
}
/**
* Invalidate the prefetched content.
*
* @param moduleId The module ID.
* @param courseId The course ID the module belongs to.
* @return Promise resolved when the data is invalidated.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async invalidateContent(moduleId: number, courseId: number): Promise<void> {
// To be overridden.
return;
}
/**
* Invalidate WS calls needed to determine module status (usually, to check if module is downloadable).
* It doesn't need to invalidate check updates. It should NOT invalidate files nor all the prefetched data.
*
* @param module Module.
* @param courseId Course ID the module belongs to.
* @return Promise resolved when invalidated.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
invalidateModule(module: CoreCourseWSModule, courseId: number): Promise<void> {
return CoreCourse.instance.invalidateModule(module.id);
}
/**
* Check if a module can be downloaded. If the function is not defined, we assume that all modules are downloadable.
*
* @param module Module.
* @param courseId Course ID the module belongs to.
* @return Whether the module can be downloaded. The promise should never be rejected.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async isDownloadable(module: CoreCourseWSModule, courseId: number): Promise<boolean> {
// By default, mark all instances as downloadable.
return true;
}
/**
* Check if a there's an ongoing download for the given identifier.
*
* @param id Unique identifier per component.
* @param siteId Site ID. If not defined, current site.
* @return True if downloading, false otherwise.
*/
isDownloading(id: number, siteId?: string): boolean {
siteId = siteId || CoreSites.instance.getCurrentSiteId();
return !!(this.downloadPromises[siteId] && this.downloadPromises[siteId][this.getUniqueId(id)]);
}
/**
* Whether or not the handler is enabled on a site level.
*
* @return A boolean, or a promise resolved with a boolean, indicating if the handler is enabled.
*/
async isEnabled(): Promise<boolean> {
return true;
}
/**
* Check if a file is downloadable.
*
* @param file File to check.
* @return Whether the file is downloadable.
*/
isFileDownloadable(file: CoreCourseModuleContentFile): boolean {
return file.type === 'file';
}
/**
* Load module contents into module.contents if they aren't loaded already.
*
* @param module Module to load the contents.
* @param courseId The course ID. Recommended to speed up the process and minimize data usage.
* @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down).
* @return Promise resolved when loaded.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async loadContents(module: CoreCourseWSModule, courseId: number, ignoreCache?: boolean): Promise<void> {
// To be overridden.
return;
}
/**
* Prefetch a module.
*
* @param module Module.
* @param courseId Course ID the module belongs to.
* @param single True if we're downloading a single module, false if we're downloading a whole section.
* @param dirPath Path of the directory where to store all the content files.
* @return Promise resolved when done.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async prefetch(module: CoreCourseWSModule, courseId?: number, single?: boolean, dirPath?: string): Promise<void> {
// To be overridden.
return;
}
/**
* Remove module downloaded files. If not defined, we'll use getFiles to remove them (slow).
*
* @param module Module.
* @param courseId Course ID the module belongs to.
* @return Promise resolved when done.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
removeFiles(module: CoreCourseWSModule, courseId: number): Promise<void> {
return CoreFilepool.instance.removeFilesByComponent(CoreSites.instance.getCurrentSiteId(), this.component, module.id);
}
}
/**
* Properties a module instance should have to be able to retrieve its intro files.
*/
type ModuleInstance = {
introfiles?: CoreWSExternalFile[];
intro?: string;
};

View File

@ -0,0 +1,203 @@
// (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 { CoreError } from '@classes/errors/error';
import { CoreNetworkError } from '@classes/errors/network-error';
import { CoreFilterHelper } from '@features/filter/services/filter-helper';
import { CoreApp } from '@services/app';
import { CoreFilepool } from '@services/filepool';
import { CoreSites } from '@services/sites';
import { CoreWSExternalFile } from '@services/ws';
import { CoreCourse, CoreCourseWSModule } from '../services/course';
import { CoreCourseModulePrefetchHandlerBase } from './module-prefetch-handler';
/**
* Base prefetch handler to be registered in CoreCourseModulePrefetchDelegate. It is useful to minimize the amount of
* functions that handlers need to implement. It also provides some helper features like preventing a module to be
* downloaded twice at the same time.
*
* If your handler inherits from this service, you just need to override the functions that you want to change.
*
* This class should be used for RESOURCES whose main purpose is downloading files present in module.contents.
*/
export class CoreCourseResourcePrefetchHandlerBase extends CoreCourseModulePrefetchHandlerBase {
/**
* Download the module.
*
* @param module The module object returned by WS.
* @param courseId Course ID.
* @param dirPath Path of the directory where to store all the content files.
* @return Promise resolved when all content is downloaded.
*/
download(module: CoreCourseWSModule, courseId: number, dirPath?: string): Promise<void> {
return this.downloadOrPrefetch(module, courseId, false, dirPath);
}
/**
* Download or prefetch the content.
*
* @param module The module object returned by WS.
* @param courseId Course ID.
* @param prefetch True to prefetch, false to download right away.
* @param dirPath Path of the directory where to store all the content files. This is to keep the files
* relative paths and make the package work in an iframe. Undefined to download the files
* in the filepool root folder.
* @return Promise resolved when all content is downloaded.
*/
async downloadOrPrefetch(module: CoreCourseWSModule, courseId: number, prefetch?: boolean, dirPath?: string): Promise<void> {
if (!CoreApp.instance.isOnline()) {
// Cannot download in offline.
throw new CoreNetworkError();
}
const siteId = CoreSites.instance.getCurrentSiteId();
if (this.isDownloading(module.id, siteId)) {
// There's already a download ongoing for this module, return the promise.
return this.getOngoingDownload(module.id, siteId);
}
// Get module info to be able to handle links.
const prefetchPromise = this.performDownloadOrPrefetch(siteId, module, courseId, !!prefetch, dirPath);
return this.addOngoingDownload(module.id, prefetchPromise, siteId);
}
/**
* Download or prefetch the content.
*
* @param module The module object returned by WS.
* @param courseId Course ID.
* @param prefetch True to prefetch, false to download right away.
* @param dirPath Path of the directory where to store all the content files.
* @return Promise resolved when all content is downloaded.
*/
protected async performDownloadOrPrefetch(
siteId: string,
module: CoreCourseWSModule,
courseId: number,
prefetch: boolean,
dirPath?: string,
): Promise<void> {
// Get module info to be able to handle links.
await CoreCourse.instance.getModuleBasicInfo(module.id, siteId);
// Load module contents (ignore cache so we always have the latest data).
await this.loadContents(module, courseId, true);
// Get the intro files.
const introFiles = await this.getIntroFiles(module, courseId, true);
const contentFiles = this.getContentDownloadableFiles(module);
const promises: Promise<unknown>[] = [];
if (dirPath) {
// Download intro files in filepool root folder.
promises.push(
CoreFilepool.instance.downloadOrPrefetchFiles(siteId, introFiles, prefetch, false, this.component, module.id),
);
// Download content files inside dirPath.
promises.push(CoreFilepool.instance.downloadOrPrefetchPackage(
siteId,
contentFiles,
prefetch,
this.component,
module.id,
undefined,
dirPath,
));
} else {
// No dirPath, download everything in filepool root folder.
promises.push(CoreFilepool.instance.downloadOrPrefetchPackage(
siteId,
introFiles.concat(contentFiles),
prefetch,
this.component,
module.id,
));
}
promises.push(CoreFilterHelper.instance.getFilters('module', module.id, { courseId }));
await Promise.all(promises);
}
/**
* Get list of files. If not defined, we'll assume they're in module.contents.
*
* @param module Module.
* @param courseId Course ID the module belongs to.
* @param single True if we're downloading a single module, false if we're downloading a whole section.
* @return Promise resolved with the list of files.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async getFiles(module: CoreCourseWSModule, courseId: number, single?: boolean): Promise<CoreWSExternalFile[]> {
// Load module contents if needed.
await this.loadContents(module, courseId);
const files = await this.getIntroFiles(module, courseId);
return files.concat(this.getContentDownloadableFiles(module));
}
/**
* Invalidate the prefetched content.
*
* @param moduleId The module ID.
* @param courseId The course ID the module belongs to.
* @return Promise resolved when the data is invalidated.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async invalidateContent(moduleId: number, courseId: number): Promise<void> {
const siteId = CoreSites.instance.getCurrentSiteId();
await Promise.all([
CoreCourse.instance.invalidateModule(moduleId),
CoreFilepool.instance.invalidateFilesByComponent(siteId, this.component, moduleId),
]);
}
/**
* Load module contents into module.contents if they aren't loaded already.
*
* @param module Module to load the contents.
* @param courseId The course ID. Recommended to speed up the process and minimize data usage.
* @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down).
* @return Promise resolved when loaded.
*/
loadContents(module: CoreCourseWSModule, courseId: number, ignoreCache?: boolean): Promise<void> {
return CoreCourse.instance.loadModuleContents(module, courseId, undefined, false, ignoreCache);
}
/**
* Prefetch a module.
*
* @param module Module.
* @param courseId Course ID the module belongs to.
* @param single True if we're downloading a single module, false if we're downloading a whole section.
* @param dirPath Path of the directory where to store all the content files.
* @return Promise resolved when done.
*/
prefetch(module: CoreCourseWSModule, courseId?: number, single?: boolean, dirPath?: string): Promise<void> {
courseId = courseId || module.course;
if (!courseId) {
throw new CoreError('Course ID not supplied.');
}
return this.downloadOrPrefetch(module, courseId, true, dirPath);
}
}

View File

@ -1332,17 +1332,17 @@ export class CoreCourseHelperProvider {
moduleInfo.status = results[1];
switch (results[1]) {
case CoreConstants.NOT_DOWNLOADED:
moduleInfo.statusIcon = 'cloud-download';
moduleInfo.statusIcon = 'fas-cloud-download-alt';
break;
case CoreConstants.DOWNLOADING:
moduleInfo.statusIcon = 'spinner';
break;
case CoreConstants.OUTDATED:
moduleInfo.statusIcon = 'refresh';
moduleInfo.statusIcon = 'fas-redo';
break;
case CoreConstants.DOWNLOADED:
if (!CoreCourseModulePrefetchDelegate.instance.canCheckUpdates()) {
moduleInfo.statusIcon = 'refresh';
moduleInfo.statusIcon = 'fas-redo';
}
break;
default:

View File

@ -797,7 +797,7 @@ export class CoreFilepoolProvider {
* @param onProgress Function to call on progress.
* @return Promise resolved when the package is downloaded.
*/
protected downloadOrPrefetchPackage(
downloadOrPrefetchPackage(
siteId: string,
fileList: CoreWSExternalFile[],
prefetch: boolean,

View File

@ -20,6 +20,7 @@ import { CoreWSExternalFile } from '@services/ws';
import { CoreConstants } from '@/core/constants';
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
import { makeSingleton } from '@singletons';
import { CoreSites } from './sites';
/**
* Delegate to register pluginfile information handlers.
@ -133,11 +134,13 @@ export class CorePluginFileDelegateService extends CoreDelegate<CorePluginFileHa
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with file size and a boolean to indicate if it is the total size or only partial.
*/
async getFilesDownloadSize(files: CoreWSExternalFile[], siteId: string): Promise<CoreFileSizeSum> {
async getFilesDownloadSize(files: CoreWSExternalFile[], siteId?: string): Promise<CoreFileSizeSum> {
siteId = siteId || CoreSites.instance.getCurrentSiteId();
const filteredFiles = <CoreWSExternalFile[]>[];
await Promise.all(files.map(async (file) => {
const state = await CoreFilepool.instance.getFileStateByUrl(siteId, file.fileurl, file.timemodified);
const state = await CoreFilepool.instance.getFileStateByUrl(siteId!, file.fileurl, file.timemodified);
if (state != CoreConstants.DOWNLOADED && state != CoreConstants.NOT_DOWNLOADABLE) {
filteredFiles.push(file);

View File

@ -1181,7 +1181,7 @@ export class CoreDomUtilsProvider {
* @return Promise resolved with the alert modal.
*/
async showAlert(
header: string,
header: string | undefined,
message: string,
buttonText?: string,
autocloseTime?: number,
@ -1263,12 +1263,17 @@ export class CoreDomUtilsProvider {
* @param autocloseTime Number of milliseconds to wait to close the modal. If not defined, modal won't be closed.
* @return Promise resolved with the alert modal.
*/
showAlertTranslated(title: string, message: string, buttonText?: string, autocloseTime?: number): Promise<HTMLIonAlertElement> {
title = title ? Translate.instance.instant(title) : title;
showAlertTranslated(
header: string | undefined,
message: string,
buttonText?: string,
autocloseTime?: number,
): Promise<HTMLIonAlertElement> {
header = header ? Translate.instance.instant(header) : header;
message = message ? Translate.instance.instant(message) : message;
buttonText = buttonText ? Translate.instance.instant(buttonText) : buttonText;
return this.showAlert(title, message, buttonText, autocloseTime);
return this.showAlert(header, message, buttonText, autocloseTime);
}
/**