MOBILE-3636 assign: Submission review and edit pages
parent
a4b356350b
commit
5350f715a3
|
@ -12,22 +12,61 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { conditionalRoutes } from '@/app/app-routing.module';
|
||||||
import { CoreSharedModule } from '@/core/shared.module';
|
import { CoreSharedModule } from '@/core/shared.module';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { CanLeaveGuard } from '@guards/can-leave';
|
||||||
|
import { CoreScreen } from '@services/screen';
|
||||||
import { AddonModAssignComponentsModule } from './components/components.module';
|
import { AddonModAssignComponentsModule } from './components/components.module';
|
||||||
|
import { AddonModAssignEditPage } from './pages/edit/edit';
|
||||||
import { AddonModAssignIndexPage } from './pages/index/index.page';
|
import { AddonModAssignIndexPage } from './pages/index/index.page';
|
||||||
import { AddonModAssignSubmissionListPage } from './pages/submission-list/submission-list.page';
|
import { AddonModAssignSubmissionListPage } from './pages/submission-list/submission-list.page';
|
||||||
|
import { AddonModAssignSubmissionReviewPage } from './pages/submission-review/submission-review';
|
||||||
|
|
||||||
const routes: Routes = [
|
const commonRoutes: Routes = [
|
||||||
{
|
{
|
||||||
path: ':courseId/:cmId',
|
path: ':courseId/:cmId',
|
||||||
component: AddonModAssignIndexPage,
|
component: AddonModAssignIndexPage,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: ':courseId/:cmId/submission-list',
|
path: ':courseId/:cmId/edit',
|
||||||
|
component: AddonModAssignEditPage,
|
||||||
|
canDeactivate: [CanLeaveGuard],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const mobileRoutes: Routes = [
|
||||||
|
...commonRoutes,
|
||||||
|
{
|
||||||
|
path: ':courseId/:cmId/submission',
|
||||||
component: AddonModAssignSubmissionListPage,
|
component: AddonModAssignSubmissionListPage,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: ':courseId/:cmId/submission/:submitId',
|
||||||
|
component: AddonModAssignSubmissionReviewPage,
|
||||||
|
canDeactivate: [CanLeaveGuard],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const tabletRoutes: Routes = [
|
||||||
|
...commonRoutes,
|
||||||
|
{
|
||||||
|
path: ':courseId/:cmId/submission',
|
||||||
|
component: AddonModAssignSubmissionListPage,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: ':submitId',
|
||||||
|
component: AddonModAssignSubmissionReviewPage,
|
||||||
|
canDeactivate: [CanLeaveGuard],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
...conditionalRoutes(mobileRoutes, () => CoreScreen.instance.isMobile),
|
||||||
|
...conditionalRoutes(tabletRoutes, () => CoreScreen.instance.isTablet),
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@ -39,6 +78,8 @@ const routes: Routes = [
|
||||||
declarations: [
|
declarations: [
|
||||||
AddonModAssignIndexPage,
|
AddonModAssignIndexPage,
|
||||||
AddonModAssignSubmissionListPage,
|
AddonModAssignSubmissionListPage,
|
||||||
|
AddonModAssignSubmissionReviewPage,
|
||||||
|
AddonModAssignEditPage,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AddonModAssignLazyModule {}
|
export class AddonModAssignLazyModule {}
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
<ion-item class="ion-text-wrap">
|
<ion-item class="ion-text-wrap">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<core-format-text [text]="description" [component]="component" [componentId]="componentId" maxHeight="120"
|
<core-format-text [text]="description" [component]="component" [componentId]="componentId" maxHeight="120"
|
||||||
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId" (click)="expandDescription($event)">
|
contextLevel="module" [contextInstanceId]="module!.id" [courseId]="courseId" (click)="expandDescription($event)">
|
||||||
</core-format-text>
|
</core-format-text>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
@ -97,7 +97,7 @@
|
||||||
<!-- Summary of submissions with draft status. -->
|
<!-- Summary of submissions with draft status. -->
|
||||||
<ion-item class="ion-text-wrap" *ngIf="assign.submissiondrafts && summary && summary.submissionsenabled"
|
<ion-item class="ion-text-wrap" *ngIf="assign.submissiondrafts && summary && summary.submissionsenabled"
|
||||||
[detail]="!showNumbers || summary.submissiondraftscount"
|
[detail]="!showNumbers || summary.submissiondraftscount"
|
||||||
(click)="goToSubmissionList(submissionStatusDraft, summary.submissiondraftscount)">
|
(click)="goToSubmissionList(submissionStatusDraft, !!summary.submissiondraftscount)">
|
||||||
<ion-label><h2>{{ 'addon.mod_assign.numberofdraftsubmissions' | translate }}</h2></ion-label>
|
<ion-label><h2>{{ 'addon.mod_assign.numberofdraftsubmissions' | translate }}</h2></ion-label>
|
||||||
<ion-badge slot="end" *ngIf="showNumbers" color="primary">
|
<ion-badge slot="end" *ngIf="showNumbers" color="primary">
|
||||||
{{ summary.submissiondraftscount }}
|
{{ summary.submissiondraftscount }}
|
||||||
|
@ -107,7 +107,7 @@
|
||||||
<!-- Summary of submissions with submitted status. -->
|
<!-- Summary of submissions with submitted status. -->
|
||||||
<ion-item class="ion-text-wrap" *ngIf="summary && summary.submissionsenabled"
|
<ion-item class="ion-text-wrap" *ngIf="summary && summary.submissionsenabled"
|
||||||
[detail]="!showNumbers || summary.submissionssubmittedcount"
|
[detail]="!showNumbers || summary.submissionssubmittedcount"
|
||||||
(click)="goToSubmissionList(submissionStatusSubmitted, summary.submissionssubmittedcount)">
|
(click)="goToSubmissionList(submissionStatusSubmitted, !!summary.submissionssubmittedcount)">
|
||||||
<ion-label><h2>{{ 'addon.mod_assign.numberofsubmittedassignments' | translate }}</h2></ion-label>
|
<ion-label><h2>{{ 'addon.mod_assign.numberofsubmittedassignments' | translate }}</h2></ion-label>
|
||||||
<ion-badge slot="end" *ngIf="showNumbers" color="primary">
|
<ion-badge slot="end" *ngIf="showNumbers" color="primary">
|
||||||
{{ summary.submissionssubmittedcount }}
|
{{ summary.submissionssubmittedcount }}
|
||||||
|
@ -136,7 +136,7 @@
|
||||||
|
|
||||||
<!-- If it's a student, display his submission. -->
|
<!-- If it's a student, display his submission. -->
|
||||||
<addon-mod-assign-submission *ngIf="loaded && !canViewAllSubmissions && canViewOwnSubmission" [courseId]="courseId"
|
<addon-mod-assign-submission *ngIf="loaded && !canViewAllSubmissions && canViewOwnSubmission" [courseId]="courseId"
|
||||||
[moduleId]="module.id">
|
[moduleId]="module!.id">
|
||||||
</addon-mod-assign-submission>
|
</addon-mod-assign-submission>
|
||||||
|
|
||||||
</core-loading>
|
</core-loading>
|
||||||
|
|
|
@ -34,6 +34,7 @@ import {
|
||||||
AddonModAssignGradedEventData,
|
AddonModAssignGradedEventData,
|
||||||
AddonModAssignProvider,
|
AddonModAssignProvider,
|
||||||
AddonModAssignSubmissionGradingSummary,
|
AddonModAssignSubmissionGradingSummary,
|
||||||
|
AddonModAssignSubmissionSavedEventData,
|
||||||
AddonModAssignSubmittedForGradingEventData,
|
AddonModAssignSubmittedForGradingEventData,
|
||||||
} from '../../services/assign';
|
} from '../../services/assign';
|
||||||
import { AddonModAssignOffline } from '../../services/assign-offline';
|
import { AddonModAssignOffline } from '../../services/assign-offline';
|
||||||
|
@ -106,12 +107,16 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
this.currentSite = CoreSites.instance.getCurrentSite();
|
this.currentSite = CoreSites.instance.getCurrentSite();
|
||||||
|
|
||||||
// Listen to events.
|
// Listen to events.
|
||||||
this.savedObserver = CoreEvents.on<any>(AddonModAssignProvider.SUBMISSION_SAVED_EVENT, (data) => {
|
this.savedObserver = CoreEvents.on<AddonModAssignSubmissionSavedEventData>(
|
||||||
if (this.assign && data.assignmentId == this.assign.id && data.userId == this.currentUserId) {
|
AddonModAssignProvider.SUBMISSION_SAVED_EVENT,
|
||||||
|
(data) => {
|
||||||
|
if (this.assign && data.assignmentId == this.assign.id && data.userId == this.currentUserId) {
|
||||||
// Assignment submission saved, refresh data.
|
// Assignment submission saved, refresh data.
|
||||||
this.showLoadingAndRefresh(true, false);
|
this.showLoadingAndRefresh(true, false);
|
||||||
}
|
}
|
||||||
}, this.siteId);
|
},
|
||||||
|
this.siteId,
|
||||||
|
);
|
||||||
|
|
||||||
this.submittedObserver = CoreEvents.on<AddonModAssignSubmittedForGradingEventData>(
|
this.submittedObserver = CoreEvents.on<AddonModAssignSubmittedForGradingEventData>(
|
||||||
AddonModAssignProvider.SUBMITTED_FOR_GRADING_EVENT,
|
AddonModAssignProvider.SUBMITTED_FOR_GRADING_EVENT,
|
||||||
|
@ -262,7 +267,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
* @param groupId Group ID.
|
* @param groupId Group ID.
|
||||||
* @return Resolved when done.
|
* @return Resolved when done.
|
||||||
*/
|
*/
|
||||||
async setGroup(groupId: number): Promise<void> {
|
async setGroup(groupId = 0): Promise<void> {
|
||||||
this.group = groupId;
|
this.group = groupId;
|
||||||
|
|
||||||
const submissionStatus = await AddonModAssign.instance.getSubmissionStatus(this.assign!.id, {
|
const submissionStatus = await AddonModAssign.instance.getSubmissionStatus(this.assign!.id, {
|
||||||
|
@ -303,10 +308,10 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
* Go to view a list of submissions.
|
* Go to view a list of submissions.
|
||||||
*
|
*
|
||||||
* @param status Status to see.
|
* @param status Status to see.
|
||||||
* @param count Number of submissions with the status.
|
* @param hasSubmissions If the status has any submission.
|
||||||
*/
|
*/
|
||||||
goToSubmissionList(status: string, count: number): void {
|
goToSubmissionList(status?: string, hasSubmissions = false): void {
|
||||||
if (typeof status != 'undefined' && !count && this.showNumbers) {
|
if (typeof status != 'undefined' && !hasSubmissions && this.showNumbers) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||||
|
|
||||||
<!-- User and status of the submission. -->
|
<!-- User and status of the submission. -->
|
||||||
<ion-item class="ion-text-wrap" *ngIf="!blindMarking && user" core-user-link [userId]="submitId" [courseId]="courseId" [title]="user.fullname">
|
<ion-item class="ion-text-wrap" *ngIf="!blindMarking && user" core-user-link [userId]="submitId" [courseId]="courseId" [title]="user!.fullname">
|
||||||
<core-user-avatar [user]="user" slot="start"></core-user-avatar>
|
<core-user-avatar [user]="user" slot="start"></core-user-avatar>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>{{ user.fullname }}</h2>
|
<h2>{{ user!.fullname }}</h2>
|
||||||
<ng-container *ngTemplateOutlet="submissionStatus"></ng-container>
|
<ng-container *ngTemplateOutlet="submissionStatus"></ng-container>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ng-container *ngTemplateOutlet="submissionStatusBadges"></ng-container>
|
<ng-container *ngTemplateOutlet="submissionStatusBadges"></ng-container>
|
||||||
|
@ -39,10 +39,10 @@
|
||||||
|
|
||||||
<!-- Render some data about the submission. -->
|
<!-- Render some data about the submission. -->
|
||||||
<ion-item class="ion-text-wrap"
|
<ion-item class="ion-text-wrap"
|
||||||
*ngIf="userSubmission && userSubmission.status != statusNew && userSubmission.timemodified">
|
*ngIf="userSubmission && userSubmission!.status != statusNew && userSubmission!.timemodified">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>{{ 'addon.mod_assign.timemodified' | translate }}</h2>
|
<h2>{{ 'addon.mod_assign.timemodified' | translate }}</h2>
|
||||||
<p>{{ userSubmission.timemodified * 1000 | coreFormatDate }}</p>
|
<p>{{ userSubmission!.timemodified * 1000 | coreFormatDate }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
|
@ -55,49 +55,49 @@
|
||||||
|
|
||||||
<ion-item class="ion-text-wrap" *ngIf="fromDate && !isSubmittedForGrading">
|
<ion-item class="ion-text-wrap" *ngIf="fromDate && !isSubmittedForGrading">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<p *ngIf="assign.intro"
|
<p *ngIf="assign!.intro"
|
||||||
[innerHTML]="'addon.mod_assign.allowsubmissionsfromdatesummary' | translate: {'$a': fromDate}">
|
[innerHTML]="'addon.mod_assign.allowsubmissionsfromdatesummary' | translate: {'$a': fromDate}">
|
||||||
</p>
|
</p>
|
||||||
<p *ngIf="!assign.intro"
|
<p *ngIf="!assign!.intro"
|
||||||
[innerHTML]="'addon.mod_assign.allowsubmissionsanddescriptionfromdatesummary' | translate:
|
[innerHTML]="'addon.mod_assign.allowsubmissionsanddescriptionfromdatesummary' | translate:
|
||||||
{'$a': fromDate}">
|
{'$a': fromDate}">
|
||||||
</p>
|
</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item class="ion-text-wrap" *ngIf="assign.duedate && !isSubmittedForGrading">
|
<ion-item class="ion-text-wrap" *ngIf="assign!.duedate && !isSubmittedForGrading">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>{{ 'addon.mod_assign.duedate' | translate }}</h2>
|
<h2>{{ 'addon.mod_assign.duedate' | translate }}</h2>
|
||||||
<p *ngIf="assign.duedate" >{{ assign.duedate * 1000 | coreFormatDate }}</p>
|
<p *ngIf="assign!.duedate" >{{ assign!.duedate * 1000 | coreFormatDate }}</p>
|
||||||
<p *ngIf="!assign.duedate" >{{ 'addon.mod_assign.duedateno' | translate }}</p>
|
<p *ngIf="!assign!.duedate" >{{ 'addon.mod_assign.duedateno' | translate }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item class="ion-text-wrap" *ngIf="assign.duedate && assign.cutoffdate && isSubmittedForGrading">
|
<ion-item class="ion-text-wrap" *ngIf="assign!.duedate && assign!.cutoffdate && isSubmittedForGrading">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>{{ 'addon.mod_assign.cutoffdate' | translate }}</h2>
|
<h2>{{ 'addon.mod_assign.cutoffdate' | translate }}</h2>
|
||||||
<p>{{ assign.cutoffdate * 1000 | coreFormatDate }}</p>
|
<p>{{ assign!.cutoffdate * 1000 | coreFormatDate }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item class="ion-text-wrap"
|
<ion-item class="ion-text-wrap"
|
||||||
*ngIf="assign.duedate && lastAttempt && lastAttempt.extensionduedate && !isSubmittedForGrading">
|
*ngIf="assign!.duedate && lastAttempt?.extensionduedate && !isSubmittedForGrading">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>{{ 'addon.mod_assign.extensionduedate' | translate }}</h2>
|
<h2>{{ 'addon.mod_assign.extensionduedate' | translate }}</h2>
|
||||||
<p>{{ lastAttempt.extensionduedate * 1000 | coreFormatDate }}</p>
|
<p>{{ lastAttempt!.extensionduedate * 1000 | coreFormatDate }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item class="ion-text-wrap" *ngIf="currentAttempt && !isGrading">
|
<ion-item class="ion-text-wrap" *ngIf="currentAttempt && !isGrading">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>{{ 'addon.mod_assign.attemptnumber' | translate }}</h2>
|
<h2>{{ 'addon.mod_assign.attemptnumber' | translate }}</h2>
|
||||||
<p *ngIf="assign.maxattempts == unlimitedAttempts">
|
<p *ngIf="assign!.maxattempts == unlimitedAttempts">
|
||||||
{{ 'addon.mod_assign.outof' | translate :
|
{{ 'addon.mod_assign.outof' | translate :
|
||||||
{'$a': {'current': currentAttempt, 'total': maxAttemptsText} } }}
|
{'$a': {'current': currentAttempt, 'total': maxAttemptsText} } }}
|
||||||
</p>
|
</p>
|
||||||
<p *ngIf="assign.maxattempts != unlimitedAttempts">
|
<p *ngIf="assign!.maxattempts != unlimitedAttempts">
|
||||||
{{ 'addon.mod_assign.outof' | translate :
|
{{ 'addon.mod_assign.outof' | translate :
|
||||||
{'$a': {'current': currentAttempt, 'total': assign.maxattempts} } }}
|
{'$a': {'current': currentAttempt, 'total': assign!.maxattempts} } }}
|
||||||
</p>
|
</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
@ -114,12 +114,12 @@
|
||||||
<!-- If no submission or is new, show add submission. -->
|
<!-- If no submission or is new, show add submission. -->
|
||||||
<ion-button expand="block" class="ion-text-wrap" color="primary"
|
<ion-button expand="block" class="ion-text-wrap" color="primary"
|
||||||
*ngIf="!hasOffline &&
|
*ngIf="!hasOffline &&
|
||||||
(!userSubmission || !userSubmission.status || userSubmission.status == statusNew)"
|
(!userSubmission || !userSubmission!.status || userSubmission!.status == statusNew)"
|
||||||
(click)="goToEdit()">
|
(click)="goToEdit()">
|
||||||
{{ 'addon.mod_assign.addsubmission' | translate }}
|
{{ 'addon.mod_assign.addsubmission' | translate }}
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<!-- If reopened, show addfromprevious and addnewattempt. -->
|
<!-- If reopened, show addfromprevious and addnewattempt. -->
|
||||||
<ng-container *ngIf="!hasOffline && userSubmission && userSubmission.status == statusReopened">
|
<ng-container *ngIf="!hasOffline && userSubmission?.status == statusReopened">
|
||||||
<ion-button *ngIf="!isPreviousAttemptEmpty" expand="block" class="ion-text-wrap" color="primary"
|
<ion-button *ngIf="!isPreviousAttemptEmpty" expand="block" class="ion-text-wrap" color="primary"
|
||||||
(click)="copyPrevious()">
|
(click)="copyPrevious()">
|
||||||
{{ 'addon.mod_assign.addnewattemptfromprevious' | translate }}
|
{{ 'addon.mod_assign.addnewattemptfromprevious' | translate }}
|
||||||
|
@ -130,9 +130,9 @@
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<!-- Else show editsubmission. -->
|
<!-- Else show editsubmission. -->
|
||||||
<ion-button expand="block" class="ion-text-wrap" color="primary"
|
<ion-button expand="block" class="ion-text-wrap" color="primary"
|
||||||
*ngIf="!hasOffline && userSubmission && userSubmission.status &&
|
*ngIf="!hasOffline && userSubmission && userSubmission!.status &&
|
||||||
userSubmission.status != statusNew &&
|
userSubmission!.status != statusNew &&
|
||||||
userSubmission.status != statusReopened" (click)="goToEdit()">
|
userSubmission!.status != statusReopened" (click)="goToEdit()">
|
||||||
{{ 'addon.mod_assign.editsubmission' | translate }}
|
{{ 'addon.mod_assign.editsubmission' | translate }}
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -176,25 +176,25 @@
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<!-- Team members that need to submit it too. -->
|
<!-- Team members that need to submit it too. -->
|
||||||
<ion-item class="ion-text-wrap" *ngIf="membersToSubmit && membersToSubmit.length > 0">
|
<ion-item-divider class="ion-text-wrap" *ngIf="membersToSubmit && membersToSubmit.length > 0">
|
||||||
<ion-label><h2>{{ 'addon.mod_assign.userswhoneedtosubmit' | translate: {$a: ''} }}</h2></ion-label>
|
<ion-label><h2>{{ 'addon.mod_assign.userswhoneedtosubmit' | translate: {$a: ''} }}</h2></ion-label>
|
||||||
<ng-container *ngIf="!blindMarking">
|
</ion-item-divider>
|
||||||
<ng-container *ngFor="let user of membersToSubmit">
|
<ng-container *ngIf="membersToSubmit && membersToSubmit.length > 0 && !blindMarking">
|
||||||
<ion-item class="ion-text-wrap" core-user-link [userId]="user.id"
|
<ng-container *ngFor="let user of membersToSubmit">
|
||||||
[courseId]="courseId" [title]="user.fullname">
|
<ion-item class="ion-text-wrap" core-user-link [userId]="user.id"
|
||||||
<core-user-avatar [user]="user" slot="start"></core-user-avatar>
|
[courseId]="courseId" [title]="user.fullname">
|
||||||
<ion-label><h2>{{ user.fullname }}</h2></ion-label>
|
<core-user-avatar [user]="user" slot="start"></core-user-avatar>
|
||||||
</ion-item>
|
<ion-label><h2>{{ user.fullname }}</h2></ion-label>
|
||||||
</ng-container>
|
</ion-item>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="blindMarking">
|
</ng-container>
|
||||||
<ng-container *ngFor="let blindId of membersToSubmitBlind">
|
<ng-container *ngIf="membersToSubmit && membersToSubmit.length > 0 && blindMarking">
|
||||||
<ion-item class="ion-text-wrap">
|
<ng-container *ngFor="let blindId of membersToSubmitBlind">
|
||||||
<ion-label>{{ 'addon.mod_assign.hiddenuser' | translate }} {{ blindId }}</ion-label>
|
<ion-item class="ion-text-wrap">
|
||||||
</ion-item>
|
<ion-label>{{ 'addon.mod_assign.hiddenuser' | translate }} {{ blindId }}</ion-label>
|
||||||
</ng-container>
|
</ion-item>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ion-item>
|
</ng-container>
|
||||||
|
|
||||||
<!-- Submission is locked. -->
|
<!-- Submission is locked. -->
|
||||||
<ion-item class="ion-text-wrap" *ngIf="lastAttempt?.locked">
|
<ion-item class="ion-text-wrap" *ngIf="lastAttempt?.locked">
|
||||||
|
@ -219,12 +219,12 @@
|
||||||
<ng-template>
|
<ng-template>
|
||||||
<!-- Current grade if method is advanced. -->
|
<!-- Current grade if method is advanced. -->
|
||||||
<ion-item class="ion-text-wrap core-grading-summary"
|
<ion-item class="ion-text-wrap core-grading-summary"
|
||||||
*ngIf="feedback.gradefordisplay && (!isGrading || grade.method != 'simple')">
|
*ngIf="feedback?.gradefordisplay && (!isGrading || grade.method != 'simple')">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>{{ 'addon.mod_assign.currentgrade' | translate }}</h2>
|
<h2>{{ 'addon.mod_assign.currentgrade' | translate }}</h2>
|
||||||
<p><core-format-text [text]="feedback.gradefordisplay" [filter]="false"></core-format-text></p>
|
<p><core-format-text [text]="feedback!.gradefordisplay" [filter]="false"></core-format-text></p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-button slot="end" *ngIf="feedback.advancedgrade" (click)="showAdvancedGrade()">
|
<ion-button slot="end" *ngIf="feedback!.advancedgrade" (click)="showAdvancedGrade()">
|
||||||
<ion-icon name="fas-search" slot="icon-only"></ion-icon>
|
<ion-icon name="fas-search" slot="icon-only"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
@ -236,7 +236,7 @@
|
||||||
<ion-label position="stacked">
|
<ion-label position="stacked">
|
||||||
<h2>{{ 'addon.mod_assign.gradeoutof' | translate: {$a: gradeInfo!.grade} }}</h2>
|
<h2>{{ 'addon.mod_assign.gradeoutof' | translate: {$a: gradeInfo!.grade} }}</h2>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-input *ngIf="!grade.disabled" type="text" [(ngModel)]="grade.grade" min="0" [max]="gradeInfo.grade"
|
<ion-input *ngIf="!grade.disabled" type="text" [(ngModel)]="grade.grade" min="0" [max]="gradeInfo!.grade"
|
||||||
[lang]="grade.lang">
|
[lang]="grade.lang">
|
||||||
</ion-input>
|
</ion-input>
|
||||||
<p item-content *ngIf="grade.disabled">{{ 'addon.mod_assign.gradelocked' | translate }}</p>
|
<p item-content *ngIf="grade.disabled">{{ 'addon.mod_assign.gradelocked' | translate }}</p>
|
||||||
|
@ -256,7 +256,7 @@
|
||||||
<ion-item class="ion-text-wrap" *ngFor="let outcome of gradeInfo!.outcomes">
|
<ion-item class="ion-text-wrap" *ngFor="let outcome of gradeInfo!.outcomes">
|
||||||
<ion-label><h2>{{ outcome.name }}</h2></ion-label>
|
<ion-label><h2>{{ outcome.name }}</h2></ion-label>
|
||||||
<ion-select *ngIf="canSaveGrades && outcome.itemNumber" [(ngModel)]="outcome.selectedId"
|
<ion-select *ngIf="canSaveGrades && outcome.itemNumber" [(ngModel)]="outcome.selectedId"
|
||||||
interface="action-sheet" [disabled]="gradeInfo.disabled">
|
interface="action-sheet" [disabled]="gradeInfo!.disabled">
|
||||||
<ion-select-option *ngFor="let grade of outcome.options" [value]="grade.value">
|
<ion-select-option *ngFor="let grade of outcome.options" [value]="grade.value">
|
||||||
{{grade.label}}
|
{{grade.label}}
|
||||||
</ion-select-option>
|
</ion-select-option>
|
||||||
|
@ -268,18 +268,18 @@
|
||||||
<ion-item class="ion-text-wrap" *ngIf="grade.method == 'simple'">
|
<ion-item class="ion-text-wrap" *ngIf="grade.method == 'simple'">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>{{ 'addon.mod_assign.currentgrade' | translate }}</h2>
|
<h2>{{ 'addon.mod_assign.currentgrade' | translate }}</h2>
|
||||||
<p *ngIf="grade.gradebookGrade !== false && grade.gradebookGrade !== null && !grade.scale">
|
<p *ngIf="grade.gradebookGrade && !grade.scale">
|
||||||
{{ grade.gradebookGrade }}
|
{{ grade.gradebookGrade }}
|
||||||
</p>
|
</p>
|
||||||
<p *ngIf="grade.gradebookGrade !== false && grade.gradebookGrade !== null && grade.scale">
|
<p *ngIf="grade.gradebookGrade && grade.scale">
|
||||||
{{ grade.scale[grade.gradebookGrade].label }}
|
{{ grade.scale[grade.gradebookGrade].label }}
|
||||||
</p>
|
</p>
|
||||||
<p *ngIf="grade.gradebookGrade === false || grade.gradebookGrade === null">-</p>
|
<p *ngIf="!grade.gradebookGrade">-</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<addon-mod-assign-feedback-plugin *ngFor="let plugin of feedback.plugins" [assign]="assign"
|
<addon-mod-assign-feedback-plugin *ngFor="let plugin of feedback!.plugins" [assign]="assign"
|
||||||
[submission]="userSubmission" [userId]="submitId" [plugin]="plugin" [canEdit]="canSaveGrades">
|
[submission]="userSubmission" [userId]="submitId" [plugin]="plugin" [canEdit]="canSaveGrades">
|
||||||
</addon-mod-assign-feedback-plugin>
|
</addon-mod-assign-feedback-plugin>
|
||||||
|
|
||||||
|
@ -292,28 +292,30 @@
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<!--- Apply grade to all team members. -->
|
<!--- Apply grade to all team members. -->
|
||||||
<ion-item class="ion-text-wrap" *ngIf="assign.teamsubmission && canSaveGrades">
|
<ion-item class="ion-text-wrap" *ngIf="assign!.teamsubmission && canSaveGrades">
|
||||||
<h2>{{ 'addon.mod_assign.groupsubmissionsettings' | translate }}</h2>
|
<ion-label>
|
||||||
<ion-label>{{ 'addon.mod_assign.applytoteam' | translate }}</ion-label>
|
<h2>{{ 'addon.mod_assign.groupsubmissionsettings' | translate }}</h2>
|
||||||
|
<p>{{ 'addon.mod_assign.applytoteam' | translate }}</p>
|
||||||
|
</ion-label>
|
||||||
<ion-toggle [(ngModel)]="grade.applyToAll"></ion-toggle>
|
<ion-toggle [(ngModel)]="grade.applyToAll"></ion-toggle>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<!-- Attempt status. -->
|
<!-- Attempt status. -->
|
||||||
<ng-container *ngIf="isGrading && assign.attemptreopenmethod != attemptReopenMethodNone">
|
<ng-container *ngIf="isGrading && assign!.attemptreopenmethod != attemptReopenMethodNone">
|
||||||
<ion-item class="ion-text-wrap">
|
<ion-item class="ion-text-wrap">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>{{ 'addon.mod_assign.attemptsettings' | translate }}</h2>
|
<h2>{{ 'addon.mod_assign.attemptsettings' | translate }}</h2>
|
||||||
<p *ngIf="assign.maxattempts == unlimitedAttempts">
|
<p *ngIf="assign!.maxattempts == unlimitedAttempts">
|
||||||
{{ 'addon.mod_assign.outof' | translate :
|
{{ 'addon.mod_assign.outof' | translate :
|
||||||
{'$a': {'current': currentAttempt, 'total': maxAttemptsText} } }}
|
{'$a': {'current': currentAttempt, 'total': maxAttemptsText} } }}
|
||||||
</p>
|
</p>
|
||||||
<p *ngIf="assign.maxattempts != unlimitedAttempts">
|
<p *ngIf="assign!.maxattempts != unlimitedAttempts">
|
||||||
{{ 'addon.mod_assign.outof' | translate :
|
{{ 'addon.mod_assign.outof' | translate :
|
||||||
{'$a': {'current': currentAttempt, 'total': assign.maxattempts} } }}
|
{'$a': {'current': currentAttempt, 'total': assign!.maxattempts} } }}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{{ 'addon.mod_assign.attemptreopenmethod' | translate }}:
|
{{ 'addon.mod_assign.attemptreopenmethod' | translate }}:
|
||||||
{{ 'addon.mod_assign.attemptreopenmethod_' + assign.attemptreopenmethod | translate }}
|
{{ 'addon.mod_assign.attemptreopenmethod_' + assign!.attemptreopenmethod | translate }}
|
||||||
</p>
|
</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
@ -324,33 +326,37 @@
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<!-- Data about the grader (teacher who graded). -->
|
<!-- Data about the grader (teacher who graded). -->
|
||||||
<ion-item class="ion-text-wrap" *ngIf="grader" core-user-link [userId]="grader.id" [courseId]="courseId"
|
<ion-item class="ion-text-wrap" *ngIf="grader" core-user-link [userId]="grader!.id" [courseId]="courseId"
|
||||||
[title]="grader.fullname" detail="true">
|
[title]="grader!.fullname" detail="true">
|
||||||
<core-user-avatar [user]="grader" slot="start"></core-user-avatar>
|
<core-user-avatar [user]="grader" slot="start"></core-user-avatar>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>{{ 'addon.mod_assign.gradedby' | translate }}</h2>
|
<h2>{{ 'addon.mod_assign.gradedby' | translate }}</h2>
|
||||||
<h2>{{ grader.fullname }}</h2>
|
<h2>{{ grader!.fullname }}</h2>
|
||||||
<p *ngIf="feedback.gradeddate">{{ feedback.gradeddate * 1000 | coreFormatDate }}</p>
|
<p *ngIf="feedback!.gradeddate">{{ feedback!.gradeddate * 1000 | coreFormatDate }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<!-- Grader is hidden, display only the grade date. -->
|
<!-- Grader is hidden, display only the grade date. -->
|
||||||
<ion-item class="ion-text-wrap" *ngIf="!grader && feedback.gradeddate">
|
<ion-item class="ion-text-wrap" *ngIf="!grader && feedback!.gradeddate">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>{{ 'addon.mod_assign.gradedon' | translate }}</h2>
|
<h2>{{ 'addon.mod_assign.gradedon' | translate }}</h2>
|
||||||
<p>{{ feedback.gradeddate * 1000 | coreFormatDate }}</p>
|
<p>{{ feedback!.gradeddate * 1000 | coreFormatDate }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<!-- Warning message if cannot save grades. -->
|
<!-- Warning message if cannot save grades. -->
|
||||||
<div *ngIf="isGrading && !canSaveGrades" class="core-warning-card">
|
<ion-card *ngIf="isGrading && !canSaveGrades" class="core-warning-card">
|
||||||
<ion-icon name="fas-exclamation-triangle" slot="start"></ion-icon>
|
<ion-item>
|
||||||
<p>{{ 'addon.mod_assign.cannotgradefromapp' | translate }}</p>
|
<ion-icon name="fas-exclamation-triangle" slot="start"></ion-icon>
|
||||||
<ion-button expand="block" *ngIf="gradeUrl" [href]="gradeUrl" core-link >
|
<ion-label>
|
||||||
{{ 'core.openinbrowser' | translate }}
|
<p>{{ 'addon.mod_assign.cannotgradefromapp' | translate }}</p>
|
||||||
<ion-icon name="fas-external-link-alt" slot="end"></ion-icon>
|
<ion-button expand="block" *ngIf="gradeUrl" [href]="gradeUrl" core-link >
|
||||||
</ion-button>
|
{{ 'core.openinbrowser' | translate }}
|
||||||
</div>
|
<ion-icon name="fas-external-link-alt" slot="end"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ion-card>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</core-tab>
|
</core-tab>
|
||||||
</core-tabs>
|
</core-tabs>
|
||||||
|
@ -360,20 +366,20 @@
|
||||||
<ng-template #submissionStatus>
|
<ng-template #submissionStatus>
|
||||||
<ng-container *ngIf="assign && assign!.teamsubmission && lastAttempt">
|
<ng-container *ngIf="assign && assign!.teamsubmission && lastAttempt">
|
||||||
<p *ngIf="lastAttempt!.submissiongroup && lastAttempt!.submissiongroupname">{{lastAttempt!.submissiongroupname}}</p>
|
<p *ngIf="lastAttempt!.submissiongroup && lastAttempt!.submissiongroupname">{{lastAttempt!.submissiongroupname}}</p>
|
||||||
<ng-container *ngIf="assign.preventsubmissionnotingroup &&
|
<ng-container *ngIf="assign!.preventsubmissionnotingroup &&
|
||||||
!lastAttempt!.submissiongroup &&
|
!lastAttempt!.submissiongroup &&
|
||||||
(!lastAttempt!.usergroups || lastAttempt!.usergroups.length <= 0)">
|
(!lastAttempt!.usergroups || lastAttempt!.usergroups.length <= 0)">
|
||||||
<p class="text-danger"><strong>{{ 'addon.mod_assign.noteam' | translate }}</strong></p>
|
<p class="text-danger"><strong>{{ 'addon.mod_assign.noteam' | translate }}</strong></p>
|
||||||
<p class="text-danger">{{ 'addon.mod_assign.noteam_desc' | translate }}</p>
|
<p class="text-danger">{{ 'addon.mod_assign.noteam_desc' | translate }}</p>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="assign.preventsubmissionnotingroup &&
|
<ng-container *ngIf="assign!.preventsubmissionnotingroup &&
|
||||||
!lastAttempt!.submissiongroup &&
|
!lastAttempt!.submissiongroup &&
|
||||||
lastAttempt!.usergroups &&
|
lastAttempt!.usergroups &&
|
||||||
lastAttempt!.usergroups.length > 1">
|
lastAttempt!.usergroups.length > 1">
|
||||||
<p class="text-danger"><strong>{{ 'addon.mod_assign.multipleteams' | translate }}</strong></p>
|
<p class="text-danger"><strong>{{ 'addon.mod_assign.multipleteams' | translate }}</strong></p>
|
||||||
<p class="text-danger">{{ 'addon.mod_assign.multipleteams_desc' | translate }}</p>
|
<p class="text-danger">{{ 'addon.mod_assign.multipleteams_desc' | translate }}</p>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<p *ngIf="!assign.preventsubmissionnotingroup && !lastAttempt!.submissiongroup">
|
<p *ngIf="!assign!.preventsubmissionnotingroup && !lastAttempt!.submissiongroup">
|
||||||
{{ 'addon.mod_assign.defaultteam' | translate }}
|
{{ 'addon.mod_assign.defaultteam' | translate }}
|
||||||
</p>
|
</p>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
|
@ -55,6 +55,7 @@ import { CoreError } from '@classes/errors/error';
|
||||||
import { CoreGroups } from '@services/groups';
|
import { CoreGroups } from '@services/groups';
|
||||||
import { CoreSync } from '@services/sync';
|
import { CoreSync } from '@services/sync';
|
||||||
import { AddonModAssignSubmissionPluginComponent } from '../submission-plugin/submission-plugin';
|
import { AddonModAssignSubmissionPluginComponent } from '../submission-plugin/submission-plugin';
|
||||||
|
import { AddonModAssignModuleHandlerService } from '../../services/handlers/module';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that displays an assignment submission.
|
* Component that displays an assignment submission.
|
||||||
|
@ -73,7 +74,6 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy {
|
||||||
@Input() moduleId!: number; // Module ID the submission belongs to.
|
@Input() moduleId!: number; // Module ID the submission belongs to.
|
||||||
@Input() submitId!: number; // User that did the submission.
|
@Input() submitId!: number; // User that did the submission.
|
||||||
@Input() blindId?: number; // Blinded user ID (if it's blinded).
|
@Input() blindId?: number; // Blinded user ID (if it's blinded).
|
||||||
@Input() showGrade = false; // Whether to display the grade tab at start.
|
|
||||||
|
|
||||||
loaded = false; // Whether data has been loaded.
|
loaded = false; // Whether data has been loaded.
|
||||||
selectedTab = 'submission'; // Tab selected on start.
|
selectedTab = 'submission'; // Tab selected on start.
|
||||||
|
@ -121,6 +121,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy {
|
||||||
canSaveGrades = false; // Whether the user can save the grades.
|
canSaveGrades = false; // Whether the user can save the grades.
|
||||||
allowAddAttempt = false; // Allow adding a new attempt when grading.
|
allowAddAttempt = false; // Allow adding a new attempt when grading.
|
||||||
gradeUrl?: string; // URL to grade in browser.
|
gradeUrl?: string; // URL to grade in browser.
|
||||||
|
isPreviousAttemptEmpty = true; // Whether the previous attempt contains an empty submission.
|
||||||
|
|
||||||
// Some constants.
|
// Some constants.
|
||||||
statusNew = AddonModAssignProvider.SUBMISSION_STATUS_NEW;
|
statusNew = AddonModAssignProvider.SUBMISSION_STATUS_NEW;
|
||||||
|
@ -131,7 +132,6 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy {
|
||||||
protected siteId: string; // Current site ID.
|
protected siteId: string; // Current site ID.
|
||||||
protected currentUserId: number; // Current user ID.
|
protected currentUserId: number; // Current user ID.
|
||||||
protected previousAttempt?: AddonModAssignSubmissionPreviousAttempt; // The previous attempt.
|
protected previousAttempt?: AddonModAssignSubmissionPreviousAttempt; // The previous attempt.
|
||||||
protected isPreviousAttemptEmpty = true; // Whether the previous attempt contains an empty submission.
|
|
||||||
protected submissionStatusAvailable = false; // Whether we were able to retrieve the submission status.
|
protected submissionStatusAvailable = false; // Whether we were able to retrieve the submission status.
|
||||||
protected originalGrades: AddonModAssignSubmissionOriginalGrades = {
|
protected originalGrades: AddonModAssignSubmissionOriginalGrades = {
|
||||||
addAttempt: false,
|
addAttempt: false,
|
||||||
|
@ -180,7 +180,6 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy {
|
||||||
* Component being initialized.
|
* Component being initialized.
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.selectedTab = this.showGrade ? 'grade' : 'submission';
|
|
||||||
this.isSubmittedForGrading = !!this.submitId;
|
this.isSubmittedForGrading = !!this.submitId;
|
||||||
|
|
||||||
this.loadData(true);
|
this.loadData(true);
|
||||||
|
@ -343,13 +342,14 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy {
|
||||||
* Go to the page to add or edit submission.
|
* Go to the page to add or edit submission.
|
||||||
*/
|
*/
|
||||||
goToEdit(): void {
|
goToEdit(): void {
|
||||||
CoreNavigator.instance.navigate('AddonModAssignEditPage', {
|
CoreNavigator.instance.navigateToSitePath(
|
||||||
params: {
|
AddonModAssignModuleHandlerService.PAGE_NAME + '/' + this.courseId + '/' + this.moduleId + '/edit',
|
||||||
moduleId: this.moduleId,
|
{
|
||||||
courseId: this.courseId,
|
params: {
|
||||||
userId: this.submitId,
|
blindId: this.blindId,
|
||||||
blindId: this.blindId,
|
},
|
||||||
} });
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -393,7 +393,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy {
|
||||||
try {
|
try {
|
||||||
return AddonModAssignHelper.instance.hasFeedbackDataChanged(
|
return AddonModAssignHelper.instance.hasFeedbackDataChanged(
|
||||||
this.assign!,
|
this.assign!,
|
||||||
this.userSubmission!, // @todo
|
this.userSubmission,
|
||||||
this.feedback,
|
this.feedback,
|
||||||
this.submitId,
|
this.submitId,
|
||||||
);
|
);
|
||||||
|
|
|
@ -24,11 +24,10 @@
|
||||||
|
|
||||||
<!-- Edit -->
|
<!-- Edit -->
|
||||||
<ion-item class="ion-text-wrap" *ngIf="edit && loaded">
|
<ion-item class="ion-text-wrap" *ngIf="edit && loaded">
|
||||||
<ion-label>
|
<ion-label></ion-label>
|
||||||
<core-rich-text-editor item-content [control]="control" [placeholder]="plugin.name"
|
<core-rich-text-editor item-content [control]="control" [placeholder]="plugin.name"
|
||||||
name="assignfeedbackcomments_editor" [component]="component" [componentId]="assign.cmid" [autoSave]="true"
|
name="assignfeedbackcomments_editor" [component]="component" [componentId]="assign.cmid" [autoSave]="true"
|
||||||
contextLevel="module" [contextInstanceId]="assign.cmid" elementId="assignfeedbackcomments_editor"
|
contextLevel="module" [contextInstanceId]="assign.cmid" elementId="assignfeedbackcomments_editor"
|
||||||
[draftExtraParams]="{userid: userId, action: 'grade'}">
|
[draftExtraParams]="{userid: userId, action: 'grade'}">
|
||||||
</core-rich-text-editor>
|
</core-rich-text-editor>
|
||||||
</ion-label>
|
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title>
|
||||||
|
<core-format-text [text]="title" contextLevel="module" [contextInstanceId]="moduleId" [courseId]="courseId">
|
||||||
|
</core-format-text>
|
||||||
|
</ion-title>
|
||||||
|
<ion-buttons slot="end">
|
||||||
|
<ion-button fill="clear" (click)="save()" [attr.aria-label]="'core.save' | translate">
|
||||||
|
{{ 'core.save' | translate }}
|
||||||
|
</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content>
|
||||||
|
<core-loading [hideUntil]="loaded">
|
||||||
|
<ion-list *ngIf="userSubmission && userSubmission.plugins && userSubmission.plugins.length">
|
||||||
|
<!-- @todo: plagiarism_print_disclosure -->
|
||||||
|
<form name="addon-mod_assign-edit-form" #editSubmissionForm>
|
||||||
|
<!-- Submission statement. -->
|
||||||
|
<ion-item class="ion-text-wrap" *ngIf="submissionStatement">
|
||||||
|
<ion-label>
|
||||||
|
<core-format-text [text]="submissionStatement" [filter]="false">
|
||||||
|
</core-format-text>
|
||||||
|
</ion-label>
|
||||||
|
<ion-checkbox slot="end" name="submissionstatement" [(ngModel)]="submissionStatementAccepted"></ion-checkbox>
|
||||||
|
<!-- ion-checkbox doesn't use an input. Create a hidden input to hold the value. -->
|
||||||
|
<input item-content type="hidden" [ngModel]="submissionStatementAccepted" name="submissionstatement">
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<addon-mod-assign-submission-plugin *ngFor="let plugin of userSubmission.plugins" [assign]="assign"
|
||||||
|
[submission]="userSubmission" [plugin]="plugin" [edit]="true" [allowOffline]="allowOffline">
|
||||||
|
</addon-mod-assign-submission-plugin>
|
||||||
|
</form>
|
||||||
|
</ion-list>
|
||||||
|
</core-loading>
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,408 @@
|
||||||
|
// (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 { CoreIonLoadingElement } from '@classes/ion-loading';
|
||||||
|
import { CoreFileUploaderHelper } from '@features/fileuploader/services/fileuploader-helper';
|
||||||
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
|
||||||
|
import { CoreSync } from '@services/sync';
|
||||||
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
import { Translate } from '@singletons';
|
||||||
|
import { CoreEventActivityDataSentData, CoreEvents } from '@singletons/events';
|
||||||
|
import {
|
||||||
|
AddonModAssignAssign,
|
||||||
|
AddonModAssignSubmission,
|
||||||
|
AddonModAssignProvider,
|
||||||
|
AddonModAssign,
|
||||||
|
AddonModAssignSubmissionStatusOptions,
|
||||||
|
AddonModAssignGetSubmissionStatusWSResponse,
|
||||||
|
AddonModAssignSavePluginData,
|
||||||
|
AddonModAssignSubmissionSavedEventData,
|
||||||
|
AddonModAssignSubmittedForGradingEventData,
|
||||||
|
} 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 {
|
||||||
|
|
||||||
|
@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.instance.getCurrentSiteUserId(); // Right now we can only edit current user's submissions.
|
||||||
|
this.editText = Translate.instance.instant('addon.mod_assign.editsubmission');
|
||||||
|
this.title = this.editText;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component being initialized.
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.moduleId = CoreNavigator.instance.getRouteNumberParam('cmId')!;
|
||||||
|
this.courseId = CoreNavigator.instance.getRouteNumberParam('courseId')!;
|
||||||
|
this.isBlind = !!CoreNavigator.instance.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 ionViewCanLeave(): Promise<void> {
|
||||||
|
if (this.forceLeave) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if data has changed.
|
||||||
|
const changed = await this.hasDataChanged();
|
||||||
|
if (changed) {
|
||||||
|
await CoreDomUtils.instance.showConfirm(Translate.instance.instant('core.confirmcanceledit'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing has changed or user confirmed to leave. Clear temporary data from plugins.
|
||||||
|
AddonModAssignHelper.instance.clearSubmissionPluginTmpData(this.assign!, this.userSubmission, this.getInputData());
|
||||||
|
|
||||||
|
CoreDomUtils.instance.triggerFormCancelledEvent(this.formElement, CoreSites.instance.getCurrentSiteId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch assignment data.
|
||||||
|
*
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected async fetchAssignment(): Promise<void> {
|
||||||
|
const currentUserId = CoreSites.instance.getCurrentSiteUserId();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get assignment data.
|
||||||
|
this.assign = await AddonModAssign.instance.getAssignment(this.courseId, this.moduleId);
|
||||||
|
this.title = this.assign.name || this.title;
|
||||||
|
|
||||||
|
if (!this.isDestroyed) {
|
||||||
|
// Block the assignment.
|
||||||
|
CoreSync.instance.blockOperation(AddonModAssignProvider.COMPONENT, this.assign.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for sync to be over (if any).
|
||||||
|
await AddonModAssignSync.instance.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.instance.getSubmissionStatus(this.assign.id, options);
|
||||||
|
this.userSubmission =
|
||||||
|
AddonModAssign.instance.getSubmissionObjectFromAttempt(this.assign, submissionStatus.lastattempt);
|
||||||
|
} catch (error) {
|
||||||
|
// Cannot connect. Get cached data.
|
||||||
|
options.filter = true;
|
||||||
|
options.readingStrategy = CoreSitesReadingStrategy.PreferCache;
|
||||||
|
|
||||||
|
submissionStatus = await AddonModAssign.instance.getSubmissionStatus(this.assign.id, options);
|
||||||
|
this.userSubmission =
|
||||||
|
AddonModAssign.instance.getSubmissionObjectFromAttempt(this.assign, submissionStatus.lastattempt);
|
||||||
|
|
||||||
|
// Check if the user can edit it in offline.
|
||||||
|
const canEditOffline =
|
||||||
|
await AddonModAssignHelper.instance.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.instance.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.instance.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.instance.showErrorModalDefault(error, 'Error getting assigment data.');
|
||||||
|
|
||||||
|
// Leave the player.
|
||||||
|
this.leaveWithoutCheck();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the input data.
|
||||||
|
*
|
||||||
|
* @return Input data.
|
||||||
|
*/
|
||||||
|
protected getInputData(): Record<string, unknown> {
|
||||||
|
return CoreDomUtils.instance.getDataFromForm(document.forms['addon-mod_assign-edit-form']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if data has changed.
|
||||||
|
*
|
||||||
|
* @return Promise resolved with boolean: whether data has changed.
|
||||||
|
*/
|
||||||
|
protected 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".
|
||||||
|
let modal: CoreIonLoadingElement;
|
||||||
|
let showModal = true;
|
||||||
|
|
||||||
|
setTimeout(async () => {
|
||||||
|
if (showModal) {
|
||||||
|
modal = await CoreDomUtils.instance.showModalLoading();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
const data = this.getInputData();
|
||||||
|
|
||||||
|
return AddonModAssignHelper.instance.hasSubmissionDataChanged(this.assign!, this.userSubmission, data).finally(() => {
|
||||||
|
if (modal) {
|
||||||
|
modal.dismiss();
|
||||||
|
} else {
|
||||||
|
showModal = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Leave the view without checking for changes.
|
||||||
|
*/
|
||||||
|
protected leaveWithoutCheck(): void {
|
||||||
|
this.forceLeave = true;
|
||||||
|
CoreNavigator.instance.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: Record<string, unknown>): Promise<AddonModAssignSavePluginData> {
|
||||||
|
// If there's offline data, always save it in offline.
|
||||||
|
this.saveOffline = this.hasOffline;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return AddonModAssignHelper.instance.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.instance.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.instance.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.instance.instant('addon.mod_assign.acceptsubmissionstatement');
|
||||||
|
}
|
||||||
|
|
||||||
|
let modal = await CoreDomUtils.instance.showModalLoading();
|
||||||
|
let size = -1;
|
||||||
|
|
||||||
|
// Get size to ask for confirmation.
|
||||||
|
try {
|
||||||
|
size = await AddonModAssignHelper.instance.getSubmissionSizeForEdit(this.assign!, this.userSubmission!, inputData);
|
||||||
|
} catch (error) {
|
||||||
|
// Error calculating size, return -1.
|
||||||
|
size = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
modal.dismiss();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Confirm action.
|
||||||
|
await CoreFileUploaderHelper.instance.confirmUploadFile(size, true, this.allowOffline);
|
||||||
|
|
||||||
|
modal = await CoreDomUtils.instance.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.instance.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.instance.saveSubmission(
|
||||||
|
this.assign!.id,
|
||||||
|
this.courseId,
|
||||||
|
pluginData,
|
||||||
|
this.allowOffline,
|
||||||
|
this.userSubmission!.timemodified,
|
||||||
|
!!this.assign!.submissiondrafts,
|
||||||
|
this.userId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear temporary data from plugins.
|
||||||
|
AddonModAssignHelper.instance.clearSubmissionPluginTmpData(this.assign!, this.userSubmission, inputData);
|
||||||
|
|
||||||
|
if (sent) {
|
||||||
|
CoreEvents.trigger<CoreEventActivityDataSentData>(CoreEvents.ACTIVITY_DATA_SENT, { module: 'assign' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Submission saved, trigger events.
|
||||||
|
CoreDomUtils.instance.triggerFormSubmittedEvent(this.formElement, sent, CoreSites.instance.getCurrentSiteId());
|
||||||
|
|
||||||
|
CoreEvents.trigger<AddonModAssignSubmissionSavedEventData>(
|
||||||
|
AddonModAssignProvider.SUBMISSION_SAVED_EVENT,
|
||||||
|
{
|
||||||
|
assignmentId: this.assign!.id,
|
||||||
|
submissionId: this.userSubmission!.id,
|
||||||
|
userId: this.userId,
|
||||||
|
},
|
||||||
|
CoreSites.instance.getCurrentSiteId(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!this.assign!.submissiondrafts) {
|
||||||
|
// No drafts allowed, so it was submitted. Trigger event.
|
||||||
|
CoreEvents.trigger<AddonModAssignSubmittedForGradingEventData>(
|
||||||
|
AddonModAssignProvider.SUBMITTED_FOR_GRADING_EVENT,
|
||||||
|
{
|
||||||
|
assignmentId: this.assign!.id,
|
||||||
|
submissionId: this.userSubmission!.id,
|
||||||
|
userId: this.userId,
|
||||||
|
},
|
||||||
|
CoreSites.instance.getCurrentSiteId(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
modal.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component being destroyed.
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.isDestroyed = true;
|
||||||
|
|
||||||
|
// Unblock the assignment.
|
||||||
|
if (this.assign) {
|
||||||
|
CoreSync.instance.unblockOperation(AddonModAssignProvider.COMPONENT, this.assign.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -61,15 +61,15 @@
|
||||||
{{ 'addon.mod_assign.defaultteam' | translate }}
|
{{ 'addon.mod_assign.defaultteam' | translate }}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
|
<ion-badge class="ion-text-center ion-text-wrap" [color]="submission.statusColor"
|
||||||
|
*ngIf="submission.statusTranslated">
|
||||||
|
{{ submission.statusTranslated }}
|
||||||
|
</ion-badge>
|
||||||
|
<ion-badge class="ion-text-center ion-text-wrap" [color]="submission.gradingColor"
|
||||||
|
*ngIf="submission.gradingStatusTranslationId">
|
||||||
|
{{ submission.gradingStatusTranslationId | translate }}
|
||||||
|
</ion-badge>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-badge class="ion-text-center ion-text-wrap" [color]="submission.statusColor"
|
|
||||||
*ngIf="submission.statusTranslated">
|
|
||||||
{{ submission.statusTranslated }}
|
|
||||||
</ion-badge>
|
|
||||||
<ion-badge class="ion-text-center ion-text-wrap" [color]="submission.gradingColor"
|
|
||||||
*ngIf="submission.gradingStatusTranslationId">
|
|
||||||
{{ submission.gradingStatusTranslationId | translate }}
|
|
||||||
</ion-badge>
|
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ import { ActivatedRoute } from '@angular/router';
|
||||||
import { IonRefresher } from '@ionic/angular';
|
import { IonRefresher } from '@ionic/angular';
|
||||||
import { CoreGroupInfo, CoreGroups } from '@services/groups';
|
import { CoreGroupInfo, CoreGroups } from '@services/groups';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
import { CoreScreen } from '@services/screen';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
@ -37,6 +38,7 @@ import {
|
||||||
AddonModAssignManualSyncData,
|
AddonModAssignManualSyncData,
|
||||||
AddonModAssignAutoSyncData,
|
AddonModAssignAutoSyncData,
|
||||||
} from '../../services/assign-sync';
|
} from '../../services/assign-sync';
|
||||||
|
import { AddonModAssignModuleHandlerService } from '../../services/handlers/module';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays a list of submissions of an assignment.
|
* Page that displays a list of submissions of an assignment.
|
||||||
|
@ -137,10 +139,10 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy {
|
||||||
this.title = Translate.instance.instant('addon.mod_assign.numberofparticipants');
|
this.title = Translate.instance.instant('addon.mod_assign.numberofparticipants');
|
||||||
}
|
}
|
||||||
this.fetchAssignment(true).finally(() => {
|
this.fetchAssignment(true).finally(() => {
|
||||||
/* if (!this.selectedSubmissionId && this.splitviewCtrl.isOn() && this.submissions.length > 0) {
|
if (!this.selectedSubmissionId && CoreScreen.instance.isTablet && this.submissions.length > 0) {
|
||||||
// Take first and load it.
|
// Take first and load it.
|
||||||
this.loadSubmission(this.submissions[0]);
|
this.loadSubmission(this.submissions[0]);
|
||||||
}*/
|
}
|
||||||
|
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
});
|
});
|
||||||
|
@ -153,7 +155,7 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy {
|
||||||
* @param sync Whether to try to synchronize data.
|
* @param sync Whether to try to synchronize data.
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
protected async fetchAssignment(sync?: boolean): Promise<void> {
|
protected async fetchAssignment(sync = false): Promise<void> {
|
||||||
try {
|
try {
|
||||||
// Get assignment data.
|
// Get assignment data.
|
||||||
this.assign = await AddonModAssign.instance.getAssignment(this.courseId, this.moduleId);
|
this.assign = await AddonModAssign.instance.getAssignment(this.courseId, this.moduleId);
|
||||||
|
@ -310,19 +312,21 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy {
|
||||||
* @param submission The submission to load.
|
* @param submission The submission to load.
|
||||||
*/
|
*/
|
||||||
loadSubmission(submission: AddonModAssignSubmissionForList): void {
|
loadSubmission(submission: AddonModAssignSubmissionForList): void {
|
||||||
/* if (this.selectedSubmissionId === submission.submitid && this.splitviewCtrl.isOn()) {
|
if (this.selectedSubmissionId === submission.submitid) {
|
||||||
// Already selected.
|
// Already selected.
|
||||||
return;
|
return;
|
||||||
}*/
|
}
|
||||||
|
|
||||||
this.selectedSubmissionId = submission.submitid;
|
this.selectedSubmissionId = submission.submitid;
|
||||||
|
|
||||||
/* this.splitviewCtrl.push('AddonModAssignSubmissionReviewPage', {
|
CoreNavigator.instance.navigateToSitePath(
|
||||||
courseId: this.courseId,
|
AddonModAssignModuleHandlerService.PAGE_NAME+'/'+this.courseId+'/'+this.moduleId+'/submission/'+submission.submitid,
|
||||||
moduleId: this.moduleId,
|
{
|
||||||
submitId: submission.submitid,
|
params: {
|
||||||
blindId: submission.blindid,
|
blindId: submission.blindid,
|
||||||
});*/
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title>
|
||||||
|
<core-format-text [text]="title" contextLevel="module" [contextInstanceId]="moduleId" [courseId]="courseId">
|
||||||
|
</core-format-text>
|
||||||
|
</ion-title>
|
||||||
|
|
||||||
|
<ion-buttons slot="end"></ion-buttons>
|
||||||
|
</ion-toolbar>
|
||||||
|
|
||||||
|
<core-navbar-buttons slot="end">
|
||||||
|
<ion-button [hidden]="!canSaveGrades" fill="clear" (click)="submitGrade()" [attr.aria-label]="'core.done' | translate">
|
||||||
|
{{ 'core.done' | translate }}
|
||||||
|
</ion-button>
|
||||||
|
</core-navbar-buttons>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content>
|
||||||
|
|
||||||
|
<ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshSubmission($event)">
|
||||||
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
|
</ion-refresher>
|
||||||
|
<core-loading [hideUntil]="loaded">
|
||||||
|
<addon-mod-assign-submission [courseId]="courseId" [moduleId]="moduleId" [submitId]="submitId" [blindId]="blindId">
|
||||||
|
</addon-mod-assign-submission>
|
||||||
|
</core-loading>
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,184 @@
|
||||||
|
// (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, ViewChild } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { CoreCourse } from '@features/course/services/course';
|
||||||
|
import { IonRefresher } from '@ionic/angular';
|
||||||
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
import { CoreScreen } from '@services/screen';
|
||||||
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
import { AddonModAssignSubmissionComponent } from '../../components/submission/submission';
|
||||||
|
import { AddonModAssign, AddonModAssignAssign } from '../../services/assign';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page that displays a submission.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'page-addon-mod-assign-submission-review',
|
||||||
|
templateUrl: 'submission-review.html',
|
||||||
|
})
|
||||||
|
export class AddonModAssignSubmissionReviewPage implements OnInit {
|
||||||
|
|
||||||
|
@ViewChild(AddonModAssignSubmissionComponent) submissionComponent?: AddonModAssignSubmissionComponent;
|
||||||
|
|
||||||
|
title = ''; // Title to display.
|
||||||
|
moduleId!: number; // Module ID the submission belongs to.
|
||||||
|
courseId!: number; // Course ID the assignment belongs to.
|
||||||
|
submitId!: number; // User that did the submission.
|
||||||
|
blindId?: number; // Blinded user ID (if it's blinded).
|
||||||
|
loaded = false; // Whether data has been loaded.
|
||||||
|
canSaveGrades = false; // Whether the user can save grades.
|
||||||
|
|
||||||
|
protected assign?: AddonModAssignAssign; // The assignment the submission belongs to.
|
||||||
|
protected blindMarking = false; // Whether it uses blind marking.
|
||||||
|
protected forceLeave = false; // To allow leaving the page without checking for changes.
|
||||||
|
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected route: ActivatedRoute,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component being initialized.
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.route.queryParams.subscribe((params) => {
|
||||||
|
this.moduleId = CoreNavigator.instance.getRouteNumberParam('cmId')!;
|
||||||
|
this.courseId = CoreNavigator.instance.getRouteNumberParam('courseId')!;
|
||||||
|
this.submitId = CoreNavigator.instance.getRouteNumberParam('submitId') || 0;
|
||||||
|
this.blindId = CoreNavigator.instance.getRouteNumberParam('blindId', params);
|
||||||
|
|
||||||
|
this.fetchSubmission().finally(() => {
|
||||||
|
this.loaded = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if we can leave the page or not.
|
||||||
|
*
|
||||||
|
* @return Resolved if we can leave it, rejected if not.
|
||||||
|
*/
|
||||||
|
ionViewCanLeave(): boolean | Promise<void> {
|
||||||
|
if (!this.submissionComponent || this.forceLeave) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if data has changed.
|
||||||
|
return this.submissionComponent.canLeave();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User entered the page.
|
||||||
|
*/
|
||||||
|
ionViewDidEnter(): void {
|
||||||
|
this.submissionComponent?.ionViewDidEnter();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User left the page.
|
||||||
|
*/
|
||||||
|
ionViewDidLeave(): void {
|
||||||
|
this.submissionComponent?.ionViewDidLeave();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the submission.
|
||||||
|
*
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected async fetchSubmission(): Promise<void> {
|
||||||
|
this.assign = await AddonModAssign.instance.getAssignment(this.courseId, this.moduleId);
|
||||||
|
this.title = this.assign.name;
|
||||||
|
|
||||||
|
this.blindMarking = !!this.assign.blindmarking && !this.assign.revealidentities;
|
||||||
|
|
||||||
|
const gradeInfo = await CoreCourse.instance.getModuleBasicGradeInfo(this.moduleId);
|
||||||
|
if (!gradeInfo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grades can be saved if simple grading.
|
||||||
|
if (gradeInfo.advancedgrading && gradeInfo.advancedgrading[0] &&
|
||||||
|
typeof gradeInfo.advancedgrading[0].method != 'undefined') {
|
||||||
|
|
||||||
|
const method = gradeInfo.advancedgrading[0].method || 'simple';
|
||||||
|
this.canSaveGrades = method == 'simple';
|
||||||
|
} else {
|
||||||
|
this.canSaveGrades = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh all the data.
|
||||||
|
*
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected async refreshAllData(): Promise<void> {
|
||||||
|
const promises: Promise<void>[] = [];
|
||||||
|
|
||||||
|
promises.push(AddonModAssign.instance.invalidateAssignmentData(this.courseId));
|
||||||
|
if (this.assign) {
|
||||||
|
promises.push(AddonModAssign.instance.invalidateSubmissionData(this.assign.id));
|
||||||
|
promises.push(AddonModAssign.instance.invalidateAssignmentUserMappingsData(this.assign.id));
|
||||||
|
promises.push(AddonModAssign.instance.invalidateSubmissionStatusData(
|
||||||
|
this.assign.id,
|
||||||
|
this.submitId,
|
||||||
|
undefined,
|
||||||
|
this.blindMarking,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Promise.all(promises);
|
||||||
|
} finally {
|
||||||
|
this.submissionComponent && this.submissionComponent.invalidateAndRefresh(true);
|
||||||
|
|
||||||
|
await this.fetchSubmission();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the data.
|
||||||
|
*
|
||||||
|
* @param refresher Refresher.
|
||||||
|
*/
|
||||||
|
refreshSubmission(refresher?: CustomEvent<IonRefresher>): void {
|
||||||
|
this.refreshAllData().finally(() => {
|
||||||
|
refresher?.detail.complete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit a grade and feedback.
|
||||||
|
*/
|
||||||
|
async submitGrade(): Promise<void> {
|
||||||
|
if (!this.submissionComponent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.submissionComponent.submitGrade();
|
||||||
|
// Grade submitted, leave the view if not in tablet.
|
||||||
|
if (!CoreScreen.instance.isTablet) {
|
||||||
|
this.forceLeave = true;
|
||||||
|
CoreNavigator.instance.back();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
CoreDomUtils.instance.showErrorModalDefault(error, 'core.error', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -49,7 +49,7 @@ export class AddonModAssignHelperProvider {
|
||||||
* @param submission Submission.
|
* @param submission Submission.
|
||||||
* @return Whether it can be edited offline.
|
* @return Whether it can be edited offline.
|
||||||
*/
|
*/
|
||||||
async canEditSubmissionOffline(assign: AddonModAssignAssign, submission: AddonModAssignSubmission): Promise<boolean> {
|
async canEditSubmissionOffline(assign: AddonModAssignAssign, submission?: AddonModAssignSubmission): Promise<boolean> {
|
||||||
if (!submission) {
|
if (!submission) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,15 @@ export class AddonModAssignHelperProvider {
|
||||||
* @param submission Submission to clear the data for.
|
* @param submission Submission to clear the data for.
|
||||||
* @param inputData Data entered in the submission form.
|
* @param inputData Data entered in the submission form.
|
||||||
*/
|
*/
|
||||||
clearSubmissionPluginTmpData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, inputData: any): void {
|
clearSubmissionPluginTmpData(
|
||||||
|
assign: AddonModAssignAssign,
|
||||||
|
submission: AddonModAssignSubmission | undefined,
|
||||||
|
inputData: Record<string, unknown>,
|
||||||
|
): void {
|
||||||
|
if (!submission) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
submission.plugins?.forEach((plugin) => {
|
submission.plugins?.forEach((plugin) => {
|
||||||
AddonModAssignSubmissionDelegate.instance.clearTmpData(assign, submission, plugin, inputData);
|
AddonModAssignSubmissionDelegate.instance.clearTmpData(assign, submission, plugin, inputData);
|
||||||
});
|
});
|
||||||
|
@ -356,7 +364,7 @@ export class AddonModAssignHelperProvider {
|
||||||
async getSubmissionSizeForEdit(
|
async getSubmissionSizeForEdit(
|
||||||
assign: AddonModAssignAssign,
|
assign: AddonModAssignAssign,
|
||||||
submission: AddonModAssignSubmission,
|
submission: AddonModAssignSubmission,
|
||||||
inputData: any,
|
inputData: Record<string, unknown>,
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
|
|
||||||
let totalSize = 0;
|
let totalSize = 0;
|
||||||
|
@ -492,27 +500,28 @@ export class AddonModAssignHelperProvider {
|
||||||
*/
|
*/
|
||||||
async hasFeedbackDataChanged(
|
async hasFeedbackDataChanged(
|
||||||
assign: AddonModAssignAssign,
|
assign: AddonModAssignAssign,
|
||||||
submission: AddonModAssignSubmission | AddonModAssignSubmissionFormatted,
|
submission: AddonModAssignSubmission | AddonModAssignSubmissionFormatted | undefined,
|
||||||
feedback: AddonModAssignSubmissionFeedback,
|
feedback: AddonModAssignSubmissionFeedback,
|
||||||
userId: number,
|
userId: number,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
|
if (!submission || !feedback.plugins) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
let hasChanged = false;
|
let hasChanged = false;
|
||||||
|
|
||||||
const promises = feedback.plugins
|
const promises = feedback.plugins.map((plugin) =>
|
||||||
? feedback.plugins.map((plugin) =>
|
this.prepareFeedbackPluginData(assign.id, userId, feedback).then(async (inputData) => {
|
||||||
this.prepareFeedbackPluginData(assign.id, userId, feedback).then(async (inputData) => {
|
const changed = await CoreUtils.instance.ignoreErrors(
|
||||||
const changed = await CoreUtils.instance.ignoreErrors(
|
AddonModAssignFeedbackDelegate.instance.hasPluginDataChanged(assign, submission, plugin, inputData, userId),
|
||||||
AddonModAssignFeedbackDelegate.instance.hasPluginDataChanged(assign, submission, plugin, inputData, userId),
|
false,
|
||||||
false,
|
);
|
||||||
);
|
if (changed) {
|
||||||
if (changed) {
|
hasChanged = true;
|
||||||
hasChanged = true;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}))
|
}));
|
||||||
: [];
|
|
||||||
|
|
||||||
await CoreUtils.instance.allPromises(promises);
|
await CoreUtils.instance.allPromises(promises);
|
||||||
|
|
||||||
|
@ -529,9 +538,13 @@ export class AddonModAssignHelperProvider {
|
||||||
*/
|
*/
|
||||||
async hasSubmissionDataChanged(
|
async hasSubmissionDataChanged(
|
||||||
assign: AddonModAssignAssign,
|
assign: AddonModAssignAssign,
|
||||||
submission: AddonModAssignSubmission,
|
submission: AddonModAssignSubmission | undefined,
|
||||||
inputData: any,
|
inputData: Record<string, unknown>,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
|
if (!submission) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
let hasChanged = false;
|
let hasChanged = false;
|
||||||
|
|
||||||
const promises = submission.plugins
|
const promises = submission.plugins
|
||||||
|
@ -544,7 +557,7 @@ export class AddonModAssignHelperProvider {
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
// Ignore errors.
|
// Ignore errors.
|
||||||
}))
|
}))
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
|
@ -591,23 +604,25 @@ export class AddonModAssignHelperProvider {
|
||||||
*/
|
*/
|
||||||
async prepareSubmissionPluginData(
|
async prepareSubmissionPluginData(
|
||||||
assign: AddonModAssignAssign,
|
assign: AddonModAssignAssign,
|
||||||
submission: AddonModAssignSubmission,
|
submission: AddonModAssignSubmission | undefined,
|
||||||
inputData: any,
|
inputData: Record<string, unknown>,
|
||||||
offline = false,
|
offline = false,
|
||||||
): Promise<any> {
|
): Promise<AddonModAssignSavePluginData> {
|
||||||
|
|
||||||
const pluginData = {};
|
if (!submission || !submission.plugins) {
|
||||||
const promises = submission.plugins
|
return {};
|
||||||
? submission.plugins.map((plugin) =>
|
}
|
||||||
AddonModAssignSubmissionDelegate.instance.preparePluginSubmissionData(
|
|
||||||
assign,
|
const pluginData: AddonModAssignSavePluginData = {};
|
||||||
submission,
|
const promises = submission.plugins.map((plugin) =>
|
||||||
plugin,
|
AddonModAssignSubmissionDelegate.instance.preparePluginSubmissionData(
|
||||||
inputData,
|
assign,
|
||||||
pluginData,
|
submission,
|
||||||
offline,
|
plugin,
|
||||||
))
|
inputData,
|
||||||
: [];
|
pluginData,
|
||||||
|
offline,
|
||||||
|
));
|
||||||
|
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ import { CoreUtils } from '@services/utils/utils';
|
||||||
import { AddonModAssignOffline } from './assign-offline';
|
import { AddonModAssignOffline } from './assign-offline';
|
||||||
import { AddonModAssignSubmissionDelegate } from './submission-delegate';
|
import { AddonModAssignSubmissionDelegate } from './submission-delegate';
|
||||||
import { CoreComments } from '@features/comments/services/comments';
|
import { CoreComments } from '@features/comments/services/comments';
|
||||||
|
import { AddonModAssignSubmissionFormatted } from './assign-helper';
|
||||||
|
|
||||||
const ROOT_CACHE_KEY = 'mmaModAssign:';
|
const ROOT_CACHE_KEY = 'mmaModAssign:';
|
||||||
|
|
||||||
|
@ -1011,7 +1012,7 @@ export class AddonModAssignProvider {
|
||||||
* @param assignId Assignment ID.
|
* @param assignId Assignment ID.
|
||||||
* @return Promise resolved with boolean: whether it needs to be graded or not.
|
* @return Promise resolved with boolean: whether it needs to be graded or not.
|
||||||
*/
|
*/
|
||||||
async needsSubmissionToBeGraded(submission: any, assignId: number): Promise<boolean> {
|
async needsSubmissionToBeGraded(submission: AddonModAssignSubmissionFormatted, assignId: number): Promise<boolean> {
|
||||||
if (!submission.gradingstatus) {
|
if (!submission.gradingstatus) {
|
||||||
// This should not happen, but it's better to show rather than not showing any of the submissions.
|
// This should not happen, but it's better to show rather than not showing any of the submissions.
|
||||||
return true;
|
return true;
|
||||||
|
@ -1864,11 +1865,12 @@ export type AddonModAssignSubmittedForGradingEventData = {
|
||||||
userId: number;
|
userId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data sent by SUBMISSION_SAVED_EVENT event.
|
||||||
|
*/
|
||||||
|
export type AddonModAssignSubmissionSavedEventData = AddonModAssignSubmittedForGradingEventData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data sent by GRADED_EVENT event.
|
* Data sent by GRADED_EVENT event.
|
||||||
*/
|
*/
|
||||||
export type AddonModAssignGradedEventData = {
|
export type AddonModAssignGradedEventData = AddonModAssignSubmittedForGradingEventData;
|
||||||
assignmentId: number;
|
|
||||||
submissionId: number;
|
|
||||||
userId: number;
|
|
||||||
};
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ export class AddonModAssignSubmissionFileComponent extends AddonModAssignSubmiss
|
||||||
/**
|
/**
|
||||||
* Component being initialized.
|
* Component being initialized.
|
||||||
*/
|
*/
|
||||||
async nOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
// Get the offline data.
|
// Get the offline data.
|
||||||
const filesData = await CoreUtils.instance.ignoreErrors(
|
const filesData = await CoreUtils.instance.ignoreErrors(
|
||||||
AddonModAssignOffline.instance.getSubmission(this.assign.id),
|
AddonModAssignOffline.instance.getSubmission(this.assign.id),
|
||||||
|
|
|
@ -24,12 +24,11 @@
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item class="ion-text-wrap">
|
<ion-item class="ion-text-wrap">
|
||||||
<ion-label>
|
<ion-label></ion-label>
|
||||||
<core-rich-text-editor [control]="control" [placeholder]="plugin.name"
|
<core-rich-text-editor [control]="control" [placeholder]="plugin.name"
|
||||||
name="onlinetext_editor_text" (contentChanged)="onChange($event)" [component]="component"
|
name="onlinetext_editor_text" (contentChanged)="onChange($event)" [component]="component"
|
||||||
[componentId]="assign.cmid" [autoSave]="true" contextLevel="module" [contextInstanceId]="assign.cmid"
|
[componentId]="assign.cmid" [autoSave]="true" contextLevel="module" [contextInstanceId]="assign.cmid"
|
||||||
elementId="onlinetext_editor" [draftExtraParams]="{userid: currentUserId, action: 'editsubmission'}">
|
elementId="onlinetext_editor" [draftExtraParams]="{userid: currentUserId, action: 'editsubmission'}">
|
||||||
</core-rich-text-editor>
|
</core-rich-text-editor>
|
||||||
</ion-label>
|
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -56,7 +56,7 @@ export class AddonModAssignSubmissionOnlineTextComponent extends AddonModAssignS
|
||||||
/**
|
/**
|
||||||
* Component being initialized.
|
* Component being initialized.
|
||||||
*/
|
*/
|
||||||
async nOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
// Get the text. Check if we have anything offline.
|
// Get the text. Check if we have anything offline.
|
||||||
const offlineData = await CoreUtils.instance.ignoreErrors(
|
const offlineData = await CoreUtils.instance.ignoreErrors(
|
||||||
AddonModAssignOffline.instance.getSubmission(this.assign.id),
|
AddonModAssignOffline.instance.getSubmission(this.assign.id),
|
||||||
|
|
|
@ -267,7 +267,7 @@ export class CoreCronDelegateService {
|
||||||
* @return True if handler uses network or not defined, false otherwise.
|
* @return True if handler uses network or not defined, false otherwise.
|
||||||
*/
|
*/
|
||||||
protected handlerUsesNetwork(name: string): boolean {
|
protected handlerUsesNetwork(name: string): boolean {
|
||||||
if (!this.handlers[name] || this.handlers[name].usesNetwork) {
|
if (!this.handlers[name] || !this.handlers[name].usesNetwork) {
|
||||||
// Invalid, return default.
|
// Invalid, return default.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -298,3 +298,10 @@ export type CoreEventSectionStatusChangedData = CoreEventSiteData & {
|
||||||
courseId: number;
|
courseId: number;
|
||||||
sectionId?: number;
|
sectionId?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data passed to ACTIVITY_DATA_SENT event.
|
||||||
|
*/
|
||||||
|
export type CoreEventActivityDataSentData = CoreEventSiteData & {
|
||||||
|
module: string;
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in New Issue