MOBILE-2906 Add course option type that appears in menu
This allows addons to add items to the course menu. The items work in the same way as other course options, except that instead of appearing as a tab, they appear as an option in the course menu.main
parent
c0697e4436
commit
bc931df813
|
@ -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>
|
||||
|
|
|
@ -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.getCoursesByField('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.
|
||||
*/
|
||||
|
|
|
@ -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';
|
||||
|
@ -273,10 +274,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,
|
||||
|
@ -297,15 +299,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;
|
||||
|
@ -340,6 +350,7 @@ export class CoreCourseHelperProvider {
|
|||
const subPromises = [];
|
||||
let sections,
|
||||
handlers,
|
||||
menuHandlers,
|
||||
success = true;
|
||||
|
||||
// Get the sections and the handlers.
|
||||
|
@ -349,9 +360,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;
|
||||
|
||||
|
@ -1162,11 +1176,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]) {
|
||||
|
@ -1195,6 +1210,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()) {
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue