MOBILE-3636 assign: Add assignment submission page

main
Pau Ferrer Ocaña 2021-02-11 12:27:11 +01:00
parent a4eefeb25a
commit 3281196ec0
19 changed files with 2239 additions and 96 deletions

View File

@ -21,13 +21,16 @@ import { TranslateModule } from '@ngx-translate/core';
import { CoreSharedModule } from '@/core/shared.module';
import { CoreCourseComponentsModule } from '@features/course/components/components.module';
import { AddonModAssignIndexComponent } from './index/index';
import { AddonModAssignSubmissionComponent } from './submission/submission';
import { AddonModAssignSubmissionPluginComponent } from './submission-plugin/submission-plugin';
import { AddonModAssignFeedbackPluginComponent } from './feedback-plugin/feedback-plugin';
@NgModule({
declarations: [
AddonModAssignIndexComponent,
/* AddonModAssignSubmissionComponent,
AddonModAssignSubmissionComponent,
AddonModAssignSubmissionPluginComponent,
AddonModAssignFeedbackPluginComponent*/
AddonModAssignFeedbackPluginComponent,
],
imports: [
CommonModule,
@ -39,9 +42,9 @@ import { AddonModAssignIndexComponent } from './index/index';
],
exports: [
AddonModAssignIndexComponent,
/* AddonModAssignSubmissionComponent,
AddonModAssignSubmissionComponent,
AddonModAssignSubmissionPluginComponent,
AddonModAssignFeedbackPluginComponent */
AddonModAssignFeedbackPluginComponent,
],
})
export class AddonModAssignComponentsModule {}

View File

@ -0,0 +1,23 @@
<core-dynamic-component [component]="pluginComponent" [data]="data">
<!-- This content will be replaced by the component if found. -->
<core-loading [hideUntil]="pluginLoaded">
<ion-item class="ion-text-wrap" *ngIf="text.length > 0 || files.length > 0">
<ion-label>
<h2>{{ plugin.name }}</h2>
<ion-badge *ngIf="notSupported" color="primary">
{{ 'addon.mod_assign.feedbacknotsupported' | translate }}
</ion-badge>
<p *ngIf="text">
<core-format-text [component]="component" [componentId]="assign.cmid" [maxHeight]="80" [fullOnClick]="true"
[fullTitle]="plugin.name" [text]="text" contextLevel="module" [contextInstanceId]="assign.cmid"
[courseId]="assign.course">
</core-format-text>
</p>
<core-file *ngFor="let file of files" [file]="file" [component]="component" [componentId]="assign.cmid"
[alwaysDownload]="true">
</core-file>
</ion-label>
</ion-item>
</core-loading>
</core-dynamic-component>

View File

@ -0,0 +1,117 @@
// (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, Input, OnInit, ViewChild, Type } from '@angular/core';
import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-component';
import { CoreWSExternalFile } from '@services/ws';
import {
AddonModAssignAssign,
AddonModAssignSubmission,
AddonModAssignPlugin,
AddonModAssignProvider,
AddonModAssign,
} from '../../services/assign';
import { AddonModAssignHelper, AddonModAssignPluginConfig } from '../../services/assign-helper';
import { AddonModAssignFeedbackDelegate } from '../../services/feedback-delegate';
/**
* Component that displays an assignment feedback plugin.
*/
@Component({
selector: 'addon-mod-assign-feedback-plugin',
templateUrl: 'addon-mod-assign-feedback-plugin.html',
})
export class AddonModAssignFeedbackPluginComponent implements OnInit {
@ViewChild(CoreDynamicComponent) dynamicComponent!: CoreDynamicComponent;
@Input() assign!: AddonModAssignAssign; // The assignment.
@Input() submission!: AddonModAssignSubmission; // The submission.
@Input() plugin!: AddonModAssignPlugin; // The plugin object.
@Input() userId!: number; // The user ID of the submission.
@Input() canEdit = false; // Whether the user can edit.
@Input() edit = false; // Whether the user is editing.
pluginComponent?: Type<unknown>; // Component to render the plugin.
data?: AddonModAssignFeedbackPluginData; // Data to pass to the component.
// Data to render the plugin if it isn't supported.
component = AddonModAssignProvider.COMPONENT;
text = '';
files: CoreWSExternalFile[] = [];
notSupported = false;
pluginLoaded = false;
/**
* Component being initialized.
*/
async ngOnInit(): Promise<void> {
if (!this.plugin) {
this.pluginLoaded = true;
return;
}
const name = AddonModAssignFeedbackDelegate.instance.getPluginName(this.plugin);
if (!name) {
this.pluginLoaded = true;
return;
}
this.plugin.name = name;
// Check if the plugin has defined its own component to render itself.
this.pluginComponent = await AddonModAssignFeedbackDelegate.instance.getComponentForPlugin(this.plugin);
if (this.pluginComponent) {
// Prepare the data to pass to the component.
this.data = {
assign: this.assign,
submission: this.submission,
plugin: this.plugin,
userId: this.userId,
configs: AddonModAssignHelper.instance.getPluginConfig(this.assign, 'assignfeedback', this.plugin.type),
edit: this.edit,
canEdit: this.canEdit,
};
} else {
// Data to render the plugin.
this.text = AddonModAssign.instance.getSubmissionPluginText(this.plugin);
this.files = AddonModAssign.instance.getSubmissionPluginAttachments(this.plugin);
this.notSupported = AddonModAssignFeedbackDelegate.instance.isPluginSupported(this.plugin.type);
this.pluginLoaded = true;
}
}
/**
* Invalidate the plugin data.
*
* @return Promise resolved when done.
*/
async invalidate(): Promise<void> {
await this.dynamicComponent.callComponentFunction('invalidate', []);
}
}
export type AddonModAssignFeedbackPluginData = {
assign: AddonModAssignAssign;
submission: AddonModAssignSubmission;
plugin: AddonModAssignPlugin;
configs: AddonModAssignPluginConfig;
edit: boolean;
canEdit: boolean;
userId: number;
};

View File

@ -135,8 +135,8 @@
</ng-container>
<!-- If it's a student, display his submission. -->
<!-- @todo <addon-mod-assign-submission *ngIf="loaded && !canViewAllSubmissions && canViewOwnSubmission" [courseId]="courseId"
<addon-mod-assign-submission *ngIf="loaded && !canViewAllSubmissions && canViewOwnSubmission" [courseId]="courseId"
[moduleId]="module.id">
</addon-mod-assign-submission>-->
</addon-mod-assign-submission>
</core-loading>

View File

