commit
9af7648275
|
@ -2125,6 +2125,7 @@
|
||||||
"core.resources": "moodle",
|
"core.resources": "moodle",
|
||||||
"core.restore": "moodle",
|
"core.restore": "moodle",
|
||||||
"core.restricted": "moodle",
|
"core.restricted": "moodle",
|
||||||
|
"core.resume": "local_moodlemobileapp",
|
||||||
"core.retry": "local_moodlemobileapp",
|
"core.retry": "local_moodlemobileapp",
|
||||||
"core.save": "moodle",
|
"core.save": "moodle",
|
||||||
"core.savechanges": "assign",
|
"core.savechanges": "assign",
|
||||||
|
@ -2251,7 +2252,7 @@
|
||||||
"core.sorry": "local_moodlemobileapp",
|
"core.sorry": "local_moodlemobileapp",
|
||||||
"core.sort": "moodle",
|
"core.sort": "moodle",
|
||||||
"core.sortby": "moodle",
|
"core.sortby": "moodle",
|
||||||
"core.start": "grouptool",
|
"core.start": "local_moodlemobileapp",
|
||||||
"core.storingfiles": "local_moodlemobileapp",
|
"core.storingfiles": "local_moodlemobileapp",
|
||||||
"core.strftimedate": "langconfig",
|
"core.strftimedate": "langconfig",
|
||||||
"core.strftimedatefullshort": "langconfig",
|
"core.strftimedatefullshort": "langconfig",
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { CoreUtils } from '@services/utils/utils';
|
||||||
import { AddonCalendar, AddonCalendarEventType, AddonCalendarProvider } from '../calendar';
|
import { AddonCalendar, AddonCalendarEventType, AddonCalendarProvider } from '../calendar';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Database variables for AddonDatabase service.
|
* Database variables for AddonCalendarProvider service.
|
||||||
*/
|
*/
|
||||||
export const EVENTS_TABLE = 'addon_calendar_events_3';
|
export const EVENTS_TABLE = 'addon_calendar_events_3';
|
||||||
export const REMINDERS_TABLE = 'addon_calendar_reminders';
|
export const REMINDERS_TABLE = 'addon_calendar_reminders';
|
||||||
|
|
|
@ -54,7 +54,10 @@ export class AddonModAssignPushClickHandlerService implements CorePushNotificati
|
||||||
const moduleId = Number(contextUrlParams.id);
|
const moduleId = Number(contextUrlParams.id);
|
||||||
|
|
||||||
await CoreUtils.ignoreErrors(AddonModAssign.invalidateContent(moduleId, courseId, notification.site));
|
await CoreUtils.ignoreErrors(AddonModAssign.invalidateContent(moduleId, courseId, notification.site));
|
||||||
await CoreCourseHelper.navigateToModule(moduleId, notification.site, courseId);
|
await CoreCourseHelper.navigateToModule(moduleId, {
|
||||||
|
courseId,
|
||||||
|
siteId: notification.site,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,10 @@ const routes: Routes = [
|
||||||
path: ':courseId/:cmId',
|
path: ':courseId/:cmId',
|
||||||
component: AddonModBookIndexPage,
|
component: AddonModBookIndexPage,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: ':courseId/:cmId/contents',
|
||||||
|
loadChildren: () => import('./pages/contents/contents.module').then(m => m.AddonModBookContentsPageModule),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|
|
@ -26,6 +26,8 @@ import { AddonModBookListLinkHandler } from './services/handlers/list-link';
|
||||||
import { AddonModBookPrefetchHandler } from './services/handlers/prefetch';
|
import { AddonModBookPrefetchHandler } from './services/handlers/prefetch';
|
||||||
import { AddonModBookTagAreaHandler } from './services/handlers/tag-area';
|
import { AddonModBookTagAreaHandler } from './services/handlers/tag-area';
|
||||||
import { AddonModBookProvider } from './services/book';
|
import { AddonModBookProvider } from './services/book';
|
||||||
|
import { CORE_SITE_SCHEMAS } from '@services/sites';
|
||||||
|
import { BOOK_SITE_SCHEMA } from './services/database/book';
|
||||||
|
|
||||||
export const ADDON_MOD_BOOK_SERVICES: Type<unknown>[] = [
|
export const ADDON_MOD_BOOK_SERVICES: Type<unknown>[] = [
|
||||||
AddonModBookProvider,
|
AddonModBookProvider,
|
||||||
|
@ -44,6 +46,11 @@ const routes: Routes = [
|
||||||
AddonModBookComponentsModule,
|
AddonModBookComponentsModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
|
{
|
||||||
|
provide: CORE_SITE_SCHEMAS,
|
||||||
|
useValue: [BOOK_SITE_SCHEMA],
|
||||||
|
multi: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
provide: APP_INITIALIZER,
|
provide: APP_INITIALIZER,
|
||||||
multi: true,
|
multi: true,
|
||||||
|
|
|
@ -16,7 +16,6 @@ import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
import { CoreSharedModule } from '@/core/shared.module';
|
import { CoreSharedModule } from '@/core/shared.module';
|
||||||
import { CoreCourseComponentsModule } from '@features/course/components/components.module';
|
import { CoreCourseComponentsModule } from '@features/course/components/components.module';
|
||||||
import { CoreTagComponentsModule } from '@features/tag/components/components.module';
|
|
||||||
|
|
||||||
import { AddonModBookIndexComponent } from './index/index';
|
import { AddonModBookIndexComponent } from './index/index';
|
||||||
import { AddonModBookTocComponent } from './toc/toc';
|
import { AddonModBookTocComponent } from './toc/toc';
|
||||||
|
@ -29,7 +28,6 @@ import { AddonModBookTocComponent } from './toc/toc';
|
||||||
imports: [
|
imports: [
|
||||||
CoreSharedModule,
|
CoreSharedModule,
|
||||||
CoreCourseComponentsModule,
|
CoreCourseComponentsModule,
|
||||||
CoreTagComponentsModule,
|
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
AddonModBookIndexComponent,
|
AddonModBookIndexComponent,
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
<!-- Buttons to add to the header. -->
|
<!-- Buttons to add to the header. -->
|
||||||
<core-navbar-buttons slot="end">
|
<core-navbar-buttons slot="end">
|
||||||
<ion-button (click)="showToc()" [attr.aria-label]="'addon.mod_book.toc' | translate" aria-haspopup="true" *ngIf="loaded">
|
|
||||||
<ion-icon name="fas-bookmark" slot="icon-only" aria-hidden="true"></ion-icon>
|
|
||||||
</ion-button>
|
|
||||||
<core-context-menu>
|
<core-context-menu>
|
||||||
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
|
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
|
||||||
iconAction="fas-external-link-alt" [showBrowserWarning]="false"></core-context-menu-item>
|
iconAction="fas-external-link-alt" [showBrowserWarning]="false"></core-context-menu-item>
|
||||||
|
@ -28,31 +25,31 @@
|
||||||
[courseId]="courseId">
|
[courseId]="courseId">
|
||||||
</core-course-module-info>
|
</core-course-module-info>
|
||||||
|
|
||||||
<ion-card class="core-warning-card" *ngIf="warning">
|
<ion-list>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-icon name="fas-exclamation-triangle" slot="start" aria-hidden="true"></ion-icon>
|
<ion-label>
|
||||||
<ion-label><span [innerHTML]="warning"></span></ion-label>
|
<h2>{{ 'addon.mod_book.toc' | translate }}</h2>
|
||||||
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-card>
|
|
||||||
|
|
||||||
<div class="safe-area-padding-horizontal">
|
<ion-item class="ion-text-wrap" *ngFor="let chapter of chapters" [class.item-dimmed]="chapter.hidden" button detail="true"
|
||||||
<core-navigation-bar *ngIf="displayNavBar" [items]="navigationItems" [showTitles]="displayTitlesInNavBar"
|
(click)="openBook(chapter.id)">
|
||||||
previousTranslate="addon.mod_book.navprevtitle" nextTranslate="addon.mod_book.navnexttitle" (action)="changeChapter($event.id)">
|
<ion-label>
|
||||||
</core-navigation-bar>
|
<p [class.ion-padding-start]="addPadding && chapter.level == 1 ? true : null">
|
||||||
|
<span *ngIf="showNumbers" class="addon-mod-book-number">{{chapter.indexNumber}} </span>
|
||||||
|
<span *ngIf="showBullets" class="addon-mod-book-bullet">• </span>
|
||||||
|
<core-format-text [text]="chapter.title" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
|
||||||
|
</core-format-text>
|
||||||
|
</p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
<core-swipe-slides [manager]="manager" [options]="slidesOpts">
|
<ion-button class="ion-margin" expand="block" (click)="openBook()">
|
||||||
<ng-template let-chapter="item">
|
<span *ngIf="!hasStartedBook">{{ 'core.start' | translate }}</span>
|
||||||
<div class="ion-padding">
|
<span *ngIf="hasStartedBook">{{ 'core.resume' | translate }}</span>
|
||||||
<core-format-text [component]="component" [componentId]="componentId" [text]="chapter.content" contextLevel="module"
|
</ion-button>
|
||||||
[contextInstanceId]="module.id" [courseId]="courseId"></core-format-text>
|
|
||||||
<div class="ion-margin-top" *ngIf="chapter.tags?.length > 0">
|
</ion-list>
|
||||||
<strong>{{ 'core.tag.tags' | translate }}: </strong>
|
|
||||||
<core-tag-list [tags]="chapter.tags"></core-tag-list>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
</core-swipe-slides>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</core-loading>
|
</core-loading>
|
||||||
|
|
||||||
|
|
|
@ -12,33 +12,15 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, Optional, Input, OnInit, ViewChild, ElementRef, OnDestroy } from '@angular/core';
|
import { Component, Optional, OnInit, OnDestroy } from '@angular/core';
|
||||||
import { CoreCourseModuleMainResourceComponent } from '@features/course/classes/main-resource-component';
|
import { CoreCourseModuleMainResourceComponent } from '@features/course/classes/main-resource-component';
|
||||||
import {
|
import { AddonModBook, AddonModBookBookWSData, AddonModBookNumbering, AddonModBookTocChapter } from '../../services/book';
|
||||||
AddonModBookProvider,
|
|
||||||
AddonModBookContentsMap,
|
|
||||||
AddonModBookTocChapter,
|
|
||||||
AddonModBookNavStyle,
|
|
||||||
AddonModBook,
|
|
||||||
AddonModBookBookWSData,
|
|
||||||
} from '../../services/book';
|
|
||||||
import { CoreTag, CoreTagItem } from '@features/tag/services/tag';
|
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
|
||||||
import { CoreCourseContentsPage } from '@features/course/pages/contents/contents';
|
import { CoreCourseContentsPage } from '@features/course/pages/contents/contents';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
|
||||||
import { CoreCourse } from '@features/course/services/course';
|
import { CoreCourse } from '@features/course/services/course';
|
||||||
import { AddonModBookTocComponent } from '../toc/toc';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
import { CoreNavigationBarItem } from '@components/navigation-bar/navigation-bar';
|
|
||||||
import { CoreError } from '@classes/errors/error';
|
|
||||||
import { Translate } from '@singletons';
|
|
||||||
import { CoreSwipeSlidesComponent, CoreSwipeSlidesOptions } from '@components/swipe-slides/swipe-slides';
|
|
||||||
import { CoreSwipeSlidesItemsManagerSource } from '@classes/items-management/swipe-slides-items-manager-source';
|
|
||||||
import { CoreCourseModule } from '@features/course/services/course-helper';
|
|
||||||
import { CoreSwipeSlidesItemsManager } from '@classes/items-management/swipe-slides-items-manager';
|
|
||||||
import { CoreTextUtils } from '@services/utils/text';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that displays a book.
|
* Component that displays a book entry page.
|
||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'addon-mod-book-index',
|
selector: 'addon-mod-book-index',
|
||||||
|
@ -46,191 +28,86 @@ import { CoreTextUtils } from '@services/utils/text';
|
||||||
})
|
})
|
||||||
export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy {
|
export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
@ViewChild(CoreSwipeSlidesComponent) slides?: CoreSwipeSlidesComponent;
|
showNumbers = true;
|
||||||
|
addPadding = true;
|
||||||
|
showBullets = false;
|
||||||
|
chapters: AddonModBookTocChapter[] = [];
|
||||||
|
hasStartedBook = false;
|
||||||
|
|
||||||
@Input() initialChapterId?: number; // The initial chapter ID to load.
|
protected book?: AddonModBookBookWSData;
|
||||||
|
|
||||||
component = AddonModBookProvider.COMPONENT;
|
constructor( @Optional() courseContentsPage?: CoreCourseContentsPage) {
|
||||||
manager?: CoreSwipeSlidesItemsManager<LoadedChapter, AddonModBookSlidesItemsManagerSource>;
|
|
||||||
warning = '';
|
|
||||||
displayNavBar = true;
|
|
||||||
navigationItems: CoreNavigationBarItem<AddonModBookTocChapter>[] = [];
|
|
||||||
displayTitlesInNavBar = false;
|
|
||||||
slidesOpts: CoreSwipeSlidesOptions = {
|
|
||||||
autoHeight: true,
|
|
||||||
scrollOnChange: 'top',
|
|
||||||
};
|
|
||||||
|
|
||||||
protected firstLoad = true;
|
|
||||||
protected element: HTMLElement;
|
|
||||||
protected managerUnsubscribe?: () => void;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
elementRef: ElementRef,
|
|
||||||
@Optional() courseContentsPage?: CoreCourseContentsPage,
|
|
||||||
) {
|
|
||||||
super('AddonModBookIndexComponent', courseContentsPage);
|
super('AddonModBookIndexComponent', courseContentsPage);
|
||||||
|
|
||||||
this.element = elementRef.nativeElement;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component being initialized.
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
super.ngOnInit();
|
super.ngOnInit();
|
||||||
|
|
||||||
const source = new AddonModBookSlidesItemsManagerSource(
|
|
||||||
this.courseId,
|
|
||||||
this.module,
|
|
||||||
CoreTag.areTagsAvailableInSite(),
|
|
||||||
this.initialChapterId,
|
|
||||||
);
|
|
||||||
this.manager = new CoreSwipeSlidesItemsManager(source);
|
|
||||||
this.managerUnsubscribe = this.manager.addListener({
|
|
||||||
onSelectedItemUpdated: (item) => {
|
|
||||||
this.onChapterViewed(item.id);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
this.loadContent();
|
this.loadContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
get book(): AddonModBookBookWSData | undefined {
|
/**
|
||||||
return this.manager?.getSource().book;
|
* @inheritdoc
|
||||||
}
|
*/
|
||||||
|
protected async fetchContent(refresh?: boolean): Promise<void> {
|
||||||
get chapters(): AddonModBookTocChapter[] {
|
try {
|
||||||
return this.manager?.getSource().chapters || [];
|
await Promise.all([
|
||||||
|
this.loadBook(),
|
||||||
|
this.loadTOC(),
|
||||||
|
]);
|
||||||
|
} finally {
|
||||||
|
this.fillContextMenu(refresh);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the TOC.
|
* Load book data.
|
||||||
|
*
|
||||||
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async showToc(): Promise<void> {
|
protected async loadBook(): Promise<void> {
|
||||||
// Create the toc modal.
|
this.book = await AddonModBook.getBook(this.courseId, this.module.id);
|
||||||
const visibleChapter = this.manager?.getSelectedItem();
|
|
||||||
|
|
||||||
const modalData = await CoreDomUtils.openSideModal<number>({
|
this.dataRetrieved.emit(this.book);
|
||||||
component: AddonModBookTocComponent,
|
|
||||||
componentProps: {
|
this.description = this.book.intro;
|
||||||
moduleId: this.module.id,
|
this.showNumbers = this.book.numbering == AddonModBookNumbering.NUMBERS;
|
||||||
chapters: this.chapters,
|
this.showBullets = this.book.numbering == AddonModBookNumbering.BULLETS;
|
||||||
selected: visibleChapter,
|
this.addPadding = this.book.numbering != AddonModBookNumbering.NONE;
|
||||||
|
|
||||||
|
const lastChapterViewed = await AddonModBook.getLastChapterViewed(this.book.id);
|
||||||
|
this.hasStartedBook = lastChapterViewed !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load book TOC.
|
||||||
|
*
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected async loadTOC(): Promise<void> {
|
||||||
|
const contents = await CoreCourse.getModuleContents(this.module, this.courseId);
|
||||||
|
|
||||||
|
this.chapters = AddonModBook.getTocList(contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the book in a certain chapter.
|
||||||
|
*
|
||||||
|
* @param chapterId Chapter to open, undefined for first chapter.
|
||||||
|
*/
|
||||||
|
openBook(chapterId?: number): void {
|
||||||
|
CoreNavigator.navigate('contents', {
|
||||||
|
params: {
|
||||||
|
cmId: this.module.id,
|
||||||
courseId: this.courseId,
|
courseId: this.courseId,
|
||||||
book: this.book,
|
chapterId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (modalData) {
|
this.hasStartedBook = true;
|
||||||
this.changeChapter(modalData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Change the current chapter.
|
|
||||||
*
|
|
||||||
* @param chapterId Chapter to load.
|
|
||||||
* @return Promise resolved when done.
|
|
||||||
*/
|
|
||||||
changeChapter(chapterId: number): void {
|
|
||||||
if (!chapterId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.slides?.slideToItem({ id: chapterId });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform the invalidate content function.
|
|
||||||
*
|
|
||||||
* @return Resolved when done.
|
|
||||||
*/
|
|
||||||
protected async invalidateContent(): Promise<void> {
|
|
||||||
await this.manager?.getSource().invalidateContent();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Download book contents and load the current chapter.
|
|
||||||
*
|
|
||||||
* @param refresh Whether we're refreshing data.
|
|
||||||
* @return Promise resolved when done.
|
|
||||||
*/
|
|
||||||
protected async fetchContent(refresh = false): Promise<void> {
|
|
||||||
try {
|
|
||||||
const source = this.manager?.getSource();
|
|
||||||
if (!source) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const downloadResult = await this.downloadResourceIfNeeded(refresh);
|
|
||||||
|
|
||||||
const book = await source.loadBookData();
|
|
||||||
|
|
||||||
if (book) {
|
|
||||||
this.dataRetrieved.emit(book);
|
|
||||||
|
|
||||||
this.description = book.intro;
|
|
||||||
this.displayNavBar = book.navstyle != AddonModBookNavStyle.TOC_ONLY;
|
|
||||||
this.displayTitlesInNavBar = book.navstyle == AddonModBookNavStyle.TEXT;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get contents. No need to refresh, it has been done in downloadResourceIfNeeded.
|
|
||||||
await source.loadContents();
|
|
||||||
|
|
||||||
await source.load();
|
|
||||||
|
|
||||||
this.warning = downloadResult?.failed ? this.getErrorDownloadingSomeFilesMessage(downloadResult.error || '') : '';
|
|
||||||
} finally {
|
|
||||||
// Pass false because downloadResourceIfNeeded already invalidates and refresh data if refresh=true.
|
|
||||||
this.fillContextMenu(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update data related to chapter being viewed.
|
|
||||||
*
|
|
||||||
* @param chapterId Chapter viewed.
|
|
||||||
* @return Promise resolved when done.
|
|
||||||
*/
|
|
||||||
protected async onChapterViewed(chapterId: number): Promise<void> {
|
|
||||||
// Don't log the chapter ID when the user has just opened the book.
|
|
||||||
const logChapterId = this.firstLoad;
|
|
||||||
this.firstLoad = false;
|
|
||||||
|
|
||||||
if (this.displayNavBar) {
|
|
||||||
this.navigationItems = this.getNavigationItems(chapterId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chapter loaded, log view.
|
|
||||||
await CoreUtils.ignoreErrors(AddonModBook.logView(
|
|
||||||
this.module.instance,
|
|
||||||
logChapterId ? chapterId : undefined,
|
|
||||||
this.module.name,
|
|
||||||
));
|
|
||||||
|
|
||||||
const currentChapterIndex = this.chapters.findIndex((chapter) => chapter.id == chapterId);
|
|
||||||
const isLastChapter = currentChapterIndex < 0 || this.chapters[currentChapterIndex + 1] === undefined;
|
|
||||||
|
|
||||||
// Module is completed when last chapter is viewed, so we only check completion if the last is reached.
|
|
||||||
if (isLastChapter) {
|
|
||||||
CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts chapters to navigation items.
|
|
||||||
*
|
|
||||||
* @param chapterId Current chapter Id.
|
|
||||||
* @return Navigation items.
|
|
||||||
*/
|
|
||||||
protected getNavigationItems(chapterId: number): CoreNavigationBarItem<AddonModBookTocChapter>[] {
|
|
||||||
return this.chapters.map((chapter) => ({
|
|
||||||
item: chapter,
|
|
||||||
title: chapter.title,
|
|
||||||
current: chapter.id == chapterId,
|
|
||||||
enabled: true,
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -238,99 +115,6 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp
|
||||||
*/
|
*/
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
super.ngOnDestroy();
|
super.ngOnDestroy();
|
||||||
|
|
||||||
this.managerUnsubscribe && this.managerUnsubscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
type LoadedChapter = {
|
|
||||||
id: number;
|
|
||||||
content?: string;
|
|
||||||
tags?: CoreTagItem[];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper to manage swiping within a collection of chapters.
|
|
||||||
*/
|
|
||||||
class AddonModBookSlidesItemsManagerSource extends CoreSwipeSlidesItemsManagerSource<LoadedChapter> {
|
|
||||||
|
|
||||||
readonly COURSE_ID: number;
|
|
||||||
readonly MODULE: CoreCourseModule;
|
|
||||||
readonly TAGS_ENABLED: boolean;
|
|
||||||
|
|
||||||
book?: AddonModBookBookWSData;
|
|
||||||
chapters: AddonModBookTocChapter[] = [];
|
|
||||||
contentsMap: AddonModBookContentsMap = {};
|
|
||||||
|
|
||||||
constructor(courseId: number, module: CoreCourseModule, tagsEnabled: boolean, initialChapterId?: number) {
|
|
||||||
super(initialChapterId ? { id: initialChapterId } : undefined);
|
|
||||||
|
|
||||||
this.COURSE_ID = courseId;
|
|
||||||
this.MODULE = module;
|
|
||||||
this.TAGS_ENABLED = tagsEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
getItemId(item: LoadedChapter): string | number {
|
|
||||||
return item.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load book data from WS.
|
|
||||||
*
|
|
||||||
* @return Promise resolved when done.
|
|
||||||
*/
|
|
||||||
async loadBookData(): Promise<AddonModBookBookWSData> {
|
|
||||||
this.book = await AddonModBook.getBook(this.COURSE_ID, this.MODULE.id);
|
|
||||||
|
|
||||||
return this.book;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load module contents.
|
|
||||||
*/
|
|
||||||
async loadContents(): Promise<void> {
|
|
||||||
const contents = await CoreCourse.getModuleContents(this.MODULE, this.COURSE_ID);
|
|
||||||
|
|
||||||
this.contentsMap = AddonModBook.getContentsMap(contents);
|
|
||||||
this.chapters = AddonModBook.getTocList(contents);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
protected async loadItems(): Promise<LoadedChapter[]> {
|
|
||||||
try {
|
|
||||||
const newChapters = await Promise.all(this.chapters.map(async (chapter) => {
|
|
||||||
const content = await AddonModBook.getChapterContent(this.contentsMap, chapter.id, this.MODULE.id);
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: chapter.id,
|
|
||||||
content,
|
|
||||||
tags: this.TAGS_ENABLED ? this.contentsMap[chapter.id].tags : [],
|
|
||||||
};
|
|
||||||
}));
|
|
||||||
|
|
||||||
return newChapters;
|
|
||||||
} catch (error) {
|
|
||||||
if (!CoreTextUtils.getErrorMessageFromError(error)) {
|
|
||||||
throw new CoreError(Translate.instant('addon.mod_book.errorchapter'));
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform the invalidate content function.
|
|
||||||
*
|
|
||||||
* @return Resolved when done.
|
|
||||||
*/
|
|
||||||
invalidateContent(): Promise<void> {
|
|
||||||
return AddonModBook.invalidateContent(this.MODULE.id, this.COURSE_ID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button [text]="'core.back' | translate"></ion-back-button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title>
|
||||||
|
<h1>
|
||||||
|
<core-format-text [text]="title" contextLevel="module" [contextInstanceId]="cmId" [courseId]="courseId">
|
||||||
|
</core-format-text>
|
||||||
|
</h1>
|
||||||
|
</ion-title>
|
||||||
|
<ion-buttons slot="end">
|
||||||
|
<ion-button (click)="showToc()" [attr.aria-label]="'addon.mod_book.toc' | translate" aria-haspopup="true" *ngIf="loaded">
|
||||||
|
<ion-icon name="fas-bookmark" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content>
|
||||||
|
<ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="doRefresh($event.target)">
|
||||||
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
|
</ion-refresher>
|
||||||
|
|
||||||
|
<core-loading [hideUntil]="loaded">
|
||||||
|
|
||||||
|
<ion-card class="core-warning-card" *ngIf="warning">
|
||||||
|
<ion-item>
|
||||||
|
<ion-icon name="fas-exclamation-triangle" slot="start" aria-hidden="true"></ion-icon>
|
||||||
|
<ion-label><span [innerHTML]="warning"></span></ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ion-card>
|
||||||
|
|
||||||
|
<div class="safe-area-padding-horizontal">
|
||||||
|
<core-navigation-bar *ngIf="displayNavBar" [items]="navigationItems" [showTitles]="displayTitlesInNavBar"
|
||||||
|
previousTranslate="addon.mod_book.navprevtitle" nextTranslate="addon.mod_book.navnexttitle"
|
||||||
|
(action)="changeChapter($event.id)">
|
||||||
|
</core-navigation-bar>
|
||||||
|
|
||||||
|
<core-swipe-slides [manager]="manager" [options]="slidesOpts">
|
||||||
|
<ng-template let-chapter="item">
|
||||||
|
<div class="ion-padding">
|
||||||
|
<core-format-text [component]="component" [componentId]="cmId" [text]="chapter.content" contextLevel="module"
|
||||||
|
[contextInstanceId]="cmId" [courseId]="courseId"></core-format-text>
|
||||||
|
<div class="ion-margin-top" *ngIf="chapter.tags?.length > 0">
|
||||||
|
<strong>{{ 'core.tag.tags' | translate }}: </strong>
|
||||||
|
<core-tag-list [tags]="chapter.tags"></core-tag-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</core-swipe-slides>
|
||||||
|
</div>
|
||||||
|
</core-loading>
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,40 @@
|
||||||
|
// (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 { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
|
||||||
|
import { CoreSharedModule } from '@/core/shared.module';
|
||||||
|
import { AddonModBookContentsPage } from './contents';
|
||||||
|
import { CoreTagComponentsModule } from '@features/tag/components/components.module';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: AddonModBookContentsPage,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
RouterModule.forChild(routes),
|
||||||
|
CoreSharedModule,
|
||||||
|
CoreTagComponentsModule,
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
AddonModBookContentsPage,
|
||||||
|
],
|
||||||
|
exports: [RouterModule],
|
||||||
|
})
|
||||||
|
export class AddonModBookContentsPageModule {}
|
|
@ -0,0 +1,437 @@
|
||||||
|
// (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 { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||||
|
import { CoreError } from '@classes/errors/error';
|
||||||
|
import { CoreSwipeSlidesItemsManager } from '@classes/items-management/swipe-slides-items-manager';
|
||||||
|
import { CoreSwipeSlidesItemsManagerSource } from '@classes/items-management/swipe-slides-items-manager-source';
|
||||||
|
import { CoreNavigationBarItem } from '@components/navigation-bar/navigation-bar';
|
||||||
|
import { CoreSwipeSlidesComponent, CoreSwipeSlidesOptions } from '@components/swipe-slides/swipe-slides';
|
||||||
|
import { CoreCourseResourceDownloadResult } from '@features/course/classes/main-resource-component';
|
||||||
|
import { CoreCourse } from '@features/course/services/course';
|
||||||
|
import { CoreCourseModuleData } from '@features/course/services/course-helper';
|
||||||
|
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
|
||||||
|
import { CoreTag, CoreTagItem } from '@features/tag/services/tag';
|
||||||
|
import { IonRefresher } from '@ionic/angular';
|
||||||
|
import { CoreApp } from '@services/app';
|
||||||
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
import { CoreTextUtils } from '@services/utils/text';
|
||||||
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
import { Translate } from '@singletons';
|
||||||
|
import { AddonModBookTocComponent } from '../../components/toc/toc';
|
||||||
|
import {
|
||||||
|
AddonModBook,
|
||||||
|
AddonModBookBookWSData,
|
||||||
|
AddonModBookContentsMap,
|
||||||
|
AddonModBookNavStyle,
|
||||||
|
AddonModBookProvider,
|
||||||
|
AddonModBookTocChapter,
|
||||||
|
} from '../../services/book';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page that displays a book contents.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'page-addon-mod-book-contents',
|
||||||
|
templateUrl: 'contents.html',
|
||||||
|
})
|
||||||
|
export class AddonModBookContentsPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
@ViewChild(CoreSwipeSlidesComponent) slides?: CoreSwipeSlidesComponent;
|
||||||
|
|
||||||
|
title!: string;
|
||||||
|
cmId!: number;
|
||||||
|
courseId!: number;
|
||||||
|
initialChapterId?: number;
|
||||||
|
component = AddonModBookProvider.COMPONENT;
|
||||||
|
manager?: CoreSwipeSlidesItemsManager<LoadedChapter, AddonModBookSlidesItemsManagerSource>;
|
||||||
|
warning = '';
|
||||||
|
displayNavBar = true;
|
||||||
|
navigationItems: CoreNavigationBarItem<AddonModBookTocChapter>[] = [];
|
||||||
|
displayTitlesInNavBar = false;
|
||||||
|
slidesOpts: CoreSwipeSlidesOptions = {
|
||||||
|
autoHeight: true,
|
||||||
|
scrollOnChange: 'top',
|
||||||
|
};
|
||||||
|
|
||||||
|
loaded = false;
|
||||||
|
|
||||||
|
protected firstLoad = true;
|
||||||
|
protected managerUnsubscribe?: () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
try {
|
||||||
|
this.cmId = CoreNavigator.getRequiredRouteNumberParam('cmId');
|
||||||
|
this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
|
||||||
|
this.initialChapterId = CoreNavigator.getRouteNumberParam('chapterId');
|
||||||
|
} catch (error) {
|
||||||
|
CoreDomUtils.showErrorModal(error);
|
||||||
|
|
||||||
|
CoreNavigator.back();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const source = new AddonModBookSlidesItemsManagerSource(
|
||||||
|
this.courseId,
|
||||||
|
this.cmId,
|
||||||
|
CoreTag.areTagsAvailableInSite(),
|
||||||
|
this.initialChapterId,
|
||||||
|
);
|
||||||
|
this.manager = new CoreSwipeSlidesItemsManager(source);
|
||||||
|
this.managerUnsubscribe = this.manager.addListener({
|
||||||
|
onSelectedItemUpdated: (item) => {
|
||||||
|
this.onChapterViewed(item.id);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.fetchContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
get module(): CoreCourseModuleData | undefined {
|
||||||
|
return this.manager?.getSource().module;
|
||||||
|
}
|
||||||
|
|
||||||
|
get book(): AddonModBookBookWSData | undefined {
|
||||||
|
return this.manager?.getSource().book;
|
||||||
|
}
|
||||||
|
|
||||||
|
get chapters(): AddonModBookTocChapter[] {
|
||||||
|
return this.manager?.getSource().chapters || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download book contents and load the current chapter.
|
||||||
|
*
|
||||||
|
* @param refresh Whether we're refreshing data.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected async fetchContent(refresh = false): Promise<void> {
|
||||||
|
try {
|
||||||
|
const source = this.manager?.getSource();
|
||||||
|
if (!source) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { module, book } = await source.loadBookData();
|
||||||
|
|
||||||
|
const downloadResult = await this.downloadResourceIfNeeded(module, refresh);
|
||||||
|
|
||||||
|
this.displayNavBar = book.navstyle != AddonModBookNavStyle.TOC_ONLY;
|
||||||
|
this.displayTitlesInNavBar = book.navstyle == AddonModBookNavStyle.TEXT;
|
||||||
|
this.title = book.name;
|
||||||
|
|
||||||
|
// Get contents. No need to refresh, it has been done in downloadResourceIfNeeded.
|
||||||
|
await source.loadContents();
|
||||||
|
|
||||||
|
await source.load();
|
||||||
|
|
||||||
|
if (downloadResult?.failed) {
|
||||||
|
const error = CoreTextUtils.getErrorMessageFromError(downloadResult.error) || downloadResult.error;
|
||||||
|
this.warning = Translate.instant('core.errordownloadingsomefiles') + (error ? ' ' + error : '');
|
||||||
|
} else {
|
||||||
|
this.warning = '';
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.loaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(
|
||||||
|
module: CoreCourseModuleData,
|
||||||
|
refresh = false,
|
||||||
|
): Promise<CoreCourseResourceDownloadResult> {
|
||||||
|
|
||||||
|
const result: CoreCourseResourceDownloadResult = {
|
||||||
|
failed: false,
|
||||||
|
};
|
||||||
|
let contentsAlreadyLoaded = false;
|
||||||
|
|
||||||
|
// Get module status to determine if it needs to be downloaded.
|
||||||
|
const status = await CoreCourseModulePrefetchDelegate.getModuleStatus(module, this.courseId, undefined, refresh);
|
||||||
|
|
||||||
|
if (status !== CoreConstants.DOWNLOADED) {
|
||||||
|
// Download content. This function also loads module contents if needed.
|
||||||
|
try {
|
||||||
|
await CoreCourseModulePrefetchDelegate.downloadModule(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 (!module.contents?.length || (refresh && !contentsAlreadyLoaded)) {
|
||||||
|
// Try to load the contents.
|
||||||
|
const ignoreCache = refresh && CoreApp.isOnline();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await CoreCourse.loadModuleContents(module, undefined, undefined, false, ignoreCache);
|
||||||
|
} catch (error) {
|
||||||
|
// Error loading contents. If we ignored cache, try to get the cached value.
|
||||||
|
if (ignoreCache && !module.contents) {
|
||||||
|
await CoreCourse.loadModuleContents(module);
|
||||||
|
} else if (!module.contents) {
|
||||||
|
// Not able to load contents, throw the error.
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the current chapter.
|
||||||
|
*
|
||||||
|
* @param chapterId Chapter to load.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
changeChapter(chapterId: number): void {
|
||||||
|
if (!chapterId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.slides?.slideToItem({ id: chapterId });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the data.
|
||||||
|
*
|
||||||
|
* @param refresher Refresher.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async doRefresh(refresher?: IonRefresher): Promise<void> {
|
||||||
|
if (this.manager) {
|
||||||
|
await CoreUtils.ignoreErrors(Promise.all([
|
||||||
|
this.manager.getSource().invalidateContent(),
|
||||||
|
CoreCourseModulePrefetchDelegate.invalidateCourseUpdates(this.courseId), // To detect if book was updated.
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
await CoreUtils.ignoreErrors(this.fetchContent(true));
|
||||||
|
|
||||||
|
refresher?.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the TOC.
|
||||||
|
*/
|
||||||
|
async showToc(): Promise<void> {
|
||||||
|
// Create the toc modal.
|
||||||
|
const visibleChapter = this.manager?.getSelectedItem();
|
||||||
|
|
||||||
|
const modalData = await CoreDomUtils.openSideModal<number>({
|
||||||
|
component: AddonModBookTocComponent,
|
||||||
|
componentProps: {
|
||||||
|
moduleId: this.cmId,
|
||||||
|
chapters: this.chapters,
|
||||||
|
selected: visibleChapter,
|
||||||
|
courseId: this.courseId,
|
||||||
|
book: this.book,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (modalData) {
|
||||||
|
this.changeChapter(modalData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update data related to chapter being viewed.
|
||||||
|
*
|
||||||
|
* @param chapterId Chapter viewed.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected async onChapterViewed(chapterId: number): Promise<void> {
|
||||||
|
// Don't log the chapter ID when the user has just opened the book.
|
||||||
|
const logChapterId = this.firstLoad;
|
||||||
|
this.firstLoad = false;
|
||||||
|
|
||||||
|
if (this.displayNavBar) {
|
||||||
|
this.navigationItems = this.getNavigationItems(chapterId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.book) {
|
||||||
|
AddonModBook.storeLastChapterViewed(this.book.id, chapterId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.module) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chapter loaded, log view.
|
||||||
|
await CoreUtils.ignoreErrors(AddonModBook.logView(
|
||||||
|
this.module.instance,
|
||||||
|
logChapterId ? chapterId : undefined,
|
||||||
|
this.module.name,
|
||||||
|
));
|
||||||
|
|
||||||
|
const currentChapterIndex = this.chapters.findIndex((chapter) => chapter.id == chapterId);
|
||||||
|
const isLastChapter = currentChapterIndex < 0 || this.chapters[currentChapterIndex + 1] === undefined;
|
||||||
|
|
||||||
|
// Module is completed when last chapter is viewed, so we only check completion if the last is reached.
|
||||||
|
if (isLastChapter) {
|
||||||
|
CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts chapters to navigation items.
|
||||||
|
*
|
||||||
|
* @param chapterId Current chapter Id.
|
||||||
|
* @return Navigation items.
|
||||||
|
*/
|
||||||
|
protected getNavigationItems(chapterId: number): CoreNavigationBarItem<AddonModBookTocChapter>[] {
|
||||||
|
return this.chapters.map((chapter) => ({
|
||||||
|
item: chapter,
|
||||||
|
title: chapter.title,
|
||||||
|
current: chapter.id == chapterId,
|
||||||
|
enabled: true,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.managerUnsubscribe && this.managerUnsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoadedChapter = {
|
||||||
|
id: number;
|
||||||
|
content?: string;
|
||||||
|
tags?: CoreTagItem[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to manage swiping within a collection of chapters.
|
||||||
|
*/
|
||||||
|
class AddonModBookSlidesItemsManagerSource extends CoreSwipeSlidesItemsManagerSource<LoadedChapter> {
|
||||||
|
|
||||||
|
readonly COURSE_ID: number;
|
||||||
|
readonly CM_ID: number;
|
||||||
|
readonly TAGS_ENABLED: boolean;
|
||||||
|
|
||||||
|
module?: CoreCourseModuleData;
|
||||||
|
book?: AddonModBookBookWSData;
|
||||||
|
chapters: AddonModBookTocChapter[] = [];
|
||||||
|
contentsMap: AddonModBookContentsMap = {};
|
||||||
|
|
||||||
|
constructor(courseId: number, cmId: number, tagsEnabled: boolean, initialChapterId?: number) {
|
||||||
|
super(initialChapterId ? { id: initialChapterId } : undefined);
|
||||||
|
|
||||||
|
this.COURSE_ID = courseId;
|
||||||
|
this.CM_ID = cmId;
|
||||||
|
this.TAGS_ENABLED = tagsEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
getItemId(item: LoadedChapter): string | number {
|
||||||
|
return item.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load book data from WS.
|
||||||
|
*
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async loadBookData(): Promise<{ module: CoreCourseModuleData; book: AddonModBookBookWSData }> {
|
||||||
|
this.module = await CoreCourse.getModule(this.CM_ID, this.COURSE_ID);
|
||||||
|
this.book = await AddonModBook.getBook(this.COURSE_ID, this.CM_ID);
|
||||||
|
|
||||||
|
if (!this.initialItem) {
|
||||||
|
// No chapter ID specified. Calculate last viewed.
|
||||||
|
const lastViewed = await AddonModBook.getLastChapterViewed(this.book.id);
|
||||||
|
|
||||||
|
if (lastViewed) {
|
||||||
|
this.initialItem = { id: lastViewed };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
module: this.module,
|
||||||
|
book: this.book,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load module contents.
|
||||||
|
*/
|
||||||
|
async loadContents(): Promise<void> {
|
||||||
|
if (!this.module) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const contents = await CoreCourse.getModuleContents(this.module, this.COURSE_ID);
|
||||||
|
|
||||||
|
this.contentsMap = AddonModBook.getContentsMap(contents);
|
||||||
|
this.chapters = AddonModBook.getTocList(contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
protected async loadItems(): Promise<LoadedChapter[]> {
|
||||||
|
try {
|
||||||
|
const newChapters = await Promise.all(this.chapters.map(async (chapter) => {
|
||||||
|
const content = await AddonModBook.getChapterContent(this.contentsMap, chapter.id, this.CM_ID);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: chapter.id,
|
||||||
|
content,
|
||||||
|
tags: this.TAGS_ENABLED ? this.contentsMap[chapter.id].tags : [],
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
|
||||||
|
return newChapters;
|
||||||
|
} catch (error) {
|
||||||
|
if (!CoreTextUtils.getErrorMessageFromError(error)) {
|
||||||
|
throw new CoreError(Translate.instant('addon.mod_book.errorchapter'));
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the invalidate content function.
|
||||||
|
*
|
||||||
|
* @return Resolved when done.
|
||||||
|
*/
|
||||||
|
invalidateContent(): Promise<void> {
|
||||||
|
return AddonModBook.invalidateContent(this.CM_ID, this.COURSE_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -19,6 +19,6 @@
|
||||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
</ion-refresher>
|
</ion-refresher>
|
||||||
|
|
||||||
<addon-mod-book-index [module]="module" [courseId]="courseId" [initialChapterId]="chapterId" (dataRetrieved)="updateData($event)">
|
<addon-mod-book-index [module]="module" [courseId]="courseId" (dataRetrieved)="updateData($event)">
|
||||||
</addon-mod-book-index>
|
</addon-mod-book-index>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
|
@ -12,30 +12,19 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
import { Component, ViewChild } from '@angular/core';
|
||||||
import { CoreCourseModuleMainActivityPage } from '@features/course/classes/main-activity-page';
|
import { CoreCourseModuleMainActivityPage } from '@features/course/classes/main-activity-page';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
|
||||||
import { AddonModBookIndexComponent } from '../../components/index/index';
|
import { AddonModBookIndexComponent } from '../../components/index/index';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays a book.
|
* Page that displays a book entry page.
|
||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-addon-mod-book-index',
|
selector: 'page-addon-mod-book-index',
|
||||||
templateUrl: 'index.html',
|
templateUrl: 'index.html',
|
||||||
})
|
})
|
||||||
export class AddonModBookIndexPage extends CoreCourseModuleMainActivityPage<AddonModBookIndexComponent> implements OnInit {
|
export class AddonModBookIndexPage extends CoreCourseModuleMainActivityPage<AddonModBookIndexComponent> {
|
||||||
|
|
||||||
@ViewChild(AddonModBookIndexComponent) activityComponent?: AddonModBookIndexComponent;
|
@ViewChild(AddonModBookIndexComponent) activityComponent?: AddonModBookIndexComponent;
|
||||||
|
|
||||||
chapterId?: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Component being initialized.
|
|
||||||
*/
|
|
||||||
ngOnInit(): void {
|
|
||||||
super.ngOnInit();
|
|
||||||
this.chapterId = CoreNavigator.getRouteNumberParam('chapterId');
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,11 @@ import { CoreTextUtils } from '@services/utils/text';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreFile } from '@services/file';
|
import { CoreFile } from '@services/file';
|
||||||
import { CoreError } from '@classes/errors/error';
|
import { CoreError } from '@classes/errors/error';
|
||||||
|
import { lazyMap, LazyMap } from '@/core/utils/lazy-map';
|
||||||
|
import { asyncInstance, AsyncInstance } from '@/core/utils/async-instance';
|
||||||
|
import { CoreDatabaseTable } from '@classes/database/database-table';
|
||||||
|
import { AddonModBookLastChapterViewedDBRecord, LAST_CHAPTER_VIEWED_TABLE } from './database/book';
|
||||||
|
import { CoreDatabaseCachingStrategy } from '@classes/database/database-table-proxy';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constants to define how the chapters and subchapters of a book should be displayed in that table of contents.
|
* Constants to define how the chapters and subchapters of a book should be displayed in that table of contents.
|
||||||
|
@ -56,6 +61,19 @@ export class AddonModBookProvider {
|
||||||
|
|
||||||
static readonly COMPONENT = 'mmaModBook';
|
static readonly COMPONENT = 'mmaModBook';
|
||||||
|
|
||||||
|
protected lastChapterViewedTables: LazyMap<AsyncInstance<CoreDatabaseTable<AddonModBookLastChapterViewedDBRecord>>>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.lastChapterViewedTables = lazyMap(
|
||||||
|
siteId => asyncInstance(
|
||||||
|
() => CoreSites.getSiteTable(LAST_CHAPTER_VIEWED_TABLE, {
|
||||||
|
siteId,
|
||||||
|
config: { cachingStrategy: CoreDatabaseCachingStrategy.None },
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a book by course module ID.
|
* Get a book by course module ID.
|
||||||
*
|
*
|
||||||
|
@ -216,6 +234,24 @@ export class AddonModBookProvider {
|
||||||
return chapters[0].id;
|
return chapters[0].id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get last chapter viewed in the app for a book.
|
||||||
|
*
|
||||||
|
* @param id Book instance ID.
|
||||||
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @return Promise resolved with last chapter viewed, undefined if none.
|
||||||
|
*/
|
||||||
|
async getLastChapterViewed(id: number, siteId?: string): Promise<number | undefined> {
|
||||||
|
try {
|
||||||
|
const site = await CoreSites.getSite(siteId);
|
||||||
|
const entry = await this.lastChapterViewedTables[site.getId()].getOneByPrimaryKey({ id });
|
||||||
|
|
||||||
|
return entry.chapterid;
|
||||||
|
} catch {
|
||||||
|
// No last chapter viewed.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the book toc as an array.
|
* Get the book toc as an array.
|
||||||
*
|
*
|
||||||
|
@ -363,6 +399,20 @@ export class AddonModBookProvider {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store last chapter viewed in the app for a book.
|
||||||
|
*
|
||||||
|
* @param id Book instance ID.
|
||||||
|
* @param chapterId Chapter ID.
|
||||||
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @return Promise resolved with last chapter viewed, undefined if none.
|
||||||
|
*/
|
||||||
|
async storeLastChapterViewed(id: number, chapterId: number, siteId?: string): Promise<void> {
|
||||||
|
const site = await CoreSites.getSite(siteId);
|
||||||
|
|
||||||
|
await this.lastChapterViewedTables[site.getId()].insert({ id, chapterid: chapterId });
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AddonModBook = makeSingleton(AddonModBookProvider);
|
export const AddonModBook = makeSingleton(AddonModBookProvider);
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
// (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 { CoreSiteSchema } from '@services/sites';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Database variables for AddonModBookProvider service.
|
||||||
|
*/
|
||||||
|
export const LAST_CHAPTER_VIEWED_TABLE = 'addon_mod_book_last_chapter_viewed';
|
||||||
|
export const BOOK_SITE_SCHEMA: CoreSiteSchema = {
|
||||||
|
name: 'AddonModBookProvider',
|
||||||
|
version: 1,
|
||||||
|
tables: [
|
||||||
|
{
|
||||||
|
name: LAST_CHAPTER_VIEWED_TABLE,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
type: 'INTEGER',
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'chapterid',
|
||||||
|
type: 'INTEGER',
|
||||||
|
notNull: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AddonModBookLastChapterViewedDBRecord = {
|
||||||
|
id: number;
|
||||||
|
chapterid: number;
|
||||||
|
};
|
|
@ -15,6 +15,7 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Params } from '@angular/router';
|
import { Params } from '@angular/router';
|
||||||
import { CoreContentLinksModuleIndexHandler } from '@features/contentlinks/classes/module-index-handler';
|
import { CoreContentLinksModuleIndexHandler } from '@features/contentlinks/classes/module-index-handler';
|
||||||
|
import { CoreNavigationOptions } from '@services/navigator';
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
import { AddonModBook } from '../book';
|
import { AddonModBook } from '../book';
|
||||||
|
|
||||||
|
@ -31,20 +32,32 @@ export class AddonModBookIndexLinkHandlerService extends CoreContentLinksModuleI
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the mod params necessary to open an activity.
|
* @inheritdoc
|
||||||
*
|
*/
|
||||||
* @param url The URL to treat.
|
getModNavOptions(url: string, params: Record<string, string>): CoreNavigationOptions {
|
||||||
* @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
|
const chapterId = params.chapterid ? parseInt(params.chapterid, 10) : undefined;
|
||||||
* @return List of params to pass to navigateToModule / navigateToModuleByInstance.
|
|
||||||
|
return {
|
||||||
|
nextNavigation: {
|
||||||
|
path: 'contents',
|
||||||
|
options: {
|
||||||
|
params: {
|
||||||
|
chapterId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
getPageParams(url: string, params: Record<string, string>): Params {
|
getPageParams(url: string, params: Record<string, string>): Params {
|
||||||
return params.chapterid ? { chapterId: parseInt(params.chapterid, 10) } : {};
|
return params.chapterid ? { chapterId: parseInt(params.chapterid, 10) } : {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the handler is enabled for a certain site (site + user) and a URL.
|
* @inheritdoc
|
||||||
*
|
|
||||||
* @return Whether the handler is enabled for the URL and site.
|
|
||||||
*/
|
*/
|
||||||
isEnabled(siteId: string): Promise<boolean> {
|
isEnabled(siteId: string): Promise<boolean> {
|
||||||
return AddonModBook.isPluginEnabled(siteId);
|
return AddonModBook.isPluginEnabled(siteId);
|
||||||
|
|
|
@ -55,7 +55,10 @@ export class AddonModFeedbackPushClickHandlerService implements CorePushNotifica
|
||||||
if (notification.name == 'submission') {
|
if (notification.name == 'submission') {
|
||||||
return AddonModFeedbackHelper.handleShowEntriesLink(contextUrlParams, notification.site);
|
return AddonModFeedbackHelper.handleShowEntriesLink(contextUrlParams, notification.site);
|
||||||
} else {
|
} else {
|
||||||
return CoreCourseHelper.navigateToModule(moduleId, notification.site, courseId);
|
return CoreCourseHelper.navigateToModule(moduleId, {
|
||||||
|
courseId,
|
||||||
|
siteId: notification.site,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,11 @@ export class AddonModLessonGradeLinkHandlerService extends CoreContentLinksModul
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// User cannot view the report, go to lesson index.
|
// User cannot view the report, go to lesson index.
|
||||||
CoreCourseHelper.navigateToModule(moduleId, siteId, module.course, module.section);
|
CoreCourseHelper.navigateToModule(moduleId, {
|
||||||
|
courseId: module.course,
|
||||||
|
sectionId: module.section,
|
||||||
|
siteId,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true);
|
CoreDomUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true);
|
||||||
|
|
|
@ -61,7 +61,10 @@ export class AddonModLessonIndexLinkHandlerService extends CoreContentLinksModul
|
||||||
if (params.userpassword) {
|
if (params.userpassword) {
|
||||||
this.navigateToModuleWithPassword(parseInt(params.id, 10), courseId!, params.userpassword, siteId);
|
this.navigateToModuleWithPassword(parseInt(params.id, 10), courseId!, params.userpassword, siteId);
|
||||||
} else {
|
} else {
|
||||||
CoreCourseHelper.navigateToModule(parseInt(params.id, 10), siteId, courseId);
|
CoreCourseHelper.navigateToModule(parseInt(params.id, 10), {
|
||||||
|
courseId,
|
||||||
|
siteId,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}];
|
}];
|
||||||
|
@ -94,10 +97,17 @@ export class AddonModLessonIndexLinkHandlerService extends CoreContentLinksModul
|
||||||
// Store the password so it's automatically used.
|
// Store the password so it's automatically used.
|
||||||
await CoreUtils.ignoreErrors(AddonModLesson.storePassword(module.instance, password, siteId));
|
await CoreUtils.ignoreErrors(AddonModLesson.storePassword(module.instance, password, siteId));
|
||||||
|
|
||||||
await CoreCourseHelper.navigateToModule(moduleId, siteId, module.course, module.section);
|
await CoreCourseHelper.navigateToModule(moduleId, {
|
||||||
|
courseId: module.course,
|
||||||
|
sectionId: module.section,
|
||||||
|
siteId,
|
||||||
|
});
|
||||||
} catch {
|
} catch {
|
||||||
// Error, go to index page.
|
// Error, go to index page.
|
||||||
await CoreCourseHelper.navigateToModule(moduleId, siteId, courseId);
|
await CoreCourseHelper.navigateToModule(moduleId, {
|
||||||
|
courseId,
|
||||||
|
siteId,
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
modal.dismiss();
|
modal.dismiss();
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,10 @@ export class AddonModQuizPushClickHandlerService implements CorePushNotification
|
||||||
|
|
||||||
await CoreUtils.ignoreErrors(AddonModQuiz.invalidateContent(moduleId, courseId, notification.site));
|
await CoreUtils.ignoreErrors(AddonModQuiz.invalidateContent(moduleId, courseId, notification.site));
|
||||||
|
|
||||||
return CoreCourseHelper.navigateToModule(moduleId, notification.site, courseId);
|
return CoreCourseHelper.navigateToModule(moduleId, {
|
||||||
|
courseId,
|
||||||
|
siteId: notification.site,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,10 +78,11 @@ export class CoreContentLinksModuleGradeHandler extends CoreContentLinksHandlerB
|
||||||
// No user specified or current user. Navigate to module.
|
// No user specified or current user. Navigate to module.
|
||||||
CoreCourseHelper.navigateToModule(
|
CoreCourseHelper.navigateToModule(
|
||||||
Number(params.id),
|
Number(params.id),
|
||||||
siteId,
|
{
|
||||||
courseId,
|
courseId,
|
||||||
undefined,
|
modName: this.useModNameToGetModule ? this.modName : undefined,
|
||||||
this.useModNameToGetModule ? this.modName : undefined,
|
siteId,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
} else if (this.canReview) {
|
} else if (this.canReview) {
|
||||||
// Use the goToReview function.
|
// Use the goToReview function.
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { CoreContentLinksHandlerBase } from './base-handler';
|
||||||
import { Params } from '@angular/router';
|
import { Params } from '@angular/router';
|
||||||
import { CoreContentLinksAction } from '../services/contentlinks-delegate';
|
import { CoreContentLinksAction } from '../services/contentlinks-delegate';
|
||||||
import { CoreCourseHelper } from '@features/course/services/course-helper';
|
import { CoreCourseHelper } from '@features/course/services/course-helper';
|
||||||
|
import { CoreNavigationOptions } from '@services/navigator';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler to handle URLs pointing to the index of a module.
|
* Handler to handle URLs pointing to the index of a module.
|
||||||
|
@ -58,12 +59,27 @@ export class CoreContentLinksModuleIndexHandler extends CoreContentLinksHandlerB
|
||||||
* @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
|
* @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
|
||||||
* @param courseId Course ID related to the URL. Optional but recommended.
|
* @param courseId Course ID related to the URL. Optional but recommended.
|
||||||
* @return List of params to pass to navigateToModule / navigateToModuleByInstance.
|
* @return List of params to pass to navigateToModule / navigateToModuleByInstance.
|
||||||
|
* @deprecated since 4.0
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
getPageParams(url: string, params: Record<string, string>, courseId?: number): Params {
|
getPageParams(url: string, params: Record<string, string>, courseId?: number): Params {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the navigation options to open the module.
|
||||||
|
*
|
||||||
|
* @param url The URL to treat.
|
||||||
|
* @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
|
||||||
|
* @param siteId The site ID.
|
||||||
|
* @param courseId Course ID related to the URL. Optional but recommended.
|
||||||
|
* @return Navigation options to open the module.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
getModNavOptions(url: string, params: Record<string, string>, siteId: string, courseId?: number): CoreNavigationOptions {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the list of actions for a link (url).
|
* Get the list of actions for a link (url).
|
||||||
*
|
*
|
||||||
|
@ -81,7 +97,18 @@ export class CoreContentLinksModuleIndexHandler extends CoreContentLinksHandlerB
|
||||||
): CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
|
): CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
|
||||||
|
|
||||||
courseId = Number(courseId || params.courseid || params.cid);
|
courseId = Number(courseId || params.courseid || params.cid);
|
||||||
const pageParams = this.getPageParams(url, params, courseId);
|
const getModNavOptions = (siteId: string): CoreNavigationOptions => {
|
||||||
|
let modNavOptions = this.getModNavOptions(url, params, siteId, courseId);
|
||||||
|
if (!modNavOptions) {
|
||||||
|
// Use the old function, currently deprecated.
|
||||||
|
const pageParams = this.getPageParams(url, params, courseId);
|
||||||
|
if (pageParams && Object.keys(pageParams).length > 0) {
|
||||||
|
modNavOptions = { params: pageParams };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return modNavOptions;
|
||||||
|
};
|
||||||
|
|
||||||
if (this.instanceIdParam && params[this.instanceIdParam] !== undefined) {
|
if (this.instanceIdParam && params[this.instanceIdParam] !== undefined) {
|
||||||
const instanceId = parseInt(params[this.instanceIdParam], 10);
|
const instanceId = parseInt(params[this.instanceIdParam], 10);
|
||||||
|
@ -91,11 +118,12 @@ export class CoreContentLinksModuleIndexHandler extends CoreContentLinksHandlerB
|
||||||
CoreCourseHelper.navigateToModuleByInstance(
|
CoreCourseHelper.navigateToModuleByInstance(
|
||||||
instanceId,
|
instanceId,
|
||||||
this.modName,
|
this.modName,
|
||||||
siteId,
|
{
|
||||||
courseId,
|
courseId,
|
||||||
undefined,
|
useModNameToGetModule: this.useModNameToGetModule,
|
||||||
this.useModNameToGetModule,
|
modNavOptions: getModNavOptions(siteId),
|
||||||
pageParams,
|
siteId,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
}];
|
}];
|
||||||
|
@ -105,11 +133,12 @@ export class CoreContentLinksModuleIndexHandler extends CoreContentLinksHandlerB
|
||||||
action: (siteId) => {
|
action: (siteId) => {
|
||||||
CoreCourseHelper.navigateToModule(
|
CoreCourseHelper.navigateToModule(
|
||||||
parseInt(params.id, 10),
|
parseInt(params.id, 10),
|
||||||
siteId,
|
{
|
||||||
courseId,
|
courseId,
|
||||||
undefined,
|
modName: this.useModNameToGetModule ? this.modName : undefined,
|
||||||
this.useModNameToGetModule ? this.modName : undefined,
|
modNavOptions: getModNavOptions(siteId),
|
||||||
pageParams,
|
siteId,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
}];
|
}];
|
||||||
|
|
|
@ -311,7 +311,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
|
||||||
error,
|
error,
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
error = CoreTextUtils.getErrorMessageFromError(error) || error;
|
error = CoreTextUtils.getErrorMessageFromError(error) || '';
|
||||||
|
|
||||||
return Translate.instant('core.errordownloadingsomefiles') + (error ? ' ' + error : '');
|
return Translate.instant('core.errordownloadingsomefiles') + (error ? ' ' + error : '');
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ import { CoreCourse, CoreCourseModuleCompletionStatus, CoreCourseWSSection } fro
|
||||||
import { CoreCourseHelper, CoreCourseModuleData } from '@features/course/services/course-helper';
|
import { CoreCourseHelper, CoreCourseModuleData } from '@features/course/services/course-helper';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { CoreTextUtils } from '@services/utils/text';
|
import { CoreTextUtils } from '@services/utils/text';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigationOptions, CoreNavigator } from '@services/navigator';
|
||||||
import { CONTENTS_PAGE_NAME } from '@features/course/course.module';
|
import { CONTENTS_PAGE_NAME } from '@features/course/course.module';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreCollapsibleHeaderDirective } from '@directives/collapsible-header';
|
import { CoreCollapsibleHeaderDirective } from '@directives/collapsible-header';
|
||||||
|
@ -57,7 +57,7 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
|
||||||
protected sections: CoreCourseWSSection[] = []; // List of course sections.
|
protected sections: CoreCourseWSSection[] = []; // List of course sections.
|
||||||
protected firstTabName?: string;
|
protected firstTabName?: string;
|
||||||
protected module?: CoreCourseModuleData;
|
protected module?: CoreCourseModuleData;
|
||||||
protected modParams?: Params;
|
protected modNavOptions?: CoreNavigationOptions;
|
||||||
protected isGuest = false;
|
protected isGuest = false;
|
||||||
protected contentsTab: CoreTabsOutletTab & { pageParams: Params } = {
|
protected contentsTab: CoreTabsOutletTab & { pageParams: Params } = {
|
||||||
page: CONTENTS_PAGE_NAME,
|
page: CONTENTS_PAGE_NAME,
|
||||||
|
@ -136,8 +136,15 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
this.firstTabName = CoreNavigator.getRouteParam('selectedTab');
|
this.firstTabName = CoreNavigator.getRouteParam('selectedTab');
|
||||||
this.module = CoreNavigator.getRouteParam<CoreCourseModuleData>('module');
|
this.module = CoreNavigator.getRouteParam<CoreCourseModuleData>('module');
|
||||||
this.modParams = CoreNavigator.getRouteParam<Params>('modParams');
|
|
||||||
this.isGuest = !!CoreNavigator.getRouteBooleanParam('isGuest');
|
this.isGuest = !!CoreNavigator.getRouteBooleanParam('isGuest');
|
||||||
|
this.modNavOptions = CoreNavigator.getRouteParam<CoreNavigationOptions>('modNavOptions');
|
||||||
|
if (!this.modNavOptions) {
|
||||||
|
// Fallback to old way of passing params. @deprecated since 4.0.
|
||||||
|
const modParams = CoreNavigator.getRouteParam<Params>('modParams');
|
||||||
|
if (modParams) {
|
||||||
|
this.modNavOptions = { params: modParams };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.currentPagePath = CoreNavigator.getCurrentPath();
|
this.currentPagePath = CoreNavigator.getCurrentPath();
|
||||||
this.contentsTab.page = CoreTextUtils.concatenatePaths(this.currentPagePath, this.contentsTab.page);
|
this.contentsTab.page = CoreTextUtils.concatenatePaths(this.currentPagePath, this.contentsTab.page);
|
||||||
|
@ -171,7 +178,10 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Now that the first tab has been selected we can load the module.
|
// Now that the first tab has been selected we can load the module.
|
||||||
CoreCourseHelper.openModule(this.module, this.course.id, this.contentsTab.pageParams.sectionId, this.modParams);
|
CoreCourseHelper.openModule(this.module, this.course.id, {
|
||||||
|
sectionId: this.contentsTab.pageParams.sectionId,
|
||||||
|
modNavOptions: this.modNavOptions,
|
||||||
|
});
|
||||||
|
|
||||||
delete this.module;
|
delete this.module;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1573,36 +1573,27 @@ export class CoreCourseHelperProvider {
|
||||||
*
|
*
|
||||||
* @param instanceId Activity instance ID.
|
* @param instanceId Activity instance ID.
|
||||||
* @param modName Module name of the activity.
|
* @param modName Module name of the activity.
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param options Other options.
|
||||||
* @param courseId Course ID. If not defined we'll try to retrieve it from the site.
|
|
||||||
* @param sectionId Section the module belongs to. If not defined we'll try to retrieve it from the site.
|
|
||||||
* @param useModNameToGetModule If true, the app will retrieve all modules of this type with a single WS call. This reduces the
|
|
||||||
* number of WS calls, but it isn't recommended for modules that can return a lot of contents.
|
|
||||||
* @param modParams Params to pass to the module
|
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async navigateToModuleByInstance(
|
async navigateToModuleByInstance(
|
||||||
instanceId: number,
|
instanceId: number,
|
||||||
modName: string,
|
modName: string,
|
||||||
siteId?: string,
|
options: CoreCourseNavigateToModuleByInstanceOptions = {},
|
||||||
courseId?: number,
|
|
||||||
sectionId?: number,
|
|
||||||
useModNameToGetModule: boolean = false,
|
|
||||||
modParams?: Params,
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
|
||||||
const modal = await CoreDomUtils.showModalLoading();
|
const modal = await CoreDomUtils.showModalLoading();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const module = await CoreCourse.getModuleBasicInfoByInstance(instanceId, modName, { siteId });
|
const module = await CoreCourse.getModuleBasicInfoByInstance(instanceId, modName, { siteId: options.siteId });
|
||||||
|
|
||||||
this.navigateToModule(
|
this.navigateToModule(
|
||||||
module.id,
|
module.id,
|
||||||
siteId,
|
{
|
||||||
module.course,
|
...options,
|
||||||
sectionId,
|
courseId: module.course,
|
||||||
useModNameToGetModule ? modName : undefined,
|
modName: options.useModNameToGetModule ? modName : undefined,
|
||||||
modParams,
|
},
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true);
|
CoreDomUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true);
|
||||||
|
@ -1616,23 +1607,16 @@ export class CoreCourseHelperProvider {
|
||||||
* Navigate to a module.
|
* Navigate to a module.
|
||||||
*
|
*
|
||||||
* @param moduleId Module's ID.
|
* @param moduleId Module's ID.
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param options Other options.
|
||||||
* @param courseId Course ID. If not defined we'll try to retrieve it from the site.
|
|
||||||
* @param sectionId Section the module belongs to. If not defined we'll try to retrieve it from the site.
|
|
||||||
* @param modName If set, the app will retrieve all modules of this type with a single WS call. This reduces the
|
|
||||||
* number of WS calls, but it isn't recommended for modules that can return a lot of contents.
|
|
||||||
* @param modParams Params to pass to the module
|
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async navigateToModule(
|
async navigateToModule(
|
||||||
moduleId: number,
|
moduleId: number,
|
||||||
siteId?: string,
|
options: CoreCourseNavigateToModuleOptions = {},
|
||||||
courseId?: number,
|
|
||||||
sectionId?: number,
|
|
||||||
modName?: string,
|
|
||||||
modParams?: Params,
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
siteId = siteId || CoreSites.getCurrentSiteId();
|
const siteId = options.siteId || CoreSites.getCurrentSiteId();
|
||||||
|
let courseId = options.courseId;
|
||||||
|
let sectionId = options.sectionId;
|
||||||
|
|
||||||
const modal = await CoreDomUtils.showModalLoading();
|
const modal = await CoreDomUtils.showModalLoading();
|
||||||
|
|
||||||
|
@ -1651,10 +1635,9 @@ export class CoreCourseHelperProvider {
|
||||||
const site = await CoreSites.getSite(siteId);
|
const site = await CoreSites.getSite(siteId);
|
||||||
|
|
||||||
// Get the module.
|
// Get the module.
|
||||||
const module =
|
const module = await CoreCourse.getModule(moduleId, courseId, sectionId, false, false, siteId, options.modName);
|
||||||
await CoreCourse.getModule(moduleId, courseId, sectionId, false, false, siteId, modName);
|
|
||||||
|
|
||||||
if (CoreSites.getCurrentSiteId() == site.getId()) {
|
if (CoreSites.getCurrentSiteId() === site.getId()) {
|
||||||
// Try to use the module's handler to navigate cleanly.
|
// Try to use the module's handler to navigate cleanly.
|
||||||
module.handlerData = await CoreCourseModuleDelegate.getModuleDataFor(
|
module.handlerData = await CoreCourseModuleDelegate.getModuleDataFor(
|
||||||
module.modname,
|
module.modname,
|
||||||
|
@ -1667,7 +1650,7 @@ export class CoreCourseHelperProvider {
|
||||||
if (module.handlerData?.action) {
|
if (module.handlerData?.action) {
|
||||||
modal.dismiss();
|
modal.dismiss();
|
||||||
|
|
||||||
return module.handlerData.action(new Event('click'), module, courseId, { params: modParams });
|
return module.handlerData.action(new Event('click'), module, courseId, options.modNavOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1675,7 +1658,7 @@ export class CoreCourseHelperProvider {
|
||||||
course: { id: courseId },
|
course: { id: courseId },
|
||||||
module,
|
module,
|
||||||
sectionId,
|
sectionId,
|
||||||
modParams,
|
modNavOptions: options.modNavOptions,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (courseId == site.getSiteHomeId()) {
|
if (courseId == site.getSiteHomeId()) {
|
||||||
|
@ -1704,23 +1687,25 @@ export class CoreCourseHelperProvider {
|
||||||
*
|
*
|
||||||
* @param module The module to open.
|
* @param module The module to open.
|
||||||
* @param courseId The course ID of the module.
|
* @param courseId The course ID of the module.
|
||||||
* @param sectionId The section ID of the module.
|
* @param options Other options.
|
||||||
* @param modParams Params to pass to the module
|
|
||||||
* @param True if module can be opened, false otherwise.
|
* @param True if module can be opened, false otherwise.
|
||||||
*/
|
*/
|
||||||
async openModule(module: CoreCourseModuleData, courseId: number, sectionId?: number, modParams?: Params): Promise<boolean> {
|
async openModule(module: CoreCourseModuleData, courseId: number, options: CoreCourseOpenModuleOptions = {}): Promise<boolean> {
|
||||||
if (!module.handlerData) {
|
if (!module.handlerData) {
|
||||||
module.handlerData = await CoreCourseModuleDelegate.getModuleDataFor(
|
module.handlerData = await CoreCourseModuleDelegate.getModuleDataFor(
|
||||||
module.modname,
|
module.modname,
|
||||||
module,
|
module,
|
||||||
courseId,
|
courseId,
|
||||||
sectionId,
|
options.sectionId,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (module.handlerData?.action) {
|
if (module.handlerData?.action) {
|
||||||
module.handlerData.action(new Event('click'), module, courseId, { animated: false, params: modParams });
|
module.handlerData.action(new Event('click'), module, courseId, {
|
||||||
|
animated: false,
|
||||||
|
...options.modNavOptions,
|
||||||
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -2206,6 +2191,39 @@ export type CoreCourseConfirmPrefetchCoursesOptions = CoreCoursePrefetchCoursesO
|
||||||
onProgress?: (data: CoreCourseCoursesProgress) => void;
|
onProgress?: (data: CoreCourseCoursesProgress) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common options for navigate to module functions.
|
||||||
|
*/
|
||||||
|
type CoreCourseNavigateToModuleCommonOptions = {
|
||||||
|
courseId?: number; // Course ID. If not defined we'll try to retrieve it from the site.
|
||||||
|
sectionId?: number; // Section the module belongs to. If not defined we'll try to retrieve it from the site.
|
||||||
|
modNavOptions?: CoreNavigationOptions; // Navigation options to open the module, including params to pass to the module.
|
||||||
|
siteId?: string; // Site ID. If not defined, current site.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for navigate to module by instance function.
|
||||||
|
*/
|
||||||
|
export type CoreCourseNavigateToModuleByInstanceOptions = CoreCourseNavigateToModuleCommonOptions & {
|
||||||
|
// True to retrieve all instances with a single WS call. Not recommended if can return a lot of contents.
|
||||||
|
useModNameToGetModule?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for navigate to module function.
|
||||||
|
*/
|
||||||
|
export type CoreCourseNavigateToModuleOptions = CoreCourseNavigateToModuleCommonOptions & {
|
||||||
|
modName?: string; // To retrieve all instances with a single WS call. Not recommended if can return a lot of contents.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for open module function.
|
||||||
|
*/
|
||||||
|
export type CoreCourseOpenModuleOptions = {
|
||||||
|
sectionId?: number; // Section the module belongs to.
|
||||||
|
modNavOptions?: CoreNavigationOptions; // Navigation options to open the module, including params to pass to the module.
|
||||||
|
};
|
||||||
|
|
||||||
type ComponentWithContextMenu = {
|
type ComponentWithContextMenu = {
|
||||||
prefetchStatusIcon?: string;
|
prefetchStatusIcon?: string;
|
||||||
isDestroyed?: boolean;
|
isDestroyed?: boolean;
|
||||||
|
|
|
@ -26,7 +26,7 @@ import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
import { CoreCourseHelper, CoreCourseModuleData } from '@features/course/services/course-helper';
|
import { CoreCourseHelper, CoreCourseModuleData } from '@features/course/services/course-helper';
|
||||||
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
|
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
|
||||||
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
|
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigationOptions, CoreNavigator } from '@services/navigator';
|
||||||
import { CoreBlockHelper } from '@features/block/services/block-helper';
|
import { CoreBlockHelper } from '@features/block/services/block-helper';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
|
||||||
|
@ -73,8 +73,15 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
const module = CoreNavigator.getRouteParam<CoreCourseModuleData>('module');
|
const module = CoreNavigator.getRouteParam<CoreCourseModuleData>('module');
|
||||||
if (module) {
|
if (module) {
|
||||||
const modParams = CoreNavigator.getRouteParam<Params>('modParams');
|
let modNavOptions = CoreNavigator.getRouteParam<CoreNavigationOptions>('modNavOptions');
|
||||||
CoreCourseHelper.openModule(module, this.siteHomeId, undefined, modParams);
|
if (!modNavOptions) {
|
||||||
|
// Fallback to old way of passing params. @deprecated since 4.0.
|
||||||
|
const modParams = CoreNavigator.getRouteParam<Params>('modParams');
|
||||||
|
if (modParams) {
|
||||||
|
modNavOptions = { params: modParams };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CoreCourseHelper.openModule(module, this.siteHomeId, { modNavOptions });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loadContent().finally(() => {
|
this.loadContent().finally(() => {
|
||||||
|
|
|
@ -248,6 +248,7 @@
|
||||||
"restore": "Restore",
|
"restore": "Restore",
|
||||||
"restricted": "Restricted",
|
"restricted": "Restricted",
|
||||||
"retry": "Retry",
|
"retry": "Retry",
|
||||||
|
"resume": "Resume",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"savechanges": "Save changes",
|
"savechanges": "Save changes",
|
||||||
"scanqr": "Scan QR code",
|
"scanqr": "Scan QR code",
|
||||||
|
|
|
@ -19,6 +19,7 @@ information provided here is intended especially for developers.
|
||||||
- displaySectionSelector has been deprecated on CoreCourseFormatHandler, use displayCourseIndex instead.
|
- displaySectionSelector has been deprecated on CoreCourseFormatHandler, use displayCourseIndex instead.
|
||||||
- Most of the functions or callbacks that handle redirects/deeplinks have been modified to accept an object instead of just path + options. E.g.: CoreLoginHelper.isSiteLoggedOut, CoreLoginHelper.openBrowserForSSOLogin, CoreLoginHelper.openBrowserForOAuthLogin, CoreLoginHelper.prepareForSSOLogin, CoreApp.storeRedirect, CoreSites.loadSite.
|
- Most of the functions or callbacks that handle redirects/deeplinks have been modified to accept an object instead of just path + options. E.g.: CoreLoginHelper.isSiteLoggedOut, CoreLoginHelper.openBrowserForSSOLogin, CoreLoginHelper.openBrowserForOAuthLogin, CoreLoginHelper.prepareForSSOLogin, CoreApp.storeRedirect, CoreSites.loadSite.
|
||||||
- Course preview page route has changed from course/:courseId/preview to course/:courseId/summary to match with the page name and characteristics.
|
- Course preview page route has changed from course/:courseId/preview to course/:courseId/summary to match with the page name and characteristics.
|
||||||
|
- The parameters of the following functions in CoreCourseHelper have changed: navigateToModuleByInstance, navigateToModule, openModule.
|
||||||
|
|
||||||
=== 3.9.5 ===
|
=== 3.9.5 ===
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue