diff --git a/src/addon/mod/book/components/index/index.html b/src/addon/mod/book/components/index/index.html
index dcfef3a15..5463e70a5 100644
--- a/src/addon/mod/book/components/index/index.html
+++ b/src/addon/mod/book/components/index/index.html
@@ -7,8 +7,8 @@
-
-
+
+
diff --git a/src/addon/mod/book/components/index/index.ts b/src/addon/mod/book/components/index/index.ts
index 64b9ea7a0..48e2592ed 100644
--- a/src/addon/mod/book/components/index/index.ts
+++ b/src/addon/mod/book/components/index/index.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import { Component, OnInit, Input, Output, EventEmitter, Optional } from '@angular/core';
+import { Component, OnInit, OnDestroy, Input, Output, EventEmitter, Optional } from '@angular/core';
import { NavParams, NavController, Content, PopoverController } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { CoreAppProvider } from '../../../../../providers/app';
@@ -32,24 +32,31 @@ import { AddonModBookTocPopoverComponent } from '../../components/toc-popover/to
selector: 'addon-mod-book-index',
templateUrl: 'index.html',
})
-export class AddonModBookIndexComponent implements OnInit, CoreCourseModuleMainComponent {
+export class AddonModBookIndexComponent implements OnInit, OnDestroy, CoreCourseModuleMainComponent {
@Input() module: any; // The module of the book.
@Input() courseId: number; // Course ID the book belongs to.
@Output() bookRetrieved?: EventEmitter;
- externalUrl: string;
- description: string;
loaded: boolean;
component = AddonModBookProvider.COMPONENT;
componentId: number;
chapterContent: string;
previousChapter: string;
nextChapter: string;
+
+ // Data for context menu.
+ externalUrl: string;
+ description: string;
refreshIcon: string;
+ prefetchStatusIcon: string;
+ prefetchText: string;
+ size: string;
protected chapters: AddonModBookTocChapter[];
protected currentChapter: string;
protected contentsMap: AddonModBookContentsMap;
+ protected isDestroyed = false;
+ protected statusObserver;
constructor(private bookProvider: AddonModBookProvider, private courseProvider: CoreCourseProvider,
private domUtils: CoreDomUtilsProvider, private appProvider: CoreAppProvider, private textUtils: CoreTextUtilsProvider,
@@ -136,7 +143,7 @@ export class AddonModBookIndexComponent implements OnInit, CoreCourseModuleMainC
* Prefetch the module.
*/
prefetch(): void {
- // @todo this.courseHelper.contextMenuPrefetch($scope, this.module, this.courseId);
+ this.courseHelper.contextMenuPrefetch(this, this.module, this.courseId);
}
/**
@@ -192,7 +199,7 @@ export class AddonModBookIndexComponent implements OnInit, CoreCourseModuleMainC
}
// All data obtained, now fill the context menu.
- // @todo this.courseHelper.fillContextMenu($scope, module, courseId, refresh, mmaModBookComponent);
+ this.courseHelper.fillContextMenu(this, this.module, this.courseId, refresh, this.component);
}).catch(() => {
// Ignore errors, they're handled inside the loadChapter function.
});
@@ -235,4 +242,9 @@ export class AddonModBookIndexComponent implements OnInit, CoreCourseModuleMainC
this.refreshIcon = 'refresh';
});
}
+
+ ngOnDestroy(): void {
+ this.isDestroyed = true;
+ this.statusObserver && this.statusObserver.off();
+ }
}
diff --git a/src/components/context-menu/context-menu.scss b/src/components/context-menu/context-menu.scss
new file mode 100644
index 000000000..9730ca1e4
--- /dev/null
+++ b/src/components/context-menu/context-menu.scss
@@ -0,0 +1,6 @@
+core-context-menu-popover {
+ .item-md ion-icon[item-start] + .item-inner,
+ .item-md ion-icon[item-start] + .item-input {
+ @include margin-horizontal(5px, null);
+ }
+}
diff --git a/src/core/course/providers/helper.ts b/src/core/course/providers/helper.ts
index d4b76e357..36fbc2433 100644
--- a/src/core/course/providers/helper.ts
+++ b/src/core/course/providers/helper.ts
@@ -15,6 +15,7 @@
import { Injectable } from '@angular/core';
import { NavController } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
+import { CoreEventsProvider } from '../../../providers/events';
import { CoreFilepoolProvider } from '../../../providers/filepool';
import { CoreSitesProvider } from '../../../providers/sites';
import { CoreDomUtilsProvider } from '../../../providers/utils/dom';
@@ -114,7 +115,8 @@ export class CoreCourseHelperProvider {
private filepoolProvider: CoreFilepoolProvider, private sitesProvider: CoreSitesProvider,
private textUtils: CoreTextUtilsProvider, private timeUtils: CoreTimeUtilsProvider,
private utils: CoreUtilsProvider, private translate: TranslateService, private loginHelper: CoreLoginHelperProvider,
- private courseOptionsDelegate: CoreCourseOptionsDelegate, private siteHomeProvider: CoreSiteHomeProvider) { }
+ private courseOptionsDelegate: CoreCourseOptionsDelegate, private siteHomeProvider: CoreSiteHomeProvider,
+ private eventsProvider: CoreEventsProvider) { }
/**
* This function treats every module on the sections provided to load the handler data, treat completion
@@ -358,8 +360,12 @@ export class CoreCourseHelperProvider {
* @return {Promise} Promise resolved when done.
*/
confirmAndRemoveFiles(module: any, courseId: number): Promise {
- return this.domUtils.showConfirm(this.translate.instant('course.confirmdeletemodulefiles')).then(() => {
+ return this.domUtils.showConfirm(this.translate.instant('core.course.confirmdeletemodulefiles')).then(() => {
return this.prefetchDelegate.removeModuleFiles(module, courseId);
+ }).catch((error) => {
+ if (error) {
+ this.domUtils.showErrorModal(error);
+ }
});
}
@@ -405,6 +411,39 @@ export class CoreCourseHelperProvider {
});
}
+ /**
+ * Helper function to prefetch a module, showing a confirmation modal if the size is big.
+ * This function is meant to be called from a context menu option. It will also modify some data like the prefetch icon.
+ *
+ * @param {any} instance The component instance that has the context menu. It should have prefetchStatusIcon and isDestroyed.
+ * @param {any} module Module to be prefetched
+ * @param {number} courseId Course ID the module belongs to.
+ * @return {Promise} Promise resolved when done.
+ */
+ contextMenuPrefetch(instance: any, module: any, courseId: number): Promise {
+ const initialIcon = instance.prefetchStatusIcon;
+ let cancelled = false;
+
+ instance.prefetchStatusIcon = 'spinner'; // Show spinner since this operation might take a while.
+
+ // We need to call getDownloadSize, the package might have been updated.
+ return this.prefetchDelegate.getModuleDownloadSize(module, courseId, true).then((size) => {
+ return this.domUtils.confirmDownloadSize(size).catch(() => {
+ // User hasn't confirmed, stop.
+ cancelled = true;
+
+ return Promise.reject(null);
+ }).then(() => {
+ return this.prefetchDelegate.prefetchModule(module, courseId, true);
+ });
+ }).catch((error) => {
+ instance.prefetchStatusIcon = initialIcon;
+ if (!instance.isDestroyed && !cancelled) {
+ this.domUtils.showErrorModalDefault(error, 'core.errordownloading', true);
+ }
+ });
+ }
+
/**
* Determine the status of a list of courses.
*
@@ -431,6 +470,41 @@ export class CoreCourseHelperProvider {
});
}
+ /**
+ * Fill the Context Menu for a certain module.
+ *
+ * @param {any} instance The component instance that has the context menu.
+ * @param {any} module Module to be prefetched
+ * @param {number} courseId Course ID the module belongs to.
+ * @param {boolean} [invalidateCache] Invalidates the cache first.
+ * @param {string} [component] Component of the module.
+ * @return {Promise} Promise resolved when done.
+ */
+ fillContextMenu(instance: any, module: any, courseId: number, invalidateCache?: boolean, component?: string): Promise {
+ return this.getModulePrefetchInfo(module, courseId, invalidateCache, component).then((moduleInfo) => {
+ instance.size = moduleInfo.size > 0 ? moduleInfo.sizeReadable : 0;
+ instance.prefetchStatusIcon = moduleInfo.statusIcon;
+
+ if (moduleInfo.status != CoreConstants.NOT_DOWNLOADABLE) {
+ // Module is downloadable, get the text to display to prefetch.
+ if (moduleInfo.downloadTime > 0) {
+ instance.prefetchText = this.translate.instant('core.lastdownloaded') + ': ' + moduleInfo.downloadTimeReadable;
+ } else {
+ // Module not downloaded, show a default text.
+ instance.prefetchText = this.translate.instant('core.download');
+ }
+ }
+
+ if (typeof instance.statusObserver == 'undefined' && component) {
+ instance.statusObserver = this.eventsProvider.on(CoreEventsProvider.PACKAGE_STATUS_CHANGED, (data) => {
+ if (data.componentId == module.id && data.component == component) {
+ this.fillContextMenu(instance, module, courseId, false, component);
+ }
+ }, this.sitesProvider.getCurrentSiteId());
+ }
+ });
+ }
+
/**
* Get a course download promise (if any).
*
diff --git a/src/core/course/providers/module-prefetch-delegate.ts b/src/core/course/providers/module-prefetch-delegate.ts
index de95a6287..a80e9c74f 100644
--- a/src/core/course/providers/module-prefetch-delegate.ts
+++ b/src/core/course/providers/module-prefetch-delegate.ts
@@ -225,6 +225,11 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate {
super('CoreCourseModulePrefetchDelegate', loggerProvider, sitesProvider, eventsProvider);
this.sitesProvider.createTableFromSchema(this.checkUpdatesTableSchema);
+
+ eventsProvider.on(CoreEventsProvider.LOGOUT, this.clearStatusCache.bind(this));
+ eventsProvider.on(CoreEventsProvider.PACKAGE_STATUS_CHANGED, (data) => {
+ this.updateStatusCache(data.status, data.component, data.componentId);
+ }, this.sitesProvider.getCurrentSiteId());
}
/**
@@ -653,6 +658,8 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate {
promise;
if (!refresh && typeof status != 'undefined') {
+ this.storeCourseAndSection(packageId, courseId, sectionId);
+
return Promise.resolve(this.determineModuleStatus(module, status, canCheck));
}
@@ -664,7 +671,7 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate {
// Get the saved package status.
return this.filepoolProvider.getPackageStatus(siteId, component, module.id).then((currentStatus) => {
- status = handler.determineStatus ? handler.determineStatus(module, status, canCheck) : status;
+ status = handler.determineStatus ? handler.determineStatus(module, currentStatus, canCheck) : currentStatus;
if (status != CoreConstants.DOWNLOADED) {
return status;
}
@@ -696,7 +703,7 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate {
// Has updates, mark the module as outdated.
status = CoreConstants.OUTDATED;
- return this.filepoolProvider.storePackageStatus(siteId, component, module.id, status).catch(() => {
+ return this.filepoolProvider.storePackageStatus(siteId, status, component, module.id).catch(() => {
// Ignore errors.
}).then(() => {
return status;
@@ -710,13 +717,14 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate {
}, () => {
// Error getting updates, show the stored status.
updateStatus = false;
+ this.storeCourseAndSection(packageId, courseId, sectionId);
return currentStatus;
});
});
}).then((status) => {
if (updateStatus) {
- this.updateStatusCache(status, courseId, component, module.id, sectionId);
+ this.updateStatusCache(status, component, module.id, courseId, sectionId);
}
return this.determineModuleStatus(module, status, canCheck);
@@ -770,11 +778,6 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate {
promises.push(this.getModuleStatus(module, courseId, updates, refresh).then((modStatus) => {
if (modStatus != CoreConstants.NOT_DOWNLOADABLE) {
- if (sectionId && sectionId > 0) {
- // Store the section ID.
- this.statusCache.setValue(packageId, 'sectionId', sectionId);
- }
-
status = this.filepoolProvider.determinePackagesStatus(status, modStatus);
result[modStatus].push(module);
result.total++;
@@ -1123,7 +1126,8 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate {
// Update status of the module.
const packageId = this.filepoolProvider.getPackageId(handler.component, module.id);
this.statusCache.setValue(packageId, 'downloadedSize', 0);
- this.filepoolProvider.storePackageStatus(siteId, handler.component, module.id, CoreConstants.NOT_DOWNLOADED);
+
+ return this.filepoolProvider.storePackageStatus(siteId, CoreConstants.NOT_DOWNLOADED, handler.component, module.id);
}
});
}
@@ -1144,6 +1148,22 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate {
}
}
+ /**
+ * If courseId or sectionId is set, save them in the cache.
+ *
+ * @param {string} packageId The package ID.
+ * @param {number} [courseId] Course ID.
+ * @param {number} [sectionId] Section ID.
+ */
+ storeCourseAndSection(packageId: string, courseId?: number, sectionId?: number): void {
+ if (courseId) {
+ this.statusCache.setValue(packageId, 'courseId', courseId);
+ }
+ if (sectionId && sectionId > 0) {
+ this.statusCache.setValue(packageId, 'sectionId', sectionId);
+ }
+ }
+
/**
* Treat the result of the check updates WS call.
*
@@ -1181,12 +1201,12 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate {
* Update the status of a module in the "cache".
*
* @param {string} status New status.
- * @param {number} courseId Course ID of the module.
* @param {string} component Package's component.
* @param {string|number} [componentId] An ID to use in conjunction with the component.
+ * @param {number} [courseId] Course ID of the module.
* @param {number} [sectionId] Section ID of the module.
*/
- updateStatusCache(status: string, courseId: number, component: string, componentId?: string | number, sectionId?: number)
+ updateStatusCache(status: string, component: string, componentId?: string | number, courseId?: number, sectionId?: number)
: void {
const packageId = this.filepoolProvider.getPackageId(component, componentId),
cachedStatus = this.statusCache.getValue(packageId, 'status', true);
@@ -1195,7 +1215,13 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate {
// If the status has changed, notify that the section has changed.
notify = typeof cachedStatus != 'undefined' && cachedStatus !== status;
+ // If courseId/sectionId is set, store it.
+ this.storeCourseAndSection(packageId, courseId, sectionId);
+
if (notify) {
+ if (!courseId) {
+ courseId = this.statusCache.getValue(packageId, 'courseId', true);
+ }
if (!sectionId) {
sectionId = this.statusCache.getValue(packageId, 'sectionId', true);
}
@@ -1205,8 +1231,6 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate {
this.statusCache.setValue(packageId, 'status', status);
if (sectionId) {
- this.statusCache.setValue(packageId, 'sectionId', sectionId);
-
this.eventsProvider.trigger(CoreEventsProvider.SECTION_STATUS_CHANGED, {
sectionId: sectionId,
courseId: courseId