@ -34,6 +34,7 @@ import {
AddonModAssignGradedEventData,
AddonModAssignProvider,
AddonModAssignSubmissionGradingSummary,
AddonModAssignSubmittedForGradingEventData,
} from '../../services/assign';
import { AddonModAssignOffline } from '../../services/assign-offline';
import {
@ -42,6 +43,7 @@ import {
AddonModAssignSyncProvider,
AddonModAssignSyncResult,
} from '../../services/assign-sync';
import { AddonModAssignSubmissionComponent } from '../submission/submission';
/**
* Component that displays an assignment.
@ -52,8 +54,7 @@ import {
})
export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit, OnDestroy {
// @todo @ViewChild(AddonModAssignSubmissionComponent) submissionComponent?: AddonModAssignSubmissionComponent;
submissionComponent?: any;
@ViewChild(AddonModAssignSubmissionComponent) submissionComponent?: AddonModAssignSubmissionComponent;
component = AddonModAssignProvider.COMPONENT;
moduleName = 'assign';
@ -112,15 +113,19 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
}
}, this.siteId);
this.submittedObserver = CoreEvents.on<any>(AddonModAssignProvider.SUBMITTED_FOR_GRADING_EVENT, (data) => {
if (this.assign && data.assignmentId == this.assign.id && data.userId == this.currentUserId) {
this.submittedObserver = CoreEvents.on<AddonModAssignSubmittedForGradingEventData>(
AddonModAssignProvider.SUBMITTED_FOR_GRADING_EVENT,
(data) => {
if (this.assign && data.assignmentId == this.assign.id && data.userId == this.currentUserId) {
// Assignment submitted, check completion.
CoreCourse.instance.checkModuleCompletion(this.courseId!, this.module!.completiondata);
CoreCourse.instance.checkModuleCompletion(this.courseId!, this.module!.completiondata);
// Reload data since it can have offline data now.
this.showLoadingAndRefresh(true, false);
}
}, this.siteId);
// Reload data since it can have offline data now.
this.showLoadingAndRefresh(true, false);
}
},
this.siteId,
);
this.gradedObserver = CoreEvents.on<AddonModAssignGradedEventData>(AddonModAssignProvider.GRADED_EVENT, (data) => {
if (this.assign && data.assignmentId == this.assign.id && data.userId == this.currentUserId) {

View File

@ -0,0 +1,23 @@
<core-dynamic-component [component]="pluginComponent" [data]="data">
<!-- This content will be replaced by the component if found. -->
<core-loading [hideUntil]="pluginLoaded">
<ion-item class="ion-text-wrap" *ngIf="text.length > 0 || files.length > 0">
<ion-label>
<h2>{{ plugin.name }}</h2>
<ion-badge *ngIf="notSupported" color="primary">
{{ 'addon.mod_assign.submissionnotsupported' | translate }}
</ion-badge>
<p *ngIf="text">
<core-format-text [component]="component" [componentId]="assign.cmid" [maxHeight]="80" [fullOnClick]="true"
[fullTitle]="plugin.name" [text]="text" contextLevel="module" [contextInstanceId]="assign.cmid"
[courseId]="assign.course">
</core-format-text>
</p>
<core-file *ngFor="let file of files" [file]="file" [component]="component" [componentId]="assign.cmid"
[alwaysDownload]="true">
</core-file>
</ion-label>
</ion-item>
</core-loading>
</core-dynamic-component>

View File

@ -0,0 +1,114 @@
// (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, Input, OnInit, Type, ViewChild } from '@angular/core';
import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-component';
import { CoreWSExternalFile } from '@services/ws';
import {
AddonModAssignAssign,
AddonModAssignSubmission,
AddonModAssignPlugin,
AddonModAssignProvider,
AddonModAssign,
} from '../../services/assign';
import { AddonModAssignHelper, AddonModAssignPluginConfig } from '../../services/assign-helper';
import { AddonModAssignSubmissionDelegate } from '../../services/submission-delegate';
/**
* Component that displays an assignment submission plugin.
*/
@Component({
selector: 'addon-mod-assign-submission-plugin',
templateUrl: 'addon-mod-assign-submission-plugin.html',
})
export class AddonModAssignSubmissionPluginComponent implements OnInit {
@ViewChild(CoreDynamicComponent) dynamicComponent!: CoreDynamicComponent;
@Input() assign!: AddonModAssignAssign; // The assignment.
@Input() submission!: AddonModAssignSubmission; // The submission.
@Input() plugin!: AddonModAssignPlugin; // The plugin object.
@Input() edit = false; // Whether the user is editing.
@Input() allowOffline = false; // Whether to allow offline.
pluginComponent?: Type<unknown>; // Component to render the plugin.
data?: AddonModAssignSubmissionPluginData; // Data to pass to the component.
// Data to render the plugin if it isn't supported.
component = AddonModAssignProvider.COMPONENT;
text = '';
files: CoreWSExternalFile[] = [];
notSupported = false;
pluginLoaded = false;
/**
* Component being initialized.
*/
async ngOnInit(): Promise<void> {
if (!this.plugin) {
this.pluginLoaded = true;
return;
}
const name = AddonModAssignSubmissionDelegate.instance.getPluginName(this.plugin);
if (!name) {
this.pluginLoaded = true;
return;
}
this.plugin.name = name;
// Check if the plugin has defined its own component to render itself.
this.pluginComponent = await AddonModAssignSubmissionDelegate.instance.getComponentForPlugin(this.plugin, this.edit);
if (this.pluginComponent) {
// Prepare the data to pass to the component.
this.data = {
assign: this.assign,
submission: this.submission,
plugin: this.plugin,
configs: AddonModAssignHelper.instance.getPluginConfig(this.assign, 'assignsubmission', this.plugin.type),
edit: this.edit,
allowOffline: this.allowOffline,
};
} else {
// Data to render the plugin.
this.text = AddonModAssign.instance.getSubmissionPluginText(this.plugin);
this.files = AddonModAssign.instance.getSubmissionPluginAttachments(this.plugin);
this.notSupported = AddonModAssignSubmissionDelegate.instance.isPluginSupported(this.plugin.type);
this.pluginLoaded = true;
}
}
/**
* Invalidate the plugin data.
*
* @return Promise resolved when done.
*/
async invalidate(): Promise<void> {
await this.dynamicComponent.callComponentFunction('invalidate', []);
}
}
export type AddonModAssignSubmissionPluginData = {
assign: AddonModAssignAssign;
submission: AddonModAssignSubmission;
plugin: AddonModAssignPlugin;
configs: AddonModAssignPluginConfig;
edit: boolean;
allowOffline: boolean;
};

View File

@ -0,0 +1,388 @@
<core-loading [hideUntil]="loaded" class="core-loading-center">
<!-- 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">
<core-user-avatar [user]="user" slot="start"></core-user-avatar>
<ion-label>
<h2>{{ user.fullname }}</h2>
<ng-container *ngTemplateOutlet="submissionStatus"></ng-container>
</ion-label>
<ng-container *ngTemplateOutlet="submissionStatusBadges"></ng-container>
</ion-item>
<!-- Status of the submission if user is blinded. -->
<ion-item class="ion-text-wrap" *ngIf="blindMarking && !user">
<ion-label>
<h2>{{ 'addon.mod_assign.hiddenuser' | translate }} {{blindId}}</h2>
<ng-container *ngTemplateOutlet="submissionStatus"></ng-container>
</ion-label>
<ng-container *ngTemplateOutlet="submissionStatusBadges"></ng-container>
</ion-item>
<!-- Status of the submission in the rest of cases. -->
<ion-item class="ion-text-wrap" *ngIf="(blindMarking && user) || (!blindMarking && !user)">
<ion-label>
<h2>{{ 'addon.mod_assign.submissionstatus' | translate }}</h2>
<ng-container *ngTemplateOutlet="submissionStatus"></ng-container>
</ion-label>
<ng-container *ngTemplateOutlet="submissionStatusBadges"></ng-container>
</ion-item>
<!-- Tabs: see the submission or grade it. -->
<core-tabs [selectedIndex]="selectedTab" [hideUntil]="loaded" parentScrollable="true" (ionChange)="tabSelected($event)">
<!-- View the submission tab. -->
<core-tab [title]="'addon.mod_assign.submission' | translate" id="submission">
<ng-template>
<addon-mod-assign-submission-plugin *ngFor="let plugin of submissionPlugins"
[assign]="assign" [submission]="userSubmission" [plugin]="plugin">
</addon-mod-assign-submission-plugin>
<!-- Render some data about the submission. -->
<ion-item class="ion-text-wrap"
*ngIf="userSubmission && userSubmission.status != statusNew && userSubmission.timemodified">
<ion-label>
<h2>{{ 'addon.mod_assign.timemodified' | translate }}</h2>
<p>{{ userSubmission.timemodified * 1000 | coreFormatDate }}</p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="timeRemaining" [ngClass]="[timeRemainingClass]">
<ion-label>
<h2>{{ 'addon.mod_assign.timeremaining' | translate }}</h2>
<p [innerHTML]="timeRemaining"></p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="fromDate && !isSubmittedForGrading">
<ion-label>
<p *ngIf="assign.intro"
[innerHTML]="'addon.mod_assign.allowsubmissionsfromdatesummary' | translate: {'$a': fromDate}">
</p>
<p *ngIf="!assign.intro"
[innerHTML]="'addon.mod_assign.allowsubmissionsanddescriptionfromdatesummary' | translate:
{'$a': fromDate}">
</p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="assign.duedate && !isSubmittedForGrading">
<ion-label>
<h2>{{ 'addon.mod_assign.duedate' | translate }}</h2>
<p *ngIf="assign.duedate" >{{ assign.duedate * 1000 | coreFormatDate }}</p>
<p *ngIf="!assign.duedate" >{{ 'addon.mod_assign.duedateno' | translate }}</p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="assign.duedate && assign.cutoffdate && isSubmittedForGrading">
<ion-label>
<h2>{{ 'addon.mod_assign.cutoffdate' | translate }}</h2>
<p>{{ assign.cutoffdate * 1000 | coreFormatDate }}</p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap"
*ngIf="assign.duedate && lastAttempt && lastAttempt.extensionduedate && !isSubmittedForGrading">
<ion-label>
<h2>{{ 'addon.mod_assign.extensionduedate' | translate }}</h2>
<p>{{ lastAttempt.extensionduedate * 1000 | coreFormatDate }}</p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="currentAttempt && !isGrading">
<ion-label>
<h2>{{ 'addon.mod_assign.attemptnumber' | translate }}</h2>
<p *ngIf="assign.maxattempts == unlimitedAttempts">
{{ 'addon.mod_assign.outof' | translate :
{'$a': {'current': currentAttempt, 'total': maxAttemptsText} } }}
</p>
<p *ngIf="assign.maxattempts != unlimitedAttempts">
{{ 'addon.mod_assign.outof' | translate :
{'$a': {'current': currentAttempt, 'total': assign.maxattempts} } }}
</p>
</ion-label>
</ion-item>
<!-- Add or edit submission. -->
<ion-item class="ion-text-wrap" *ngIf="canEdit">
<ion-label>
<div *ngIf="!unsupportedEditPlugins.length && !showErrorStatementEdit">
<!-- If has offline data, show edit. -->
<ion-button expand="block" class="ion-text-wrap" color="primary" *ngIf="hasOffline"
(click)="goToEdit()">
{{ 'addon.mod_assign.editsubmission' | translate }}
</ion-button>
<!-- If no submission or is new, show add submission. -->
<ion-button expand="block" class="ion-text-wrap" color="primary"
*ngIf="!hasOffline &&
(!userSubmission || !userSubmission.status || userSubmission.status == statusNew)"
(click)="goToEdit()">
{{ 'addon.mod_assign.addsubmission' | translate }}
</ion-button>
<!-- If reopened, show addfromprevious and addnewattempt. -->
<ng-container *ngIf="!hasOffline && userSubmission && userSubmission.status == statusReopened">
<ion-button *ngIf="!isPreviousAttemptEmpty" expand="block" class="ion-text-wrap" color="primary"
(click)="copyPrevious()">
{{ 'addon.mod_assign.addnewattemptfromprevious' | translate }}
</ion-button>
<ion-button expand="block" class="ion-text-wrap" color="primary" (click)="goToEdit()">
{{ 'addon.mod_assign.addnewattempt' | translate }}
</ion-button>
</ng-container>
<!-- Else show editsubmission. -->
<ion-button expand="block" class="ion-text-wrap" color="primary"
*ngIf="!hasOffline && userSubmission && userSubmission.status &&
userSubmission.status != statusNew &&
userSubmission.status != statusReopened" (click)="goToEdit()">
{{ 'addon.mod_assign.editsubmission' | translate }}
</ion-button>
</div>
<div *ngIf="unsupportedEditPlugins && unsupportedEditPlugins.length && !showErrorStatementEdit">
<p class="core-danger-item">{{ 'addon.mod_assign.erroreditpluginsnotsupported' | translate }}</p>
<p class="core-danger-item" *ngFor="let name of unsupportedEditPlugins">{{ name }}</p>
</div>
<div *ngIf="showErrorStatementEdit">
<p class="core-danger-item">{{ 'addon.mod_assign.cannoteditduetostatementsubmission' | translate }}</p>
</div>
</ion-label>
</ion-item>
<!-- Submit for grading form. -->
<ng-container *ngIf="canSubmit">
<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)]="acceptStatement">
</ion-checkbox>
</ion-item>
<!-- Submit button. -->
<ion-item class="ion-text-wrap" *ngIf="!showErrorStatementSubmit">
<ion-label>
<ion-button expand="block" class="ion-text-wrap"
(click)="submitForGrading(acceptStatement)">
{{ 'addon.mod_assign.submitassignment' | translate }}
</ion-button>
<p>{{ 'addon.mod_assign.submitassignment_help' | translate }}</p>
</ion-label>
</ion-item>
<!-- Error because we lack submissions statement. -->
<ion-item class="ion-text-wrap" *ngIf="showErrorStatementSubmit">
<ion-label>
<p class="core-danger-item">
{{ 'addon.mod_assign.cannotsubmitduetostatementsubmission' | translate }}
</p>
</ion-label>
</ion-item>
</ng-container>
<!-- Team members that need to submit it too. -->
<ion-item class="ion-text-wrap" *ngIf="membersToSubmit && membersToSubmit.length > 0">
<ion-label><h2>{{ 'addon.mod_assign.userswhoneedtosubmit' | translate: {$a: ''} }}</h2></ion-label>
<ng-container *ngIf="!blindMarking">
<ng-container *ngFor="let user of membersToSubmit">
<ion-item class="ion-text-wrap" core-user-link [userId]="user.id"
[courseId]="courseId" [title]="user.fullname">
<core-user-avatar [user]="user" slot="start"></core-user-avatar>
<ion-label><h2>{{ user.fullname }}</h2></ion-label>
</ion-item>
</ng-container>
</ng-container>
<ng-container *ngIf="blindMarking">
<ng-container *ngFor="let blindId of membersToSubmitBlind">
<ion-item class="ion-text-wrap">
<ion-label>{{ 'addon.mod_assign.hiddenuser' | translate }} {{ blindId }}</ion-label>
</ion-item>
</ng-container>
</ng-container>
</ion-item>
<!-- Submission is locked. -->
<ion-item class="ion-text-wrap" *ngIf="lastAttempt?.locked">
<ion-label><h2>{{ 'addon.mod_assign.submissionslocked' | translate }}</h2></ion-label>
</ion-item>
<!-- Editing status. -->
<ion-item class="ion-text-wrap"
*ngIf="lastAttempt && isSubmittedForGrading && lastAttempt!.caneditowner !== undefined"
[ngClass]="{submissioneditable: lastAttempt!.caneditowner, submissionnoteditable: !lastAttempt!.caneditowner}">
<ion-label>
<h2>{{ 'addon.mod_assign.editingstatus' | translate }}</h2>
<p *ngIf="lastAttempt!.caneditowner">{{ 'addon.mod_assign.submissioneditable' | translate }}</p>
<p *ngIf="!lastAttempt!.caneditowner">{{ 'addon.mod_assign.submissionnoteditable' | translate }}</p>
</ion-label>
</ion-item>
</ng-template>
</core-tab>
<!-- Grade the submission tab. -->
<core-tab [title]="'addon.mod_assign.grade' | translate" *ngIf="feedback || isGrading" id="grade">
<ng-template>
<!-- Current grade if method is advanced. -->
<ion-item class="ion-text-wrap core-grading-summary"
*ngIf="feedback.gradefordisplay && (!isGrading || grade.method != 'simple')">
<ion-label>
<h2>{{ 'addon.mod_assign.currentgrade' | translate }}</h2>
<p><core-format-text [text]="feedback.gradefordisplay" [filter]="false"></core-format-text></p>
</ion-label>
<ion-button slot="end" *ngIf="feedback.advancedgrade" (click)="showAdvancedGrade()">
<ion-icon name="fas-search" slot="icon-only"></ion-icon>
</ion-button>
</ion-item>
<ng-container *ngIf="isGrading">
<!-- Numeric grade.
Use a text input because otherwise we cannot readthe value if it has an invalid character. -->
<ion-item class="ion-text-wrap" *ngIf="grade.method == 'simple' && !grade.scale">
<ion-label position="stacked">
<h2>{{ 'addon.mod_assign.gradeoutof' | translate: {$a: gradeInfo!.grade} }}</h2>
</ion-label>
<ion-input *ngIf="!grade.disabled" type="text" [(ngModel)]="grade.grade" min="0" [max]="gradeInfo.grade"
[lang]="grade.lang">
</ion-input>
<p item-content *ngIf="grade.disabled">{{ 'addon.mod_assign.gradelocked' | translate }}</p>
</ion-item>
<!-- Grade using a scale. -->
<ion-item class="ion-text-wrap" *ngIf="grade.method == 'simple' && grade.scale">
<ion-label><h2>{{ 'addon.mod_assign.grade' | translate }}</h2></ion-label>
<ion-select [(ngModel)]="grade.grade" interface="action-sheet" [disabled]="grade.disabled">
<ion-select-option *ngFor="let grade of grade.scale" [value]="grade.value">
{{grade.label}}
</ion-select-option>
</ion-select>
</ion-item>
<!-- Outcomes. -->
<ion-item class="ion-text-wrap" *ngFor="let outcome of gradeInfo!.outcomes">
<ion-label><h2>{{ outcome.name }}</h2></ion-label>
<ion-select *ngIf="canSaveGrades && outcome.itemNumber" [(ngModel)]="outcome.selectedId"
interface="action-sheet" [disabled]="gradeInfo.disabled">
<ion-select-option *ngFor="let grade of outcome.options" [value]="grade.value">
{{grade.label}}
</ion-select-option>
</ion-select>
<p item-content *ngIf="!canSaveGrades || !outcome.itemNumber">{{ outcome.selected }}</p>
</ion-item>
<!-- Gradebook grade for simple grading. -->
<ion-item class="ion-text-wrap" *ngIf="grade.method == 'simple'">
<ion-label>
<h2>{{ 'addon.mod_assign.currentgrade' | translate }}</h2>
<p *ngIf="grade.gradebookGrade !== false && grade.gradebookGrade !== null && !grade.scale">
{{ grade.gradebookGrade }}
</p>
<p *ngIf="grade.gradebookGrade !== false && grade.gradebookGrade !== null && grade.scale">
{{ grade.scale[grade.gradebookGrade].label }}
</p>
<p *ngIf="grade.gradebookGrade === false || grade.gradebookGrade === null">-</p>
</ion-label>
</ion-item>
</ng-container>
<addon-mod-assign-feedback-plugin *ngFor="let plugin of feedback.plugins" [assign]="assign"
[submission]="userSubmission" [userId]="submitId" [plugin]="plugin" [canEdit]="canSaveGrades">
</addon-mod-assign-feedback-plugin>
<!-- Workflow status. -->
<ion-item class="ion-text-wrap" *ngIf="workflowStatusTranslationId">
<ion-label>
<h2>{{ 'addon.mod_assign.markingworkflowstate' | translate }}</h2>
<p>{{ workflowStatusTranslationId | translate }}</p>
</ion-label>
</ion-item>
<!--- Apply grade to all team members. -->
<ion-item class="ion-text-wrap" *ngIf="assign.teamsubmission && canSaveGrades">
<h2>{{ 'addon.mod_assign.groupsubmissionsettings' | translate }}</h2>
<ion-label>{{ 'addon.mod_assign.applytoteam' | translate }}</ion-label>
<ion-toggle [(ngModel)]="grade.applyToAll"></ion-toggle>
</ion-item>
<!-- Attempt status. -->
<ng-container *ngIf="isGrading && assign.attemptreopenmethod != attemptReopenMethodNone">
<ion-item class="ion-text-wrap">
<ion-label>
<h2>{{ 'addon.mod_assign.attemptsettings' | translate }}</h2>
<p *ngIf="assign.maxattempts == unlimitedAttempts">
{{ 'addon.mod_assign.outof' | translate :
{'$a': {'current': currentAttempt, 'total': maxAttemptsText} } }}
</p>
<p *ngIf="assign.maxattempts != unlimitedAttempts">
{{ 'addon.mod_assign.outof' | translate :
{'$a': {'current': currentAttempt, 'total': assign.maxattempts} } }}
</p>
<p>
{{ 'addon.mod_assign.attemptreopenmethod' | translate }}:
{{ 'addon.mod_assign.attemptreopenmethod_' + assign.attemptreopenmethod | translate }}
</p>
</ion-label>
</ion-item>
<ion-item *ngIf="canSaveGrades && allowAddAttempt" >
<ion-label>{{ 'addon.mod_assign.addattempt' | translate }}</ion-label>
<ion-toggle [(ngModel)]="grade.addAttempt"></ion-toggle>
</ion-item>
</ng-container>
<!-- Data about the grader (teacher who graded). -->
<ion-item class="ion-text-wrap" *ngIf="grader" core-user-link [userId]="grader.id" [courseId]="courseId"
[title]="grader.fullname" detail="true">
<core-user-avatar [user]="grader" slot="start"></core-user-avatar>
<ion-label>
<h2>{{ 'addon.mod_assign.gradedby' | translate }}</h2>
<h2>{{ grader.fullname }}</h2>
<p *ngIf="feedback.gradeddate">{{ feedback.gradeddate * 1000 | coreFormatDate }}</p>
</ion-label>
</ion-item>
<!-- Grader is hidden, display only the grade date. -->
<ion-item class="ion-text-wrap" *ngIf="!grader && feedback.gradeddate">
<ion-label>
<h2>{{ 'addon.mod_assign.gradedon' | translate }}</h2>
<p>{{ feedback.gradeddate * 1000 | coreFormatDate }}</p>
</ion-label>
</ion-item>
<!-- Warning message if cannot save grades. -->
<div *ngIf="isGrading && !canSaveGrades" class="core-warning-card">
<ion-icon name="fas-exclamation-triangle" slot="start"></ion-icon>
<p>{{ 'addon.mod_assign.cannotgradefromapp' | translate }}</p>
<ion-button expand="block" *ngIf="gradeUrl" [href]="gradeUrl" core-link >
{{ 'core.openinbrowser' | translate }}
<ion-icon name="fas-external-link-alt" slot="end"></ion-icon>
</ion-button>
</div>
</ng-template>
</core-tab>
</core-tabs>
</core-loading>
<!-- Template to render some data regarding the submission status. -->
<ng-template #submissionStatus>
<ng-container *ngIf="assign && assign!.teamsubmission && lastAttempt">
<p *ngIf="lastAttempt!.submissiongroup && lastAttempt!.submissiongroupname">{{lastAttempt!.submissiongroupname}}</p>
<ng-container *ngIf="assign.preventsubmissionnotingroup &&
!lastAttempt!.submissiongroup &&
(!lastAttempt!.usergroups || lastAttempt!.usergroups.length <= 0)">
<p class="text-danger"><strong>{{ 'addon.mod_assign.noteam' | translate }}</strong></p>
<p class="text-danger">{{ 'addon.mod_assign.noteam_desc' | translate }}</p>
</ng-container>
<ng-container *ngIf="assign.preventsubmissionnotingroup &&
!lastAttempt!.submissiongroup &&
lastAttempt!.usergroups &&
lastAttempt!.usergroups.length > 1">
<p class="text-danger"><strong>{{ 'addon.mod_assign.multipleteams' | translate }}</strong></p>
<p class="text-danger">{{ 'addon.mod_assign.multipleteams_desc' | translate }}</p>
</ng-container>
<p *ngIf="!assign.preventsubmissionnotingroup && !lastAttempt!.submissiongroup">
{{ 'addon.mod_assign.defaultteam' | translate }}
</p>
</ng-container>
</ng-template>
<ng-template #submissionStatusBadges>
<ion-badge slot="end" *ngIf="statusTranslated" [color]="statusColor">
{{ statusTranslated }}
</ion-badge>
<ion-badge slot="end" *ngIf="gradingStatusTranslationId" [color]="gradingColor">
{{ gradingStatusTranslationId | translate }}
</ion-badge>
</ng-template>

