MOBILE-3636 assign: Add assignment submission page
parent
a4eefeb25a
commit
3281196ec0
|
@ -21,13 +21,16 @@ import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { CoreSharedModule } from '@/core/shared.module';
|
import { CoreSharedModule } from '@/core/shared.module';
|
||||||
import { CoreCourseComponentsModule } from '@features/course/components/components.module';
|
import { CoreCourseComponentsModule } from '@features/course/components/components.module';
|
||||||
import { AddonModAssignIndexComponent } from './index/index';
|
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({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
AddonModAssignIndexComponent,
|
AddonModAssignIndexComponent,
|
||||||
/* AddonModAssignSubmissionComponent,
|
AddonModAssignSubmissionComponent,
|
||||||
AddonModAssignSubmissionPluginComponent,
|
AddonModAssignSubmissionPluginComponent,
|
||||||
AddonModAssignFeedbackPluginComponent*/
|
AddonModAssignFeedbackPluginComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
@ -39,9 +42,9 @@ import { AddonModAssignIndexComponent } from './index/index';
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
AddonModAssignIndexComponent,
|
AddonModAssignIndexComponent,
|
||||||
/* AddonModAssignSubmissionComponent,
|
AddonModAssignSubmissionComponent,
|
||||||
AddonModAssignSubmissionPluginComponent,
|
AddonModAssignSubmissionPluginComponent,
|
||||||
AddonModAssignFeedbackPluginComponent */
|
AddonModAssignFeedbackPluginComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AddonModAssignComponentsModule {}
|
export class AddonModAssignComponentsModule {}
|
||||||
|
|
|
@ -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>
|
|
@ -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;
|
||||||
|
};
|
|
@ -135,8 +135,8 @@
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<!-- If it's a student, display his submission. -->
|
<!-- 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">
|
[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,
|
||||||
|
AddonModAssignSubmittedForGradingEventData,
|
||||||
} from '../../services/assign';
|
} from '../../services/assign';
|
||||||
import { AddonModAssignOffline } from '../../services/assign-offline';
|
import { AddonModAssignOffline } from '../../services/assign-offline';
|
||||||
import {
|
import {
|
||||||
|
@ -42,6 +43,7 @@ import {
|
||||||
AddonModAssignSyncProvider,
|
AddonModAssignSyncProvider,
|
||||||
AddonModAssignSyncResult,
|
AddonModAssignSyncResult,
|
||||||
} from '../../services/assign-sync';
|
} from '../../services/assign-sync';
|
||||||
|
import { AddonModAssignSubmissionComponent } from '../submission/submission';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that displays an assignment.
|
* Component that displays an assignment.
|
||||||
|
@ -52,8 +54,7 @@ import {
|
||||||
})
|
})
|
||||||
export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit, OnDestroy {
|
export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
// @todo @ViewChild(AddonModAssignSubmissionComponent) submissionComponent?: AddonModAssignSubmissionComponent;
|
@ViewChild(AddonModAssignSubmissionComponent) submissionComponent?: AddonModAssignSubmissionComponent;
|
||||||
submissionComponent?: any;
|
|
||||||
|
|
||||||
component = AddonModAssignProvider.COMPONENT;
|
component = AddonModAssignProvider.COMPONENT;
|
||||||
moduleName = 'assign';
|
moduleName = 'assign';
|
||||||
|
@ -112,15 +113,19 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
}
|
}
|
||||||
}, this.siteId);
|
}, this.siteId);
|
||||||
|
|
||||||
this.submittedObserver = CoreEvents.on<any>(AddonModAssignProvider.SUBMITTED_FOR_GRADING_EVENT, (data) => {
|
this.submittedObserver = CoreEvents.on<AddonModAssignSubmittedForGradingEventData>(
|
||||||
if (this.assign && data.assignmentId == this.assign.id && data.userId == this.currentUserId) {
|
AddonModAssignProvider.SUBMITTED_FOR_GRADING_EVENT,
|
||||||
|
(data) => {
|
||||||
|
if (this.assign && data.assignmentId == this.assign.id && data.userId == this.currentUserId) {
|
||||||
// Assignment submitted, check completion.
|
// 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.
|
// Reload data since it can have offline data now.
|
||||||
this.showLoadingAndRefresh(true, false);
|
this.showLoadingAndRefresh(true, false);
|
||||||
}
|
}
|
||||||
}, this.siteId);
|
},
|
||||||
|
this.siteId,
|
||||||
|
);
|
||||||
|
|
||||||
this.gradedObserver = CoreEvents.on<AddonModAssignGradedEventData>(AddonModAssignProvider.GRADED_EVENT, (data) => {
|
this.gradedObserver = CoreEvents.on<AddonModAssignGradedEventData>(AddonModAssignProvider.GRADED_EVENT, (data) => {
|
||||||
if (this.assign && data.assignmentId == this.assign.id && data.userId == this.currentUserId) {
|
if (this.assign && data.assignmentId == this.assign.id && data.userId == this.currentUserId) {
|
||||||
|
|
|
@ -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>
|
|
@ -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;
|
||||||
|
};
|
|
@ -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>
|
|
@ -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
|
@ -24,6 +24,8 @@ import {
|
||||||
AddonModAssignParticipant,
|
AddonModAssignParticipant,
|
||||||
AddonModAssignSubmissionFeedback,
|
AddonModAssignSubmissionFeedback,
|
||||||
AddonModAssign,
|
AddonModAssign,
|
||||||
|
AddonModAssignPlugin,
|
||||||
|
AddonModAssignSavePluginData,
|
||||||
} from './assign';
|
} from './assign';
|
||||||
import { AddonModAssignOffline } from './assign-offline';
|
import { AddonModAssignOffline } from './assign-offline';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
@ -98,7 +100,7 @@ export class AddonModAssignHelperProvider {
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async copyPreviousAttempt(assign: AddonModAssignAssign, previousSubmission: AddonModAssignSubmission): Promise<void> {
|
async copyPreviousAttempt(assign: AddonModAssignAssign, previousSubmission: AddonModAssignSubmission): Promise<void> {
|
||||||
const pluginData: any = {};
|
const pluginData: AddonModAssignSavePluginData = {};
|
||||||
const promises = previousSubmission.plugins
|
const promises = previousSubmission.plugins
|
||||||
? previousSubmission.plugins.map((plugin) =>
|
? previousSubmission.plugins.map((plugin) =>
|
||||||
AddonModAssignSubmissionDelegate.instance.copyPluginSubmissionData(assign, plugin, pluginData))
|
AddonModAssignSubmissionDelegate.instance.copyPluginSubmissionData(assign, plugin, pluginData))
|
||||||
|
@ -121,8 +123,8 @@ export class AddonModAssignHelperProvider {
|
||||||
createEmptyFeedback(): AddonModAssignSubmissionFeedback {
|
createEmptyFeedback(): AddonModAssignSubmissionFeedback {
|
||||||
return {
|
return {
|
||||||
grade: undefined,
|
grade: undefined,
|
||||||
gradefordisplay: undefined,
|
gradefordisplay: '',
|
||||||
gradeddate: undefined,
|
gradeddate: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,13 +135,13 @@ export class AddonModAssignHelperProvider {
|
||||||
*/
|
*/
|
||||||
createEmptySubmission(): AddonModAssignSubmissionFormatted {
|
createEmptySubmission(): AddonModAssignSubmissionFormatted {
|
||||||
return {
|
return {
|
||||||
id: undefined,
|
id: 0,
|
||||||
userid: undefined,
|
userid: 0,
|
||||||
attemptnumber: undefined,
|
attemptnumber: 0,
|
||||||
timecreated: undefined,
|
timecreated: 0,
|
||||||
timemodified: undefined,
|
timemodified: 0,
|
||||||
status: undefined,
|
status: '',
|
||||||
groupid: undefined,
|
groupid: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,14 +285,15 @@ export class AddonModAssignHelperProvider {
|
||||||
* @param subtype Subtype name (assignsubmission or assignfeedback)
|
* @param subtype Subtype name (assignsubmission or assignfeedback)
|
||||||
* @return List of enabled plugins for the assign.
|
* @return List of enabled plugins for the assign.
|
||||||
*/
|
*/
|
||||||
getPluginsEnabled(assign: AddonModAssignAssign, subtype: string): AddonModAssignPluginsEnabled {
|
getPluginsEnabled(assign: AddonModAssignAssign, subtype: string): AddonModAssignPlugin[] {
|
||||||
const enabled: AddonModAssignPluginsEnabled = [];
|
const enabled: AddonModAssignPlugin[] = [];
|
||||||
|
|
||||||
assign.configs.forEach((config) => {
|
assign.configs.forEach((config) => {
|
||||||
if (config.subtype == subtype && config.name == 'enabled' && parseInt(config.value, 10) === 1) {
|
if (config.subtype == subtype && config.name == 'enabled' && parseInt(config.value, 10) === 1) {
|
||||||
// Format the plugin objects.
|
// Format the plugin objects.
|
||||||
enabled.push({
|
enabled.push({
|
||||||
type: config.plugin,
|
type: config.plugin,
|
||||||
|
name: config.plugin,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -564,7 +567,7 @@ export class AddonModAssignHelperProvider {
|
||||||
userId: number,
|
userId: number,
|
||||||
feedback: AddonModAssignSubmissionFeedback,
|
feedback: AddonModAssignSubmissionFeedback,
|
||||||
siteId?: string,
|
siteId?: string,
|
||||||
): Promise<any> {
|
): Promise<AddonModAssignSavePluginData> {
|
||||||
|
|
||||||
const pluginData = {};
|
const pluginData = {};
|
||||||
const promises = feedback.plugins
|
const promises = feedback.plugins
|
||||||
|
@ -673,20 +676,22 @@ export class AddonModAssignHelperProvider {
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
uploadOrStoreFiles(
|
async uploadOrStoreFiles(
|
||||||
assignId: number,
|
assignId: number,
|
||||||
folderName: string,
|
folderName: string,
|
||||||
files: (CoreWSExternalFile | FileEntry)[],
|
files: (CoreWSExternalFile | FileEntry)[],
|
||||||
offline = false,
|
offline = false,
|
||||||
userId?: number,
|
userId?: number,
|
||||||
siteId?: string,
|
siteId?: string,
|
||||||
): Promise<any> {
|
): Promise<void> {
|
||||||
|
|
||||||
if (offline) {
|
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.
|
* Assign submission with some calculated data.
|
||||||
*/
|
*/
|
||||||
export type AddonModAssignSubmissionFormatted =
|
export type AddonModAssignSubmissionFormatted =
|
||||||
Omit<AddonModAssignSubmission, 'id' | 'userid' | 'attemptnumber' | 'timecreated' | 'timemodified' | 'status' | 'groupid'> & {
|
Omit<AddonModAssignSubmission, 'userid'> & {
|
||||||
id?: number; // Submission id.
|
|
||||||
userid?: number; // Student id.
|
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.
|
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.
|
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.
|
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.
|
* Assignment plugin config.
|
||||||
*/
|
|
||||||
export type AddonModAssignPluginsEnabled = {
|
|
||||||
type: string; // Plugin type.
|
|
||||||
}[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assingment plugin config.
|
|
||||||
*/
|
*/
|
||||||
export type AddonModAssignPluginConfig = {[name: string]: string};
|
export type AddonModAssignPluginConfig = {[name: string]: string};
|
||||||
|
|
|
@ -114,7 +114,7 @@ export class AddonModAssignOfflineProvider {
|
||||||
* @return Promise resolved with submissions.
|
* @return Promise resolved with submissions.
|
||||||
*/
|
*/
|
||||||
async getAssignSubmissions(assignId: number, siteId?: string): Promise<AddonModAssignSubmissionsDBRecordFormatted[]> {
|
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,
|
assignId: number,
|
||||||
siteId?: string,
|
siteId?: string,
|
||||||
): Promise<AddonModAssignSubmissionsGradingDBRecordFormatted[]> {
|
): Promise<AddonModAssignSubmissionsGradingDBRecordFormatted[]> {
|
||||||
return this.getAssignSubmissionsGradeFormatted({ assingid: assignId }, siteId);
|
return this.getAssignSubmissionsGradeFormatted({ assignid: assignId }, siteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -453,7 +453,7 @@ export class AddonModAssignSyncProvider extends CoreCourseActivitySyncBaseProvid
|
||||||
|
|
||||||
const status = await AddonModAssign.instance.getSubmissionStatus(assign.id, options);
|
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) {
|
if (timemodified > offlineData.timemodified) {
|
||||||
// The submission grade was modified in Moodle, discard it.
|
// The submission grade was modified in Moodle, discard it.
|
||||||
|
@ -480,7 +480,7 @@ export class AddonModAssignSyncProvider extends CoreCourseActivitySyncBaseProvid
|
||||||
if (gradeInfo && gradeInfo.scale) {
|
if (gradeInfo && gradeInfo.scale) {
|
||||||
offlineData.grade = this.getSelectedScaleId(gradeInfo.scale, grade.grade || '');
|
offlineData.grade = this.getSelectedScaleId(gradeInfo.scale, grade.grade || '');
|
||||||
} else {
|
} else {
|
||||||
offlineData.grade = parseFloat(grade.grade || '') || undefined;
|
offlineData.grade = parseFloat(grade.grade || '');
|
||||||
}
|
}
|
||||||
} else if (gradeInfo && grade.outcomeid && AddonModAssign.instance.isOutcomesEditEnabled() && gradeInfo.outcomes) {
|
} else if (gradeInfo && grade.outcomeid && AddonModAssign.instance.isOutcomesEditEnabled() && gradeInfo.outcomes) {
|
||||||
gradeInfo.outcomes.forEach((outcome, index) => {
|
gradeInfo.outcomes.forEach((outcome, index) => {
|
||||||
|
|
|
@ -1326,7 +1326,7 @@ export class AddonModAssignProvider {
|
||||||
async submitGradingFormOnline(
|
async submitGradingFormOnline(
|
||||||
assignId: number,
|
assignId: number,
|
||||||
userId: number,
|
userId: number,
|
||||||
grade: number | undefined,
|
grade: number,
|
||||||
attemptNumber: number,
|
attemptNumber: number,
|
||||||
addAttempt: boolean,
|
addAttempt: boolean,
|
||||||
workflowState: string,
|
workflowState: string,
|
||||||
|
@ -1553,7 +1553,7 @@ export type AddonModAssignSubmissionPreviousAttempt = {
|
||||||
* Feedback of an assign submission.
|
* Feedback of an assign submission.
|
||||||
*/
|
*/
|
||||||
export type AddonModAssignSubmissionFeedback = {
|
export type AddonModAssignSubmissionFeedback = {
|
||||||
grade: AddonModAssignGrade; // Grade information.
|
grade?: AddonModAssignGrade; // Grade information.
|
||||||
gradefordisplay: string; // Grade rendered into a format suitable for display.
|
gradefordisplay: string; // Grade rendered into a format suitable for display.
|
||||||
gradeddate: number; // The date the user was graded.
|
gradeddate: number; // The date the user was graded.
|
||||||
plugins?: AddonModAssignPlugin[]; // Plugins info.
|
plugins?: AddonModAssignPlugin[]; // Plugins info.
|
||||||
|
@ -1853,3 +1853,12 @@ type AddonModAssignSaveGradeWSParams = {
|
||||||
* Assignment grade outcomes.
|
* Assignment grade outcomes.
|
||||||
*/
|
*/
|
||||||
export type AddonModAssignOutcomes = { [itemNumber: number]: number };
|
export type AddonModAssignOutcomes = { [itemNumber: number]: number };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data sent by SUBMITTED_FOR_GRADING_EVENT event.
|
||||||
|
*/
|
||||||
|
export type AddonModAssignSubmittedForGradingEventData = {
|
||||||
|
assignmentId: number;
|
||||||
|
submissionId: number;
|
||||||
|
userId: number;
|
||||||
|
};
|
||||||
|
|
|
@ -139,7 +139,7 @@ export type AddonModAssignSubmissionsGradingDBRecord = {
|
||||||
assignid: number; // Primary key.
|
assignid: number; // Primary key.
|
||||||
userid: number; // Primary key.
|
userid: number; // Primary key.
|
||||||
courseid: number;
|
courseid: number;
|
||||||
grade?: number; // Real.
|
grade: number; // Real.
|
||||||
attemptnumber: number;
|
attemptnumber: number;
|
||||||
addattempt: number;
|
addattempt: number;
|
||||||
workflowstate: string;
|
workflowstate: string;
|
||||||
|
|
|
@ -12,10 +12,10 @@
|
||||||
// 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 { Injectable } from '@angular/core';
|
import { Injectable, Type } from '@angular/core';
|
||||||
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
|
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
|
||||||
import { AddonModAssignDefaultFeedbackHandler } from './handlers/default-feedback';
|
import { AddonModAssignDefaultFeedbackHandler } from './handlers/default-feedback';
|
||||||
import { AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin } from './assign';
|
import { AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin, AddonModAssignSavePluginData } from './assign';
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
import { CoreWSExternalFile } from '@services/ws';
|
import { CoreWSExternalFile } from '@services/ws';
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ export interface AddonModAssignFeedbackHandler extends CoreDelegateHandler {
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
* @return If the function is async, it should return a Promise resolved when done.
|
* @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.
|
* Return the Component to use to display the plugin data.
|
||||||
|
@ -46,7 +46,8 @@ export interface AddonModAssignFeedbackHandler extends CoreDelegateHandler {
|
||||||
* @param plugin The plugin object.
|
* @param plugin The plugin object.
|
||||||
* @return The component (or promise resolved with component) to use, undefined if not found.
|
* @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.
|
* Return the draft saved data of the feedback plugin.
|
||||||
|
@ -126,7 +127,7 @@ export interface AddonModAssignFeedbackHandler extends CoreDelegateHandler {
|
||||||
submission: AddonModAssignSubmission,
|
submission: AddonModAssignSubmission,
|
||||||
plugin: AddonModAssignPlugin,
|
plugin: AddonModAssignPlugin,
|
||||||
siteId?: string,
|
siteId?: string,
|
||||||
): Promise<any>;
|
): Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare and add to pluginData the data to send to the server based on the draft data saved.
|
* 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,
|
assignId: number,
|
||||||
userId: number,
|
userId: number,
|
||||||
plugin: AddonModAssignPlugin,
|
plugin: AddonModAssignPlugin,
|
||||||
pluginData: any,
|
pluginData: AddonModAssignSavePluginData,
|
||||||
siteId?: string,
|
siteId?: string,
|
||||||
): void | Promise<any>;
|
): void | Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save draft data of the feedback plugin.
|
* Save draft data of the feedback plugin.
|
||||||
|
@ -197,7 +198,7 @@ export class AddonModAssignFeedbackDelegateService extends CoreDelegate<AddonMod
|
||||||
* @param plugin The plugin object.
|
* @param plugin The plugin object.
|
||||||
* @return Promise resolved with the component to use, undefined if not found.
|
* @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]);
|
return await this.executeFunctionOnEnabled(plugin.type, 'getComponent', [plugin]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,7 +318,7 @@ export class AddonModAssignFeedbackDelegateService extends CoreDelegate<AddonMod
|
||||||
submission: AddonModAssignSubmission,
|
submission: AddonModAssignSubmission,
|
||||||
plugin: AddonModAssignPlugin,
|
plugin: AddonModAssignPlugin,
|
||||||
siteId?: string,
|
siteId?: string,
|
||||||
): Promise<any> {
|
): Promise<void> {
|
||||||
return await this.executeFunctionOnEnabled(plugin.type, 'prefetch', [assign, submission, plugin, siteId]);
|
return await this.executeFunctionOnEnabled(plugin.type, 'prefetch', [assign, submission, plugin, siteId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -335,9 +336,9 @@ export class AddonModAssignFeedbackDelegateService extends CoreDelegate<AddonMod
|
||||||
assignId: number,
|
assignId: number,
|
||||||
userId: number,
|
userId: number,
|
||||||
plugin: AddonModAssignPlugin,
|
plugin: AddonModAssignPlugin,
|
||||||
pluginData: any,
|
pluginData: AddonModAssignSavePluginData,
|
||||||
siteId?: string,
|
siteId?: string,
|
||||||
): Promise<any> {
|
): Promise<void> {
|
||||||
|
|
||||||
return await this.executeFunctionOnEnabled(
|
return await this.executeFunctionOnEnabled(
|
||||||
plugin.type,
|
plugin.type,
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
import { CoreWSExternalFile } from '@services/ws';
|
||||||
import { Translate } from '@singletons';
|
import { Translate } from '@singletons';
|
||||||
import { AddonModAssignPlugin } from '../assign';
|
import { AddonModAssignPlugin } from '../assign';
|
||||||
import { AddonModAssignFeedbackHandler } from '../feedback-delegate';
|
import { AddonModAssignFeedbackHandler } from '../feedback-delegate';
|
||||||
|
@ -35,16 +36,6 @@ export class AddonModAssignDefaultFeedbackHandler implements AddonModAssignFeedb
|
||||||
// Nothing to do.
|
// 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.
|
* 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).
|
* @return The files (or promise resolved with the files).
|
||||||
*/
|
*/
|
||||||
getPluginFiles(): any[] {
|
getPluginFiles(): CoreWSExternalFile[] {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,7 +112,7 @@ export class AddonModAssignDefaultFeedbackHandler implements AddonModAssignFeedb
|
||||||
*
|
*
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async prefetch(): Promise<any> {
|
async prefetch(): Promise<void> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
import { CoreWSExternalFile } from '@services/ws';
|
||||||
import { Translate } from '@singletons';
|
import { Translate } from '@singletons';
|
||||||
import { AddonModAssignPlugin } from '../assign';
|
import { AddonModAssignPlugin } from '../assign';
|
||||||
import { AddonModAssignSubmissionHandler } from '../submission-delegate';
|
import { AddonModAssignSubmissionHandler } from '../submission-delegate';
|
||||||
|
@ -72,23 +73,13 @@ export class AddonModAssignDefaultSubmissionHandler implements AddonModAssignSub
|
||||||
// Nothing to do.
|
// 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.
|
* Get files used by this plugin.
|
||||||
* The files returned by this function will be prefetched when the user prefetches the assign.
|
* The files returned by this function will be prefetched when the user prefetches the assign.
|
||||||
*
|
*
|
||||||
* @return The files (or promise resolved with the files).
|
* @return The files (or promise resolved with the files).
|
||||||
*/
|
*/
|
||||||
getPluginFiles(): any[] {
|
getPluginFiles(): CoreWSExternalFile[] {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,7 +167,7 @@ export class AddonModAssignDefaultSubmissionHandler implements AddonModAssignSub
|
||||||
*
|
*
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async prefetch(): Promise<any> {
|
async prefetch(): Promise<void> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,10 +12,10 @@
|
||||||
// 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 { Injectable } from '@angular/core';
|
import { Injectable, Type } from '@angular/core';
|
||||||
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
|
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
|
||||||
import { AddonModAssignDefaultSubmissionHandler } from './handlers/default-submission';
|
import { AddonModAssignDefaultSubmissionHandler } from './handlers/default-submission';
|
||||||
import { AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin } from './assign';
|
import { AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin, AddonModAssignSavePluginData } from './assign';
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
import { CoreWSExternalFile } from '@services/ws';
|
import { CoreWSExternalFile } from '@services/ws';
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler {
|
||||||
copySubmissionData?(
|
copySubmissionData?(
|
||||||
assign: AddonModAssignAssign,
|
assign: AddonModAssignAssign,
|
||||||
plugin: AddonModAssignPlugin,
|
plugin: AddonModAssignPlugin,
|
||||||
pluginData: any,
|
pluginData: AddonModAssignSavePluginData,
|
||||||
userId?: number,
|
userId?: number,
|
||||||
siteId?: string,
|
siteId?: string,
|
||||||
): void | Promise<void>;
|
): void | Promise<void>;
|
||||||
|
@ -120,7 +120,7 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler {
|
||||||
getComponent?(
|
getComponent?(
|
||||||
plugin: AddonModAssignPlugin,
|
plugin: AddonModAssignPlugin,
|
||||||
edit?: boolean,
|
edit?: boolean,
|
||||||
): any | Promise<any>;
|
): Type<unknown> | undefined | Promise<Type<unknown> | undefined>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get files used by this plugin.
|
* Get files used by this plugin.
|
||||||
|
@ -233,7 +233,7 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler {
|
||||||
submission: AddonModAssignSubmission,
|
submission: AddonModAssignSubmission,
|
||||||
plugin: AddonModAssignPlugin,
|
plugin: AddonModAssignPlugin,
|
||||||
inputData: any,
|
inputData: any,
|
||||||
pluginData: any,
|
pluginData: AddonModAssignSavePluginData,
|
||||||
offline?: boolean,
|
offline?: boolean,
|
||||||
userId?: number,
|
userId?: number,
|
||||||
siteId?: string,
|
siteId?: string,
|
||||||
|
@ -321,7 +321,7 @@ export class AddonModAssignSubmissionDelegateService extends CoreDelegate<AddonM
|
||||||
async copyPluginSubmissionData(
|
async copyPluginSubmissionData(
|
||||||
assign: AddonModAssignAssign,
|
assign: AddonModAssignAssign,
|
||||||
plugin: AddonModAssignPlugin,
|
plugin: AddonModAssignPlugin,
|
||||||
pluginData: any,
|
pluginData: AddonModAssignSavePluginData,
|
||||||
userId?: number,
|
userId?: number,
|
||||||
siteId?: string,
|
siteId?: string,
|
||||||
): Promise<void | undefined> {
|
): Promise<void | undefined> {
|
||||||
|
@ -363,7 +363,7 @@ export class AddonModAssignSubmissionDelegateService extends CoreDelegate<AddonM
|
||||||
* @param edit Whether the user is editing.
|
* @param edit Whether the user is editing.
|
||||||
* @return Promise resolved with the component to use, undefined if not found.
|
* @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]);
|
return await this.executeFunctionOnEnabled(plugin.type, 'getComponent', [plugin, edit]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue