MOBILE-2354 workshop: Edit submission page

main
Pau Ferrer Ocaña 2018-06-01 13:31:33 +02:00 committed by Albert Gasset
parent b25e5b3ec1
commit a420d4eb8a
6 changed files with 464 additions and 8 deletions

View File

@ -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.

View File

@ -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

View File

@ -0,0 +1,30 @@
<ion-header>
<ion-navbar>
<ion-title>{{ 'addon.mod_workshop.editsubmission' | translate }}</ion-title>
<ion-buttons end>
<button ion-button clear (click)="save()" [attr.aria-label]="'core.save' | translate">
{{ 'core.save' | translate }}
</button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content>
<ion-refresher [enabled]="loaded" (ionRefresh)="refreshSubmission($event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-loading [hideUntil]="loaded">
<form ion-list [formGroup]="editForm" *ngIf="workshop">
<ion-item text-wrap>
<ion-label stacked core-mark-required="true">{{ 'addon.mod_workshop.submissiontitle' | translate }}</ion-label>
<ion-input name="title" type="text" [placeholder]="'addon.mod_workshop.submissiontitle' | translate" formControlName="title"></ion-input>
</ion-item>
<ion-item>
<ion-label stacked>{{ 'addon.mod_workshop.submissioncontent' | translate }}</ion-label>
<core-rich-text-editor item-content [control]="editForm.controls['content']" formControlName="content" [placeholder]="'addon.mod_workshop.submissioncontent' | translate" name="content" [component]="component" [componentId]="componentId"></core-rich-text-editor>
</ion-item>
<core-attachments *ngIf="workshop.nattachments > 0" [files]="submission.attachmentfiles" [maxSize]="workshop.maxbytes" [maxSubmissions]="workshop.nattachments" [component]="component" [componentId]="workshop.cmid" allowOffline="true" [acceptedTypes]="workshop.submissionfiletypes"></core-attachments>
</form>
</core-loading>
</ion-content>

View File

@ -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 {}

View File

@ -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<void>} Resolved if we can leave it, rejected if not.
*/
ionViewCanLeave(): boolean | Promise<void> {
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<void>} Resolved when done.
*/
protected fetchSubmissionData(): Promise<void> {
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<any>} Resolved when done.
*/
protected saveSubmission(): Promise<any> {
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);
}
}

View File

@ -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;
}
}