399 lines
14 KiB
TypeScript
399 lines
14 KiB
TypeScript
// (C) Copyright 2015 Moodle Pty Ltd.
|
|
//
|
|
// 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, ViewChild, ElementRef } from '@angular/core';
|
|
import { ActivatedRoute } from '@angular/router';
|
|
import { CoreError } from '@classes/errors/error';
|
|
import { CoreFileUploaderHelper } from '@features/fileuploader/services/fileuploader-helper';
|
|
import { CanLeave } from '@guards/can-leave';
|
|
import { CoreNavigator } from '@services/navigator';
|
|
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
|
|
import { CoreSync } from '@services/sync';
|
|
import { CoreDomUtils } from '@services/utils/dom';
|
|
import { CoreFormFields, CoreForms } from '@singletons/form';
|
|
import { Translate } from '@singletons';
|
|
import { CoreEvents } from '@singletons/events';
|
|
import {
|
|
AddonModAssignAssign,
|
|
AddonModAssignSubmission,
|
|
AddonModAssignProvider,
|
|
AddonModAssign,
|
|
AddonModAssignSubmissionStatusOptions,
|
|
AddonModAssignGetSubmissionStatusWSResponse,
|
|
AddonModAssignSavePluginData,
|
|
} from '../../services/assign';
|
|
import { AddonModAssignHelper } from '../../services/assign-helper';
|
|
import { AddonModAssignOffline } from '../../services/assign-offline';
|
|
import { AddonModAssignSync } from '../../services/assign-sync';
|
|
|
|
/**
|
|
* Page that allows adding or editing an assigment submission.
|
|
*/
|
|
@Component({
|
|
selector: 'page-addon-mod-assign-edit',
|
|
templateUrl: 'edit.html',
|
|
})
|
|
export class AddonModAssignEditPage implements OnInit, OnDestroy, CanLeave {
|
|
|
|
@ViewChild('editSubmissionForm') formElement?: ElementRef;
|
|
|
|
title: string; // Title to display.
|
|
assign?: AddonModAssignAssign; // Assignment.
|
|
courseId!: number; // Course ID the assignment belongs to.
|
|
moduleId!: number; // Module ID the submission belongs to.
|
|
userSubmission?: AddonModAssignSubmission; // The user submission.
|
|
allowOffline = false; // Whether offline is allowed.
|
|
submissionStatement?: string; // The submission statement.
|
|
submissionStatementAccepted = false; // Whether submission statement is accepted.
|
|
loaded = false; // Whether data has been loaded.
|
|
|
|
protected userId: number; // User doing the submission.
|
|
protected isBlind = false; // Whether blind is used.
|
|
protected editText: string; // "Edit submission" translated text.
|
|
protected saveOffline = false; // Whether to save data in offline.
|
|
protected hasOffline = false; // Whether the assignment has offline data.
|
|
protected isDestroyed = false; // Whether the component has been destroyed.
|
|
protected forceLeave = false; // To allow leaving the page without checking for changes.
|
|
|
|
constructor(
|
|
protected route: ActivatedRoute,
|
|
) {
|
|
this.userId = CoreSites.getCurrentSiteUserId(); // Right now we can only edit current user's submissions.
|
|
this.editText = Translate.instant('addon.mod_assign.editsubmission');
|
|
this.title = this.editText;
|
|
}
|
|
|
|
/**
|
|
* Component being initialized.
|
|
*/
|
|
ngOnInit(): void {
|
|
this.moduleId = CoreNavigator.getRouteNumberParam('cmId')!;
|
|
this.courseId = CoreNavigator.getRouteNumberParam('courseId')!;
|
|
this.isBlind = !!CoreNavigator.getRouteNumberParam('blindId');
|
|
|
|
this.fetchAssignment().finally(() => {
|
|
this.loaded = true;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Check if we can leave the page or not.
|
|
*
|
|
* @return Resolved if we can leave it, rejected if not.
|
|
*/
|
|
async canLeave(): Promise<boolean> {
|
|
if (this.forceLeave) {
|
|
return true;
|
|
}
|
|
|
|
// Check if data has changed.
|
|
const changed = await this.hasDataChanged();
|
|
if (changed) {
|
|
await CoreDomUtils.showConfirm(Translate.instant('core.confirmcanceledit'));
|
|
}
|
|
|
|
// Nothing has changed or user confirmed to leave. Clear temporary data from plugins.
|
|
AddonModAssignHelper.clearSubmissionPluginTmpData(this.assign!, this.userSubmission, this.getInputData());
|
|
|
|
CoreForms.triggerFormCancelledEvent(this.formElement, CoreSites.getCurrentSiteId());
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Fetch assignment data.
|
|
*
|
|
* @return Promise resolved when done.
|
|
*/
|
|
protected async fetchAssignment(): Promise<void> {
|
|
const currentUserId = CoreSites.getCurrentSiteUserId();
|
|
|
|
try {
|
|
// Get assignment data.
|
|
this.assign = await AddonModAssign.getAssignment(this.courseId, this.moduleId);
|
|
this.title = this.assign.name || this.title;
|
|
|
|
if (!this.isDestroyed) {
|
|
// Block the assignment.
|
|
CoreSync.blockOperation(AddonModAssignProvider.COMPONENT, this.assign.id);
|
|
}
|
|
|
|
// Wait for sync to be over (if any).
|
|
await AddonModAssignSync.waitForSync(this.assign.id);
|
|
|
|
// Get submission status. Ignore cache to get the latest data.
|
|
const options: AddonModAssignSubmissionStatusOptions = {
|
|
userId: this.userId,
|
|
isBlind: this.isBlind,
|
|
cmId: this.assign.cmid,
|
|
filter: false,
|
|
readingStrategy: CoreSitesReadingStrategy.OnlyNetwork,
|
|
};
|
|
|
|
let submissionStatus: AddonModAssignGetSubmissionStatusWSResponse;
|
|
try {
|
|
submissionStatus = await AddonModAssign.getSubmissionStatus(this.assign.id, options);
|
|
this.userSubmission =
|
|
AddonModAssign.getSubmissionObjectFromAttempt(this.assign, submissionStatus.lastattempt);
|
|
} catch (error) {
|
|
// Cannot connect. Get cached data.
|
|
options.filter = true;
|
|
options.readingStrategy = CoreSitesReadingStrategy.PreferCache;
|
|
|
|
submissionStatus = await AddonModAssign.getSubmissionStatus(this.assign.id, options);
|
|
this.userSubmission =
|
|
AddonModAssign.getSubmissionObjectFromAttempt(this.assign, submissionStatus.lastattempt);
|
|
|
|
// Check if the user can edit it in offline.
|
|
const canEditOffline =
|
|
await AddonModAssignHelper.canEditSubmissionOffline(this.assign, this.userSubmission);
|
|
if (!canEditOffline) {
|
|
// Submission cannot be edited in offline, reject.
|
|
this.allowOffline = false;
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
if (!submissionStatus.lastattempt?.canedit) {
|
|
// Can't edit. Reject.
|
|
throw new CoreError(Translate.instant('core.nopermissions', { $a: this.editText }));
|
|
}
|
|
|
|
this.allowOffline = true; // If offline isn't allowed we shouldn't have reached this point.
|
|
// Only show submission statement if we are editing our own submission.
|
|
if (this.assign.requiresubmissionstatement && !this.assign.submissiondrafts && this.userId == currentUserId) {
|
|
this.submissionStatement = this.assign.submissionstatement;
|
|
} else {
|
|
this.submissionStatement = undefined;
|
|
}
|
|
|
|
try {
|
|
// Check if there's any offline data for this submission.
|
|
const offlineData = await AddonModAssignOffline.getSubmission(this.assign.id, this.userId);
|
|
|
|
this.hasOffline = offlineData?.plugindata && Object.keys(offlineData.plugindata).length > 0;
|
|
} catch {
|
|
// No offline data found.
|
|
this.hasOffline = false;
|
|
}
|
|
} catch (error) {
|
|
CoreDomUtils.showErrorModalDefault(error, 'Error getting assigment data.');
|
|
|
|
// Leave the player.
|
|
this.leaveWithoutCheck();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the input data.
|
|
*
|
|
* @return Input data.
|
|
*/
|
|
protected getInputData(): CoreFormFields {
|
|
return CoreForms.getDataFromForm(document.forms['addon-mod_assign-edit-form']);
|
|
}
|
|
|
|
/**
|
|
* Check if data has changed.
|
|
*
|
|
* @return Promise resolved with boolean: whether data has changed.
|
|
*/
|
|
protected async hasDataChanged(): Promise<boolean> {
|
|
// Usually the hasSubmissionDataChanged call will be resolved inmediately, causing the modal to be shown just an instant.
|
|
// We'll wait a bit before showing it to prevent this "blink".
|
|
const modal = await CoreDomUtils.showModalLoading();
|
|
|
|
const data = this.getInputData();
|
|
|
|
return AddonModAssignHelper.hasSubmissionDataChanged(this.assign!, this.userSubmission, data).finally(() => {
|
|
modal.dismiss();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Leave the view without checking for changes.
|
|
*/
|
|
protected leaveWithoutCheck(): void {
|
|
this.forceLeave = true;
|
|
CoreNavigator.back();
|
|
}
|
|
|
|
/**
|
|
* Get data to submit based on the input data.
|
|
*
|
|
* @param inputData The input data.
|
|
* @return Promise resolved with the data to submit.
|
|
*/
|
|
protected prepareSubmissionData(inputData: CoreFormFields): Promise<AddonModAssignSavePluginData> {
|
|
// If there's offline data, always save it in offline.
|
|
this.saveOffline = this.hasOffline;
|
|
|
|
try {
|
|
return AddonModAssignHelper.prepareSubmissionPluginData(
|
|
this.assign!,
|
|
this.userSubmission,
|
|
inputData,
|
|
this.hasOffline,
|
|
);
|
|
} catch (error) {
|
|
if (this.allowOffline && !this.saveOffline) {
|
|
// Cannot submit in online, prepare for offline usage.
|
|
this.saveOffline = true;
|
|
|
|
return AddonModAssignHelper.prepareSubmissionPluginData(
|
|
this.assign!,
|
|
this.userSubmission,
|
|
inputData,
|
|
true,
|
|
);
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save the submission.
|
|
*/
|
|
async save(): Promise<void> {
|
|
// Check if data has changed.
|
|
const changed = await this.hasDataChanged();
|
|
if (!changed) {
|
|
// Nothing to save, just go back.
|
|
this.leaveWithoutCheck();
|
|
|
|
return;
|
|
}
|
|
try {
|
|
await this.saveSubmission();
|
|
this.leaveWithoutCheck();
|
|
} catch (error) {
|
|
CoreDomUtils.showErrorModalDefault(error, 'Error saving submission.');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save the submission.
|
|
*
|
|
* @return Promise resolved when done.
|
|
*/
|
|
protected async saveSubmission(): Promise<void> {
|
|
const inputData = this.getInputData();
|
|
|
|
if (this.submissionStatement && (!inputData.submissionstatement || inputData.submissionstatement === 'false')) {
|
|
throw Translate.instant('addon.mod_assign.acceptsubmissionstatement');
|
|
}
|
|
|
|
let modal = await CoreDomUtils.showModalLoading();
|
|
let size = -1;
|
|
|
|
// Get size to ask for confirmation.
|
|
try {
|
|
size = await AddonModAssignHelper.getSubmissionSizeForEdit(this.assign!, this.userSubmission!, inputData);
|
|
} catch (error) {
|
|
// Error calculating size, return -1.
|
|
size = -1;
|
|
}
|
|
|
|
modal.dismiss();
|
|
|
|
try {
|
|
// Confirm action.
|
|
await CoreFileUploaderHelper.confirmUploadFile(size, true, this.allowOffline);
|
|
|
|
modal = await CoreDomUtils.showModalLoading('core.sending', true);
|
|
|
|
const pluginData = await this.prepareSubmissionData(inputData);
|
|
if (!Object.keys(pluginData).length) {
|
|
// Nothing to save.
|
|
return;
|
|
}
|
|
|
|
let sent: boolean;
|
|
|
|
if (this.saveOffline) {
|
|
// Save submission in offline.
|
|
sent = false;
|
|
await AddonModAssignOffline.saveSubmission(
|
|
this.assign!.id,
|
|
this.courseId,
|
|
pluginData,
|
|
this.userSubmission!.timemodified,
|
|
!this.assign!.submissiondrafts,
|
|
this.userId,
|
|
);
|
|
} else {
|
|
// Try to send it to server.
|
|
sent = await AddonModAssign.saveSubmission(
|
|
this.assign!.id,
|
|
this.courseId,
|
|
pluginData,
|
|
this.allowOffline,
|
|
this.userSubmission!.timemodified,
|
|
!!this.assign!.submissiondrafts,
|
|
this.userId,
|
|
);
|
|
}
|
|
|
|
// Clear temporary data from plugins.
|
|
AddonModAssignHelper.clearSubmissionPluginTmpData(this.assign!, this.userSubmission, inputData);
|
|
|
|
if (sent) {
|
|
CoreEvents.trigger(CoreEvents.ACTIVITY_DATA_SENT, { module: 'assign' });
|
|
}
|
|
|
|
// Submission saved, trigger events.
|
|
CoreForms.triggerFormSubmittedEvent(this.formElement, sent, CoreSites.getCurrentSiteId());
|
|
|
|
CoreEvents.trigger(
|
|
AddonModAssignProvider.SUBMISSION_SAVED_EVENT,
|
|
{
|
|
assignmentId: this.assign!.id,
|
|
submissionId: this.userSubmission!.id,
|
|
userId: this.userId,
|
|
},
|
|
CoreSites.getCurrentSiteId(),
|
|
);
|
|
|
|
if (!this.assign!.submissiondrafts) {
|
|
// No drafts allowed, so it was submitted. Trigger event.
|
|
CoreEvents.trigger(
|
|
AddonModAssignProvider.SUBMITTED_FOR_GRADING_EVENT,
|
|
{
|
|
assignmentId: this.assign!.id,
|
|
submissionId: this.userSubmission!.id,
|
|
userId: this.userId,
|
|
},
|
|
CoreSites.getCurrentSiteId(),
|
|
);
|
|
}
|
|
} finally {
|
|
modal.dismiss();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Component being destroyed.
|
|
*/
|
|
ngOnDestroy(): void {
|
|
this.isDestroyed = true;
|
|
|
|
// Unblock the assignment.
|
|
if (this.assign) {
|
|
CoreSync.unblockOperation(AddonModAssignProvider.COMPONENT, this.assign.id);
|
|
}
|
|
}
|
|
|
|
}
|