diff --git a/src/addon/mod/book/book.module.ts b/src/addon/mod/book/book.module.ts index 5ff680aad..cebd7bb9e 100644 --- a/src/addon/mod/book/book.module.ts +++ b/src/addon/mod/book/book.module.ts @@ -13,6 +13,7 @@ // limitations under the License. import { NgModule } from '@angular/core'; +import { AddonModBookComponentsModule } from './components/components.module'; import { AddonModBookProvider } from './providers/book'; import { AddonModBookModuleHandler } from './providers/module-handler'; import { AddonModBookLinkHandler } from './providers/link-handler'; @@ -25,6 +26,7 @@ import { CoreCourseModulePrefetchDelegate } from '../../../core/course/providers declarations: [ ], imports: [ + AddonModBookComponentsModule ], providers: [ AddonModBookProvider, diff --git a/src/addon/mod/book/components/components.module.ts b/src/addon/mod/book/components/components.module.ts new file mode 100644 index 000000000..1ea1ff532 --- /dev/null +++ b/src/addon/mod/book/components/components.module.ts @@ -0,0 +1,52 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { CommonModule } from '@angular/common'; +import { IonicModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreComponentsModule } from '../../../../components/components.module'; +import { CoreDirectivesModule } from '../../../../directives/directives.module'; +import { CoreCourseComponentsModule } from '../../../../core/course/components/components.module'; +import { AddonModBookIndexComponent } from './index/index'; +import { AddonModBookTocPopoverComponent } from './toc-popover/toc-popover'; +import { AddonModBookNavigationArrowsComponent } from './navigation-arrows/navigation-arrows'; + +@NgModule({ + declarations: [ + AddonModBookIndexComponent, + AddonModBookTocPopoverComponent, + AddonModBookNavigationArrowsComponent + ], + imports: [ + CommonModule, + IonicModule, + TranslateModule.forChild(), + CoreComponentsModule, + CoreDirectivesModule, + CoreCourseComponentsModule + ], + providers: [ + ], + exports: [ + AddonModBookIndexComponent, + AddonModBookTocPopoverComponent, + AddonModBookNavigationArrowsComponent + ], + entryComponents: [ + AddonModBookIndexComponent, + AddonModBookTocPopoverComponent + ] +}) +export class AddonModBookComponentsModule {} diff --git a/src/addon/mod/book/components/index/index.html b/src/addon/mod/book/components/index/index.html new file mode 100644 index 000000000..dcfef3a15 --- /dev/null +++ b/src/addon/mod/book/components/index/index.html @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + +
+ + + +
+ +
diff --git a/src/addon/mod/book/components/index/index.ts b/src/addon/mod/book/components/index/index.ts new file mode 100644 index 000000000..f79093719 --- /dev/null +++ b/src/addon/mod/book/components/index/index.ts @@ -0,0 +1,237 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { Component, OnInit, Input, Output, EventEmitter, Optional } from '@angular/core'; +import { NavParams, NavController, Content, PopoverController } from 'ionic-angular'; +import { TranslateService } from '@ngx-translate/core'; +import { CoreAppProvider } from '../../../../../providers/app'; +import { CoreDomUtilsProvider } from '../../../../../providers/utils/dom'; +import { CoreTextUtilsProvider } from '../../../../../providers/utils/text'; +import { CoreCourseProvider } from '../../../../../core/course/providers/course'; +import { CoreCourseHelperProvider } from '../../../../../core/course/providers/helper'; +import { AddonModBookProvider, AddonModBookContentsMap, AddonModBookTocChapter } from '../../providers/book'; +import { AddonModBookPrefetchHandler } from '../../providers/prefetch-handler'; +import { AddonModBookTocPopoverComponent } from '../../components/toc-popover/toc-popover'; + +/** + * Component that displays a book. + */ +@Component({ + selector: 'addon-mod-book-index', + templateUrl: 'index.html', +}) +export class AddonModBookIndexComponent implements OnInit { + @Input() module: any; // The module of the book. + @Input() courseId: number; // Course ID the book belongs to. + @Output() bookRetrieved?: EventEmitter; + + externalUrl: string; + description: string; + loaded: boolean; + component = AddonModBookProvider.COMPONENT; + componentId: number; + chapterContent: string; + previousChapter: string; + nextChapter: string; + refreshIcon: string; + + protected chapters: AddonModBookTocChapter[]; + protected currentChapter: string; + protected contentsMap: AddonModBookContentsMap; + + constructor(private bookProvider: AddonModBookProvider, private courseProvider: CoreCourseProvider, + private domUtils: CoreDomUtilsProvider, private appProvider: CoreAppProvider, private textUtils: CoreTextUtilsProvider, + private courseHelper: CoreCourseHelperProvider, private prefetchDelegate: AddonModBookPrefetchHandler, + private popoverCtrl: PopoverController, private translate: TranslateService, @Optional() private content: Content) { + this.bookRetrieved = new EventEmitter(); + } + + /** + * Component being initialized. + */ + ngOnInit(): void { + this.description = this.module.description; + this.componentId = this.module.id; + this.externalUrl = this.module.url; + this.loaded = false; + this.refreshIcon = 'spinner'; + + this.fetchContent(); + } + + /** + * Refresh the data. + * + * @param {any} [refresher] Refresher. + * @param {Function} [done] Function to call when done. + * @return {Promise} Promise resolved when done. + */ + doRefresh(refresher?: any, done?: () => void): Promise { + this.refreshIcon = 'spinner'; + + return this.bookProvider.invalidateContent(this.module.id, this.courseId).catch(() => { + // Ignore errors. + }).then(() => { + return this.fetchContent(this.currentChapter, true); + }).finally(() => { + this.refreshIcon = 'refresh'; + refresher && refresher.complete(); + done && done(); + }); + } + + /** + * Show the TOC. + * + * @param {MouseEvent} event Event. + */ + showToc(event: MouseEvent): void { + const popover = this.popoverCtrl.create(AddonModBookTocPopoverComponent, { + chapters: this.chapters + }); + + popover.onDidDismiss((chapterId) => { + this.changeChapter(chapterId); + }); + + popover.present({ + ev: event + }); + } + + /** + * Change the current chapter. + * + * @param {string} chapterId Chapter to load. + * @return {Promise} Promise resolved when done. + */ + changeChapter(chapterId: string): void { + if (chapterId && chapterId != this.currentChapter) { + this.loaded = false; + this.refreshIcon = 'spinner'; + this.loadChapter(chapterId); + } + } + + /** + * Expand the description. + */ + expandDescription(): void { + this.textUtils.expandText(this.translate.instant('core.description'), this.description, this.component, this.module.id); + } + + /** + * Prefetch the module. + */ + prefetch(): void { + // @todo this.courseHelper.contextMenuPrefetch($scope, this.module, this.courseId); + } + + /** + * Confirm and remove downloaded files. + */ + removeFiles(): void { + this.courseHelper.confirmAndRemoveFiles(this.module, this.courseId); + } + + /** + * Download book contents and load the current chapter. + * + * @param {string} [chapterId] Chapter to load. + * @param {boolean} [refresh] Whether we're refreshing data. + * @return {Promise} Promise resolved when done. + */ + protected fetchContent(chapterId?: string, refresh?: boolean): Promise { + const promises = []; + let downloadFailed = false; + + // Try to get the book data. + promises.push(this.bookProvider.getBook(this.courseId, this.module.id).then((book) => { + this.bookRetrieved.emit(book); + this.description = book.intro || this.description; + }).catch(() => { + // Ignore errors since this WS isn't available in some Moodle versions. + })); + + // Download content. This function also loads module contents if needed. + promises.push(this.prefetchDelegate.download(this.module, this.courseId).catch(() => { + // Mark download as failed but go on since the main files could have been downloaded. + downloadFailed = true; + + if (!this.module.contents.length) { + // Try to load module contents for offline usage. + return this.courseProvider.loadModuleContents(this.module, this.courseId); + } + })); + + return Promise.all(promises).then(() => { + this.contentsMap = this.bookProvider.getContentsMap(this.module.contents); + this.chapters = this.bookProvider.getTocList(this.module.contents); + + if (typeof this.currentChapter == 'undefined') { + this.currentChapter = this.bookProvider.getFirstChapter(this.chapters); + } + + // Show chapter. + return this.loadChapter(chapterId || this.currentChapter).then(() => { + if (downloadFailed && this.appProvider.isOnline()) { + // We could load the main file but the download failed. Show error message. + this.domUtils.showErrorModal('core.errordownloadingsomefiles', true); + } + + // All data obtained, now fill the context menu. + // @todo this.courseHelper.fillContextMenu($scope, module, courseId, refresh, mmaModBookComponent); + }).catch(() => { + // Ignore errors, they're handled inside the loadChapter function. + }); + }).catch((error) => { + // Error getting data, fail. + this.loaded = true; + this.refreshIcon = 'refresh'; + this.domUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true); + }); + } + + /** + * Load a book chapter. + * + * @param {string} chapterId Chapter to load. + * @return {Promise} Promise resolved when done. + */ + protected loadChapter(chapterId: string): Promise { + this.currentChapter = chapterId; + this.content && this.content.scrollToTop(); + + return this.bookProvider.getChapterContent(this.contentsMap, chapterId, this.module.id).then((content) => { + this.chapterContent = content; + this.previousChapter = this.bookProvider.getPreviousChapter(this.chapters, chapterId); + this.nextChapter = this.bookProvider.getNextChapter(this.chapters, chapterId); + + // Chapter loaded, log view. We don't return the promise because we don't want to block the user for this. + this.bookProvider.logView(this.module.instance, chapterId).then(() => { + // Module is completed when last chapter is viewed, so we only check completion if the last is reached. + if (!this.nextChapter) { + this.courseProvider.checkModuleCompletion(this.courseId, this.module.completionstatus); + } + }); + }).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'addon.mod_book.errorchapter', true); + + return Promise.reject(null); + }).finally(() => { + this.loaded = true; + this.refreshIcon = 'refresh'; + }); + } +} diff --git a/src/addon/mod/book/components/navigation-arrows/navigation-arrows.html b/src/addon/mod/book/components/navigation-arrows/navigation-arrows.html new file mode 100644 index 000000000..44e6a24d4 --- /dev/null +++ b/src/addon/mod/book/components/navigation-arrows/navigation-arrows.html @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/addon/mod/book/components/navigation-arrows/navigation-arrows.ts b/src/addon/mod/book/components/navigation-arrows/navigation-arrows.ts new file mode 100644 index 000000000..acb60d156 --- /dev/null +++ b/src/addon/mod/book/components/navigation-arrows/navigation-arrows.ts @@ -0,0 +1,32 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { Component, Input, Output, EventEmitter } from '@angular/core'; + +/** + * Component to navigate to previous or next chapter in a book. + */ +@Component({ + selector: 'addon-mod-book-navigation-arrows', + templateUrl: 'navigation-arrows.html' +}) +export class AddonModBookNavigationArrowsComponent { + @Input() previous?: string; // Previous chapter ID. + @Input() next?: string; // Next chapter ID. + @Output() action?: EventEmitter; // Will emit an event when the item clicked. + + constructor() { + this.action = new EventEmitter(); + } +} diff --git a/src/addon/mod/book/components/toc-popover/toc-popover.html b/src/addon/mod/book/components/toc-popover/toc-popover.html new file mode 100644 index 000000000..6d3dca6f0 --- /dev/null +++ b/src/addon/mod/book/components/toc-popover/toc-popover.html @@ -0,0 +1,5 @@ + + +

{{chapter.title}}

+
+
diff --git a/src/addon/mod/book/components/toc-popover/toc-popover.ts b/src/addon/mod/book/components/toc-popover/toc-popover.ts new file mode 100644 index 000000000..01ce767ab --- /dev/null +++ b/src/addon/mod/book/components/toc-popover/toc-popover.ts @@ -0,0 +1,41 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { Component } from '@angular/core'; +import { NavParams, ViewController } from 'ionic-angular'; +import { AddonModBookTocChapter } from '../../providers/book'; + +/** + * Component to display the TOC of a book. + */ +@Component({ + selector: 'addon-mod-book-toc-popover', + templateUrl: 'toc-popover.html' +}) +export class AddonModBookTocPopoverComponent { + chapters: AddonModBookTocChapter[]; + + constructor(navParams: NavParams, private viewCtrl: ViewController) { + this.chapters = navParams.get('chapters') || []; + } + + /** + * Function called when a course is clicked. + * + * @param {string} id ID of the clicked chapter. + */ + loadChapter(id: string): void { + this.viewCtrl.dismiss(id); + } +} diff --git a/src/addon/mod/book/pages/index/index.html b/src/addon/mod/book/pages/index/index.html new file mode 100644 index 000000000..c0a0d5fa3 --- /dev/null +++ b/src/addon/mod/book/pages/index/index.html @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/addon/mod/book/pages/index/index.module.ts b/src/addon/mod/book/pages/index/index.module.ts new file mode 100644 index 000000000..02b409af8 --- /dev/null +++ b/src/addon/mod/book/pages/index/index.module.ts @@ -0,0 +1,33 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreDirectivesModule } from '../../../../../directives/directives.module'; +import { AddonModBookComponentsModule } from '../../components/components.module'; +import { AddonModBookIndexPage } from './index'; + +@NgModule({ + declarations: [ + AddonModBookIndexPage, + ], + imports: [ + CoreDirectivesModule, + AddonModBookComponentsModule, + IonicPageModule.forChild(AddonModBookIndexPage), + TranslateModule.forChild() + ], +}) +export class AddonModBookIndexPageModule {} diff --git a/src/addon/mod/book/pages/index/index.ts b/src/addon/mod/book/pages/index/index.ts new file mode 100644 index 000000000..a45a34e1d --- /dev/null +++ b/src/addon/mod/book/pages/index/index.ts @@ -0,0 +1,48 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { Component, ViewChild } from '@angular/core'; +import { IonicPage, NavParams } from 'ionic-angular'; +import { AddonModBookIndexComponent } from '../../components/index/index'; + +/** + * Page that displays a book. + */ +@IonicPage({ segment: 'addon-mod-book-index' }) +@Component({ + selector: 'page-addon-mod-book-index', + templateUrl: 'index.html', +}) +export class AddonModBookIndexPage { + @ViewChild(AddonModBookIndexComponent) bookComponent: AddonModBookIndexComponent; + + title: string; + module: any; + courseId: number; + + constructor(navParams: NavParams) { + this.module = navParams.get('module') || {}; + this.courseId = navParams.get('courseId'); + this.title = this.module.name; + } + + /** + * Update some data based on the book instance. + * + * @param {any} book Book instance. + */ + updateData(book: any): void { + this.title = book.name || this.title; + } +} diff --git a/src/addon/mod/book/providers/module-handler.ts b/src/addon/mod/book/providers/module-handler.ts index 179b6e536..495bdbbd3 100644 --- a/src/addon/mod/book/providers/module-handler.ts +++ b/src/addon/mod/book/providers/module-handler.ts @@ -50,7 +50,7 @@ export class AddonModBookModuleHandler implements CoreCourseModuleHandler { title: module.name, class: 'addon-mod_book-handler', action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void { - // @todo + navCtrl.push('AddonModBookIndexPage', {module: module, courseId: courseId}, options); } }; }