Merge pull request #1850 from sammarshallou/MOBILE-2906

MOBILE-2906 Add course option type that appears in menu
main
Juan Leyva 2019-04-26 15:17:17 +02:00 committed by GitHub
commit 839fe09fa7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 196 additions and 18 deletions

View File

@ -15,6 +15,7 @@
<core-context-menu-item *ngIf="displayEnableDownload" [priority]="2000" [content]="'core.settings.showdownloadoptions' | translate" (action)="toggleDownload()" [iconAction]="downloadEnabledIcon"></core-context-menu-item>
<core-context-menu-item [hidden]="!downloadCourseEnabled" [priority]="1900" [content]="prefetchCourseData.title | translate" (action)="prefetchCourse()" [iconAction]="prefetchCourseData.prefetchCourseIcon" [closeOnClick]="false"></core-context-menu-item>
<core-context-menu-item [priority]="1800" [content]="'core.course.coursesummary' | translate" (action)="openCourseSummary()" iconAction="fa-graduation-cap"></core-context-menu-item>
<core-context-menu-item *ngFor="let item of courseMenuHandlers" [priority]="item.priority" (action)="openMenuItem(item)" [content]="item.data.title | translate" [iconAction]="item.data.icon" [class]="item.data.class"></core-context-menu-item>
</core-context-menu>
</core-navbar-buttons>
<ion-content #courseSectionContent>

View File

