diff --git a/src/addon/mod/wiki/pages/edit/edit.ts b/src/addon/mod/wiki/pages/edit/edit.ts index b64a0fbe5..0a7dfdc14 100644 --- a/src/addon/mod/wiki/pages/edit/edit.ts +++ b/src/addon/mod/wiki/pages/edit/edit.ts @@ -482,8 +482,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy { pageId: this.pageId, subwikiId: this.subwikiId, pageTitle: title, - siteId: this.sitesProvider.getCurrentSiteId() - }); + }, this.sitesProvider.getCurrentSiteId()); }); } else { // Page stored in offline. Go to see the offline page. diff --git a/src/addon/mod/workshop/components/index/index.ts b/src/addon/mod/workshop/components/index/index.ts index ecbb91bbf..dbe91b367 100644 --- a/src/addon/mod/workshop/components/index/index.ts +++ b/src/addon/mod/workshop/components/index/index.ts @@ -117,7 +117,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity * @param {any} data Data received by the event. */ protected eventReceived(data: any): void { - if ((this.workshop && this.workshop.id === data.workshopid) || data.cmid === module.id) { + if ((this.workshop && this.workshop.id === data.workshopId) || data.cmId === this.module.id) { this.content && this.content.scrollToTop(); this.loaded = false; @@ -297,7 +297,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity if (task.code == 'submit' && this.canSubmit && ((this.access.creatingsubmissionallowed && !this.submission) || (this.access.modifyingsubmissionallowed && this.submission))) { const params = { - module: module, + module: this.module, access: this.access, courseId: this.courseId, submission: this.submission diff --git a/src/addon/mod/workshop/pages/edit-submission/edit-submission.html b/src/addon/mod/workshop/pages/edit-submission/edit-submission.html new file mode 100644 index 000000000..e03d7087c --- /dev/null +++ b/src/addon/mod/workshop/pages/edit-submission/edit-submission.html @@ -0,0 +1,30 @@ + + + {{ 'addon.mod_workshop.editsubmission' | translate }} + + + + + + + + + + +
+ + {{ 'addon.mod_workshop.submissiontitle' | translate }} + + + + + {{ 'addon.mod_workshop.submissioncontent' | translate }} + + + + +
+
+
diff --git a/src/addon/mod/workshop/pages/edit-submission/edit-submission.module.ts b/src/addon/mod/workshop/pages/edit-submission/edit-submission.module.ts new file mode 100644 index 000000000..c49f5e606 --- /dev/null +++ b/src/addon/mod/workshop/pages/edit-submission/edit-submission.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 { AddonModWorkshopEditSubmissionPage } from './edit-submission'; + +@NgModule({ + declarations: [ + AddonModWorkshopEditSubmissionPage, + ], + imports: [ + CoreDirectivesModule, + CoreComponentsModule, + IonicPageModule.forChild(AddonModWorkshopEditSubmissionPage), + TranslateModule.forChild() + ], +}) +export class AddonModWorkshopEditSubmissionPageModule {} diff --git a/src/addon/mod/workshop/pages/edit-submission/edit-submission.ts b/src/addon/mod/workshop/pages/edit-submission/edit-submission.ts new file mode 100644 index 000000000..213aebe00 --- /dev/null +++ b/src/addon/mod/workshop/pages/edit-submission/edit-submission.ts @@ -0,0 +1,395 @@ +// (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 } from '@angular/core'; +import { IonicPage, NavParams, NavController } from 'ionic-angular'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; +import { CoreEventsProvider } from '@providers/events'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreSyncProvider } from '@providers/sync'; +import { CoreFileSessionProvider } from '@providers/file-session'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreTextUtilsProvider } from '@providers/utils/text'; +import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader'; +import { AddonModWorkshopProvider } from '../../providers/workshop'; +import { AddonModWorkshopHelperProvider } from '../../providers/helper'; +import { AddonModWorkshopOfflineProvider } from '../../providers/offline'; + +/** + * Page that displays the workshop edit submission. + */ +@IonicPage({ segment: 'addon-mod-workshop-edit-submission' }) +@Component({ + selector: 'page-addon-mod-workshop-edit-submission', + templateUrl: 'edit-submission.html', +}) +export class AddonModWorkshopEditSubmissionPage implements OnInit, OnDestroy { + + module: any; + courseId: number; + access: any; + submission: any; + + title: string; + loaded = false; + component = AddonModWorkshopProvider.COMPONENT; + componentId: number; + editForm: FormGroup; // The form group. + + protected workshopId: number; + protected userId: number; + protected originalData: any = {}; + protected hasOffline = false; + protected editing = false; + protected forceLeave = false; + protected siteId: string; + protected workshop: any; + + constructor(navParams: NavParams, sitesProvider: CoreSitesProvider, protected fileUploaderProvider: CoreFileUploaderProvider, + protected workshopProvider: AddonModWorkshopProvider, protected workshopOffline: AddonModWorkshopOfflineProvider, + protected workshopHelper: AddonModWorkshopHelperProvider, protected navCtrl: NavController, + protected fileSessionprovider: CoreFileSessionProvider, protected syncProvider: CoreSyncProvider, + protected textUtils: CoreTextUtilsProvider, protected domUtils: CoreDomUtilsProvider, protected fb: FormBuilder, + protected translate: TranslateService, protected eventsProvider: CoreEventsProvider) { + this.module = navParams.get('module'); + this.courseId = navParams.get('courseId'); + this.access = navParams.get('access'); + this.submission = navParams.get('submission') || {}; + + this.title = this.module.name; + this.workshopId = this.module.instance; + this.componentId = this.module.id; + this.userId = sitesProvider.getCurrentSiteUserId(); + this.siteId = sitesProvider.getCurrentSiteId(); + + this.editForm = new FormGroup({}); + this.editForm.addControl('title', this.fb.control('', Validators.required)); + this.editForm.addControl('content', this.fb.control('')); + } + + /** + * Component being initialized. + */ + ngOnInit(): void { + this.fetchSubmissionData(); + } + + /** + * Check if we can leave the page or not. + * + * @return {boolean|Promise} Resolved if we can leave it, rejected if not. + */ + ionViewCanLeave(): boolean | Promise { + if (this.forceLeave) { + return true; + } + + let promise; + + // Check if data has changed. + if (!this.hasDataChanged()) { + promise = Promise.resolve(); + } else { + // Show confirmation if some data has been modified. + promise = this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit')); + } + + return promise.then(() => { + if (this.submission.attachmentfiles) { + // Delete the local files from the tmp folder. + this.fileUploaderProvider.clearTmpFiles(this.submission.attachmentfiles); + } + }); + } + + /** + * Fetch the submission data. + * + * @return {Promise} Resolved when done. + */ + protected fetchSubmissionData(): Promise { + return this.workshopProvider.getWorkshop(this.courseId, this.module.id).then((workshopData) => { + this.workshop = workshopData; + + if (this.submission && this.submission.id > 0) { + this.editing = true; + + return this.workshopHelper.getSubmissionById(this.workshopId, this.submission.id).then((submissionData) => { + this.submission = submissionData; + this.submission.text = submissionData.content; + + const canEdit = (this.userId == submissionData.authorid && this.access.cansubmit && + this.access.modifyingsubmissionallowed); + if (!canEdit) { + // Should not happen, but go back if does. + this.forceLeavePage(); + + return; + } + }); + } else if (!this.access.cansubmit || !this.access.creatingsubmissionallowed) { + // Should not happen, but go back if does. + this.forceLeavePage(); + + return; + } + + }).then(() => { + return this.workshopOffline.getSubmissions(this.workshopId).then((submissionsActions) => { + if (submissionsActions && submissionsActions.length) { + this.hasOffline = true; + const actions = this.workshopHelper.filterSubmissionActions(submissionsActions, this.editing ? + this.submission.id : false); + + return this.workshopHelper.applyOfflineData(this.submission, actions).then((offlineSubmission) => { + this.submission.title = offlineSubmission.title; + this.submission.text = offlineSubmission.content; + this.submission.attachmentfiles = offlineSubmission.attachmentfiles; + }); + } else { + this.hasOffline = false; + } + }).finally(() => { + this.originalData.title = this.submission.title; + this.originalData.content = this.submission.text; + this.originalData.attachmentfiles = []; + + this.submission.attachmentfiles.forEach((file) => { + let filename; + if (file.filename) { + filename = file.filename; + } else { + // We don't have filename, extract it from the path. + filename = file.filepath[0] == '/' ? file.filepath.substr(1) : file.filepath; + } + + this.originalData.attachmentfiles.push({ + filename : filename, + fileurl: file.fileurl + }); + }); + }); + }).then(() => { + // Create the form group and its controls. + this.editForm.controls['title'].setValue(this.submission.title); + this.editForm.controls['content'].setValue(this.submission.content); + + const submissionId = this.submission.id || 'newsub'; + this.fileSessionprovider.setFiles(this.component, + this.workshopId + '_' + submissionId, this.submission.attachmentfiles || []); + + this.loaded = true; + }).catch((message) => { + this.loaded = false; + + this.domUtils.showErrorModalDefault(message, 'core.course.errorgetmodule', true); + + this.forceLeavePage(); + }); + } + + /** + * Force leaving the page, without checking for changes. + */ + protected forceLeavePage(): void { + this.forceLeave = true; + this.navCtrl.pop(); + } + + /** + * Get the form input data. + * + * @return {any} Object with all the info. + */ + protected getInputData(): any { + const submissionId = this.submission.id || 'newsub'; + + const values = this.editForm.value; + values['attachmentfiles'] = this.fileSessionprovider.getFiles(this.component, this.workshopId + '_' + submissionId) || []; + + return values; + } + + /** + * Check if data has changed. + * + * @return {boolean} True if changed or false if not. + */ + protected hasDataChanged(): boolean { + if (!this.loaded) { + return false; + } + + const inputData = this.getInputData(); + if (!this.originalData || typeof this.originalData.title == 'undefined') { + // There is no original data, assume it hasn't changed. + return false; + } + + if (this.originalData.title != inputData.title || this.originalData.content != inputData.content) { + return true; + } + + return this.fileUploaderProvider.areFileListDifferent(inputData.attachmentfiles, this.originalData.attachmentfiles); + } + + /** + * Pull to refresh. + * + * @param {any} refresher Refresher. + */ + refreshSubmission(refresher: any): void { + if (this.loaded) { + const promises = []; + + promises.push(this.workshopProvider.invalidateSubmissionData(this.workshopId, this.submission.id)); + promises.push(this.workshopProvider.invalidateSubmissionsData(this.workshopId)); + + Promise.all(promises).finally(() => { + return this.fetchSubmissionData(); + }).finally(() => { + refresher.complete(); + }); + } + } + + /** + * Save the submission. + */ + save(): void { + // Check if data has changed. + if (this.hasDataChanged()) { + this.saveSubmission().then(() => { + // Go back to entry list. + this.forceLeavePage(); + }).catch(() => { + // Nothing to do. + }); + } else { + // Nothing to save, just go back. + this.forceLeavePage(); + } + } + + /** + * Send submission and save. + * + * @return {Promise} Resolved when done. + */ + protected saveSubmission(): Promise { + const inputData = this.getInputData(); + + if (!inputData.title) { + this.domUtils.showAlert('core.notice', 'core.requireduserdatamissing'); + + return Promise.reject(null); + } + if (!inputData.content) { + this.domUtils.showAlert('core.notice', 'addon.mod_workshop.submissionrequiredcontent'); + + return Promise.reject(null); + } + + let allowOffline = true, + saveOffline = false; + + const modal = this.domUtils.showModalLoading('core.sending', true), + submissionId = this.submission && (this.submission.id || this.submission.submissionid) || false; + + // Check if rich text editor is enabled or not. + return this.domUtils.isRichTextEditorEnabled().then((rteEnabled) => { + if (rteEnabled) { + inputData.content = this.textUtils.restorePluginfileUrls(inputData.content, this.submission.inlinefiles); + } else { + // Rich text editor not enabled, add some HTML to the message if needed. + inputData.content = this.textUtils.formatHtmlLines(inputData.content); + } + + // Upload attachments first if any. + allowOffline = !inputData.attachmentfiles.length; + + return this.workshopHelper.uploadOrStoreSubmissionFiles(this.workshopId, submissionId, inputData.attachmentfiles, + this.editing, saveOffline).catch(() => { + // Cannot upload them in online, save them in offline. + saveOffline = true; + allowOffline = true; + + return this.workshopHelper.uploadOrStoreSubmissionFiles(this.workshopId, submissionId, inputData.attachmentfiles, + this.editing, saveOffline); + }); + }).then((attachmentsId) => { + if (this.editing) { + if (saveOffline) { + // Save submission in offline. + return this.workshopOffline.saveSubmission(this.workshopId, this.courseId, inputData.title, + inputData.content, attachmentsId, submissionId, 'update').then(() => { + // Don't return anything. + }); + } + + // Try to send it to server. + // Don't allow offline if there are attachments since they were uploaded fine. + return this.workshopProvider.updateSubmission(this.workshopId, submissionId, this.courseId, inputData.title, + inputData.content, attachmentsId, undefined, allowOffline); + } + + if (saveOffline) { + // Save submission in offline. + return this.workshopOffline.saveSubmission(this.workshopId, this.courseId, inputData.title, inputData.content, + attachmentsId, submissionId, 'add').then(() => { + // Don't return anything. + }); + } + + // Try to send it to server. + // Don't allow offline if there are attachments since they were uploaded fine. + return this.workshopProvider.addSubmission(this.workshopId, this.courseId, inputData.title, inputData.content, + attachmentsId, undefined, submissionId, allowOffline); + }).then((newSubmissionId) => { + const data = { + workshopId: this.workshopId, + cmId: this.module.cmid + }; + + if (newSubmissionId && submissionId) { + // Data sent to server, delete stored files (if any). + this.workshopOffline.deleteSubmissionAction(this.workshopId, submissionId, this.editing ? 'update' : 'add'); + this.workshopHelper.deleteSubmissionStoredFiles(this.workshopId, submissionId, this.editing); + data['submissionId'] = newSubmissionId; + } + + const promise = newSubmissionId ? this.workshopProvider.invalidateSubmissionData(this.workshopId, newSubmissionId) : + Promise.resolve(); + + return promise.finally(() => { + this.eventsProvider.trigger(AddonModWorkshopProvider.SUBMISSION_CHANGED, data, this.siteId); + + // Delete the local files from the tmp folder. + this.fileUploaderProvider.clearTmpFiles(inputData.attachmentfiles); + }); + }).catch((message) => { + this.domUtils.showErrorModalDefault(message, 'Cannot save submission'); + }).finally(() => { + modal.dismiss(); + }); + } + + /** + * Component being destroyed. + */ + ngOnDestroy(): void { + this.syncProvider.unblockOperation(this.component, this.workshopId); + } +} diff --git a/src/addon/mod/workshop/providers/workshop.ts b/src/addon/mod/workshop/providers/workshop.ts index 9962b836d..27660ef6f 100644 --- a/src/addon/mod/workshop/providers/workshop.ts +++ b/src/addon/mod/workshop/providers/workshop.ts @@ -215,10 +215,9 @@ export class AddonModWorkshopProvider { return site.read('mod_workshop_get_workshops_by_courses', params, preSets).then((response) => { if (response && response.workshops) { - for (const x in response.workshops) { - if (response.workshops[x][key] == value) { - return response.workshops[x]; - } + const workshopFound = response.workshops.find((workshop) => workshop[key] == value); + if (workshopFound) { + return workshopFound; } }