From 8c8028e2772bb23ad2c3efa72e215202d4e2411e Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Fri, 25 May 2018 15:56:15 +0200 Subject: [PATCH] MOBILE-2342 glossary: Implement index, entry and edit pages --- src/addon/mod/glossary/pages/edit/edit.html | 51 ++++ .../mod/glossary/pages/edit/edit.module.ts | 33 +++ src/addon/mod/glossary/pages/edit/edit.ts | 253 ++++++++++++++++++ src/addon/mod/glossary/pages/entry/entry.html | 45 ++++ .../mod/glossary/pages/entry/entry.module.ts | 35 +++ src/addon/mod/glossary/pages/entry/entry.ts | 110 ++++++++ src/addon/mod/glossary/pages/index/index.html | 11 + .../mod/glossary/pages/index/index.module.ts | 33 +++ src/addon/mod/glossary/pages/index/index.ts | 48 ++++ 9 files changed, 619 insertions(+) create mode 100644 src/addon/mod/glossary/pages/edit/edit.html create mode 100644 src/addon/mod/glossary/pages/edit/edit.module.ts create mode 100644 src/addon/mod/glossary/pages/edit/edit.ts create mode 100644 src/addon/mod/glossary/pages/entry/entry.html create mode 100644 src/addon/mod/glossary/pages/entry/entry.module.ts create mode 100644 src/addon/mod/glossary/pages/entry/entry.ts create mode 100644 src/addon/mod/glossary/pages/index/index.html create mode 100644 src/addon/mod/glossary/pages/index/index.module.ts create mode 100644 src/addon/mod/glossary/pages/index/index.ts diff --git a/src/addon/mod/glossary/pages/edit/edit.html b/src/addon/mod/glossary/pages/edit/edit.html new file mode 100644 index 000000000..f42d75b3e --- /dev/null +++ b/src/addon/mod/glossary/pages/edit/edit.html @@ -0,0 +1,51 @@ + + + + + + + + + + + + + {{ 'addon.mod_glossary.concept' | translate }} + + + + {{ 'addon.mod_glossary.definition' | translate }} + + + + + {{ 'addon.mod_glossary.categories' | translate }} + + {{ category.name }} + + + + {{ 'addon.mod_glossary.aliases' | translate }} + + + {{ 'addon.mod_glossary.attachment' | translate }} + + + {{ 'addon.mod_glossary.linking' | translate }} + + {{ 'addon.mod_glossary.entryusedynalink' | translate }} + + + + {{ 'addon.mod_glossary.casesensitive' | translate }} + + + + {{ 'addon.mod_glossary.fullmatch' | translate }} + + + + + + diff --git a/src/addon/mod/glossary/pages/edit/edit.module.ts b/src/addon/mod/glossary/pages/edit/edit.module.ts new file mode 100644 index 000000000..50cc3a93c --- /dev/null +++ b/src/addon/mod/glossary/pages/edit/edit.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 { CoreComponentsModule } from '@components/components.module'; +import { CoreDirectivesModule } from '@directives/directives.module'; +import { AddonModGlossaryEditPage } from './edit'; + +@NgModule({ + declarations: [ + AddonModGlossaryEditPage, + ], + imports: [ + CoreComponentsModule, + CoreDirectivesModule, + IonicPageModule.forChild(AddonModGlossaryEditPage), + TranslateModule.forChild() + ], +}) +export class AddonModGlossaryNewDiscussionPageModule {} diff --git a/src/addon/mod/glossary/pages/edit/edit.ts b/src/addon/mod/glossary/pages/edit/edit.ts new file mode 100644 index 000000000..ab1bf46eb --- /dev/null +++ b/src/addon/mod/glossary/pages/edit/edit.ts @@ -0,0 +1,253 @@ +// (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 } from '@angular/core'; +import { FormControl } from '@angular/forms'; +import { IonicPage, NavController, NavParams } from 'ionic-angular'; +import { TranslateService } from '@ngx-translate/core'; +import { CoreEventsProvider } from '@providers/events'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreTextUtilsProvider } from '@providers/utils/text'; +import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader'; +import { AddonModGlossaryProvider } from '../../providers/glossary'; +import { AddonModGlossaryOfflineProvider } from '../../providers/offline'; +import { AddonModGlossaryHelperProvider } from '../../providers/helper'; + +/** + * Page that displays the edit form. + */ +@IonicPage({ segment: 'addon-mod-glossary-edit' }) +@Component({ + selector: 'page-addon-mod-glossary-edit', + templateUrl: 'edit.html', +}) +export class AddonModGlossaryEditPage implements OnInit { + component = AddonModGlossaryProvider.COMPONENT; + loaded = false; + entry = { + concept: '', + definition: '', + timecreated: 0, + }; + options = { + categories: [], + aliases: '', + usedynalink: false, + casesensitive: false, + fullmatch: false + }; + attachments = []; + definitionControl = new FormControl(); + categories = []; + + protected courseId: number; + protected module: any; + protected glossary: any; + protected syncId: string; + protected syncObserver: any; + protected isDestroyed = false; + protected originalData: any; + protected saved = false; + + constructor(private navParams: NavParams, + private navCtrl: NavController, + private translate: TranslateService, + private domUtils: CoreDomUtilsProvider, + private eventsProvider: CoreEventsProvider, + private sitesProvider: CoreSitesProvider, + private uploaderProvider: CoreFileUploaderProvider, + private textUtils: CoreTextUtilsProvider, + private glossaryProvider: AddonModGlossaryProvider, + private glossaryOffline: AddonModGlossaryOfflineProvider, + private glossaryHelper: AddonModGlossaryHelperProvider) { + this.courseId = navParams.get('courseId'); + this.module = navParams.get('module'); + this.glossary = navParams.get('glossary'); + } + + /** + * Component being initialized. + */ + ngOnInit(): void { + const entry = this.navParams.get('entry'); + + let promise; + + if (entry) { + this.entry.concept = entry.concept || ''; + this.entry.definition = entry.definition || ''; + + this.originalData = { + concept: this.entry.concept, + definition: this.entry.definition, + files: [], + }; + + if (entry.options) { + this.options.categories = entry.options.categories || []; + this.options.aliases = entry.options.aliases || ''; + this.options.usedynalink = !!entry.options.usedynalink; + if (this.options.usedynalink) { + this.options.casesensitive = !!entry.options.casesensitive; + this.options.fullmatch = !!entry.options.fullmatch; + } + } + + // Treat offline attachments if any. + if (entry.attachments && entry.attachments.offline) { + promise = this.glossaryHelper.getStoredFiles(this.glossary.id, entry.concept, entry.timecreated).then((files) => { + this.attachments = files; + this.originalData.files = files.slice(); + }); + } + } + + this.definitionControl.setValue(this.entry.definition); + + Promise.resolve(promise).then(() => { + this.glossaryProvider.getAllCategories(this.glossary.id).then((categories) => { + this.categories = categories; + }).finally(() => { + this.loaded = true; + }); + }); + } + + /** + * Definition changed. + * + * @param {string} text The new text. + */ + onDefinitionChange(text: string): void { + this.entry.definition = text; + } + + /** + * Check if we can leave the page or not. + * + * @return {boolean|Promise} Resolved if we can leave it, rejected if not. + */ + ionViewCanLeave(): boolean | Promise { + let promise: any; + + if (!this.saved && this.glossaryHelper.hasEntryDataChanged(this.entry, this.attachments, this.originalData)) { + // Show confirmation if some data has been modified. + promise = this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit')); + } else { + promise = Promise.resolve(); + } + + return promise.then(() => { + // Delete the local files from the tmp folder. + this.uploaderProvider.clearTmpFiles(this.attachments); + }); + } + + /** + * Save the entry. + */ + save(): void { + let definition = this.entry.definition; + const timecreated = this.entry.timecreated || Date.now(); + let saveOffline = false; + + if (!this.entry.concept || !definition) { + this.domUtils.showErrorModal('addon.mod_glossary.fillfields', true); + + return; + } + + const modal = this.domUtils.showModalLoading('core.sending', true); + + // Check if rich text editor is enabled or not. + this.domUtils.isRichTextEditorEnabled().then((enabled) => { + if (!enabled) { + // Rich text editor not enabled, add some HTML to the definition if needed. + definition = this.textUtils.formatHtmlLines(definition); + } + + // Upload attachments first if any. + if (this.attachments.length > 0) { + return this.glossaryHelper.uploadOrStoreFiles(this.glossary.id, this.entry.concept, timecreated, this.attachments, + false).catch(() => { + // Cannot upload them in online, save them in offline. + saveOffline = true; + + return this.glossaryHelper.uploadOrStoreFiles(this.glossary.id, this.entry.concept, timecreated, + this.attachments, true); + }); + } + }).then((attach) => { + const options: any = { + aliases: this.options.aliases, + categories: this.options.categories.join(',') + }; + + if (this.glossary.usedynalink) { + options.usedynalink = this.options.usedynalink ? 1 : 0; + if (this.options.usedynalink) { + options.casesensitive = this.options.casesensitive ? 1 : 0; + options.fullmatch = this.options.fullmatch ? 1 : 0; + } + } + + if (saveOffline) { + let promise; + if (this.entry && !this.glossary.allowduplicatedentries) { + // Check if the entry is duplicated in online or offline mode. + promise = this.glossaryProvider.isConceptUsed(this.glossary.id, this.entry.concept, this.entry.timecreated) + .then((used) => { + if (used) { + // There's a entry with same name, reject with error message. + return Promise.reject(this.translate.instant('addon.mod_glossary.errconceptalreadyexists')); + } + }); + } else { + promise = Promise.resolve(); + } + + return promise.then(() => { + // Save entry in offline. + return this.glossaryOffline.addNewEntry(this.glossary.id, this.entry.concept, definition, this.courseId, + options, attach, timecreated, undefined, undefined, this.entry).then(() => { + // Don't return anything. + }); + }); + } else { + // Try to send it to server. + // Don't allow offline if there are attachments since they were uploaded fine. + return this.glossaryProvider.addEntry(this.glossary.id, this.entry.concept, definition, this.courseId, options, + attach, timecreated, undefined, this.entry, !this.attachments.length, !this.glossary.allowduplicatedentries); + } + }).then((entryId) => { + if (entryId) { + // Data sent to server, delete stored files (if any). + this.glossaryHelper.deleteStoredFiles(this.glossary.id, this.entry.concept, timecreated); + } + + const data = { + glossaryId: this.glossary.id, + }; + this.eventsProvider.trigger(AddonModGlossaryProvider.ADD_ENTRY_EVENT, data, this.sitesProvider.getCurrentSiteId()); + + this.saved = true; + this.navCtrl.pop(); + }).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'addon.mod_glossary.cannoteditentry', true); + }).finally(() => { + modal.dismiss(); + }); + } +} diff --git a/src/addon/mod/glossary/pages/entry/entry.html b/src/addon/mod/glossary/pages/entry/entry.html new file mode 100644 index 000000000..5bccd5f20 --- /dev/null +++ b/src/addon/mod/glossary/pages/entry/entry.html @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + +

+ {{ entry.timemodified | coreDateDayOrTime }} +

+
+ +

+ {{ entry.timemodified | coreDateDayOrTime }} +
+ + + + + + + +

{{ 'addon.mod_glossary.entrypendingapproval' | translate }}

+
+
+ + + + {{ 'addon.mod_glossary.errorloadingentry' | translate }} + + +
+
diff --git a/src/addon/mod/glossary/pages/entry/entry.module.ts b/src/addon/mod/glossary/pages/entry/entry.module.ts new file mode 100644 index 000000000..83b3642cf --- /dev/null +++ b/src/addon/mod/glossary/pages/entry/entry.module.ts @@ -0,0 +1,35 @@ +// (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 { CoreComponentsModule } from '@components/components.module'; +import { CoreDirectivesModule } from '@directives/directives.module'; +import { CorePipesModule } from '@pipes/pipes.module'; +import { AddonModGlossaryEntryPage } from './entry'; + +@NgModule({ + declarations: [ + AddonModGlossaryEntryPage, + ], + imports: [ + CoreComponentsModule, + CoreDirectivesModule, + CorePipesModule, + IonicPageModule.forChild(AddonModGlossaryEntryPage), + TranslateModule.forChild() + ], +}) +export class AddonModForumDiscussionPageModule {} diff --git a/src/addon/mod/glossary/pages/entry/entry.ts b/src/addon/mod/glossary/pages/entry/entry.ts new file mode 100644 index 000000000..f6aa7df41 --- /dev/null +++ b/src/addon/mod/glossary/pages/entry/entry.ts @@ -0,0 +1,110 @@ +// (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 { IonicPage, NavParams } from 'ionic-angular'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { AddonModGlossaryProvider } from '../../providers/glossary'; + +/** + * Page that displays a glossary entry. + */ +@IonicPage({ segment: 'addon-mod-glossary-entry' }) +@Component({ + selector: 'page-addon-mod-glossary-entry', + templateUrl: 'entry.html', +}) +export class AddonModGlossaryEntryPage { + component = AddonModGlossaryProvider.COMPONENT; + componentId: number; + entry: any; + loaded = false; + showAuthor = false; + showDate = false; + + protected courseId: number; + protected entryId: number; + + constructor(navParams: NavParams, + private domUtils: CoreDomUtilsProvider, + private glossaryProvider: AddonModGlossaryProvider) { + this.courseId = navParams.get('courseId'); + this.entryId = navParams.get('entryId'); + } + + /** + * View loaded. + */ + ionViewDidLoad(): void { + this.fetchEntry().then(() => { + this.glossaryProvider.logEntryView(this.entry.id); + }).finally(() => { + this.loaded = true; + }); + } + + /** + * Refresh the data. + * + * @param {any} [refresher] Refresher. + * @return {Promise} Promise resolved when done. + */ + doRefresh(refresher?: any): Promise { + return this.glossaryProvider.invalidateEntry(this.entry.id).catch(() => { + // Ignore errors. + }).then(() => { + return this.fetchEntry(true); + }).finally(() => { + refresher && refresher.complete(); + }); + } + + /** + * Convenience function to get the glossary entry. + * + * @param {boolean} [refresh] Whether we're refreshing data. + * @return {Promise} Promise resolved when done. + */ + protected fetchEntry(refresh?: boolean): Promise { + return this.glossaryProvider.getEntry(this.entryId).then((result) => { + this.entry = result; + + if (!refresh) { + // Load the glossary. + return this.glossaryProvider.getGlossaryById(this.courseId, this.entry.glossaryid).then((glossary) => { + this.componentId = glossary.coursemodule; + + switch (glossary.displayformat) { + case 'fullwithauthor': + case 'encyclopedia': + this.showAuthor = true; + this.showDate = true; + break; + case 'fullwithoutauthor': + this.showAuthor = false; + this.showDate = true; + break; + default: // Default, and faq, simple, entrylist, continuous. + this.showAuthor = false; + this.showDate = false; + } + }); + } + }).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'addon.mod_glossary.errorloadingentry', true); + + return Promise.reject(null); + }); + } +} diff --git a/src/addon/mod/glossary/pages/index/index.html b/src/addon/mod/glossary/pages/index/index.html new file mode 100644 index 000000000..02c599875 --- /dev/null +++ b/src/addon/mod/glossary/pages/index/index.html @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/addon/mod/glossary/pages/index/index.module.ts b/src/addon/mod/glossary/pages/index/index.module.ts new file mode 100644 index 000000000..415f45272 --- /dev/null +++ b/src/addon/mod/glossary/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 { AddonModGlossaryComponentsModule } from '../../components/components.module'; +import { AddonModGlossaryIndexPage } from './index'; + +@NgModule({ + declarations: [ + AddonModGlossaryIndexPage, + ], + imports: [ + CoreDirectivesModule, + AddonModGlossaryComponentsModule, + IonicPageModule.forChild(AddonModGlossaryIndexPage), + TranslateModule.forChild() + ], +}) +export class AddonModGlossaryIndexPageModule {} diff --git a/src/addon/mod/glossary/pages/index/index.ts b/src/addon/mod/glossary/pages/index/index.ts new file mode 100644 index 000000000..a812235fe --- /dev/null +++ b/src/addon/mod/glossary/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 { AddonModGlossaryIndexComponent } from '../../components/index/index'; + +/** + * Page that displays a glossary. + */ +@IonicPage({ segment: 'addon-mod-glossary-index' }) +@Component({ + selector: 'page-addon-mod-glossary-index', + templateUrl: 'index.html', +}) +export class AddonModGlossaryIndexPage { + @ViewChild(AddonModGlossaryIndexComponent) glossaryComponent: AddonModGlossaryIndexComponent; + + 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 glossary instance. + * + * @param {any} glossary Glossary instance. + */ + updateData(glossary: any): void { + this.title = glossary.name || this.title; + } +}