@ -24,7 +24,8 @@ import { CoreCourseProvider } from '../../providers/course';
import { CoreCourseHelperProvider } from '../../providers/helper';
import { CoreCourseFormatDelegate } from '../../providers/format-delegate';
import { CoreCourseModulePrefetchDelegate } from '../../providers/module-prefetch-delegate';
import { CoreCourseOptionsDelegate, CoreCourseOptionsHandlerToDisplay } from '../../providers/options-delegate';
import { CoreCourseOptionsDelegate, CoreCourseOptionsHandlerToDisplay,
CoreCourseOptionsMenuHandlerToDisplay } from '../../providers/options-delegate';
import { CoreCourseSyncProvider } from '../../providers/sync';
import { CoreCourseFormatComponent } from '../../components/format/format';
import { CoreCoursesProvider } from '@core/courses/providers/courses';
@ -49,6 +50,7 @@ export class CoreCourseSectionPage implements OnDestroy {
sectionId: number;
sectionNumber: number;
courseHandlers: CoreCourseOptionsHandlerToDisplay[];
courseMenuHandlers: CoreCourseOptionsMenuHandlerToDisplay[] = [];
dataLoaded: boolean;
downloadEnabled: boolean;
downloadEnabledIcon = 'square-outline'; // Disabled by default.
@ -301,6 +303,11 @@ export class CoreCourseSectionPage implements OnDestroy {
}
}));
// Load the course menu handlers.
promises.push(this.courseOptionsDelegate.getMenuHandlersToDisplay(this.injector, this.course).then((handlers) => {
this.courseMenuHandlers = handlers;
}));
// Load the course format options when course completion is enabled to show completion progress on sections.
if (this.course.enablecompletion && this.coursesProvider.isGetCoursesByFieldAvailable()) {
promises.push(this.coursesProvider.getCourseByField('id', this.course.id).catch(() => {
@ -417,7 +424,8 @@ export class CoreCourseSectionPage implements OnDestroy {
* Prefetch the whole course.
*/
prefetchCourse(): void {
this.courseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course, this.sections, this.courseHandlers)
this.courseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course, this.sections,
this.courseHandlers, this.courseMenuHandlers)
.then(() => {
if (this.downloadEnabled) {
// Recalculate the status.
@ -459,6 +467,16 @@ export class CoreCourseSectionPage implements OnDestroy {
this.navCtrl.push('CoreCoursesCoursePreviewPage', {course: this.course, avoidOpenCourse: true});
}
/**
* Opens a menu item registered to the delegate.
*
* @param {CoreCourseMenuHandlerToDisplay} item Item to open
*/
openMenuItem(item: CoreCourseOptionsMenuHandlerToDisplay): void {
const params = Object.assign({ course: this.course}, item.data.pageParams);
this.navCtrl.push(item.data.page, params);
}
/**
* Page destroyed.
*/

View File

@ -25,7 +25,8 @@ import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreTimeUtilsProvider } from '@providers/utils/time';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreCourseOptionsDelegate, CoreCourseOptionsHandlerToDisplay } from './options-delegate';
import { CoreCourseOptionsDelegate, CoreCourseOptionsHandlerToDisplay,
CoreCourseOptionsMenuHandlerToDisplay } from './options-delegate';
import { CoreSiteHomeProvider } from '@core/sitehome/providers/sitehome';
import { CoreCoursesProvider } from '@core/courses/providers/courses';
import { CoreCourseProvider } from './course';
@ -270,10 +271,11 @@ export class CoreCourseHelperProvider {
* @param {any} course Course to prefetch.
* @param {any[]} [sections] List of course sections.
* @param {CoreCourseOptionsHandlerToDisplay[]} courseHandlers List of course handlers.
* @param {CoreCourseOptionsMenuHandlerToDisplay[]} menuHandlers List of course menu handlers.
* @return {Promise<boolean>} Promise resolved when the download finishes, rejected if an error occurs or the user cancels.
*/
confirmAndPrefetchCourse(data: any, course: any, sections?: any[], courseHandlers?: CoreCourseOptionsHandlerToDisplay[])
: Promise<boolean> {
confirmAndPrefetchCourse(data: any, course: any, sections?: any[], courseHandlers?: CoreCourseOptionsHandlerToDisplay[],
menuHandlers?: CoreCourseOptionsMenuHandlerToDisplay[]): Promise<boolean> {
const initialIcon = data.prefetchCourseIcon,
initialTitle = data.title,
@ -295,15 +297,23 @@ export class CoreCourseHelperProvider {
// Confirm the download.
return this.confirmDownloadSizeSection(course.id, undefined, sections, true).then(() => {
// User confirmed, get the course handlers if needed.
if (courseHandlers) {
promise = Promise.resolve(courseHandlers);
} else {
promise = this.courseOptionsDelegate.getHandlersToDisplay(this.injector, course);
const subPromises = [];
if (!courseHandlers) {
subPromises.push(this.courseOptionsDelegate.getHandlersToDisplay(this.injector, course)
.then((cHandlers) => {
courseHandlers = cHandlers;
}));
}
if (!menuHandlers) {
subPromises.push(this.courseOptionsDelegate.getMenuHandlersToDisplay(this.injector, course)
.then((mHandlers) => {
menuHandlers = mHandlers;
}));
}
return promise.then((handlers: CoreCourseOptionsHandlerToDisplay[]) => {
return Promise.all(subPromises).then(() => {
// Now we have all the data, download the course.
return this.prefetchCourse(course, sections, handlers, siteId);
return this.prefetchCourse(course, sections, courseHandlers, menuHandlers, siteId);
}).then(() => {
// Download successful.
return true;
@ -338,6 +348,7 @@ export class CoreCourseHelperProvider {
const subPromises = [];
let sections,
handlers,
menuHandlers,
success = true;
// Get the sections and the handlers.
@ -347,9 +358,12 @@ export class CoreCourseHelperProvider {
subPromises.push(this.courseOptionsDelegate.getHandlersToDisplay(this.injector, course).then((cHandlers) => {
handlers = cHandlers;
}));
subPromises.push(this.courseOptionsDelegate.getMenuHandlersToDisplay(this.injector, course).then((mHandlers) => {
menuHandlers = mHandlers;
}));
promises.push(Promise.all(subPromises).then(() => {
return this.prefetchCourse(course, sections, handlers, siteId);
return this.prefetchCourse(course, sections, handlers, menuHandlers, siteId);
}).catch((error) => {
success = false;
@ -1187,11 +1201,12 @@ export class CoreCourseHelperProvider {
* @param {any} course The course to prefetch.
* @param {any[]} sections List of course sections.
* @param {CoreCourseOptionsHandlerToDisplay[]} courseHandlers List of course options handlers.
* @param {CoreCourseOptionsMenuHandlerToDisplay[]} courseMenuHandlers List of course menu handlers.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise} Promise resolved when the download finishes.
*/
prefetchCourse(course: any, sections: any[], courseHandlers: CoreCourseOptionsHandlerToDisplay[], siteId?: string)
: Promise<any> {
prefetchCourse(course: any, sections: any[], courseHandlers: CoreCourseOptionsHandlerToDisplay[],
courseMenuHandlers: CoreCourseOptionsMenuHandlerToDisplay[], siteId?: string): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
if (this.courseDwnPromises[siteId] && this.courseDwnPromises[siteId][course.id]) {
@ -1220,6 +1235,11 @@ export class CoreCourseHelperProvider {
promises.push(handler.prefetch(course));
}
});
courseMenuHandlers.forEach((handler) => {
if (handler.prefetch) {
promises.push(handler.prefetch(course));
}
});
// Prefetch other data needed to render the course.
if (this.coursesProvider.isGetCoursesByFieldAvailable()) {

View File

@ -31,6 +31,12 @@ export interface CoreCourseOptionsHandler extends CoreDelegateHandler {
*/
priority: number;
/**
* True if this handler should appear in menu rather than as a tab.
* @type {boolean}
*/
isMenuHandler?: boolean;
/**
* Whether or not the handler is enabled for a certain course.
*
@ -70,6 +76,21 @@ export interface CoreCourseOptionsHandler extends CoreDelegateHandler {
prefetch?(course: any): Promise<any>;
}
/**
* Interface that course options handlers implement if they appear in the menu rather than as a tab.
*/
export interface CoreCourseOptionsMenuHandler extends CoreCourseOptionsHandler {
/**
* Returns the data needed to render the handler.
*
* @param {Injector} injector Injector.
* @param {number} courseId The course ID.
* @return {CoreCourseOptionsMenuHandlerData|Promise<CoreCourseOptionsMenuHandlerData>} Data or promise resolved with data.
*/
getMenuDisplayData(injector: Injector, courseId: number):
CoreCourseOptionsMenuHandlerData | Promise<CoreCourseOptionsMenuHandlerData>;
}
/**
* Data needed to render a course handler. It's returned by the handler.
*/
@ -99,6 +120,41 @@ export interface CoreCourseOptionsHandlerData {
componentData?: any;
}
/**
* Data needed to render a course menu handler. It's returned by the handler.
*/
export interface CoreCourseOptionsMenuHandlerData {
/**
* Title to display for the handler.
* @type {string}
*/
title: string;
/**
* Class to add to the displayed handler.
* @type {string}
*/
class?: string;
/**
* Name of the page to load for the handler.
* @type {string}
*/
page: string;
/**
* Params to pass to the page (other than 'course' which is always sent).
* @type {any}
*/
pageParams?: any;
/**
* Name of the icon to display for the handler.
* @type {string}
*/
icon: string; // Name of the icon to display in the tab.
}
/**
* Data returned by the delegate for each handler.
*/
@ -130,6 +186,37 @@ export interface CoreCourseOptionsHandlerToDisplay {
prefetch?(course: any): Promise<any>;
}
/**
* Additional data returned if it is a menu item.
*/
export interface CoreCourseOptionsMenuHandlerToDisplay {
/**
* Data to display.
* @type {CoreCourseOptionsMenuHandlerData}
*/
data: CoreCourseOptionsMenuHandlerData;
/**
* Name of the handler, or name and sub context (AddonMessages, AddonMessages:blockContact, ...).
* @type {string}
*/
name: string;
/**
* The highest priority is displayed first.
* @type {number}
*/
priority?: number;
/**
* Called when a course is downloaded. It should prefetch all the data to be able to see the addon in offline.
*
* @param {any} course The course.
* @return {Promise<any>} Promise resolved when done.
*/
prefetch?(course: any): Promise<any>;
}
/**
* Service to interact with plugins to be shown in each course (participants, learning plans, ...).
*/
@ -139,7 +226,8 @@ export class CoreCourseOptionsDelegate extends CoreDelegate {
protected lastUpdateHandlersForCoursesStart: any = {};
protected coursesHandlers: {
[courseId: number]: {
access?: any, navOptions?: any, admOptions?: any, deferred?: PromiseDefer, enabledHandlers?: CoreCourseOptionsHandler[]
access?: any, navOptions?: any, admOptions?: any, deferred?: PromiseDefer,
enabledHandlers?: CoreCourseOptionsHandler[], enabledMenuHandlers?: CoreCourseOptionsMenuHandler[]
}
} = {};
@ -258,6 +346,43 @@ export class CoreCourseOptionsDelegate extends CoreDelegate {
*/
getHandlersToDisplay(injector: Injector, course: any, refresh?: boolean, isGuest?: boolean, navOptions?: any, admOptions?: any):
Promise<CoreCourseOptionsHandlerToDisplay[]> {
return <Promise<CoreCourseOptionsHandlerToDisplay[]>> this.getHandlersToDisplayInternal(
false, injector, course, refresh, isGuest, navOptions, admOptions);
}
/**
* Get the list of menu handlers that should be displayed for a course.
* This function should be called only when the handlers need to be displayed, since it can call several WebServices.
*
* @param {Injector} injector Injector.
* @param {any} course The course object.
* @param {boolean} [refresh] True if it should refresh the list.
* @param {boolean} [isGuest] Whether it's guest.
* @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions.
* @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
* @return {Promise<CoreCourseOptionsMenuHandlerToDisplay[]>} Promise resolved with array of handlers.
*/
getMenuHandlersToDisplay(injector: Injector, course: any, refresh?: boolean, isGuest?: boolean,
navOptions?: any, admOptions?: any): Promise<CoreCourseOptionsMenuHandlerToDisplay[]> {
return <Promise<CoreCourseOptionsMenuHandlerToDisplay[]>> this.getHandlersToDisplayInternal(
true, injector, course, refresh, isGuest, navOptions, admOptions);
}
/**
* Get the list of menu handlers that should be displayed for a course.
* This function should be called only when the handlers need to be displayed, since it can call several WebServices.
*
* @param {boolean} menu If true, gets menu handlers; false, gets tab handlers
* @param {Injector} injector Injector.
* @param {any} course The course object.
* @param {boolean} refresh True if it should refresh the list.
* @param {boolean} isGuest Whether it's guest.
* @param {any} navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions.
* @param {any} admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
* @return {Promise<any[]>} Promise resolved with array of handlers.
*/
protected getHandlersToDisplayInternal(menu: boolean, injector: Injector, course: any, refresh: boolean, isGuest: boolean,
navOptions: any, admOptions: any): Promise<any[]> {
course.id = parseInt(course.id, 10);
const accessData = {
@ -278,8 +403,16 @@ export class CoreCourseOptionsDelegate extends CoreDelegate {
}).then(() => {
const promises = [];
this.coursesHandlers[course.id].enabledHandlers.forEach((handler) => {
promises.push(Promise.resolve(handler.getDisplayData(injector, course)).then((data) => {
let handlerList;
if (menu) {
handlerList = this.coursesHandlers[course.id].enabledMenuHandlers;
} else {
handlerList = this.coursesHandlers[course.id].enabledHandlers;
}
handlerList.forEach((handler) => {
const getFunction = menu ? handler.getMenuDisplayData : handler.getDisplayData;
promises.push(Promise.resolve(getFunction.call(handler, injector, course)).then((data) => {
handlersToDisplay.push({
data: data,
priority: handler.priority,
@ -444,6 +577,7 @@ export class CoreCourseOptionsDelegate extends CoreDelegate {
updateHandlersForCourse(courseId: number, accessData: any, navOptions?: any, admOptions?: any): Promise<any> {
const promises = [],
enabledForCourse = [],
enabledForCourseMenu = [],
siteId = this.sitesProvider.getCurrentSiteId(),
now = Date.now();
@ -456,7 +590,11 @@ export class CoreCourseOptionsDelegate extends CoreDelegate {
promises.push(Promise.resolve(handler.isEnabledForCourse(courseId, accessData, navOptions, admOptions))
.then((enabled) => {
if (enabled) {
enabledForCourse.push(handler);
if (handler.isMenuHandler) {
enabledForCourseMenu.push(<CoreCourseOptionsMenuHandler> handler);
} else {
enabledForCourse.push(handler);
}
} else {
return Promise.reject(null);
}
@ -476,6 +614,7 @@ export class CoreCourseOptionsDelegate extends CoreDelegate {
if (this.isLastUpdateCourseCall(courseId, now) && this.sitesProvider.getCurrentSiteId() === siteId) {
// Update the coursesHandlers array with the new enabled addons.
this.coursesHandlers[courseId].enabledHandlers = enabledForCourse;
this.coursesHandlers[courseId].enabledMenuHandlers = enabledForCourseMenu;
this.loaded[courseId] = true;
// Resolve the promise.