View File

@ -0,0 +1,263 @@
:host {
div.latesubmission,
div.overdue {
// @extend .core-danger-item;
}
div.earlysubmission {
// @extend .core-success-item;
}
div.submissioneditable p {
color: $red;
@include darkmode() {
color: $red-light;
}
}
.core-grading-summary .advancedgrade {
display: none;
}
}
core-format-text {
.gradingform_rubric_editform .status {
font-weight: normal;
text-transform: uppercase;
font-size: 60%;
padding: 0.25em;
border: 1px solid $gray-light;
}
.gradingform_rubric_editform .status.ready {
background-color: $green-light;
border-color: $green;
}
.gradingform_rubric_editform .status.draft {
background-color: $yellow-light;
border-color: $yellow;
}
.gradingform_rubric {
overflow: auto;
padding-bottom: 1.5em;
max-width: 720px;
position: relative;
margin: 0 auto;
tbody {
background: $white;
color: $text-color;
}
}
// Do not display remark column.
.gradingform_rubric .criterion .remark {
display: none;
}
.gradingform_rubric.editor .criterion .controls,
.gradingform_rubric .criterion .description,
.gradingform_rubric .criterion .levels,
.gradingform_rubric.editor .criterion .addlevel,
.gradingform_rubric .criterion .remark,
.gradingform_rubric .criterion .levels .level {
vertical-align: top;
}
.gradingform_rubric.editor .criterion .controls,
.gradingform_rubric .criterion .description,
.gradingform_rubric.editor .criterion .addlevel,
.gradingform_rubric .criterion .remark,
.gradingform_rubric .criterion .levels .level {
padding: 3px;
}
.gradingform_rubric .criteria {
height: 100%;
}
.gradingform_rubric .criterion {
border: 1px solid $gray;
overflow: hidden;
}
.gradingform_rubric .criterion.even {
background: $gray-lighter;
}
.gradingform_rubric .criterion .description {
width: 150px;
font-weight: bold;
}
.gradingform_rubric .criterion .levels table {
width: 100%;
height: 100%;
}
.gradingform_rubric .criterion .levels,
.gradingform_rubric .criterion .levels table,
.gradingform_rubric .criterion .levels table tbody {
padding: 0;
margin: 0;
}
.gradingform_rubric .criterion .levels .level {
border-left: 1px solid $gray;
max-width: 150px;
}
.gradingform_rubric .criterion .levels .level .level-wrapper {
position: relative;
}
.gradingform_rubric .criterion .levels .level.last {
border-right: 1px solid $gray;
}
.gradingform_rubric .plainvalue.empty {
font-style: italic;
color: $gray-dark;
}
.gradingform_rubric.editor .criterion .levels .level .delete {
position: absolute;
right: 0;
}
.gradingform_rubric .criterion .levels .level .score {
font-style: italic;
color: $green;
font-weight: bold;
margin-top: 5px;
white-space: nowrap;
}
.gradingform_rubric .criterion .levels .level .score .scorevalue {
padding-right: 5px;
}
/* Make invisible the buttons 'Move up' for the first criterion and
'Move down' for the last, because those buttons will make no change */
.gradingform_rubric.editor .criterion.first .controls .moveup input,
.gradingform_rubric.editor .criterion.last .controls .movedown input {
display: none;
}
/* evaluation */
.gradingform_rubric .criterion .levels .level.currentchecked {
background: #fff0f0;
}
.gradingform_rubric .criterion .levels .level.checked {
background: $green-light;
border: 1px solid $gray-darker;
}
.gradingform_rubric .options .optionsheading {
font-weight: bold;
font-size: 1.1em;
padding-bottom: 5px;
}
.gradingform_rubric .options .option {
padding-bottom: 2px;
}
.gradingform_rubric .options .option label {
margin-left: 5px;
}
.gradingform_rubric .options .option .value {
margin-left: 5px;
font-weight: bold;
}
.gradingform_rubric .criterion .levels.error {
border: 1px solid $red;
}
.gradingform_rubric .criterion .description.error,
.gradingform_rubric .criterion .levels .level .definition.error,
.gradingform_rubric .criterion .levels .level .score.error {
background: $gray-lighter;
}
.gradingform_rubric-regrade {
padding: 10px;
background: $gray-lighter;
border: 1px solid $red-light;
margin-bottom: 10px;
}
.gradingform_rubric-restored {
padding: 10px;
background: $yellow-light;
border: 1px solid $yellow;
margin-bottom: 10px;
}
.gradingform_rubric-error {
color: $red;
font-weight: bold;
}
/* special classes for elements created by rubriceditor.js */
.gradingform_rubric.editor .hiddenelement {
display: none;
}
.gradingform_rubric.editor .pseudotablink {
background-color: transparent;
border: 0 solid;
height: 1px;
width: 1px;
color: transparent;
padding: 0;
margin: 0;
position: relative;
float: right;
}
.gradingform_rubric {
padding-bottom: 0;
max-width: none;
}
.gradingform_rubric .criterion .description {
font-weight: 500;
min-width: 150px;
}
.gradingform_rubric .criterion .levels {
background-color: $white;
}
.gradingform_rubric .criterion,
.gradingform_rubric .criterion.even {
background-color: transparent;
}
.gradingform_rubric.evaluate .criterion .levels .level:hover {
background-color: $green-light;
}
.gradingform_rubric .criterion .levels .level.checked {
background-color: $green-light;
border: none;
border-left: 1px solid $gray;
}
.gradingform_rubric .criterion .levels .level .score {
color: $green;
font-weight: 500;
font-style: normal;
margin-top: 20px;
}
.gradingform_rubric .criterion .remark textarea {
margin-bottom: 0;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -24,6 +24,8 @@ import {
AddonModAssignParticipant,
AddonModAssignSubmissionFeedback,
AddonModAssign,
AddonModAssignPlugin,
AddonModAssignSavePluginData,
} from './assign';
import { AddonModAssignOffline } from './assign-offline';
import { CoreUtils } from '@services/utils/utils';
@ -98,7 +100,7 @@ export class AddonModAssignHelperProvider {
* @return Promise resolved when done.
*/
async copyPreviousAttempt(assign: AddonModAssignAssign, previousSubmission: AddonModAssignSubmission): Promise<void> {
const pluginData: any = {};
const pluginData: AddonModAssignSavePluginData = {};
const promises = previousSubmission.plugins
? previousSubmission.plugins.map((plugin) =>
AddonModAssignSubmissionDelegate.instance.copyPluginSubmissionData(assign, plugin, pluginData))
@ -121,8 +123,8 @@ export class AddonModAssignHelperProvider {
createEmptyFeedback(): AddonModAssignSubmissionFeedback {
return {
grade: undefined,
gradefordisplay: undefined,
gradeddate: undefined,
gradefordisplay: '',
gradeddate: 0,
};
}
@ -133,13 +135,13 @@ export class AddonModAssignHelperProvider {
*/
createEmptySubmission(): AddonModAssignSubmissionFormatted {
return {
id: undefined,
userid: undefined,
attemptnumber: undefined,
timecreated: undefined,
timemodified: undefined,
status: undefined,
groupid: undefined,
id: 0,
userid: 0,
attemptnumber: 0,
timecreated: 0,
timemodified: 0,
status: '',
groupid: 0,
};
}
@ -283,14 +285,15 @@ export class AddonModAssignHelperProvider {
* @param subtype Subtype name (assignsubmission or assignfeedback)
* @return List of enabled plugins for the assign.
*/
getPluginsEnabled(assign: AddonModAssignAssign, subtype: string): AddonModAssignPluginsEnabled {
const enabled: AddonModAssignPluginsEnabled = [];
getPluginsEnabled(assign: AddonModAssignAssign, subtype: string): AddonModAssignPlugin[] {
const enabled: AddonModAssignPlugin[] = [];
assign.configs.forEach((config) => {
if (config.subtype == subtype && config.name == 'enabled' && parseInt(config.value, 10) === 1) {
// Format the plugin objects.
enabled.push({
type: config.plugin,
name: config.plugin,
});
}
});
@ -564,7 +567,7 @@ export class AddonModAssignHelperProvider {
userId: number,
feedback: AddonModAssignSubmissionFeedback,
siteId?: string,
): Promise<any> {
): Promise<AddonModAssignSavePluginData> {
const pluginData = {};
const promises = feedback.plugins
@ -673,20 +676,22 @@ export class AddonModAssignHelperProvider {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when done.
*/
uploadOrStoreFiles(
async uploadOrStoreFiles(
assignId: number,
folderName: string,
files: (CoreWSExternalFile | FileEntry)[],
offline = false,
userId?: number,
siteId?: string,
): Promise<any> {
): Promise<void> {
if (offline) {
return this.storeSubmissionFiles(assignId, folderName, files, userId, siteId);
await this.storeSubmissionFiles(assignId, folderName, files, userId, siteId);
return;
}
return this.uploadFiles(assignId, files, siteId);
await this.uploadFiles(assignId, files, siteId);
}
}
@ -697,14 +702,8 @@ export const AddonModAssignHelper = makeSingleton(AddonModAssignHelperProvider);
* Assign submission with some calculated data.
*/
export type AddonModAssignSubmissionFormatted =
Omit<AddonModAssignSubmission, 'id' | 'userid' | 'attemptnumber' | 'timecreated' | 'timemodified' | 'status' | 'groupid'> & {
id?: number; // Submission id.
Omit<AddonModAssignSubmission, 'userid'> & {
userid?: number; // Student id.
attemptnumber?: number; // Attempt number.
timecreated?: number; // Submission creation time.
timemodified?: number; // Submission last modified time.
status?: string; // Submission status.
groupid?: number; // Group id.
blindid?: number; // Calculated in the app. Blindid of the user that did the submission.
submitid?: number; // Calculated in the app. Userid or blindid of the user that did the submission.
userfullname?: string; // Calculated in the app. Full name of the user that did the submission.
@ -715,13 +714,6 @@ export type AddonModAssignSubmissionFormatted =
};
/**
* Assingment subplugins type enabled.
*/
export type AddonModAssignPluginsEnabled = {
type: string; // Plugin type.
}[];
/**
* Assingment plugin config.
* Assignment plugin config.
*/
export type AddonModAssignPluginConfig = {[name: string]: string};

View File

@ -114,7 +114,7 @@ export class AddonModAssignOfflineProvider {
* @return Promise resolved with submissions.
*/
async getAssignSubmissions(assignId: number, siteId?: string): Promise<AddonModAssignSubmissionsDBRecordFormatted[]> {
return this.getAssignSubmissionsFormatted({ assingid: assignId }, siteId);
return this.getAssignSubmissionsFormatted({ assignid: assignId }, siteId);
}
/**
@ -167,7 +167,7 @@ export class AddonModAssignOfflineProvider {
assignId: number,
siteId?: string,
): Promise<AddonModAssignSubmissionsGradingDBRecordFormatted[]> {
return this.getAssignSubmissionsGradeFormatted({ assingid: assignId }, siteId);
return this.getAssignSubmissionsGradeFormatted({ assignid: assignId }, siteId);
}
/**

View File

@ -453,7 +453,7 @@ export class AddonModAssignSyncProvider extends CoreCourseActivitySyncBaseProvid
const status = await AddonModAssign.instance.getSubmissionStatus(assign.id, options);
const timemodified = (status.feedback && (status.feedback.gradeddate || status.feedback.grade.timemodified)) || 0;
const timemodified = (status.feedback && (status.feedback.gradeddate || status.feedback.grade?.timemodified)) || 0;
if (timemodified > offlineData.timemodified) {
// The submission grade was modified in Moodle, discard it.
@ -480,7 +480,7 @@ export class AddonModAssignSyncProvider extends CoreCourseActivitySyncBaseProvid
if (gradeInfo && gradeInfo.scale) {
offlineData.grade = this.getSelectedScaleId(gradeInfo.scale, grade.grade || '');
} else {
offlineData.grade = parseFloat(grade.grade || '') || undefined;
offlineData.grade = parseFloat(grade.grade || '');
}
} else if (gradeInfo && grade.outcomeid && AddonModAssign.instance.isOutcomesEditEnabled() && gradeInfo.outcomes) {
gradeInfo.outcomes.forEach((outcome, index) => {

View File

@ -1326,7 +1326,7 @@ export class AddonModAssignProvider {
async submitGradingFormOnline(
assignId: number,
userId: number,
grade: number | undefined,
grade: number,
attemptNumber: number,
addAttempt: boolean,
workflowState: string,
@ -1553,7 +1553,7 @@ export type AddonModAssignSubmissionPreviousAttempt = {
* Feedback of an assign submission.
*/
export type AddonModAssignSubmissionFeedback = {
grade: AddonModAssignGrade; // Grade information.
grade?: AddonModAssignGrade; // Grade information.
gradefordisplay: string; // Grade rendered into a format suitable for display.
gradeddate: number; // The date the user was graded.
plugins?: AddonModAssignPlugin[]; // Plugins info.
@ -1853,3 +1853,12 @@ type AddonModAssignSaveGradeWSParams = {
* Assignment grade outcomes.
*/
export type AddonModAssignOutcomes = { [itemNumber: number]: number };
/**
* Data sent by SUBMITTED_FOR_GRADING_EVENT event.
*/
export type AddonModAssignSubmittedForGradingEventData = {
assignmentId: number;
submissionId: number;
userId: number;
};

View File

@ -139,7 +139,7 @@ export type AddonModAssignSubmissionsGradingDBRecord = {
assignid: number; // Primary key.
userid: number; // Primary key.
courseid: number;
grade?: number; // Real.
grade: number; // Real.
attemptnumber: number;
addattempt: number;
workflowstate: string;

View File

@ -12,10 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Type } from '@angular/core';
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
import { AddonModAssignDefaultFeedbackHandler } from './handlers/default-feedback';
import { AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin } from './assign';
import { AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin, AddonModAssignSavePluginData } from './assign';
import { makeSingleton } from '@singletons';
import { CoreWSExternalFile } from '@services/ws';
@ -37,7 +37,7 @@ export interface AddonModAssignFeedbackHandler extends CoreDelegateHandler {
* @param siteId Site ID. If not defined, current site.
* @return If the function is async, it should return a Promise resolved when done.
*/
discardDraft?(assignId: number, userId: number, siteId?: string): void | Promise<any>;
discardDraft?(assignId: number, userId: number, siteId?: string): void | Promise<void>;
/**
* Return the Component to use to display the plugin data.
@ -46,7 +46,8 @@ export interface AddonModAssignFeedbackHandler extends CoreDelegateHandler {
* @param plugin The plugin object.
* @return The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent?(plugin: AddonModAssignPlugin): any | Promise<any>;
getComponent?(plugin: AddonModAssignPlugin): Type<unknown> | undefined | Promise<Type<unknown> | undefined>;
/**
* Return the draft saved data of the feedback plugin.
@ -126,7 +127,7 @@ export interface AddonModAssignFeedbackHandler extends CoreDelegateHandler {
submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin,
siteId?: string,
): Promise<any>;
): Promise<void>;
/**
* Prepare and add to pluginData the data to send to the server based on the draft data saved.
@ -142,9 +143,9 @@ export interface AddonModAssignFeedbackHandler extends CoreDelegateHandler {
assignId: number,
userId: number,
plugin: AddonModAssignPlugin,
pluginData: any,
pluginData: AddonModAssignSavePluginData,
siteId?: string,
): void | Promise<any>;
): void | Promise<void>;
/**
* Save draft data of the feedback plugin.
@ -197,7 +198,7 @@ export class AddonModAssignFeedbackDelegateService extends CoreDelegate<AddonMod
* @param plugin The plugin object.
* @return Promise resolved with the component to use, undefined if not found.
*/
async getComponentForPlugin(plugin: AddonModAssignPlugin): Promise<any | undefined> {
async getComponentForPlugin(plugin: AddonModAssignPlugin): Promise<Type<unknown> | undefined> {
return await this.executeFunctionOnEnabled(plugin.type, 'getComponent', [plugin]);
}
@ -317,7 +318,7 @@ export class AddonModAssignFeedbackDelegateService extends CoreDelegate<AddonMod
submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin,
siteId?: string,
): Promise<any> {
): Promise<void> {
return await this.executeFunctionOnEnabled(plugin.type, 'prefetch', [assign, submission, plugin, siteId]);
}
@ -335,9 +336,9 @@ export class AddonModAssignFeedbackDelegateService extends CoreDelegate<AddonMod
assignId: number,
userId: number,
plugin: AddonModAssignPlugin,
pluginData: any,
pluginData: AddonModAssignSavePluginData,
siteId?: string,
): Promise<any> {
): Promise<void> {
return await this.executeFunctionOnEnabled(
plugin.type,

View File

@ -13,6 +13,7 @@
// limitations under the License.
import { Injectable } from '@angular/core';
import { CoreWSExternalFile } from '@services/ws';
import { Translate } from '@singletons';
import { AddonModAssignPlugin } from '../assign';
import { AddonModAssignFeedbackHandler } from '../feedback-delegate';
@ -35,16 +36,6 @@ export class AddonModAssignDefaultFeedbackHandler implements AddonModAssignFeedb
// Nothing to do.
}
/**
* Return the Component to use to display the plugin data.
* It's recommended to return the class of the component, but you can also return an instance of the component.
*
* @return The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent(): void {
// Nothing to do.
}
/**
* Return the draft saved data of the feedback plugin.
*
@ -60,7 +51,7 @@ export class AddonModAssignDefaultFeedbackHandler implements AddonModAssignFeedb
*
* @return The files (or promise resolved with the files).
*/
getPluginFiles(): any[] {
getPluginFiles(): CoreWSExternalFile[] {
return [];
}
@ -121,7 +112,7 @@ export class AddonModAssignDefaultFeedbackHandler implements AddonModAssignFeedb
*
* @return Promise resolved when done.
*/
async prefetch(): Promise<any> {
async prefetch(): Promise<void> {
return;
}

View File

@ -13,6 +13,7 @@
// limitations under the License.
import { Injectable } from '@angular/core';
import { CoreWSExternalFile } from '@services/ws';
import { Translate } from '@singletons';
import { AddonModAssignPlugin } from '../assign';
import { AddonModAssignSubmissionHandler } from '../submission-delegate';
@ -72,23 +73,13 @@ export class AddonModAssignDefaultSubmissionHandler implements AddonModAssignSub
// Nothing to do.
}
/**
* Return the Component to use to display the plugin data, either in read or in edit mode.
* It's recommended to return the class of the component, but you can also return an instance of the component.
*
* @return The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent(): void {
// Nothing to do.
}
/**
* Get files used by this plugin.
* The files returned by this function will be prefetched when the user prefetches the assign.
*
* @return The files (or promise resolved with the files).
*/
getPluginFiles(): any[] {
getPluginFiles(): CoreWSExternalFile[] {
return [];
}
@ -176,7 +167,7 @@ export class AddonModAssignDefaultSubmissionHandler implements AddonModAssignSub
*
* @return Promise resolved when done.
*/
async prefetch(): Promise<any> {
async prefetch(): Promise<void> {
return;
}

View File

@ -12,10 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Type } from '@angular/core';
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
import { AddonModAssignDefaultSubmissionHandler } from './handlers/default-submission';
import { AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin } from './assign';
import { AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin, AddonModAssignSavePluginData } from './assign';
import { makeSingleton } from '@singletons';
import { CoreWSExternalFile } from '@services/ws';
@ -86,7 +86,7 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler {
copySubmissionData?(
assign: AddonModAssignAssign,
plugin: AddonModAssignPlugin,
pluginData: any,
pluginData: AddonModAssignSavePluginData,
userId?: number,
siteId?: string,
): void | Promise<void>;
@ -120,7 +120,7 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler {
getComponent?(
plugin: AddonModAssignPlugin,
edit?: boolean,
): any | Promise<any>;
): Type<unknown> | undefined | Promise<Type<unknown> | undefined>;
/**
* Get files used by this plugin.
@ -233,7 +233,7 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler {
submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin,
inputData: any,
pluginData: any,
pluginData: AddonModAssignSavePluginData,
offline?: boolean,
userId?: number,
siteId?: string,
@ -321,7 +321,7 @@ export class AddonModAssignSubmissionDelegateService extends CoreDelegate<AddonM
async copyPluginSubmissionData(
assign: AddonModAssignAssign,
plugin: AddonModAssignPlugin,
pluginData: any,
pluginData: AddonModAssignSavePluginData,
userId?: number,
siteId?: string,
): Promise<void | undefined> {
@ -363,7 +363,7 @@ export class AddonModAssignSubmissionDelegateService extends CoreDelegate<AddonM
* @param edit Whether the user is editing.
* @return Promise resolved with the component to use, undefined if not found.
*/
async getComponentForPlugin(plugin: AddonModAssignPlugin, edit?: boolean): Promise<any | undefined> {
async getComponentForPlugin(plugin: AddonModAssignPlugin, edit?: boolean): Promise<Type<unknown> | undefined> {
return await this.executeFunctionOnEnabled(plugin.type, 'getComponent', [plugin, edit]);
}