diff --git a/src/addon/messages/components/discussions/discussions.html b/src/addon/messages/components/discussions/discussions.html
index 2e7f4f993..cade6e7c3 100644
--- a/src/addon/messages/components/discussions/discussions.html
+++ b/src/addon/messages/components/discussions/discussions.html
@@ -33,8 +33,8 @@
0 || discussion.unread">
- 0">{{discussion.message.timecreated / 1000 | coreDateDayOrTime}}
-
+
+ 0">{{discussion.message.timecreated / 1000 | coreDateDayOrTime}}
diff --git a/src/addon/messages/providers/mainmenu-handler.ts b/src/addon/messages/providers/mainmenu-handler.ts
index f49660b56..8afb58d63 100644
--- a/src/addon/messages/providers/mainmenu-handler.ts
+++ b/src/addon/messages/providers/mainmenu-handler.ts
@@ -32,8 +32,15 @@ import { AddonPushNotificationsDelegate } from '@addon/pushnotifications/provide
export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCronHandler {
name = 'AddonMessages';
priority = 800;
- protected badge = '';
- protected loading = true;
+ protected handler: CoreMainMenuHandlerToDisplay = {
+ icon: 'chatbubbles',
+ title: 'addon.messages.messages',
+ page: 'AddonMessagesIndexPage',
+ class: 'addon-messages-handler',
+ showBadge: true, // Do not check isMessageCountEnabled because we'll use fallback it not enabled.
+ badge: '',
+ loading: true
+ };
constructor(private messagesProvider: AddonMessagesProvider, private sitesProvider: CoreSitesProvider,
private eventsProvider: CoreEventsProvider, private appProvider: CoreAppProvider,
@@ -51,8 +58,8 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr
// Reset info on logout.
eventsProvider.on(CoreEventsProvider.LOGOUT, (data) => {
- this.badge = '';
- this.loading = true;
+ this.handler.badge = '';
+ this.handler.loading = true;
});
// If a message push notification is received, refresh the count.
@@ -82,19 +89,11 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr
* @return {CoreMainMenuHandlerToDisplay} Data needed to render the handler.
*/
getDisplayData(): CoreMainMenuHandlerToDisplay {
- if (this.loading) {
+ if (this.handler.loading) {
this.updateBadge();
}
- return {
- icon: 'chatbubbles',
- title: 'addon.messages.messages',
- page: 'AddonMessagesIndexPage',
- class: 'addon-messages-handler',
- showBadge: true, // Do not check isMessageCountEnabled because we'll use fallback it not enabled.
- badge: this.badge,
- loading: this.loading
- };
+ return this.handler;
}
/**
@@ -110,17 +109,13 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr
this.messagesProvider.getUnreadConversationsCount(undefined, siteId).then((unread) => {
// Leave badge enter if there is a 0+ or a 0.
- this.badge = parseInt(unread, 10) > 0 ? unread : '';
+ this.handler.badge = parseInt(unread, 10) > 0 ? unread : '';
// Update badge.
this.pushNotificationsProvider.updateAddonCounter('AddonMessages', unread, siteId);
}).catch(() => {
- this.badge = '';
+ this.handler.badge = '';
}).finally(() => {
- this.loading = false;
- this.eventsProvider.trigger(CoreMainMenuDelegate.UPDATE_BADGE_EVENT, {
- name: this.name,
- badge: this.badge
- }, siteId);
+ this.handler.loading = false;
});
}
diff --git a/src/addon/mod/book/components/index/index.html b/src/addon/mod/book/components/index/index.html
index 5463e70a5..0f9b9a735 100644
--- a/src/addon/mod/book/components/index/index.html
+++ b/src/addon/mod/book/components/index/index.html
@@ -13,7 +13,7 @@
-
+
diff --git a/src/addon/mod/book/providers/book.ts b/src/addon/mod/book/providers/book.ts
index 808a77128..4f97a6e83 100644
--- a/src/addon/mod/book/providers/book.ts
+++ b/src/addon/mod/book/providers/book.ts
@@ -153,9 +153,9 @@ export class AddonModBookProvider {
return promise.then((url) => {
// Fetch the URL content.
- const observable = this.http.get(url);
+ const promise = this.http.get(url).toPromise();
- return this.utils.observableToPromise(observable).then((response: Response): any => {
+ return promise.then((response: Response): any => {
const content = response.text();
if (typeof content !== 'string') {
return Promise.reject(null);
@@ -381,19 +381,11 @@ export class AddonModBookProvider {
* @return {Promise} Promise resolved when the WS call is successful.
*/
logView(id: number, chapterId: string): Promise {
- if (id) {
- const params = {
- bookid: id,
- chapterid: chapterId
- };
+ const params = {
+ bookid: id,
+ chapterid: chapterId
+ };
- return this.sitesProvider.getCurrentSite().write('mod_book_view_book', params).then((response) => {
- if (!response.status) {
- return Promise.reject(null);
- }
- });
- }
-
- return Promise.reject(null);
+ return this.sitesProvider.getCurrentSite().write('mod_book_view_book', params);
}
}
diff --git a/src/addon/mod/resource/components/components.module.ts b/src/addon/mod/resource/components/components.module.ts
new file mode 100644
index 000000000..1f2cdda66
--- /dev/null
+++ b/src/addon/mod/resource/components/components.module.ts
@@ -0,0 +1,45 @@
+// (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 { AddonModResourceIndexComponent } from './index/index';
+
+@NgModule({
+ declarations: [
+ AddonModResourceIndexComponent
+ ],
+ imports: [
+ CommonModule,
+ IonicModule,
+ TranslateModule.forChild(),
+ CoreComponentsModule,
+ CoreDirectivesModule,
+ CoreCourseComponentsModule
+ ],
+ providers: [
+ ],
+ exports: [
+ AddonModResourceIndexComponent
+ ],
+ entryComponents: [
+ AddonModResourceIndexComponent
+ ]
+})
+export class AddonModResourceComponentsModule {}
diff --git a/src/addon/mod/resource/components/index/index.html b/src/addon/mod/resource/components/index/index.html
new file mode 100644
index 000000000..5b4289d28
--- /dev/null
+++ b/src/addon/mod/resource/components/index/index.html
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/addon/mod/resource/components/index/index.ts b/src/addon/mod/resource/components/index/index.ts
new file mode 100644
index 000000000..353ede9f9
--- /dev/null
+++ b/src/addon/mod/resource/components/index/index.ts
@@ -0,0 +1,218 @@
+// (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, OnDestroy, Input, Output, EventEmitter } from '@angular/core';
+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 { CoreCourseModuleMainComponent } from '@core/course/providers/module-delegate';
+import { AddonModResourceProvider } from '../../providers/resource';
+import { AddonModResourcePrefetchHandler } from '../../providers/prefetch-handler';
+import { AddonModResourceHelperProvider } from '../../providers/helper';
+
+/**
+ * Component that displays a resource.
+ */
+@Component({
+ selector: 'addon-mod-resource-index',
+ templateUrl: 'index.html',
+})
+export class AddonModResourceIndexComponent implements OnInit, OnDestroy, CoreCourseModuleMainComponent {
+ @Input() module: any; // The module of the resource.
+ @Input() courseId: number; // Course ID the resource belongs to.
+ @Output() resourceRetrieved?: EventEmitter;
+
+ loaded: boolean;
+ component = AddonModResourceProvider.COMPONENT;
+ componentId: number;
+
+ canGetResource: boolean;
+ mode: string;
+ src: string;
+ contentText: string;
+
+ // Data for context menu.
+ externalUrl: string;
+ description: string;
+ refreshIcon: string;
+ prefetchStatusIcon: string;
+ prefetchText: string;
+ size: string;
+
+ protected isDestroyed = false;
+ protected statusObserver;
+
+ constructor(private resourceProvider: AddonModResourceProvider, private courseProvider: CoreCourseProvider,
+ private domUtils: CoreDomUtilsProvider, private appProvider: CoreAppProvider, private textUtils: CoreTextUtilsProvider,
+ private courseHelper: CoreCourseHelperProvider, private translate: TranslateService,
+ private prefetchHandler: AddonModResourcePrefetchHandler, private resourceHelper: AddonModResourceHelperProvider) {
+ this.resourceRetrieved = 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.canGetResource = this.resourceProvider.isGetResourceWSAvailable();
+
+ this.fetchContent().then(() => {
+ this.resourceProvider.logView(this.module.instance).then(() => {
+ this.courseProvider.checkModuleCompletion(this.courseId, this.module.completionstatus);
+ });
+ });
+ }
+
+ /**
+ * 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 {
+ if (this.loaded) {
+ this.refreshIcon = 'spinner';
+
+ return this.resourceProvider.invalidateContent(this.module.id, this.courseId).catch(() => {
+ // Ignore errors.
+ }).then(() => {
+ return this.fetchContent(true);
+ }).finally(() => {
+ this.refreshIcon = 'refresh';
+ refresher && refresher.complete();
+ done && done();
+ });
+ }
+ }
+
+ /**
+ * 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 {
+ this.courseHelper.contextMenuPrefetch(this, this.module, this.courseId);
+ }
+
+ /**
+ * Confirm and remove downloaded files.
+ */
+ removeFiles(): void {
+ this.courseHelper.confirmAndRemoveFiles(this.module, this.courseId);
+ }
+
+ /**
+ * Download resource contents.
+ *
+ * @param {boolean} [refresh] Whether we're refreshing data.
+ * @return {Promise} Promise resolved when done.
+ */
+ protected fetchContent(refresh?: boolean): Promise {
+ // Load module contents if needed. Passing refresh is needed to force reloading contents.
+ return this.courseProvider.loadModuleContents(this.module, this.courseId, null, false, refresh).then(() => {
+ if (!this.module.contents || !this.module.contents.length) {
+ return Promise.reject(null);
+ }
+
+ // Get the resource instance to get the latest name/description and to know if it's embedded.
+ if (this.canGetResource) {
+ return this.resourceProvider.getResourceData(this.courseId, this.module.id).catch(() => {
+ // Ignore errors.
+ });
+ }
+
+ return this.courseProvider.getModule(this.module.id, this.courseId).catch(() => {
+ // Ignore errors.
+ });
+ }).then((resource) => {
+ if (resource) {
+ this.description = resource.intro || resource.description;
+ this.resourceRetrieved.emit(resource);
+ }
+
+ if (this.resourceHelper.isDisplayedInIframe(this.module)) {
+ let downloadFailed = false;
+
+ return this.prefetchHandler.download(this.module, this.courseId).catch(() => {
+ // Mark download as failed but go on since the main files could have been downloaded.
+ downloadFailed = true;
+ }).then(() => {
+ return this.resourceHelper.getIframeSrc(this.module).then((src) => {
+ this.mode = 'iframe';
+
+ if (this.src && src.toString() == this.src.toString()) {
+ // Re-loading same page.
+ // Set it to empty and then re-set the src in the next digest so it detects it has changed.
+ this.src = '';
+ setTimeout(() => {
+ this.src = src;
+ });
+ } else {
+ this.src = src;
+ }
+
+ if (downloadFailed && this.appProvider.isOnline()) {
+ // We could load the main file but the download failed. Show error message.
+ this.domUtils.showErrorModal('core.errordownloadingsomefiles', true);
+ }
+ });
+ });
+ } else if (this.resourceHelper.isDisplayedEmbedded(this.module, resource && resource.display)) {
+ this.mode = 'embedded';
+
+ return this.resourceHelper.getEmbeddedHtml(this.module).then((html) => {
+ this.contentText = html;
+ });
+ } else {
+ this.mode = 'external';
+ }
+ }).then(() => {
+ // All data obtained, now fill the context menu.
+ this.courseHelper.fillContextMenu(this, this.module, this.courseId, refresh, this.component);
+ }).catch((error) => {
+ // Error getting data, fail.
+ this.domUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true);
+ }).finally(() => {
+ this.loaded = true;
+ this.refreshIcon = 'refresh';
+ });
+ }
+
+ /**
+ * Opens a file.
+ */
+ open(): void {
+ this.resourceHelper.openModuleFile(this.module, this.courseId);
+ }
+
+ ngOnDestroy(): void {
+ this.isDestroyed = true;
+ this.statusObserver && this.statusObserver.off();
+ }
+}
diff --git a/src/addon/mod/resource/lang/en.json b/src/addon/mod/resource/lang/en.json
new file mode 100644
index 000000000..33c872d40
--- /dev/null
+++ b/src/addon/mod/resource/lang/en.json
@@ -0,0 +1,4 @@
+{
+ "errorwhileloadingthecontent": "Error while loading the content.",
+ "openthefile": "Open the file"
+}
\ No newline at end of file
diff --git a/src/addon/mod/resource/pages/index/index.html b/src/addon/mod/resource/pages/index/index.html
new file mode 100644
index 000000000..dad3b8a4b
--- /dev/null
+++ b/src/addon/mod/resource/pages/index/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/addon/mod/resource/pages/index/index.module.ts b/src/addon/mod/resource/pages/index/index.module.ts
new file mode 100644
index 000000000..6d6dc347d
--- /dev/null
+++ b/src/addon/mod/resource/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 { AddonModResourceComponentsModule } from '../../components/components.module';
+import { AddonModResourceIndexPage } from './index';
+
+@NgModule({
+ declarations: [
+ AddonModResourceIndexPage,
+ ],
+ imports: [
+ CoreDirectivesModule,
+ AddonModResourceComponentsModule,
+ IonicPageModule.forChild(AddonModResourceIndexPage),
+ TranslateModule.forChild()
+ ],
+})
+export class AddonModResourceIndexPageModule {}
diff --git a/src/addon/mod/resource/pages/index/index.ts b/src/addon/mod/resource/pages/index/index.ts
new file mode 100644
index 000000000..0bc1de345
--- /dev/null
+++ b/src/addon/mod/resource/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 { AddonModResourceIndexComponent } from '../../components/index/index';
+
+/**
+ * Page that displays a resource.
+ */
+@IonicPage({ segment: 'addon-mod-resource-index' })
+@Component({
+ selector: 'page-addon-mod-resource-index',
+ templateUrl: 'index.html',
+})
+export class AddonModResourceIndexPage {
+ @ViewChild(AddonModResourceIndexComponent) resourceComponent: AddonModResourceIndexComponent;
+
+ 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 resource instance.
+ *
+ * @param {any} resource Resource instance.
+ */
+ updateData(resource: any): void {
+ this.title = resource.name || this.title;
+ }
+}
diff --git a/src/addon/mod/resource/providers/helper.ts b/src/addon/mod/resource/providers/helper.ts
new file mode 100644
index 000000000..a9077b3d5
--- /dev/null
+++ b/src/addon/mod/resource/providers/helper.ts
@@ -0,0 +1,167 @@
+// (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 { Injectable } from '@angular/core';
+import { CoreDomUtilsProvider } from '@providers/utils/dom';
+import { CoreCourseProvider } from '@core/course/providers/course';
+import { CoreCourseHelperProvider } from '@core/course/providers/helper';
+import { AddonModResourceProvider } from './resource';
+import { TranslateService } from '@ngx-translate/core';
+import { CoreSitesProvider } from '@providers/sites';
+import { CoreUtilsProvider } from '@providers/utils/utils';
+import { CoreFilepoolProvider } from '@providers/filepool';
+import { CoreFileProvider } from '@providers/file';
+import { CoreAppProvider } from '@providers/app';
+import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype';
+import { CoreTextUtilsProvider } from '@providers/utils/text';
+import { CoreConstants } from '@core/constants';
+
+/**
+ * Service that provides helper functions for resources.
+ */
+@Injectable()
+export class AddonModResourceHelperProvider {
+
+ /* Constants to determine how a resource should be displayed in Moodle. */
+ // Try the best way.
+ protected DISPLAY_AUTO = 0;
+ // Display using object tag.
+ protected DISPLAY_EMBED = 1;
+
+ constructor(private courseProvider: CoreCourseProvider, private domUtils: CoreDomUtilsProvider,
+ private resourceProvider: AddonModResourceProvider, private courseHelper: CoreCourseHelperProvider,
+ private textUtils: CoreTextUtilsProvider, private mimetypeUtils: CoreMimetypeUtilsProvider,
+ private fileProvider: CoreFileProvider, private appProvider: CoreAppProvider,
+ private filepoolProvider: CoreFilepoolProvider, private utils: CoreUtilsProvider,
+ private sitesProvider: CoreSitesProvider, private translate: TranslateService) {
+ }
+
+ /**
+ * Get the HTML to display an embedded resource.
+ *
+ * @param {any} module The module object.
+ * @return {Promise} Promise resolved with the iframe src.
+ */
+ getEmbeddedHtml(module: any): Promise {
+ return this.courseHelper.downloadModuleWithMainFileIfNeeded(module, module.course, AddonModResourceProvider.COMPONENT,
+ module.id, module.contents).then((result) => {
+ const file = module.contents[0],
+ ext = this.mimetypeUtils.getFileExtension(file.filename),
+ type = this.mimetypeUtils.getExtensionType(ext),
+ mimeType = this.mimetypeUtils.getMimeType(ext);
+
+ if (type == 'image') {
+ return '';
+ }
+
+ if (type == 'audio' || type == 'video') {
+ return '<' + type + ' controls title="' + file.filename + '"" src="' + result.path + '">' +
+ '