MOBILE-2335 book: Support context menu prefetch and size

main
Dani Palou 2018-02-02 13:10:19 +01:00
parent aa95a20287
commit e8cbead5d6
5 changed files with 139 additions and 23 deletions

View File

@ -7,8 +7,8 @@
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl" [iconAction]="'open'"></core-context-menu-item>
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate" (action)="expandDescription()" [iconAction]="'arrow-forward'"></core-context-menu-item>
<core-context-menu-item [priority]="700" [content]="'core.refresh' | translate" (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false"></core-context-menu-item>
<core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="600" [content]="timemodified" (action)="prefetch()" [iconAction]="prefetchStatusIcon" [closeOnClick]="false"></core-context-menu-item>
<core-context-menu-item *ngIf="size" [priority]="500" [content]="size" [iconDescription]="'cube'" (action)="removeFiles()" [iconAction]="trash"></core-context-menu-item>
<core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="600" [content]="prefetchText" (action)="prefetch()" [iconAction]="prefetchStatusIcon" [closeOnClick]="false"></core-context-menu-item>
<core-context-menu-item *ngIf="size" [priority]="500" [content]="size" [iconDescription]="'cube'" (action)="removeFiles()" [iconAction]="'trash'"></core-context-menu-item>
</core-context-menu>
</core-navbar-buttons>

View File

@ -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<any>;
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();
}
}

View File

@ -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);
}
}

View File

@ -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<any>} Promise resolved when done.
*/
confirmAndRemoveFiles(module: any, courseId: number): Promise<any> {
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<any>} Promise resolved when done.
*/
contextMenuPrefetch(instance: any, module: any, courseId: number): Promise<any> {
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<any>} Promise resolved when done.
*/
fillContextMenu(instance: any, module: any, courseId: number, invalidateCache?: boolean, component?: string): Promise<any> {
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).
*

View File

@ -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