MOBILE-3657 workshop: Submission and assessments
parent
8db22cc54a
commit
b037c96b0b
|
@ -3,7 +3,7 @@
|
|||
|
||||
<span *ngIf="editMode" [core-mark-required]="field.required" class="core-mark-required"></span>
|
||||
<core-rich-text-editor *ngIf="editMode" [control]="form.controls['f_'+field.id]" [placeholder]="field.name"
|
||||
[formControlName]="'f_'+field.id" [component]="component" [componentId]="componentId" [autoSave]="true"
|
||||
[name]="'f_'+field.id" [component]="component" [componentId]="componentId" [autoSave]="true"
|
||||
contextLevel="module" [contextInstanceId]="componentId" [elementId]="'field_'+field.id" ngDefaultControl>
|
||||
</core-rich-text-editor>
|
||||
<core-input-errors *ngIf="error && editMode" [control]="form.controls['f_'+field.id]" [errorText]="error"></core-input-errors>
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
// (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 { APP_INITIALIZER, NgModule } from '@angular/core';
|
||||
import { AddonModWorkshopAssessmentStrategyAccumulativeComponent } from './component/accumulative';
|
||||
import { AddonModWorkshopAssessmentStrategyAccumulativeHandler } from './services/handler';
|
||||
import { AddonWorkshopAssessmentStrategyDelegate } from '../../services/assessment-strategy-delegate';
|
||||
import { CoreSharedModule } from '@/core/shared.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonModWorkshopAssessmentStrategyAccumulativeComponent,
|
||||
],
|
||||
imports: [
|
||||
CoreSharedModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
multi: true,
|
||||
deps: [],
|
||||
useFactory: () => () => {
|
||||
AddonWorkshopAssessmentStrategyDelegate.registerHandler(
|
||||
AddonModWorkshopAssessmentStrategyAccumulativeHandler.instance,
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
exports: [
|
||||
AddonModWorkshopAssessmentStrategyAccumulativeComponent,
|
||||
],
|
||||
})
|
||||
export class AddonModWorkshopAssessmentStrategyAccumulativeModule {}
|
|
@ -0,0 +1,25 @@
|
|||
// (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 } from '@angular/core';
|
||||
import { AddonModWorkshopAssessmentStrategyBaseComponent } from '../../../classes/assessment-strategy-component';
|
||||
|
||||
/**
|
||||
* Component for accumulative assessment strategy.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-mod-workshop-assessment-strategy-accumulative',
|
||||
templateUrl: 'addon-mod-workshop-assessment-strategy-accumulative.html',
|
||||
})
|
||||
export class AddonModWorkshopAssessmentStrategyAccumulativeComponent extends AddonModWorkshopAssessmentStrategyBaseComponent { }
|
|
@ -0,0 +1,50 @@
|
|||
<ng-container *ngFor="let field of assessment.form?.fields; let n = index">
|
||||
<ion-card *ngIf="n < assessment.form?.dimenssionscount">
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<h2>{{ field.dimtitle }}</h2>
|
||||
<core-format-text [text]="field.description" contextLevel="module" [contextInstanceId]="moduleId"
|
||||
[courseId]="courseId">
|
||||
</core-format-text>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="edit && field.grades">
|
||||
<ion-label position="stacked">
|
||||
<span [core-mark-required]="true">
|
||||
{{ 'addon.mod_workshop_assessment_accumulative.dimensiongradefor' | translate : {'$a': field.dimtitle } }}
|
||||
</span>
|
||||
</ion-label>
|
||||
<ion-select [(ngModel)]="selectedValues[n].grade" interface="action-sheet">
|
||||
<ion-select-option *ngFor="let grade of field.grades" [value]="grade.value">{{grade.label}}</ion-select-option>
|
||||
</ion-select>
|
||||
<core-input-errors *ngIf="fieldErrors['grade_' + n]" [errorText]="fieldErrors['grade_' + n]">
|
||||
</core-input-errors>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="!edit && field.grades" class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<h2>{{ 'addon.mod_workshop_assessment_accumulative.dimensiongradefor' | translate : {'$a': field.dimtitle } }}</h2>
|
||||
<ng-container *ngFor="let grade of field.grades">
|
||||
<p *ngIf="grade.value === selectedValues[n].grade">{{grade.label}}</p>
|
||||
</ng-container>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="edit">
|
||||
<ion-label position="stacked">
|
||||
{{ 'addon.mod_workshop_assessment_accumulative.dimensioncommentfor' | translate : {'$a': field.dimtitle } }}
|
||||
</ion-label>
|
||||
<ion-textarea aria-multiline="true" [(ngModel)]="selectedValues[n].peercomment" core-auto-rows></ion-textarea>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="!edit" class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<h2>
|
||||
{{ 'addon.mod_workshop_assessment_accumulative.dimensioncommentfor' | translate : {'$a': field.dimtitle } }}
|
||||
</h2>
|
||||
<p>
|
||||
<core-format-text [text]="selectedValues[n].peercomment" contextLevel="module" [contextInstanceId]="moduleId"
|
||||
[courseId]="courseId">
|
||||
</core-format-text>
|
||||
</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-card>
|
||||
</ng-container>
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"dimensioncommentfor": "Comment for {{$a}}",
|
||||
"dimensiongradefor": "Grade for {{$a}}",
|
||||
"dimensionnumber": "Aspect {{$a}}",
|
||||
"mustchoosegrade": "You have to select a grade for this aspect"
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
// (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 {
|
||||
AddonModWorkshopAssessmentStrategyFieldErrors,
|
||||
} from '@addons/mod/workshop/components/assessment-strategy/assessment-strategy';
|
||||
import {
|
||||
AddonModWorkshopGetAssessmentFormDefinitionData,
|
||||
AddonModWorkshopGetAssessmentFormFieldsParsedData,
|
||||
} from '@addons/mod/workshop/services/workshop';
|
||||
import { Injectable, Type } from '@angular/core';
|
||||
import { CoreGradesHelper } from '@features/grades/services/grades-helper';
|
||||
import { makeSingleton, Translate } from '@singletons';
|
||||
import { CoreFormFields } from '@singletons/form';
|
||||
import { AddonWorkshopAssessmentStrategyHandler } from '../../../services/assessment-strategy-delegate';
|
||||
import { AddonModWorkshopAssessmentStrategyAccumulativeComponent } from '../component/accumulative';
|
||||
|
||||
/**
|
||||
* Handler for accumulative assessment strategy plugin.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonModWorkshopAssessmentStrategyAccumulativeHandlerService implements AddonWorkshopAssessmentStrategyHandler {
|
||||
|
||||
name = 'AddonModWorkshopAssessmentStrategyAccumulative';
|
||||
strategyName = 'accumulative';
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async isEnabled(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
getComponent(): Type<unknown> {
|
||||
return AddonModWorkshopAssessmentStrategyAccumulativeComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async getOriginalValues(
|
||||
form: AddonModWorkshopGetAssessmentFormDefinitionData,
|
||||
): Promise<AddonModWorkshopGetAssessmentFormFieldsParsedData[]> {
|
||||
const defaultGrade = Translate.instant('core.choosedots');
|
||||
const originalValues: AddonModWorkshopGetAssessmentFormFieldsParsedData[] = [];
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
form.fields.forEach((field, n) => {
|
||||
field.dimtitle = Translate.instant('addon.mod_workshop_assessment_accumulative.dimensionnumber', { $a: field.number });
|
||||
|
||||
if (!form.current[n]) {
|
||||
form.current[n] = {};
|
||||
}
|
||||
|
||||
originalValues[n] = {};
|
||||
originalValues[n].peercomment = form.current[n].peercomment || '';
|
||||
originalValues[n].number = field.number; // eslint-disable-line id-blacklist
|
||||
|
||||
form.current[n].grade = form.current[n].grade ? parseInt(String(form.current[n].grade), 10) : -1;
|
||||
|
||||
const gradingType = parseInt(String(field.grade), 10);
|
||||
const dimension = form.dimensionsinfo.find((dimension) => dimension.id == parseInt(field.dimensionid, 10));
|
||||
const scale = dimension && gradingType < 0 ? dimension.scale : undefined;
|
||||
|
||||
promises.push(CoreGradesHelper.makeGradesMenu(gradingType, undefined, defaultGrade, -1, scale).then((grades) => {
|
||||
field.grades = grades;
|
||||
originalValues[n].grade = form.current[n].grade;
|
||||
|
||||
return;
|
||||
}));
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
return originalValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
hasDataChanged(
|
||||
originalValues: AddonModWorkshopGetAssessmentFormFieldsParsedData[],
|
||||
currentValues: AddonModWorkshopGetAssessmentFormFieldsParsedData[],
|
||||
): boolean {
|
||||
for (const x in originalValues) {
|
||||
if (originalValues[x].grade != currentValues[x].grade) {
|
||||
return true;
|
||||
}
|
||||
if (originalValues[x].peercomment != currentValues[x].peercomment) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async prepareAssessmentData(
|
||||
currentValues: AddonModWorkshopGetAssessmentFormFieldsParsedData[],
|
||||
form: AddonModWorkshopGetAssessmentFormDefinitionData,
|
||||
): Promise<CoreFormFields> {
|
||||
const data: CoreFormFields = {};
|
||||
const errors: AddonModWorkshopAssessmentStrategyFieldErrors = {};
|
||||
let hasErrors = false;
|
||||
|
||||
form.fields.forEach((field, idx) => {
|
||||
if (idx < form.dimenssionscount) {
|
||||
const grade = parseInt(String(currentValues[idx].grade), 10);
|
||||
if (!isNaN(grade) && grade >= 0) {
|
||||
data['grade__idx_' + idx] = grade;
|
||||
} else {
|
||||
errors['grade_' + idx] = Translate.instant('addon.mod_workshop_assessment_accumulative.mustchoosegrade');
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
if (currentValues[idx].peercomment) {
|
||||
data['peercomment__idx_' + idx] = currentValues[idx].peercomment;
|
||||
}
|
||||
|
||||
data['gradeid__idx_' + idx] = parseInt(form.current[idx].gradeid, 10) || 0;
|
||||
data['dimensionid__idx_' + idx] = parseInt(field.dimensionid, 10);
|
||||
data['weight__idx_' + idx] = parseInt(field.weight, 10) || 0;
|
||||
}
|
||||
});
|
||||
|
||||
if (hasErrors) {
|
||||
throw errors;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
export const AddonModWorkshopAssessmentStrategyAccumulativeHandler =
|
||||
makeSingleton(AddonModWorkshopAssessmentStrategyAccumulativeHandlerService);
|
|
@ -0,0 +1,29 @@
|
|||
// (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 { NgModule } from '@angular/core';
|
||||
import { AddonModWorkshopAssessmentStrategyAccumulativeModule } from './accumulative/accumulative.module';
|
||||
import { AddonModWorkshopAssessmentStrategyCommentsModule } from './comments/comments.module';
|
||||
import { AddonModWorkshopAssessmentStrategyNumErrorsModule } from './numerrors/numerrors.module';
|
||||
import { AddonModWorkshopAssessmentStrategyRubricModule } from './rubric/rubric.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
AddonModWorkshopAssessmentStrategyAccumulativeModule,
|
||||
AddonModWorkshopAssessmentStrategyCommentsModule,
|
||||
AddonModWorkshopAssessmentStrategyNumErrorsModule,
|
||||
AddonModWorkshopAssessmentStrategyRubricModule,
|
||||
],
|
||||
})
|
||||
export class AddonModWorkshopAssessmentStrategyModule {}
|
|
@ -0,0 +1,44 @@
|
|||
// (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 { CoreSharedModule } from '@/core/shared.module';
|
||||
import { APP_INITIALIZER, NgModule } from '@angular/core';
|
||||
import { AddonWorkshopAssessmentStrategyDelegate } from '../../services/assessment-strategy-delegate';
|
||||
import { AddonModWorkshopAssessmentStrategyCommentsComponent } from './component/comments';
|
||||
import { AddonModWorkshopAssessmentStrategyCommentsHandler } from './services/handler';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonModWorkshopAssessmentStrategyCommentsComponent,
|
||||
],
|
||||
imports: [
|
||||
CoreSharedModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
multi: true,
|
||||
deps: [],
|
||||
useFactory: () => () => {
|
||||
AddonWorkshopAssessmentStrategyDelegate.registerHandler(
|
||||
AddonModWorkshopAssessmentStrategyCommentsHandler.instance,
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
exports: [
|
||||
AddonModWorkshopAssessmentStrategyCommentsComponent,
|
||||
],
|
||||
})
|
||||
export class AddonModWorkshopAssessmentStrategyCommentsModule {}
|
|
@ -0,0 +1,30 @@
|
|||
<ng-container *ngFor="let field of assessment.form?.fields; let n = index">
|
||||
<ion-card *ngIf="n < assessment.form?.dimenssionscount">
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<h2>{{ field.dimtitle }}</h2>
|
||||
<core-format-text [text]="field.description" contextLevel="module" [contextInstanceId]="moduleId"
|
||||
[courseId]="courseId">
|
||||
</core-format-text>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="edit">
|
||||
<ion-label position="stacked">
|
||||
<span [core-mark-required]="true">
|
||||
{{ 'addon.mod_workshop_assessment_comments.dimensioncommentfor' | translate : {'$a': field.dimtitle } }}
|
||||
</span>
|
||||
</ion-label>
|
||||
<ion-textarea aria-multiline="true" [(ngModel)]="selectedValues[n].peercomment" core-auto-rows></ion-textarea>
|
||||
<core-input-errors *ngIf="fieldErrors['peercomment_' + n]" [errorText]="fieldErrors['peercomment_' + n]">
|
||||
</core-input-errors>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="!edit" class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<h2>{{ 'addon.mod_workshop_assessment_comments.dimensioncommentfor' | translate : {'$a': field.dimtitle } }}</h2>
|
||||
<p><core-format-text [text]="selectedValues[n].peercomment" contextLevel="module" [contextInstanceId]="moduleId"
|
||||
[courseId]="courseId">
|
||||
</core-format-text></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-card>
|
||||
</ng-container>
|
|
@ -0,0 +1,25 @@
|
|||
// (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 } from '@angular/core';
|
||||
import { AddonModWorkshopAssessmentStrategyBaseComponent } from '../../../classes/assessment-strategy-component';
|
||||
|
||||
/**
|
||||
* Component for comments assessment strategy.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-mod-workshop-assessment-strategy-comments',
|
||||
templateUrl: 'addon-mod-workshop-assessment-strategy-comments.html',
|
||||
})
|
||||
export class AddonModWorkshopAssessmentStrategyCommentsComponent extends AddonModWorkshopAssessmentStrategyBaseComponent { }
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"dimensioncommentfor": "Comment for {{$a}}",
|
||||
"dimensionnumber": "Aspect {{$a}}"
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
// (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 {
|
||||
AddonModWorkshopAssessmentStrategyFieldErrors,
|
||||
} from '@addons/mod/workshop/components/assessment-strategy/assessment-strategy';
|
||||
import { AddonWorkshopAssessmentStrategyHandler } from '@addons/mod/workshop/services/assessment-strategy-delegate';
|
||||
import {
|
||||
AddonModWorkshopGetAssessmentFormDefinitionData,
|
||||
AddonModWorkshopGetAssessmentFormFieldsParsedData,
|
||||
} from '@addons/mod/workshop/services/workshop';
|
||||
import { Injectable, Type } from '@angular/core';
|
||||
import { makeSingleton, Translate } from '@singletons';
|
||||
import { CoreFormFields } from '@singletons/form';
|
||||
import { AddonModWorkshopAssessmentStrategyCommentsComponent } from '../component/comments';
|
||||
|
||||
/**
|
||||
* Handler for comments assessment strategy plugin.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonModWorkshopAssessmentStrategyCommentsHandlerService implements AddonWorkshopAssessmentStrategyHandler {
|
||||
|
||||
name = 'AddonModWorkshopAssessmentStrategyComments';
|
||||
strategyName = 'comments';
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async isEnabled(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
getComponent(): Type<unknown> {
|
||||
return AddonModWorkshopAssessmentStrategyCommentsComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async getOriginalValues(
|
||||
form: AddonModWorkshopGetAssessmentFormDefinitionData,
|
||||
): Promise<AddonModWorkshopGetAssessmentFormFieldsParsedData[]> {
|
||||
const originalValues: AddonModWorkshopGetAssessmentFormFieldsParsedData[] = [];
|
||||
|
||||
form.fields.forEach((field, n) => {
|
||||
field.dimtitle = Translate.instant('addon.mod_workshop_assessment_comments.dimensionnumber', { $a: field.number });
|
||||
|
||||
if (!form.current[n]) {
|
||||
form.current[n] = {};
|
||||
}
|
||||
|
||||
originalValues[n] = {};
|
||||
originalValues[n].peercomment = form.current[n].peercomment || '';
|
||||
originalValues[n].number = field.number; // eslint-disable-line id-blacklist
|
||||
});
|
||||
|
||||
return originalValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
hasDataChanged(
|
||||
originalValues: AddonModWorkshopGetAssessmentFormFieldsParsedData[],
|
||||
currentValues: AddonModWorkshopGetAssessmentFormFieldsParsedData[],
|
||||
): boolean {
|
||||
for (const x in originalValues) {
|
||||
if (originalValues[x].peercomment != currentValues[x].peercomment) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async prepareAssessmentData(
|
||||
currentValues: AddonModWorkshopGetAssessmentFormFieldsParsedData[],
|
||||
form: AddonModWorkshopGetAssessmentFormDefinitionData,
|
||||
): Promise<CoreFormFields> {
|
||||
const data: CoreFormFields = {};
|
||||
const errors: AddonModWorkshopAssessmentStrategyFieldErrors = {};
|
||||
let hasErrors = false;
|
||||
|
||||
form.fields.forEach((field, idx) => {
|
||||
if (idx < form.dimenssionscount) {
|
||||
if (currentValues[idx].peercomment) {
|
||||
data['peercomment__idx_' + idx] = currentValues[idx].peercomment;
|
||||
} else {
|
||||
errors['peercomment_' + idx] = Translate.instant('core.err_required');
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
data['gradeid__idx_' + idx] = parseInt(form.current[idx].gradeid, 10) || 0;
|
||||
data['dimensionid__idx_' + idx] = parseInt(field.dimensionid, 10);
|
||||
}
|
||||
});
|
||||
|
||||
if (hasErrors) {
|
||||
throw errors;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
export const AddonModWorkshopAssessmentStrategyCommentsHandler =
|
||||
makeSingleton(AddonModWorkshopAssessmentStrategyCommentsHandlerService);
|
|
@ -0,0 +1,53 @@
|
|||
<ng-container *ngFor="let field of assessment.form?.fields; let n = index">
|
||||
<ion-card *ngIf="n < assessment.form?.dimenssionscount">
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<h2>{{ field.dimtitle }}</h2>
|
||||
<core-format-text [text]="field.description" contextLevel="module" [contextInstanceId]="moduleId"
|
||||
[courseId]="courseId">
|
||||
</core-format-text>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-list>
|
||||
<ion-radio-group [(ngModel)]="selectedValues[n].grade" [name]="'grade_' + n">
|
||||
<ion-item>
|
||||
<ion-label position="stacked">
|
||||
<span [core-mark-required]="edit">
|
||||
{{ 'addon.mod_workshop.yourassessmentfor' | translate : {'$a': field.dimtitle } }}
|
||||
</span>
|
||||
</ion-label>
|
||||
<core-input-errors *ngIf="edit && fieldErrors['grade_' + n]" [errorText]="fieldErrors['grade_' + n]">
|
||||
</core-input-errors>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
<core-format-text [text]="field.grade0" [filter]="false"></core-format-text>
|
||||
</ion-label>
|
||||
<ion-radio slot="start" [value]="-1" [disabled]="!edit"></ion-radio>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label><core-format-text [text]="field.grade1" [filter]="false"></core-format-text></ion-label>
|
||||
<ion-radio slot="start" [value]="1" [disabled]="!edit"></ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
</ion-list>
|
||||
<ion-item *ngIf="edit">
|
||||
<ion-label position="stacked">
|
||||
{{ 'addon.mod_workshop_assessment_numerrors.dimensioncommentfor' | translate : {'$a': field.dimtitle } }}
|
||||
</ion-label>
|
||||
<ion-textarea aria-multiline="true" [(ngModel)]="selectedValues[n].peercomment" [name]="'peercomment_' + n"
|
||||
core-auto-rows>
|
||||
</ion-textarea>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="!edit" class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<h2>{{ 'addon.mod_workshop_assessment_numerrors.dimensioncommentfor' | translate : {'$a': field.dimtitle } }}</h2>
|
||||
<p>
|
||||
<core-format-text [text]="selectedValues[n].peercomment" contextLevel="module" [contextInstanceId]="moduleId"
|
||||
[courseId]="courseId">
|
||||
</core-format-text>
|
||||
</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-card>
|
||||
</ng-container>
|
|
@ -0,0 +1,25 @@
|
|||
// (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 } from '@angular/core';
|
||||
import { AddonModWorkshopAssessmentStrategyBaseComponent } from '../../../classes/assessment-strategy-component';
|
||||
|
||||
/**
|
||||
* Component for numerrors assessment strategy.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-mod-workshop-assessment-strategy-numerrors',
|
||||
templateUrl: 'addon-mod-workshop-assessment-strategy-numerrors.html',
|
||||
})
|
||||
export class AddonModWorkshopAssessmentStrategyNumErrorsComponent extends AddonModWorkshopAssessmentStrategyBaseComponent { }
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"dimensioncommentfor": "Comment for {{$a}}",
|
||||
"dimensiongradefor": "Grade for {{$a}}",
|
||||
"dimensionnumber": "Assertion {{$a}}"
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
// (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 { CoreSharedModule } from '@/core/shared.module';
|
||||
import { APP_INITIALIZER, NgModule } from '@angular/core';
|
||||
import { AddonWorkshopAssessmentStrategyDelegate } from '../../services/assessment-strategy-delegate';
|
||||
import { AddonModWorkshopAssessmentStrategyNumErrorsComponent } from './component/numerrors';
|
||||
import { AddonModWorkshopAssessmentStrategyNumErrorsHandler } from './services/handler';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonModWorkshopAssessmentStrategyNumErrorsComponent,
|
||||
],
|
||||
imports: [
|
||||
CoreSharedModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
multi: true,
|
||||
deps: [],
|
||||
useFactory: () => () => {
|
||||
AddonWorkshopAssessmentStrategyDelegate.registerHandler(
|
||||
AddonModWorkshopAssessmentStrategyNumErrorsHandler.instance,
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
exports: [
|
||||
AddonModWorkshopAssessmentStrategyNumErrorsComponent,
|
||||
],
|
||||
})
|
||||
export class AddonModWorkshopAssessmentStrategyNumErrorsModule {}
|
|
@ -0,0 +1,134 @@
|
|||
// (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 {
|
||||
AddonModWorkshopAssessmentStrategyFieldErrors,
|
||||
} from '@addons/mod/workshop/components/assessment-strategy/assessment-strategy';
|
||||
import { AddonWorkshopAssessmentStrategyHandler } from '@addons/mod/workshop/services/assessment-strategy-delegate';
|
||||
import {
|
||||
AddonModWorkshopGetAssessmentFormDefinitionData,
|
||||
AddonModWorkshopGetAssessmentFormFieldsParsedData,
|
||||
} from '@addons/mod/workshop/services/workshop';
|
||||
import { Injectable, Type } from '@angular/core';
|
||||
import { Translate, makeSingleton } from '@singletons';
|
||||
import { CoreFormFields } from '@singletons/form';
|
||||
import { AddonModWorkshopAssessmentStrategyNumErrorsComponent } from '../component/numerrors';
|
||||
|
||||
/**
|
||||
* Handler for numerrors assessment strategy plugin.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonModWorkshopAssessmentStrategyNumErrorsHandlerService implements AddonWorkshopAssessmentStrategyHandler {
|
||||
|
||||
name = 'AddonModWorkshopAssessmentStrategyNumErrors';
|
||||
strategyName = 'numerrors';
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async isEnabled(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
getComponent(): Type<unknown> {
|
||||
return AddonModWorkshopAssessmentStrategyNumErrorsComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async getOriginalValues(
|
||||
form: AddonModWorkshopGetAssessmentFormDefinitionData,
|
||||
): Promise<AddonModWorkshopGetAssessmentFormFieldsParsedData[]> {
|
||||
const originalValues: AddonModWorkshopGetAssessmentFormFieldsParsedData[] = [];
|
||||
|
||||
form.fields.forEach((field, n) => {
|
||||
field.dimtitle = Translate.instant('addon.mod_workshop_assessment_numerrors.dimensionnumber', { $a: field.number });
|
||||
|
||||
if (!form.current[n]) {
|
||||
form.current[n] = {};
|
||||
}
|
||||
|
||||
originalValues[n] = {};
|
||||
originalValues[n].peercomment = form.current[n].peercomment || '';
|
||||
originalValues[n].number = field.number; // eslint-disable-line id-blacklist
|
||||
originalValues[n].grade = form.current[n].grade || '';
|
||||
});
|
||||
|
||||
return originalValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
hasDataChanged(
|
||||
originalValues: AddonModWorkshopGetAssessmentFormFieldsParsedData[],
|
||||
currentValues: AddonModWorkshopGetAssessmentFormFieldsParsedData[],
|
||||
): boolean {
|
||||
for (const x in originalValues) {
|
||||
if (originalValues[x].grade != currentValues[x].grade) {
|
||||
return true;
|
||||
}
|
||||
if (originalValues[x].peercomment != currentValues[x].peercomment) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async prepareAssessmentData(
|
||||
currentValues: AddonModWorkshopGetAssessmentFormFieldsParsedData[],
|
||||
form: AddonModWorkshopGetAssessmentFormDefinitionData,
|
||||
): Promise<CoreFormFields> {
|
||||
const data: CoreFormFields = {};
|
||||
const errors: AddonModWorkshopAssessmentStrategyFieldErrors = {};
|
||||
let hasErrors = false;
|
||||
|
||||
form.fields.forEach((field, idx) => {
|
||||
if (idx < form.dimenssionscount) {
|
||||
const grade = parseInt(String(currentValues[idx].grade), 10);
|
||||
if (!isNaN(grade) && (grade == 1 || grade == -1)) {
|
||||
data['grade__idx_' + idx] = grade;
|
||||
} else {
|
||||
errors['grade_' + idx] = Translate.instant('core.required');
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
if (currentValues[idx].peercomment) {
|
||||
data['peercomment__idx_' + idx] = currentValues[idx].peercomment;
|
||||
}
|
||||
|
||||
data['gradeid__idx_' + idx] = parseInt(form.current[idx].gradeid, 10) || 0;
|
||||
data['dimensionid__idx_' + idx] = parseInt(field.dimensionid, 10);
|
||||
data['weight__idx_' + idx] = parseInt(field.weight, 10) || 0;
|
||||
}
|
||||
});
|
||||
|
||||
if (hasErrors) {
|
||||
throw errors;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
export const AddonModWorkshopAssessmentStrategyNumErrorsHandler =
|
||||
makeSingleton(AddonModWorkshopAssessmentStrategyNumErrorsHandlerService);
|
|
@ -0,0 +1,26 @@
|
|||
<ng-container *ngFor="let field of assessment.form?.fields; let n = index">
|
||||
<ion-card *ngIf="n < assessment.form?.dimenssionscount">
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<h2 [core-mark-required]="edit">{{ field.dimtitle }}</h2>
|
||||
<core-format-text [text]="field.description" contextLevel="module" [contextInstanceId]="moduleId"
|
||||
[courseId]="courseId">
|
||||
</core-format-text>
|
||||
</ion-label>
|
||||
<core-input-errors *ngIf="edit && fieldErrors['chosenlevelid_' + n]" [errorText]="fieldErrors['chosenlevelid_' + n]">
|
||||
</core-input-errors>
|
||||
</ion-item>
|
||||
<ion-list>
|
||||
<ion-radio-group [(ngModel)]="selectedValues[n].chosenlevelid" [name]="'chosenlevelid_' + n">
|
||||
<ion-item *ngFor="let subfield of field.fields">
|
||||
<ion-label>
|
||||
<p><core-format-text [text]="subfield.definition" contextLevel="module"
|
||||
[contextInstanceId]="moduleId" [courseId]="courseId">
|
||||
</core-format-text></p>
|
||||
</ion-label>
|
||||
<ion-radio slot="start" [value]="subfield.levelid" [disabled]="!edit"></ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
</ion-list>
|
||||
</ion-card>
|
||||
</ng-container>
|
|
@ -0,0 +1,25 @@
|
|||
// (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 } from '@angular/core';
|
||||
import { AddonModWorkshopAssessmentStrategyBaseComponent } from '../../../classes/assessment-strategy-component';
|
||||
|
||||
/**
|
||||
* Component for rubric assessment strategy.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-mod-workshop-assessment-strategy-rubric',
|
||||
templateUrl: 'addon-mod-workshop-assessment-strategy-rubric.html',
|
||||
})
|
||||
export class AddonModWorkshopAssessmentStrategyRubricComponent extends AddonModWorkshopAssessmentStrategyBaseComponent { }
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"dimensionnumber": "Criterion {{$a}}",
|
||||
"mustchooseone": "You have to select one of these items"
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
// (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 { CoreSharedModule } from '@/core/shared.module';
|
||||
import { APP_INITIALIZER, NgModule } from '@angular/core';
|
||||
import { AddonWorkshopAssessmentStrategyDelegate } from '../../services/assessment-strategy-delegate';
|
||||
import { AddonModWorkshopAssessmentStrategyRubricComponent } from './component/rubric';
|
||||
import { AddonModWorkshopAssessmentStrategyRubricHandler } from './services/handler';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonModWorkshopAssessmentStrategyRubricComponent,
|
||||
],
|
||||
imports: [
|
||||
CoreSharedModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
multi: true,
|
||||
deps: [],
|
||||
useFactory: () => () => {
|
||||
AddonWorkshopAssessmentStrategyDelegate.registerHandler(
|
||||
AddonModWorkshopAssessmentStrategyRubricHandler.instance,
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
exports: [
|
||||
AddonModWorkshopAssessmentStrategyRubricComponent,
|
||||
],
|
||||
})
|
||||
export class AddonModWorkshopAssessmentStrategyRubricModule {}
|
|
@ -0,0 +1,125 @@
|
|||
// (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 {
|
||||
AddonModWorkshopAssessmentStrategyFieldErrors,
|
||||
} from '@addons/mod/workshop/components/assessment-strategy/assessment-strategy';
|
||||
import { AddonWorkshopAssessmentStrategyHandler } from '@addons/mod/workshop/services/assessment-strategy-delegate';
|
||||
import {
|
||||
AddonModWorkshopGetAssessmentFormDefinitionData,
|
||||
AddonModWorkshopGetAssessmentFormFieldsParsedData,
|
||||
} from '@addons/mod/workshop/services/workshop';
|
||||
import { Injectable, Type } from '@angular/core';
|
||||
import { Translate, makeSingleton } from '@singletons';
|
||||
import { CoreFormFields } from '@singletons/form';
|
||||
import { AddonModWorkshopAssessmentStrategyRubricComponent } from '../component/rubric';
|
||||
|
||||
/**
|
||||
* Handler for rubric assessment strategy plugin.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonModWorkshopAssessmentStrategyRubricHandlerService implements AddonWorkshopAssessmentStrategyHandler {
|
||||
|
||||
name = 'AddonModWorkshopAssessmentStrategyRubric';
|
||||
strategyName = 'rubric';
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async isEnabled(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
getComponent(): Type<unknown> {
|
||||
return AddonModWorkshopAssessmentStrategyRubricComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async getOriginalValues(
|
||||
form: AddonModWorkshopGetAssessmentFormDefinitionData,
|
||||
): Promise<AddonModWorkshopGetAssessmentFormFieldsParsedData[]> {
|
||||
const originalValues: AddonModWorkshopGetAssessmentFormFieldsParsedData[] = [];
|
||||
|
||||
form.fields.forEach((field, n) => {
|
||||
field.dimtitle = Translate.instant('addon.mod_workshop_assessment_rubric.dimensionnumber', { $a: field.number });
|
||||
|
||||
if (!form.current[n]) {
|
||||
form.current[n] = {};
|
||||
}
|
||||
|
||||
originalValues[n] = {};
|
||||
originalValues[n].chosenlevelid = form.current[n].chosenlevelid || '';
|
||||
originalValues[n].number = field.number; // eslint-disable-line id-blacklist
|
||||
});
|
||||
|
||||
return originalValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
hasDataChanged(
|
||||
originalValues: AddonModWorkshopGetAssessmentFormFieldsParsedData[],
|
||||
currentValues: AddonModWorkshopGetAssessmentFormFieldsParsedData[],
|
||||
): boolean {
|
||||
for (const x in originalValues) {
|
||||
if (originalValues[x].chosenlevelid != (currentValues[x].chosenlevelid || '')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async prepareAssessmentData(
|
||||
currentValues: AddonModWorkshopGetAssessmentFormFieldsParsedData[],
|
||||
form: AddonModWorkshopGetAssessmentFormDefinitionData,
|
||||
): Promise<CoreFormFields> {
|
||||
const data: CoreFormFields = {};
|
||||
const errors: AddonModWorkshopAssessmentStrategyFieldErrors = {};
|
||||
let hasErrors = false;
|
||||
|
||||
form.fields.forEach((field, idx) => {
|
||||
if (idx < form.dimenssionscount) {
|
||||
const id = parseInt(currentValues[idx].chosenlevelid, 10);
|
||||
if (!isNaN(id) && id >= 0) {
|
||||
data['chosenlevelid__idx_' + idx] = id;
|
||||
} else {
|
||||
errors['chosenlevelid_' + idx] = Translate.instant('addon.mod_workshop_assessment_rubric.mustchooseone');
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
data['gradeid__idx_' + idx] = parseInt(form.current[idx].gradeid, 10) || 0;
|
||||
data['dimensionid__idx_' + idx] = parseInt(field.dimensionid, 10);
|
||||
}
|
||||
});
|
||||
|
||||
if (hasErrors) {
|
||||
throw errors;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
export const AddonModWorkshopAssessmentStrategyRubricHandler =
|
||||
makeSingleton(AddonModWorkshopAssessmentStrategyRubricHandlerService);
|
|
@ -0,0 +1,36 @@
|
|||
// (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 } from '@angular/core';
|
||||
import { AddonModWorkshopGetAssessmentFormFieldsParsedData } from '../services/workshop';
|
||||
import { AddonModWorkshopSubmissionAssessmentWithFormData } from '../services/workshop-helper';
|
||||
|
||||
/**
|
||||
* Base class for component to render an assessment strategy.
|
||||
*/
|
||||
@Component({
|
||||
template: '',
|
||||
})
|
||||
export class AddonModWorkshopAssessmentStrategyBaseComponent {
|
||||
|
||||
@Input() workshopId!: number;
|
||||
@Input() assessment!: AddonModWorkshopSubmissionAssessmentWithFormData;
|
||||
@Input() edit!: boolean;
|
||||
@Input() selectedValues!: AddonModWorkshopGetAssessmentFormFieldsParsedData[];
|
||||
@Input() fieldErrors!: Record<string, string>;
|
||||
@Input() strategy!: string;
|
||||
@Input() moduleId!: number;
|
||||
@Input() courseId?: number;
|
||||
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
<h3 class="ion-padding">{{ 'addon.mod_workshop.assessmentform' | translate }}</h3>
|
||||
|
||||
<form name="mma-mod_workshop-assessment-form" #assessmentForm>
|
||||
<core-loading [hideUntil]="assessmentStrategyLoaded">
|
||||
<ng-container *ngIf="componentClass && assessmentStrategyLoaded">
|
||||
<core-dynamic-component [component]="componentClass" [data]="data"></core-dynamic-component>
|
||||
</ng-container>
|
||||
|
||||
<ion-card class="core-info-card" *ngIf="notSupported">
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
<p>{{ 'addon.mod_workshop.assessmentstrategynotsupported' | translate:{$a: strategy} }}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-card>
|
||||
|
||||
<ion-card *ngIf="assessmentStrategyLoaded && overallFeedkback &&
|
||||
(edit || data.assessment?.feedbackauthor || data.assessment?.feedbackattachmentfiles?.length) ">
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label><h2>{{ 'addon.mod_workshop.overallfeedback' | translate }}</h2></ion-label>
|
||||
</ion-item>
|
||||
<ion-item position="stacked" *ngIf="edit">
|
||||
<ion-label position="stacked">
|
||||
<span [core-mark-required]="overallFeedkbackRequired">
|
||||
{{ 'addon.mod_workshop.feedbackauthor' | translate }}
|
||||
</span>
|
||||
</ion-label>
|
||||
<core-rich-text-editor [control]="feedbackControl" [component]="component"
|
||||
[componentId]="workshop.coursemodule" [autoSave]="true" contextLevel="module"
|
||||
[contextInstanceId]="workshop.coursemodule" elementId="feedbackauthor_editor"
|
||||
[draftExtraParams]="{asid: assessmentId}" (contentChanged)="onFeedbackChange($event)">
|
||||
</core-rich-text-editor>
|
||||
<core-input-errors
|
||||
*ngIf="overallFeedkbackRequired && data.fieldErrors && data.fieldErrors['feedbackauthor']"
|
||||
[errorText]="data.fieldErrors['feedbackauthor']">
|
||||
</core-input-errors>
|
||||
</ion-item>
|
||||
<core-attachments *ngIf="edit && workshop.overallfeedbackfiles" [files]="data.assessment?.feedbackattachmentfiles"
|
||||
[maxSize]="workshop.overallfeedbackmaxbytes" [maxSubmissions]="workshop.overallfeedbackfiles"
|
||||
[component]="component" [componentId]="componentId" [allowOffline]="true">
|
||||
</core-attachments>
|
||||
<ion-item *ngIf="edit && access && access.canallocate">
|
||||
<ion-label position="stacked">
|
||||
<span [core-mark-required]="true">
|
||||
{{ 'addon.mod_workshop.assessmentweight' | translate }}
|
||||
</span>
|
||||
</ion-label>
|
||||
<ion-select [(ngModel)]="weight" interface="action-sheet" name="weight">
|
||||
<ion-select-option *ngFor="let w of weights" [value]="w">{{w}}</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngIf="!edit && data.assessment?.feedbackauthor">
|
||||
<ion-label>
|
||||
<core-format-text [component]="component" [componentId]="componentId" [text]="data.assessment?.feedbackauthor"
|
||||
contextLevel="module" [contextInstanceId]="workshop.coursemodule" [courseId]="workshop.course">
|
||||
</core-format-text>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="!edit && workshop.overallfeedbackfiles && data.assessment?.feedbackattachmentfiles?.length"
|
||||
lines="none">
|
||||
<ion-label>
|
||||
<core-files [files]="data.assessment?.feedbackattachmentfiles" [component]="component"
|
||||
[componentId]="componentId"></core-files>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-card>
|
||||
</core-loading>
|
||||
</form>
|
|
@ -0,0 +1,426 @@
|
|||
// (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, ElementRef, Type, OnDestroy } from '@angular/core';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { CoreError } from '@classes/errors/error';
|
||||
import { CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
|
||||
import { CoreFile } from '@services/file';
|
||||
import { CoreFileEntry, CoreFileHelper } from '@services/file-helper';
|
||||
import { CoreFileSession } from '@services/file-session';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreSync } from '@services/sync';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { Translate } from '@singletons';
|
||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||
import { CoreFormFields, CoreForms } from '@singletons/form';
|
||||
import { AddonWorkshopAssessmentStrategyDelegate } from '../../services/assessment-strategy-delegate';
|
||||
import {
|
||||
AddonModWorkshopProvider,
|
||||
AddonModWorkshopOverallFeedbackMode,
|
||||
AddonModWorkshop,
|
||||
AddonModWorkshopData,
|
||||
AddonModWorkshopGetWorkshopAccessInformationWSResponse,
|
||||
AddonModWorkshopGetAssessmentFormFieldsParsedData,
|
||||
} from '../../services/workshop';
|
||||
import { AddonModWorkshopHelper, AddonModWorkshopSubmissionAssessmentWithFormData } from '../../services/workshop-helper';
|
||||
import { AddonModWorkshopOffline } from '../../services/workshop-offline';
|
||||
|
||||
/**
|
||||
* Component that displays workshop assessment strategy form.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-mod-workshop-assessment-strategy',
|
||||
templateUrl: 'addon-mod-workshop-assessment-strategy.html',
|
||||
})
|
||||
export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDestroy {
|
||||
|
||||
@Input() workshop!: AddonModWorkshopData;
|
||||
@Input() access!: AddonModWorkshopGetWorkshopAccessInformationWSResponse;
|
||||
@Input() assessmentId!: number;
|
||||
@Input() userId!: number;
|
||||
@Input() strategy!: string;
|
||||
@Input() edit = false;
|
||||
|
||||
@ViewChild('assessmentForm') formElement!: ElementRef;
|
||||
|
||||
componentClass?: Type<unknown>;
|
||||
data: AddonModWorkshopAssessmentStrategyData = {
|
||||
workshopId: 0,
|
||||
assessment: undefined,
|
||||
edit: false,
|
||||
selectedValues: [],
|
||||
fieldErrors: {},
|
||||
strategy: '',
|
||||
moduleId: 0,
|
||||
courseId: undefined,
|
||||
};
|
||||
|
||||
assessmentStrategyLoaded = false;
|
||||
notSupported = false;
|
||||
feedbackText = '';
|
||||
feedbackControl = new FormControl();
|
||||
overallFeedkback = false;
|
||||
overallFeedkbackRequired = false;
|
||||
component = AddonModWorkshopProvider.COMPONENT;
|
||||
componentId?: number;
|
||||
weights: number[] = [];
|
||||
weight?: number;
|
||||
|
||||
protected obsInvalidated?: CoreEventObserver;
|
||||
protected hasOffline = false;
|
||||
protected originalData: {
|
||||
text: string;
|
||||
files: CoreFileEntry[];
|
||||
weight: number;
|
||||
selectedValues: AddonModWorkshopGetAssessmentFormFieldsParsedData[];
|
||||
} = {
|
||||
text: '',
|
||||
files: [],
|
||||
weight: 1,
|
||||
selectedValues: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
async ngOnInit(): Promise<void> {
|
||||
if (!this.assessmentId || !this.strategy) {
|
||||
this.assessmentStrategyLoaded = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.data.workshopId = this.workshop.id;
|
||||
this.data.edit = this.edit;
|
||||
this.data.strategy = this.strategy;
|
||||
this.data.moduleId = this.workshop.coursemodule;
|
||||
this.data.courseId = this.workshop.course;
|
||||
|
||||
this.componentClass = AddonWorkshopAssessmentStrategyDelegate.getComponentForPlugin(this.strategy);
|
||||
if (this.componentClass) {
|
||||
this.overallFeedkback = this.workshop.overallfeedbackmode != AddonModWorkshopOverallFeedbackMode.DISABLED;
|
||||
this.overallFeedkbackRequired =
|
||||
this.workshop.overallfeedbackmode == AddonModWorkshopOverallFeedbackMode.ENABLED_REQUIRED;
|
||||
this.componentId = this.workshop.coursemodule;
|
||||
|
||||
// Load Weights selector.
|
||||
if (this.edit && this.access.canallocate) {
|
||||
this.weights;
|
||||
for (let i = 16; i >= 0; i--) {
|
||||
this.weights[i] = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if rich text editor is enabled.
|
||||
if (this.edit) {
|
||||
// Block the workshop.
|
||||
CoreSync.blockOperation(AddonModWorkshopProvider.COMPONENT, this.workshop.id);
|
||||
}
|
||||
|
||||
try {
|
||||
await this.load();
|
||||
this.obsInvalidated = CoreEvents.on(
|
||||
AddonModWorkshopProvider.ASSESSMENT_INVALIDATED,
|
||||
this.load.bind(this),
|
||||
|
||||
CoreSites.getCurrentSiteId(),
|
||||
);
|
||||
} catch (error) {
|
||||
this.componentClass = undefined;
|
||||
CoreDomUtils.showErrorModalDefault(error, 'Error loading assessment.');
|
||||
} finally {
|
||||
this.assessmentStrategyLoaded = true;
|
||||
}
|
||||
} else {
|
||||
// Helper data and fallback.
|
||||
this.notSupported = !AddonWorkshopAssessmentStrategyDelegate.isPluginSupported(this.strategy);
|
||||
this.assessmentStrategyLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to load the assessment data.
|
||||
*
|
||||
* @return Promised resvoled when data is loaded.
|
||||
*/
|
||||
protected async load(): Promise<void> {
|
||||
this.data.assessment = await AddonModWorkshopHelper.getReviewerAssessmentById(this.workshop.id, this.assessmentId, {
|
||||
userId: this.userId,
|
||||
cmId: this.workshop.coursemodule,
|
||||
});
|
||||
|
||||
if (this.edit) {
|
||||
try {
|
||||
const offlineAssessment = await AddonModWorkshopOffline.getAssessment(this.workshop.id, this.assessmentId);
|
||||
const offlineData = offlineAssessment.inputdata;
|
||||
|
||||
this.hasOffline = true;
|
||||
|
||||
this.data.assessment.feedbackauthor = <string>offlineData.feedbackauthor;
|
||||
|
||||
if (this.access.canallocate) {
|
||||
this.data.assessment.weight = <number>offlineData.weight;
|
||||
}
|
||||
|
||||
// Override assessment plugins values.
|
||||
this.data.assessment.form!.current = AddonModWorkshop.parseFields(
|
||||
CoreUtils.objectToArrayOfObjects(offlineData, 'name', 'value'),
|
||||
);
|
||||
|
||||
// Override offline files.
|
||||
if (offlineData) {
|
||||
this.data.assessment.feedbackattachmentfiles =
|
||||
await AddonModWorkshopHelper.getAssessmentFilesFromOfflineFilesObject(
|
||||
<CoreFileUploaderStoreFilesResult>offlineData.feedbackauthorattachmentsid,
|
||||
this.workshop.id,
|
||||
this.assessmentId,
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
this.hasOffline = false;
|
||||
// Ignore errors.
|
||||
} finally {
|
||||
this.feedbackText = this.data.assessment.feedbackauthor;
|
||||
this.feedbackControl.setValue(this.feedbackText);
|
||||
|
||||
this.originalData.text = this.data.assessment.feedbackauthor;
|
||||
|
||||
if (this.access.canallocate) {
|
||||
this.originalData.weight = this.data.assessment.weight;
|
||||
}
|
||||
|
||||
this.originalData.files = [];
|
||||
this.data.assessment.feedbackattachmentfiles.forEach((file) => {
|
||||
let filename = CoreFile.getFileName(file);
|
||||
if (!filename) {
|
||||
// We don't have filename, extract it from the path.
|
||||
filename = CoreFileHelper.getFilenameFromPath(file) || '';
|
||||
}
|
||||
|
||||
this.originalData.files.push({
|
||||
filename,
|
||||
fileurl: '', // No needed to compare.
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
this.data.selectedValues = await AddonWorkshopAssessmentStrategyDelegate.getOriginalValues(
|
||||
this.strategy,
|
||||
this.data.assessment.form!,
|
||||
this.workshop.id,
|
||||
);
|
||||
} finally {
|
||||
this.originalData.selectedValues = CoreUtils.clone(this.data.selectedValues);
|
||||
if (this.edit) {
|
||||
CoreFileSession.setFiles(
|
||||
AddonModWorkshopProvider.COMPONENT,
|
||||
this.workshop.id + '_' + this.assessmentId,
|
||||
this.data.assessment.feedbackattachmentfiles,
|
||||
);
|
||||
if (this.access.canallocate) {
|
||||
this.weight = this.data.assessment.weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if data has changed.
|
||||
*
|
||||
* @return True if data has changed.
|
||||
*/
|
||||
hasDataChanged(): boolean {
|
||||
if (!this.assessmentStrategyLoaded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compare feedback text.
|
||||
const text = CoreTextUtils.restorePluginfileUrls(this.feedbackText, this.data.assessment?.feedbackcontentfiles || []);
|
||||
if (this.originalData.text != text) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.access.canallocate && this.originalData.weight != this.weight) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Compare feedback files.
|
||||
const files = CoreFileSession.getFiles(
|
||||
AddonModWorkshopProvider.COMPONENT,
|
||||
this.workshop.id + '_' + this.assessmentId,
|
||||
) || [];
|
||||
if (CoreFileUploader.areFileListDifferent(files, this.originalData.files)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return AddonWorkshopAssessmentStrategyDelegate.hasDataChanged(
|
||||
this.workshop.strategy!,
|
||||
this.originalData.selectedValues,
|
||||
this.data.selectedValues,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the assessment.
|
||||
*
|
||||
* @return Promise resolved when done, rejected if assessment could not be saved.
|
||||
*/
|
||||
async saveAssessment(): Promise<void> {
|
||||
const files = CoreFileSession.getFiles(
|
||||
AddonModWorkshopProvider.COMPONENT,
|
||||
this.workshop.id + '_' + this.assessmentId,
|
||||
) || [];
|
||||
|
||||
let saveOffline = false;
|
||||
let allowOffline = !files.length;
|
||||
|
||||
const modal = await CoreDomUtils.showModalLoading('core.sending', true);
|
||||
|
||||
this.data.fieldErrors = {};
|
||||
|
||||
try {
|
||||
let attachmentsId: CoreFileUploaderStoreFilesResult | number;
|
||||
try {
|
||||
// Upload attachments first if any.
|
||||
attachmentsId = await AddonModWorkshopHelper.uploadOrStoreAssessmentFiles(
|
||||
this.workshop.id,
|
||||
this.assessmentId,
|
||||
files,
|
||||
saveOffline,
|
||||
);
|
||||
} catch {
|
||||
// Cannot upload them in online, save them in offline.
|
||||
saveOffline = true;
|
||||
allowOffline = true;
|
||||
|
||||
attachmentsId = await AddonModWorkshopHelper.uploadOrStoreAssessmentFiles(
|
||||
this.workshop.id,
|
||||
this.assessmentId,
|
||||
files,
|
||||
saveOffline,
|
||||
);
|
||||
}
|
||||
|
||||
const text = CoreTextUtils.restorePluginfileUrls(this.feedbackText, this.data.assessment?.feedbackcontentfiles || []);
|
||||
|
||||
let assessmentData: CoreFormFields<unknown>;
|
||||
try {
|
||||
assessmentData = await AddonModWorkshopHelper.prepareAssessmentData(
|
||||
this.workshop,
|
||||
this.data.selectedValues,
|
||||
text,
|
||||
this.data.assessment!.form!,
|
||||
attachmentsId,
|
||||
);
|
||||
} catch (errors) {
|
||||
this.data.fieldErrors = errors;
|
||||
|
||||
throw new CoreError(Translate.instant('core.errorinvalidform'));
|
||||
}
|
||||
|
||||
let gradeUpdated = false;
|
||||
if (saveOffline) {
|
||||
// Save assessment in offline.
|
||||
await AddonModWorkshopOffline.saveAssessment(
|
||||
this.workshop.id,
|
||||
this.assessmentId,
|
||||
this.workshop.course,
|
||||
assessmentData,
|
||||
);
|
||||
|
||||
gradeUpdated = false;
|
||||
} else {
|
||||
|
||||
// Try to send it to server.
|
||||
// Don't allow offline if there are attachments since they were uploaded fine.
|
||||
gradeUpdated = await AddonModWorkshop.updateAssessment(
|
||||
this.workshop.id,
|
||||
this.assessmentId,
|
||||
this.workshop.course,
|
||||
assessmentData,
|
||||
undefined,
|
||||
allowOffline,
|
||||
);
|
||||
}
|
||||
|
||||
CoreForms.triggerFormSubmittedEvent(this.formElement, !!gradeUpdated, CoreSites.getCurrentSiteId());
|
||||
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
// If sent to the server, invalidate and clean.
|
||||
if (gradeUpdated) {
|
||||
promises.push(AddonModWorkshopHelper.deleteAssessmentStoredFiles(this.workshop.id, this.assessmentId));
|
||||
promises.push(AddonModWorkshop.invalidateAssessmentFormData(this.workshop.id, this.assessmentId));
|
||||
promises.push(AddonModWorkshop.invalidateAssessmentData(this.workshop.id, this.assessmentId));
|
||||
}
|
||||
|
||||
await CoreUtils.ignoreErrors(Promise.all(promises));
|
||||
|
||||
CoreEvents.trigger(AddonModWorkshopProvider.ASSESSMENT_SAVED, {
|
||||
workshopId: this.workshop.id,
|
||||
assessmentId: this.assessmentId,
|
||||
userId: CoreSites.getCurrentSiteUserId(),
|
||||
}, CoreSites.getCurrentSiteId());
|
||||
|
||||
if (files) {
|
||||
// Delete the local files from the tmp folder.
|
||||
CoreFileUploader.clearTmpFiles(files);
|
||||
}
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'Error saving assessment.');
|
||||
} finally {
|
||||
modal.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Feedback text changed.
|
||||
*
|
||||
* @param text The new text.
|
||||
*/
|
||||
onFeedbackChange(text: string): void {
|
||||
this.feedbackText = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.obsInvalidated?.off();
|
||||
|
||||
if (this.data.assessment?.feedbackattachmentfiles) {
|
||||
// Delete the local files from the tmp folder.
|
||||
CoreFileUploader.clearTmpFiles(this.data.assessment.feedbackattachmentfiles);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type AddonModWorkshopAssessmentStrategyData = {
|
||||
workshopId: number;
|
||||
assessment?: AddonModWorkshopSubmissionAssessmentWithFormData;
|
||||
edit: boolean;
|
||||
selectedValues: AddonModWorkshopGetAssessmentFormFieldsParsedData[];
|
||||
fieldErrors: AddonModWorkshopAssessmentStrategyFieldErrors;
|
||||
strategy: string;
|
||||
moduleId: number;
|
||||
courseId?: number;
|
||||
};
|
||||
|
||||
export type AddonModWorkshopAssessmentStrategyFieldErrors = Record<string, string>;
|
|
@ -0,0 +1,27 @@
|
|||
<core-loading [hideUntil]="loaded">
|
||||
<ion-item class="ion-text-wrap" [detail]="canViewAssessment && !canSelfAssess" (click)="gotoAssessment($event)">
|
||||
<core-user-avatar [user]="profile" slot="start" [courseId]="courseId" [userId]="profile?.id"></core-user-avatar>
|
||||
<ion-label>
|
||||
<h2 *ngIf="profile && profile.fullname">{{profile.fullname}}</h2>
|
||||
<p *ngIf="showGrade(assessment.grade)">
|
||||
{{ 'addon.mod_workshop.submissiongradeof' | translate:{$a: workshop.grade } }}: {{assessment.grade}}
|
||||
</p>
|
||||
<p *ngIf="access.canviewallsubmissions && !showGrade(assessment.gradinggradeover) && showGrade(assessment.gradinggrade)">
|
||||
{{ 'addon.mod_workshop.gradinggradeof' | translate:{$a: workshop.gradinggrade } }}: {{assessment.gradinggrade}}
|
||||
</p>
|
||||
<p *ngIf="access.canviewallsubmissions && showGrade(assessment.gradinggradeover)" class="core-overriden-grade">
|
||||
{{ 'addon.mod_workshop.gradinggradeof' | translate:{$a: workshop.gradinggrade } }}: {{assessment.gradinggradeover}}
|
||||
</p>
|
||||
<p *ngIf="assessment.weight && assessment.weight != 1">
|
||||
{{ 'addon.mod_workshop.weightinfo' | translate:{$a: assessment.weight } }}
|
||||
</p>
|
||||
<ion-badge *ngIf="!assessment.grade" color="danger">{{ 'addon.mod_workshop.notassessed' | translate }}</ion-badge>
|
||||
<ion-button expand="block" *ngIf="canSelfAssess && !showGrade(assessment.grade)" (click)="gotoOwnAssessment($event)">
|
||||
{{ 'addon.mod_workshop.assess' | translate }}
|
||||
</ion-button>
|
||||
</ion-label>
|
||||
<ion-note slot="end" *ngIf="offline">
|
||||
<ion-icon name="fas-clock"></ion-icon>{{ 'core.notsent' | translate }}
|
||||
</ion-note>
|
||||
</ion-item>
|
||||
</core-loading>
|
|
@ -0,0 +1,170 @@
|
|||
// (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 } from '@angular/core';
|
||||
import { Params } from '@angular/router';
|
||||
import { CoreCourseModule } from '@features/course/services/course-helper';
|
||||
import { CoreUser, CoreUserProfile } from '@features/user/services/user';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { AddonModWorkshopData, AddonModWorkshopGetWorkshopAccessInformationWSResponse } from '../../services/workshop';
|
||||
import {
|
||||
AddonModWorkshopHelper,
|
||||
AddonModWorkshopSubmissionAssessmentWithFormData,
|
||||
AddonModWorkshopSubmissionDataWithOfflineData,
|
||||
} from '../../services/workshop-helper';
|
||||
import { AddonModWorkshopOffline } from '../../services/workshop-offline';
|
||||
|
||||
/**
|
||||
* Component that displays workshop assessment.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-mod-workshop-assessment',
|
||||
templateUrl: 'addon-mod-workshop-assessment.html',
|
||||
})
|
||||
export class AddonModWorkshopAssessmentComponent implements OnInit {
|
||||
|
||||
@Input() assessment!: AddonModWorkshopSubmissionAssessmentWithFormData;
|
||||
@Input() courseId!: number;
|
||||
@Input() workshop!: AddonModWorkshopData;
|
||||
@Input() access!: AddonModWorkshopGetWorkshopAccessInformationWSResponse;
|
||||
@Input() protected submission!: AddonModWorkshopSubmissionDataWithOfflineData;
|
||||
@Input() protected module!: CoreCourseModule;
|
||||
|
||||
canViewAssessment = false;
|
||||
canSelfAssess = false;
|
||||
profile?: CoreUserProfile;
|
||||
showGrade: (grade?: string | number) => boolean;
|
||||
offline = false;
|
||||
loaded = false;
|
||||
|
||||
protected currentUserId: number;
|
||||
protected assessmentId?: number;
|
||||
|
||||
constructor() {
|
||||
this.currentUserId = CoreSites.getCurrentSiteUserId();
|
||||
this.showGrade = AddonModWorkshopHelper.showGrade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
const canAssess = this.access && this.access.assessingallowed;
|
||||
const userId = this.assessment.reviewerid;
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
this.assessmentId = this.assessment.id;
|
||||
this.canViewAssessment = !!this.assessment.grade;
|
||||
this.canSelfAssess = canAssess && userId == this.currentUserId;
|
||||
|
||||
if (userId) {
|
||||
promises.push(CoreUser.getProfile(userId, this.courseId, true).then((profile) => {
|
||||
this.profile = profile;
|
||||
|
||||
return;
|
||||
}));
|
||||
}
|
||||
|
||||
let assessOffline: Promise<void>;
|
||||
if (userId == this.currentUserId) {
|
||||
assessOffline = AddonModWorkshopOffline.getAssessment(this.workshop.id, this.assessmentId) .then((offlineAssess) => {
|
||||
this.offline = true;
|
||||
this.assessment.weight = <number>offlineAssess.inputdata.weight;
|
||||
|
||||
return;
|
||||
});
|
||||
} else {
|
||||
assessOffline = AddonModWorkshopOffline.getEvaluateAssessment(this.workshop.id, this.assessmentId)
|
||||
.then((offlineAssess) => {
|
||||
this.offline = true;
|
||||
this.assessment.gradinggradeover = offlineAssess.gradinggradeover;
|
||||
this.assessment.weight = <number>offlineAssess.weight;
|
||||
|
||||
return;
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
promises.push(assessOffline.catch(() => {
|
||||
this.offline = false;
|
||||
// Ignore errors.
|
||||
}));
|
||||
|
||||
Promise.all(promises).finally(() => {
|
||||
this.loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the assessment.
|
||||
*/
|
||||
async gotoAssessment(event: Event): Promise<void> {
|
||||
if (!this.canSelfAssess && this.canViewAssessment) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const params: Params = {
|
||||
assessment: this.assessment,
|
||||
submission: this.submission,
|
||||
profile: this.profile,
|
||||
};
|
||||
|
||||
if (!this.submission) {
|
||||
const modal = await CoreDomUtils.showModalLoading();
|
||||
|
||||
try {
|
||||
params.submission = await AddonModWorkshopHelper.getSubmissionById(
|
||||
this.workshop.id,
|
||||
this.assessment.submissionid,
|
||||
{ cmId: this.workshop.coursemodule },
|
||||
);
|
||||
|
||||
CoreNavigator.navigate(String(this.assessmentId), { params });
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'Cannot load submission');
|
||||
} finally {
|
||||
modal.dismiss();
|
||||
}
|
||||
} else {
|
||||
CoreNavigator.navigate(String(this.assessmentId), { params });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to my own assessment.
|
||||
*/
|
||||
gotoOwnAssessment(event: Event): void {
|
||||
if (!this.canSelfAssess) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const params: Params = {
|
||||
module: this.module,
|
||||
workshop: this.workshop,
|
||||
access: this.access,
|
||||
profile: this.profile,
|
||||
submission: this.submission,
|
||||
assessment: this.assessment,
|
||||
};
|
||||
|
||||
CoreNavigator.navigate(String(this.submission.id), params);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>
|
||||
<core-format-text [text]="title" contextLevel="module" [contextInstanceId]="workshop && workshop.coursemodule"
|
||||
[courseId]="courseId">
|
||||
</core-format-text>
|
||||
</ion-title>
|
||||
<ion-buttons slot="end" [hidden]="!evaluating">
|
||||
<ion-button fill="clear" (click)="saveEvaluation()" [attr.aria-label]="'core.save' | translate">
|
||||
{{ 'core.save' | translate }}
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshAssessment($event.target)" *ngIf="!evaluating">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="loaded">
|
||||
|
||||
<ion-item class="ion-text-wrap">
|
||||
<core-user-avatar *ngIf="profile" [user]="profile" slot="start" [courseId]="courseId" [userId]="profile.id">
|
||||
</core-user-avatar>
|
||||
|
||||
<ion-label>
|
||||
<h2 *ngIf="profile && profile.fullname">{{profile.fullname}}</h2>
|
||||
|
||||
<p *ngIf="workshop && assessment && showGrade(assessment.grade)">
|
||||
{{ 'addon.mod_workshop.submissiongradeof' | translate:{$a: workshop.grade } }}: {{assessment.grade}}
|
||||
</p>
|
||||
<p *ngIf="workshop && access && access.canviewallsubmissions && assessment && showGrade(assessment.gradinggrade)"
|
||||
[class.core-has-overriden-grade]=" showGrade(assessment.gradinggrade)">
|
||||
{{ 'addon.mod_workshop.gradinggradeof' | translate:{$a: workshop.gradinggrade } }}: {{assessment.gradinggrade}}
|
||||
</p>
|
||||
<p *ngIf="access && access.canviewallsubmissions && assessment && showGrade(assessment.gradinggradeover)"
|
||||
class="core-overriden-grade">
|
||||
{{ 'addon.mod_workshop.gradinggradeover' | translate }}: {{assessment.gradinggradeover}}
|
||||
</p>
|
||||
<p *ngIf="assessment && assessment.weight && assessment.weight != 1">
|
||||
{{ 'addon.mod_workshop.weightinfo' | translate:{$a: assessment.weight } }}
|
||||
</p>
|
||||
<ion-badge *ngIf="!assessment || !showGrade(assessment.grade)" color="danger">
|
||||
{{ 'addon.mod_workshop.notassessed' | translate }}
|
||||
</ion-badge>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<addon-mod-workshop-assessment-strategy
|
||||
*ngIf="assessment && assessmentId && showGrade(assessment.grade) && workshop && access" [workshop]="workshop"
|
||||
[access]="access" [assessmentId]="assessmentId" [userId]="profile && profile.id" [strategy]="strategy">
|
||||
</addon-mod-workshop-assessment-strategy>
|
||||
|
||||
<form ion-list [formGroup]="evaluateForm" *ngIf="evaluating" #evaluateFormEl>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label><h2>{{ 'addon.mod_workshop.assessmentsettings' | translate }}</h2></ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngIf="access?.canallocate">
|
||||
<ion-label position="stacked">
|
||||
<span core-mark-required="true">
|
||||
{{ 'addon.mod_workshop.assessmentweight' | translate }}
|
||||
</span>
|
||||
</ion-label>
|
||||
<ion-select formControlName="weight" required="true" interface="action-sheet">
|
||||
<ion-select-option *ngFor="let w of weights" [value]="w">{{ w }}</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<h2>{{ 'addon.mod_workshop.gradinggradecalculated' | translate }}</h2>
|
||||
<p>{{ assessment.gradinggrade }}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngIf="access?.canoverridegrades">
|
||||
<ion-label position="stacked">{{ 'addon.mod_workshop.gradinggradeover' | translate }}</ion-label>
|
||||
<ion-select formControlName="grade" interface="action-sheet">
|
||||
<ion-select-option *ngFor="let grade of evaluationGrades" [value]="grade.value">
|
||||
{{grade.label}}
|
||||
</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="access?.canoverridegrades">
|
||||
<ion-label position="stacked">{{ 'addon.mod_workshop.feedbackreviewer' | translate }}</ion-label>
|
||||
<core-rich-text-editor [control]="evaluateForm.controls['text']" name="text"
|
||||
[autoSave]="true" contextLevel="module" [contextInstanceId]="workshop?.coursemodule"
|
||||
elementId="feedbackreviewer_editor" [draftExtraParams]="{asid: assessmentId}">
|
||||
</core-rich-text-editor>
|
||||
</ion-item>
|
||||
</form>
|
||||
<ion-list *ngIf="!evaluating && evaluate && evaluate.text">
|
||||
<ion-item class="ion-text-wrap">
|
||||
<core-user-avatar *ngIf="evaluateByProfile" [user]="evaluateByProfile" slot="start"
|
||||
[courseId]="courseId" [userId]="evaluateByProfile.id"></core-user-avatar>
|
||||
<ion-label>
|
||||
<h2 *ngIf="evaluateByProfile && evaluateByProfile.fullname">
|
||||
{{ 'addon.mod_workshop.feedbackby' | translate : {$a: evaluateByProfile.fullname} }}
|
||||
</h2>
|
||||
<core-format-text [text]="evaluate.text" contextLevel="module" [contextInstanceId]="workshop?.coursemodule"
|
||||
[courseId]="courseId">
|
||||
</core-format-text>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</core-loading>
|
||||
</ion-content>
|
|
@ -0,0 +1,398 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
|
||||
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||
import { CoreCourse } from '@features/course/services/course';
|
||||
import { CoreGradesHelper, CoreGradesMenuItem } from '@features/grades/services/grades-helper';
|
||||
import { CoreUser, CoreUserProfile } from '@features/user/services/user';
|
||||
import { CanLeave } from '@guards/can-leave';
|
||||
import { IonRefresher } from '@ionic/angular';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreSync } from '@services/sync';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { Translate } from '@singletons';
|
||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||
import { CoreForms } from '@singletons/form';
|
||||
import {
|
||||
AddonModWorkshop,
|
||||
AddonModWorkshopAssessmentSavedChangedEventData,
|
||||
AddonModWorkshopData,
|
||||
AddonModWorkshopGetWorkshopAccessInformationWSResponse,
|
||||
AddonModWorkshopPhase,
|
||||
AddonModWorkshopProvider,
|
||||
AddonModWorkshopSubmissionData,
|
||||
} from '../../services/workshop';
|
||||
import { AddonModWorkshopHelper, AddonModWorkshopSubmissionAssessmentWithFormData } from '../../services/workshop-helper';
|
||||
import { AddonModWorkshopOffline } from '../../services/workshop-offline';
|
||||
import { AddonModWorkshopSyncProvider } from '../../services/workshop-sync';
|
||||
|
||||
/**
|
||||
* Page that displays a workshop assessment.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'page-addon-mod-workshop-assessment-page',
|
||||
templateUrl: 'assessment.html',
|
||||
})
|
||||
export class AddonModWorkshopAssessmentPage implements OnInit, OnDestroy, CanLeave {
|
||||
|
||||
@ViewChild('evaluateFormEl') formElement!: ElementRef;
|
||||
|
||||
assessment!: AddonModWorkshopSubmissionAssessmentWithFormData;
|
||||
submission!: AddonModWorkshopSubmissionData;
|
||||
profile!: CoreUserProfile;
|
||||
courseId!: number;
|
||||
access?: AddonModWorkshopGetWorkshopAccessInformationWSResponse;
|
||||
assessmentId!: number;
|
||||
evaluating = false;
|
||||
loaded = false;
|
||||
showGrade: (grade?: string | number) => boolean;
|
||||
evaluateForm: FormGroup;
|
||||
maxGrade?: number;
|
||||
workshop?: AddonModWorkshopData;
|
||||
strategy?: string;
|
||||
title = '';
|
||||
evaluate: AddonModWorkshopAssessmentEvaluation = {
|
||||
text: '',
|
||||
grade: -1,
|
||||
weight: 1,
|
||||
};
|
||||
|
||||
weights: number[] = [];
|
||||
evaluateByProfile?: CoreUserProfile;
|
||||
evaluationGrades: CoreGradesMenuItem[] =[];
|
||||
|
||||
protected workshopId!: number;
|
||||
protected originalEvaluation: AddonModWorkshopAssessmentEvaluation = {
|
||||
text: '',
|
||||
grade: -1,
|
||||
weight: 1,
|
||||
};
|
||||
|
||||
protected hasOffline = false;
|
||||
protected syncObserver: CoreEventObserver;
|
||||
protected isDestroyed = false;
|
||||
protected siteId: string;
|
||||
protected currentUserId: number;
|
||||
protected forceLeave = false;
|
||||
|
||||
constructor(
|
||||
protected fb: FormBuilder,
|
||||
) {
|
||||
this.siteId = CoreSites.getCurrentSiteId();
|
||||
this.currentUserId = CoreSites.getCurrentSiteUserId();
|
||||
|
||||
this.showGrade = AddonModWorkshopHelper.showGrade;
|
||||
|
||||
this.evaluateForm = new FormGroup({});
|
||||
this.evaluateForm.addControl('weight', this.fb.control('', Validators.required));
|
||||
this.evaluateForm.addControl('grade', this.fb.control(''));
|
||||
this.evaluateForm.addControl('text', this.fb.control(''));
|
||||
|
||||
// Refresh workshop on sync.
|
||||
this.syncObserver = CoreEvents.on(AddonModWorkshopSyncProvider.AUTO_SYNCED, (data) => {
|
||||
// Update just when all database is synced.
|
||||
if (this.workshopId === data.workshopId) {
|
||||
this.loaded = false;
|
||||
this.refreshAllData();
|
||||
}
|
||||
}, this.siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.assessment = CoreNavigator.getRouteParam<AddonModWorkshopSubmissionAssessmentWithFormData>('assessment')!;
|
||||
this.submission = CoreNavigator.getRouteParam<AddonModWorkshopSubmissionData>('submission')!;
|
||||
this.profile = CoreNavigator.getRouteParam<CoreUserProfile>('profile')!;
|
||||
this.courseId = CoreNavigator.getRouteNumberParam('courseId')!;
|
||||
|
||||
this.assessmentId = this.assessment.id;
|
||||
this.workshopId = this.submission.workshopid;
|
||||
|
||||
this.fetchAssessmentData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we can leave the page or not.
|
||||
*
|
||||
* @return Resolved if we can leave it, rejected if not.
|
||||
*/
|
||||
async canLeave(): Promise<boolean> {
|
||||
if (this.forceLeave || !this.evaluating) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!this.hasEvaluationChanged()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Show confirmation if some data has been modified.
|
||||
await CoreDomUtils.showConfirm(Translate.instant('core.confirmcanceledit'));
|
||||
|
||||
CoreForms.triggerFormCancelledEvent(this.formElement, this.siteId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the assessment data.
|
||||
*
|
||||
* @return Resolved when done.
|
||||
*/
|
||||
protected async fetchAssessmentData(): Promise<void> {
|
||||
try {
|
||||
this.workshop = await AddonModWorkshop.getWorkshopById(this.courseId, this.workshopId);
|
||||
this.title = this.workshop.name;
|
||||
this.strategy = this.workshop.strategy;
|
||||
|
||||
const gradeInfo = await CoreCourse.getModuleBasicGradeInfo(this.workshop.coursemodule);
|
||||
this.maxGrade = gradeInfo?.grade;
|
||||
|
||||
this.access = await AddonModWorkshop.getWorkshopAccessInformation(
|
||||
this.workshopId,
|
||||
{ cmId: this.workshop.coursemodule },
|
||||
);
|
||||
|
||||
// Load Weights selector.
|
||||
if (this.assessmentId && (this.access.canallocate || this.access.canoverridegrades)) {
|
||||
if (!this.isDestroyed) {
|
||||
// Block the workshop.
|
||||
CoreSync.blockOperation(AddonModWorkshopProvider.COMPONENT, this.workshopId);
|
||||
}
|
||||
|
||||
this.evaluating = true;
|
||||
} else {
|
||||
this.evaluating = false;
|
||||
}
|
||||
|
||||
if (!this.evaluating && this.workshop.phase != AddonModWorkshopPhase.PHASE_CLOSED) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all info of the assessment.
|
||||
const assessment = await AddonModWorkshopHelper.getReviewerAssessmentById(this.workshopId, this.assessmentId, {
|
||||
userId: this.profile && this.profile.id,
|
||||
cmId: this.workshop.coursemodule,
|
||||
});
|
||||
|
||||
this.assessment = AddonModWorkshopHelper.realGradeValue(this.workshop, assessment);
|
||||
this.evaluate.text = this.assessment.feedbackreviewer || '';
|
||||
this.evaluate.weight = this.assessment.weight;
|
||||
|
||||
if (this.evaluating) {
|
||||
if (this.access.canallocate) {
|
||||
this.weights = [];
|
||||
for (let i = 16; i >= 0; i--) {
|
||||
this.weights[i] = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.access.canoverridegrades) {
|
||||
const defaultGrade = Translate.instant('addon.mod_workshop.notoverridden');
|
||||
this.evaluationGrades =
|
||||
await CoreGradesHelper.makeGradesMenu(this.workshop.gradinggrade, undefined, defaultGrade, -1);
|
||||
}
|
||||
|
||||
try {
|
||||
const offlineAssess = await AddonModWorkshopOffline.getEvaluateAssessment(this.workshopId, this.assessmentId);
|
||||
this.hasOffline = true;
|
||||
this.evaluate.weight = offlineAssess.weight;
|
||||
if (this.access.canoverridegrades) {
|
||||
this.evaluate.text = offlineAssess.feedbacktext || '';
|
||||
this.evaluate.grade = parseInt(offlineAssess.gradinggradeover, 10) || -1;
|
||||
}
|
||||
} catch {
|
||||
this.hasOffline = false;
|
||||
// No offline, load online.
|
||||
if (this.access.canoverridegrades) {
|
||||
this.evaluate.text = this.assessment.feedbackreviewer || '';
|
||||
this.evaluate.grade = parseInt(String(this.assessment.gradinggradeover), 10) || -1;
|
||||
}
|
||||
} finally {
|
||||
this.originalEvaluation.weight = this.evaluate.weight;
|
||||
if (this.access.canoverridegrades) {
|
||||
this.originalEvaluation.text = this.evaluate.text;
|
||||
this.originalEvaluation.grade = this.evaluate.grade;
|
||||
}
|
||||
|
||||
this.evaluateForm.controls['weight'].setValue(this.evaluate.weight);
|
||||
if (this.access.canoverridegrades) {
|
||||
this.evaluateForm.controls['grade'].setValue(this.evaluate.grade);
|
||||
this.evaluateForm.controls['text'].setValue(this.evaluate.text);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (this.workshop.phase == AddonModWorkshopPhase.PHASE_CLOSED && this.assessment.gradinggradeoverby) {
|
||||
this.evaluateByProfile = await CoreUser.getProfile(this.assessment.gradinggradeoverby, this.courseId, true);
|
||||
}
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'mm.course.errorgetmodule', true);
|
||||
} finally {
|
||||
this.loaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force leaving the page, without checking for changes.
|
||||
*/
|
||||
protected forceLeavePage(): void {
|
||||
this.forceLeave = true;
|
||||
CoreNavigator.back();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if data has changed.
|
||||
*
|
||||
* @return True if changed, false otherwise.
|
||||
*/
|
||||
protected hasEvaluationChanged(): boolean {
|
||||
if (!this.loaded || !this.evaluating) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const inputData = this.evaluateForm.value;
|
||||
|
||||
if (this.originalEvaluation.weight != inputData.weight) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.access && this.access.canoverridegrades) {
|
||||
if (this.originalEvaluation.text != inputData.text) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.originalEvaluation.grade != inputData.grade) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to refresh all the data.
|
||||
*
|
||||
* @return Resolved when done.
|
||||
*/
|
||||
protected async refreshAllData(): Promise<void> {
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
promises.push(AddonModWorkshop.invalidateWorkshopData(this.courseId));
|
||||
promises.push(AddonModWorkshop.invalidateWorkshopAccessInformationData(this.workshopId));
|
||||
promises.push(AddonModWorkshop.invalidateReviewerAssesmentsData(this.workshopId));
|
||||
|
||||
if (this.assessmentId) {
|
||||
promises.push(AddonModWorkshop.invalidateAssessmentFormData(this.workshopId, this.assessmentId));
|
||||
promises.push(AddonModWorkshop.invalidateAssessmentData(this.workshopId, this.assessmentId));
|
||||
}
|
||||
|
||||
try {
|
||||
await Promise.all(promises);
|
||||
} finally {
|
||||
CoreEvents.trigger(AddonModWorkshopProvider.ASSESSMENT_INVALIDATED, null, this.siteId);
|
||||
|
||||
await this.fetchAssessmentData();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull to refresh.
|
||||
*
|
||||
* @param refresher Refresher.
|
||||
*/
|
||||
refreshAssessment(refresher: IonRefresher): void {
|
||||
if (this.loaded) {
|
||||
this.refreshAllData().finally(() => {
|
||||
refresher?.complete();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the assessment evaluation.
|
||||
*/
|
||||
async saveEvaluation(): Promise<void> {
|
||||
// Check if data has changed.
|
||||
if (this.hasEvaluationChanged()) {
|
||||
await this.sendEvaluation();
|
||||
}
|
||||
|
||||
// Go back.
|
||||
this.forceLeavePage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the evaluation to be saved on the server.
|
||||
*
|
||||
* @return Resolved when done.
|
||||
*/
|
||||
protected async sendEvaluation(): Promise<void> {
|
||||
const modal = await CoreDomUtils.showModalLoading('core.sending', true);
|
||||
const inputData: AddonModWorkshopAssessmentEvaluation = this.evaluateForm.value;
|
||||
|
||||
const grade = inputData.grade >= 0 ? String(inputData.grade) : '';
|
||||
// Add some HTML to the message if needed.
|
||||
const text = CoreTextUtils.formatHtmlLines(inputData.text);
|
||||
|
||||
try {
|
||||
// Try to send it to server.
|
||||
const result = await AddonModWorkshop.evaluateAssessment(
|
||||
this.workshopId,
|
||||
this.assessmentId,
|
||||
this.courseId,
|
||||
text,
|
||||
inputData.weight,
|
||||
grade,
|
||||
);
|
||||
|
||||
CoreForms.triggerFormSubmittedEvent(this.formElement, !!result, this.siteId);
|
||||
|
||||
const data: AddonModWorkshopAssessmentSavedChangedEventData = {
|
||||
workshopId: this.workshopId,
|
||||
assessmentId: this.assessmentId,
|
||||
userId: this.currentUserId,
|
||||
};
|
||||
|
||||
return AddonModWorkshop.invalidateAssessmentData(this.workshopId, this.assessmentId).finally(() => {
|
||||
CoreEvents.trigger(AddonModWorkshopProvider.ASSESSMENT_SAVED, data, this.siteId);
|
||||
});
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'Cannot save assessment evaluation');
|
||||
} finally {
|
||||
modal.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.isDestroyed = true;
|
||||
|
||||
this.syncObserver?.off();
|
||||
// Restore original back functions.
|
||||
CoreSync.unblockOperation(AddonModWorkshopProvider.COMPONENT, this.workshopId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type AddonModWorkshopAssessmentEvaluation = {
|
||||
text: string;
|
||||
grade: number;
|
||||
weight: number;
|
||||
};
|
|
@ -0,0 +1,46 @@
|
|||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>{{ 'addon.mod_workshop.editsubmission' | translate }}</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button fill="clear" (click)="save()" [attr.aria-label]="'core.save' | translate">
|
||||
{{ 'core.save' | translate }}
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<core-loading [hideUntil]="loaded">
|
||||
<form ion-list [formGroup]="editForm" *ngIf="workshop" #editFormEl>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label position="stacked">
|
||||
<span core-mark-required="true">
|
||||
{{ 'addon.mod_workshop.submissiontitle' | translate }}
|
||||
</span>
|
||||
</ion-label>
|
||||
<ion-input name="title" type="text" [placeholder]="'addon.mod_workshop.submissiontitle' | translate"
|
||||
formControlName="title">
|
||||
</ion-input>
|
||||
</ion-item>
|
||||
|
||||
<ion-item *ngIf="textAvailable">
|
||||
<ion-label position="stacked">
|
||||
<span [core-mark-required]="textRequired">
|
||||
{{ 'addon.mod_workshop.submissioncontent' | translate }}
|
||||
</span>
|
||||
</ion-label>
|
||||
<core-rich-text-editor [control]="editForm.controls['content']" name="content"
|
||||
[placeholder]="'addon.mod_workshop.submissioncontent' | translate" name="content" [component]="component"
|
||||
[componentId]="componentId" [autoSave]="true" contextLevel="module" [contextInstanceId]="module.id"
|
||||
elementId="content_editor" [draftExtraParams]="editorExtraParams"></core-rich-text-editor>
|
||||
</ion-item>
|
||||
|
||||
<core-attachments *ngIf="fileAvailable" [files]="submission?.attachmentfiles || []" [maxSize]="workshop.maxbytes"
|
||||
[maxSubmissions]="workshop.nattachments" [component]="component" [componentId]="workshop.coursemodule"
|
||||
allowOffline="true" [acceptedTypes]="workshop.submissionfiletypes" [required]="fileRequired">
|
||||
</core-attachments>
|
||||
</form>
|
||||
</core-loading>
|
||||
</ion-content>
|
|
@ -0,0 +1,476 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
|
||||
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||
import { CoreError } from '@classes/errors/error';
|
||||
import { CoreCourseModule } from '@features/course/services/course-helper';
|
||||
import { CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
|
||||
import { CanLeave } from '@guards/can-leave';
|
||||
import { CoreFile } from '@services/file';
|
||||
import { CoreFileEntry, CoreFileHelper } from '@services/file-helper';
|
||||
import { CoreFileSession } from '@services/file-session';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreSync } from '@services/sync';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { Translate } from '@singletons';
|
||||
import { CoreEvents } from '@singletons/events';
|
||||
import { CoreForms } from '@singletons/form';
|
||||
import {
|
||||
AddonModWorkshopProvider,
|
||||
AddonModWorkshop,
|
||||
AddonModWorkshopSubmissionType,
|
||||
AddonModWorkshopSubmissionChangedEventData,
|
||||
AddonModWorkshopAction,
|
||||
AddonModWorkshopGetWorkshopAccessInformationWSResponse,
|
||||
AddonModWorkshopData,
|
||||
} from '../../services/workshop';
|
||||
import { AddonModWorkshopHelper, AddonModWorkshopSubmissionDataWithOfflineData } from '../../services/workshop-helper';
|
||||
import { AddonModWorkshopOffline } from '../../services/workshop-offline';
|
||||
|
||||
/**
|
||||
* Page that displays the workshop edit submission.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'page-addon-mod-workshop-edit-submission',
|
||||
templateUrl: 'edit-submission.html',
|
||||
})
|
||||
export class AddonModWorkshopEditSubmissionPage implements OnInit, OnDestroy, CanLeave {
|
||||
|
||||
@ViewChild('editFormEl') formElement!: ElementRef;
|
||||
|
||||
module!: CoreCourseModule;
|
||||
courseId!: number;
|
||||
access!: AddonModWorkshopGetWorkshopAccessInformationWSResponse;
|
||||
submission?: AddonModWorkshopSubmissionDataWithOfflineData;
|
||||
|
||||
loaded = false;
|
||||
component = AddonModWorkshopProvider.COMPONENT;
|
||||
componentId!: number;
|
||||
editForm: FormGroup; // The form group.
|
||||
editorExtraParams: Record<string, unknown> = {}; // Extra params to identify the draft.
|
||||
workshop?: AddonModWorkshopData;
|
||||
textAvailable = false;
|
||||
textRequired = false;
|
||||
fileAvailable = false;
|
||||
fileRequired = false;
|
||||
|
||||
protected workshopId!: number;
|
||||
protected submissionId = 0;
|
||||
protected userId: number;
|
||||
protected originalData: AddonModWorkshopEditSubmissionInputData = {
|
||||
title: '',
|
||||
content: '',
|
||||
attachmentfiles: [],
|
||||
};
|
||||
|
||||
protected hasOffline = false;
|
||||
protected editing = false;
|
||||
protected forceLeave = false;
|
||||
protected siteId: string;
|
||||
protected isDestroyed = false;
|
||||
|
||||
constructor(
|
||||
protected fb: FormBuilder,
|
||||
) {
|
||||
|
||||
this.userId = CoreSites.getCurrentSiteUserId();
|
||||
this.siteId = CoreSites.getCurrentSiteId();
|
||||
|
||||
this.editForm = new FormGroup({});
|
||||
this.editForm.addControl('title', this.fb.control('', Validators.required));
|
||||
this.editForm.addControl('content', this.fb.control(''));
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.module = CoreNavigator.getRouteParam<CoreCourseModule>('module')!;
|
||||
this.courseId = CoreNavigator.getRouteNumberParam('courseId')!;
|
||||
this.access = CoreNavigator.getRouteParam<AddonModWorkshopGetWorkshopAccessInformationWSResponse>('access')!;
|
||||
this.submissionId = CoreNavigator.getRouteNumberParam('submissionId') || 0;
|
||||
|
||||
if (this.submissionId > 0) {
|
||||
this.editorExtraParams.id = this.submissionId;
|
||||
}
|
||||
|
||||
this.workshopId = this.module.instance!;
|
||||
this.componentId = this.module.id;
|
||||
|
||||
if (!this.isDestroyed) {
|
||||
// Block the workshop.
|
||||
CoreSync.blockOperation(this.component, this.workshopId);
|
||||
}
|
||||
|
||||
this.fetchSubmissionData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we can leave the page or not.
|
||||
*
|
||||
* @return Resolved if we can leave it, rejected if not.
|
||||
*/
|
||||
async canLeave(): Promise<boolean> {
|
||||
if (this.forceLeave) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if data has changed.
|
||||
if (this.hasDataChanged()) {
|
||||
// Show confirmation if some data has been modified.
|
||||
await CoreDomUtils.showConfirm(Translate.instant('core.confirmcanceledit'));
|
||||
}
|
||||
|
||||
if (this.submission?.attachmentfiles) {
|
||||
// Delete the local files from the tmp folder.
|
||||
CoreFileUploader.clearTmpFiles(this.submission.attachmentfiles);
|
||||
}
|
||||
|
||||
CoreForms.triggerFormCancelledEvent(this.formElement, this.siteId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the submission data.
|
||||
*
|
||||
* @return Resolved when done.
|
||||
*/
|
||||
protected async fetchSubmissionData(): Promise<void> {
|
||||
try {
|
||||
this.workshop = await AddonModWorkshop.getWorkshop(this.courseId, this.module.id);
|
||||
this.textAvailable = (this.workshop.submissiontypetext != AddonModWorkshopSubmissionType.SUBMISSION_TYPE_DISABLED);
|
||||
this.textRequired = (this.workshop.submissiontypetext == AddonModWorkshopSubmissionType.SUBMISSION_TYPE_REQUIRED);
|
||||
this.fileAvailable = (this.workshop.submissiontypefile != AddonModWorkshopSubmissionType.SUBMISSION_TYPE_DISABLED);
|
||||
this.fileRequired = (this.workshop.submissiontypefile == AddonModWorkshopSubmissionType.SUBMISSION_TYPE_REQUIRED);
|
||||
|
||||
this.editForm.controls.content.setValidators(this.textRequired ? Validators.required : null);
|
||||
|
||||
if (this.submissionId > 0) {
|
||||
this.editing = true;
|
||||
|
||||
this.submission =
|
||||
await AddonModWorkshopHelper.getSubmissionById(this.workshopId, this.submissionId, { cmId: this.module.id });
|
||||
|
||||
const canEdit = this.userId == this.submission.authorid &&
|
||||
this.access.cansubmit &&
|
||||
this.access.modifyingsubmissionallowed;
|
||||
|
||||
if (!canEdit) {
|
||||
// Should not happen, but go back if does.
|
||||
this.forceLeavePage();
|
||||
|
||||
return;
|
||||
}
|
||||
} else if (!this.access.cansubmit || !this.access.creatingsubmissionallowed) {
|
||||
// Should not happen, but go back if does.
|
||||
this.forceLeavePage();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const submissionsActions = await AddonModWorkshopOffline.getSubmissions(this.workshopId);
|
||||
if (submissionsActions && submissionsActions.length) {
|
||||
this.hasOffline = true;
|
||||
this.submission = await AddonModWorkshopHelper.applyOfflineData(this.submission, submissionsActions);
|
||||
} else {
|
||||
this.hasOffline = false;
|
||||
}
|
||||
|
||||
if (this.submission) {
|
||||
this.originalData.title = this.submission.title || '';
|
||||
this.originalData.content = this.submission.content || '';
|
||||
this.originalData.attachmentfiles = [];
|
||||
|
||||
(this.submission.attachmentfiles || []).forEach((file) => {
|
||||
let filename = CoreFile.getFileName(file);
|
||||
if (!filename) {
|
||||
// We don't have filename, extract it from the path.
|
||||
filename = CoreFileHelper.getFilenameFromPath(file) || '';
|
||||
}
|
||||
|
||||
this.originalData.attachmentfiles.push({
|
||||
filename,
|
||||
fileurl: 'fileurl' in file ? file.fileurl : '',
|
||||
});
|
||||
});
|
||||
|
||||
this.editForm.controls['title'].setValue(this.submission.title);
|
||||
this.editForm.controls['content'].setValue(this.submission.content);
|
||||
}
|
||||
|
||||
CoreFileSession.setFiles(
|
||||
this.component,
|
||||
this.getFilesComponentId(),
|
||||
this.submission?.attachmentfiles || [],
|
||||
);
|
||||
|
||||
this.loaded = true;
|
||||
} catch (error) {
|
||||
this.loaded = false;
|
||||
|
||||
CoreDomUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true);
|
||||
|
||||
this.forceLeavePage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force leaving the page, without checking for changes.
|
||||
*/
|
||||
protected forceLeavePage(): void {
|
||||
this.forceLeave = true;
|
||||
CoreNavigator.back();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the form input data.
|
||||
*
|
||||
* @return Object with all the info.
|
||||
*/
|
||||
protected getInputData(): AddonModWorkshopEditSubmissionInputData {
|
||||
const values: AddonModWorkshopEditSubmissionInputData = {
|
||||
title: this.editForm.value.title,
|
||||
content: '',
|
||||
attachmentfiles: [],
|
||||
};
|
||||
|
||||
if (this.textAvailable) {
|
||||
values.content = this.editForm.value.content || '';
|
||||
}
|
||||
|
||||
if (this.fileAvailable) {
|
||||
values.attachmentfiles = CoreFileSession.getFiles(this.component, this.getFilesComponentId()) || [];
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if data has changed.
|
||||
*
|
||||
* @return True if changed or false if not.
|
||||
*/
|
||||
protected hasDataChanged(): boolean {
|
||||
if (!this.loaded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const inputData = this.getInputData();
|
||||
if (this.originalData.title != inputData.title || this.textAvailable && this.originalData.content != inputData.content) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.fileAvailable) {
|
||||
return CoreFileUploader.areFileListDifferent(inputData.attachmentfiles, this.originalData.attachmentfiles);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the submission.
|
||||
*/
|
||||
async save(): Promise<void> {
|
||||
// Check if data has changed.
|
||||
if (this.hasDataChanged()) {
|
||||
try {
|
||||
await this.saveSubmission();
|
||||
// Go back to entry list.
|
||||
this.forceLeavePage();
|
||||
} catch{
|
||||
// Nothing to do.
|
||||
}
|
||||
} else {
|
||||
// Nothing to save, just go back.
|
||||
this.forceLeavePage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send submission and save.
|
||||
*
|
||||
* @return Resolved when done.
|
||||
*/
|
||||
protected async saveSubmission(): Promise<void> {
|
||||
const inputData = this.getInputData();
|
||||
|
||||
if (!inputData.title) {
|
||||
CoreDomUtils.showAlertTranslated('core.notice', 'addon.mod_workshop.submissionrequiredtitle');
|
||||
|
||||
throw new CoreError(Translate.instant('addon.mod_workshop.submissionrequiredtitle'));
|
||||
}
|
||||
|
||||
const noText = CoreTextUtils.htmlIsBlank(inputData.content);
|
||||
const noFiles = !inputData.attachmentfiles.length;
|
||||
|
||||
if ((this.textRequired && noText) || (this.fileRequired && noFiles) || (noText && noFiles)) {
|
||||
CoreDomUtils.showAlertTranslated('core.notice', 'addon.mod_workshop.submissionrequiredcontent');
|
||||
|
||||
throw new CoreError(Translate.instant('addon.mod_workshop.submissionrequiredcontent'));
|
||||
}
|
||||
|
||||
let saveOffline = false;
|
||||
|
||||
const modal = await CoreDomUtils.showModalLoading('core.sending', true);
|
||||
const submissionId = this.submission?.id;
|
||||
|
||||
// Add some HTML to the message if needed.
|
||||
if (this.textAvailable) {
|
||||
inputData.content = CoreTextUtils.formatHtmlLines(inputData.content);
|
||||
}
|
||||
|
||||
// Upload attachments first if any.
|
||||
let allowOffline = !inputData.attachmentfiles.length;
|
||||
try {
|
||||
let attachmentsId: CoreFileUploaderStoreFilesResult | number | undefined;
|
||||
try {
|
||||
attachmentsId = await AddonModWorkshopHelper.uploadOrStoreSubmissionFiles(
|
||||
this.workshopId,
|
||||
inputData.attachmentfiles,
|
||||
false,
|
||||
);
|
||||
} catch {
|
||||
// Cannot upload them in online, save them in offline.
|
||||
saveOffline = true;
|
||||
allowOffline = true;
|
||||
|
||||
attachmentsId = await AddonModWorkshopHelper.uploadOrStoreSubmissionFiles(
|
||||
this.workshopId,
|
||||
inputData.attachmentfiles,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
if (!saveOffline && !this.fileAvailable) {
|
||||
attachmentsId = undefined;
|
||||
}
|
||||
|
||||
let newSubmissionId: number | false;
|
||||
if (this.editing) {
|
||||
if (saveOffline) {
|
||||
// Save submission in offline.
|
||||
await AddonModWorkshopOffline.saveSubmission(
|
||||
this.workshopId,
|
||||
this.courseId,
|
||||
inputData.title,
|
||||
inputData.content,
|
||||
attachmentsId as CoreFileUploaderStoreFilesResult,
|
||||
submissionId,
|
||||
AddonModWorkshopAction.UPDATE,
|
||||
);
|
||||
newSubmissionId = false;
|
||||
} else {
|
||||
// Try to send it to server.
|
||||
// Don't allow offline if there are attachments since they were uploaded fine.
|
||||
newSubmissionId = await AddonModWorkshop.updateSubmission(
|
||||
this.workshopId,
|
||||
submissionId!,
|
||||
this.courseId,
|
||||
inputData.title,
|
||||
inputData.content,
|
||||
attachmentsId,
|
||||
undefined,
|
||||
allowOffline,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (saveOffline) {
|
||||
// Save submission in offline.
|
||||
await AddonModWorkshopOffline.saveSubmission(
|
||||
this.workshopId,
|
||||
this.courseId,
|
||||
inputData.title,
|
||||
inputData.content,
|
||||
attachmentsId as CoreFileUploaderStoreFilesResult,
|
||||
undefined,
|
||||
AddonModWorkshopAction.ADD,
|
||||
);
|
||||
newSubmissionId = false;
|
||||
} else {
|
||||
// Try to send it to server.
|
||||
// Don't allow offline if there are attachments since they were uploaded fine.
|
||||
newSubmissionId = await AddonModWorkshop.addSubmission(
|
||||
this.workshopId,
|
||||
this.courseId,
|
||||
inputData.title,
|
||||
inputData.content,
|
||||
attachmentsId,
|
||||
undefined,
|
||||
allowOffline,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
CoreForms.triggerFormSubmittedEvent(this.formElement, !!newSubmissionId, this.siteId);
|
||||
|
||||
const data: AddonModWorkshopSubmissionChangedEventData = {
|
||||
workshopId: this.workshopId,
|
||||
};
|
||||
|
||||
if (newSubmissionId) {
|
||||
// Data sent to server, delete stored files (if any).
|
||||
AddonModWorkshopOffline.deleteSubmissionAction(
|
||||
this.workshopId,
|
||||
this.editing ? AddonModWorkshopAction.UPDATE : AddonModWorkshopAction.ADD,
|
||||
);
|
||||
|
||||
AddonModWorkshopHelper.deleteSubmissionStoredFiles(this.workshopId, this.siteId);
|
||||
data.submissionId = newSubmissionId;
|
||||
}
|
||||
|
||||
CoreEvents.trigger(CoreEvents.ACTIVITY_DATA_SENT, { module: 'workshop' });
|
||||
|
||||
const promise = newSubmissionId ? AddonModWorkshop.invalidateSubmissionData(this.workshopId, newSubmissionId) :
|
||||
Promise.resolve();
|
||||
|
||||
await promise.finally(() => {
|
||||
CoreEvents.trigger(AddonModWorkshopProvider.SUBMISSION_CHANGED, data, this.siteId);
|
||||
|
||||
// Delete the local files from the tmp folder.
|
||||
CoreFileUploader.clearTmpFiles(inputData.attachmentfiles);
|
||||
});
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'Cannot save submission');
|
||||
} finally {
|
||||
modal.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
protected getFilesComponentId(): string {
|
||||
const id = this.submissionId > 0
|
||||
? this.submissionId
|
||||
: 'newsub';
|
||||
|
||||
return this.workshopId + '_' + id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.isDestroyed = true;
|
||||
CoreSync.unblockOperation(this.component, this.workshopId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type AddonModWorkshopEditSubmissionInputData = {
|
||||
title: string;
|
||||
content: string;
|
||||
attachmentfiles: CoreFileEntry[];
|
||||
};
|
|
@ -0,0 +1,154 @@
|
|||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>
|
||||
<core-format-text *ngIf="title" [text]="title" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
|
||||
</core-format-text>
|
||||
</ion-title>
|
||||
<ion-buttons slot="end" [hidden]="!loaded">
|
||||
<ion-button *ngIf="assessmentId && access.assessingallowed" fill="clear" (click)="saveAssessment()"
|
||||
[attr.aria-label]="'core.save' | translate">
|
||||
{{ 'core.save' | translate }}
|
||||
</ion-button>
|
||||
<ion-button *ngIf="canAddFeedback" fill="clear" (click)="saveEvaluation()" [attr.aria-label]="'core.save' | translate">
|
||||
{{ 'core.save' | translate }}
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshSubmission($event.target)"
|
||||
*ngIf="!((assessmentId && access.assessingallowed) || canAddFeedback)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="loaded">
|
||||
<ion-list *ngIf="submission">
|
||||
<addon-mod-workshop-submission [submission]="submission" [courseId]="courseId" [module]="module" [workshop]="workshop"
|
||||
[access]="access">
|
||||
</addon-mod-workshop-submission>
|
||||
<ion-item class="ion-text-wrap" *ngIf="canEdit || canDelete">
|
||||
<ion-label>
|
||||
<ion-button expand="block" *ngIf="canEdit" (click)="editSubmission()">
|
||||
<ion-icon name="fas-edit" slot="start"></ion-icon>
|
||||
{{ 'addon.mod_workshop.editsubmission' | translate }}
|
||||
</ion-button>
|
||||
<ion-button expand="block" *ngIf="!submission.deleted && canDelete" color="danger" (click)="deleteSubmission()">
|
||||
<ion-icon name="fas-trash" slot="start"></ion-icon>
|
||||
{{ 'addon.mod_workshop.deletesubmission' | translate }}
|
||||
</ion-button>
|
||||
<ion-button expand="block" fill="outline" *ngIf="submission.deleted && canDelete" color="danger"
|
||||
(click)="undoDeleteSubmission()">
|
||||
<ion-icon name="fas-undo-alt" slot="start"></ion-icon>
|
||||
{{ 'core.restore' | translate }}
|
||||
</ion-button>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
||||
<ion-list *ngIf="!canAddFeedback && evaluate?.text">
|
||||
<ion-item class="ion-text-wrap">
|
||||
<core-user-avatar *ngIf="evaluateByProfile" [user]="evaluateByProfile" slot="start" [courseId]="courseId"
|
||||
[userId]="evaluateByProfile.id"></core-user-avatar>
|
||||
<ion-label>
|
||||
<h2 *ngIf="evaluateByProfile && evaluateByProfile.fullname">
|
||||
{{ 'addon.mod_workshop.feedbackby' | translate : {$a: evaluateByProfile.fullname} }}
|
||||
</h2>
|
||||
<core-format-text [text]="evaluate?.text" contextLevel="module" [contextInstanceId]="module.id"
|
||||
[courseId]="courseId">
|
||||
</core-format-text>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
||||
<ion-list *ngIf="ownAssessment && !assessment">
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<h2>{{ 'addon.mod_workshop.yourassessment' | translate }}</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<addon-mod-workshop-assessment [submission]="submission" [assessment]="ownAssessment" [courseId]="courseId"
|
||||
[access]="access" [module]="module" [workshop]="workshop">
|
||||
</addon-mod-workshop-assessment>
|
||||
</ion-list>
|
||||
|
||||
<ion-list *ngIf="submissionInfo && submissionInfo.reviewedby && submissionInfo.reviewedby.length && !assessment">
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<h2>{{ 'addon.mod_workshop.receivedgrades' | translate }}</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ng-container *ngFor="let reviewer of submissionInfo.reviewedby">
|
||||
<addon-mod-workshop-assessment *ngIf="!reviewer.ownAssessment" [submission]="submission" [assessment]="reviewer"
|
||||
[courseId]="courseId" [access]="access" [module]="module" [workshop]="workshop">
|
||||
</addon-mod-workshop-assessment>
|
||||
</ng-container>
|
||||
</ion-list>
|
||||
|
||||
<ion-list *ngIf="submissionInfo && submissionInfo.reviewerof && submissionInfo.reviewerof.length && !assessment">
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<h2>{{ 'addon.mod_workshop.givengrades' | translate }}</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<addon-mod-workshop-assessment *ngFor="let reviewer of submissionInfo.reviewerof" [assessment]="reviewer"
|
||||
[courseId]="courseId" [module]="module" [workshop]="workshop" [access]="access">
|
||||
</addon-mod-workshop-assessment>
|
||||
</ion-list>
|
||||
|
||||
<form ion-list [formGroup]="feedbackForm" *ngIf="canAddFeedback && submission" #feedbackFormEl>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<h2>{{ 'addon.mod_workshop.feedbackauthor' | translate }}</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngIf="access.canpublishsubmissions">
|
||||
<ion-label>{{ 'addon.mod_workshop.publishsubmission' | translate }}</ion-label>
|
||||
<ion-toggle formControlName="published"></ion-toggle>
|
||||
<p class="item-help">{{ 'addon.mod_workshop.publishsubmission_help' | translate }}</p>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<h2>{{ 'addon.mod_workshop.gradecalculated' | translate }}</h2>
|
||||
<p>{{ submission.grade }}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label position="stacked">{{ 'addon.mod_workshop.gradeover' | translate }}</ion-label>
|
||||
<ion-select formControlName="grade" interface="action-sheet">
|
||||
<ion-select-option *ngFor="let grade of evaluationGrades" [value]="grade.value">
|
||||
{{grade.label}}
|
||||
</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label position="stacked">{{ 'addon.mod_workshop.feedbackauthor' | translate }}</ion-label>
|
||||
<core-rich-text-editor [control]="feedbackForm.controls['text']" name="text"
|
||||
[autoSave]="true" contextLevel="module" [contextInstanceId]="module.id" elementId="feedbackauthor_editor"
|
||||
[draftExtraParams]="{id: submissionId}">
|
||||
</core-rich-text-editor>
|
||||
</ion-item>
|
||||
</form>
|
||||
|
||||
<addon-mod-workshop-assessment-strategy *ngIf="assessmentId" [workshop]="workshop" [access]="access"
|
||||
[assessmentId]="assessmentId" [userId]="assessmentUserId" [strategy]="strategy" [edit]="access.assessingallowed">
|
||||
</addon-mod-workshop-assessment-strategy>
|
||||
|
||||
<ion-list *ngIf="assessmentId && !access.assessingallowed && assessment?.feedbackreviewer">
|
||||
<ion-item class="ion-text-wrap">
|
||||
<core-user-avatar *ngIf="evaluateGradingByProfile" [user]="evaluateGradingByProfile" slot="start"
|
||||
[courseId]="courseId" [userId]="evaluateGradingByProfile.id"></core-user-avatar>
|
||||
<ion-label>
|
||||
<h2 *ngIf="evaluateGradingByProfile && evaluateGradingByProfile.fullname">
|
||||
{{ 'addon.mod_workshop.feedbackby' | translate : {$a: evaluateGradingByProfile.fullname} }}
|
||||
</h2>
|
||||
<core-format-text [text]="assessment!.feedbackreviewer" contextLevel="module" [contextInstanceId]="module.id"
|
||||
[courseId]="courseId">
|
||||
</core-format-text>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</core-loading>
|
||||
</ion-content>
|
|
@ -0,0 +1,610 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit, OnDestroy, Optional, ViewChild, ElementRef } from '@angular/core';
|
||||
import { FormGroup, FormBuilder } from '@angular/forms';
|
||||
import { Params } from '@angular/router';
|
||||
import { CoreCourse } from '@features/course/services/course';
|
||||
import { CoreCourseModule } from '@features/course/services/course-helper';
|
||||
import { CoreGradesHelper, CoreGradesMenuItem } from '@features/grades/services/grades-helper';
|
||||
import { CoreUser, CoreUserProfile } from '@features/user/services/user';
|
||||
import { CanLeave } from '@guards/can-leave';
|
||||
import { IonContent, IonRefresher } from '@ionic/angular';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreSync } from '@services/sync';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { Translate } from '@singletons';
|
||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||
import { CoreForms } from '@singletons/form';
|
||||
import { AddonModWorkshopAssessmentStrategyComponent } from '../../components/assessment-strategy/assessment-strategy';
|
||||
import {
|
||||
AddonModWorkshopProvider,
|
||||
AddonModWorkshop,
|
||||
AddonModWorkshopPhase,
|
||||
AddonModWorkshopSubmissionChangedEventData,
|
||||
AddonModWorkshopAction,
|
||||
AddonModWorkshopData,
|
||||
AddonModWorkshopGetWorkshopAccessInformationWSResponse,
|
||||
AddonModWorkshopAssessmentSavedChangedEventData,
|
||||
} from '../../services/workshop';
|
||||
import {
|
||||
AddonModWorkshopHelper,
|
||||
AddonModWorkshopSubmissionAssessmentWithFormData,
|
||||
AddonModWorkshopSubmissionDataWithOfflineData,
|
||||
} from '../../services/workshop-helper';
|
||||
import { AddonModWorkshopOffline } from '../../services/workshop-offline';
|
||||
import { AddonModWorkshopSyncProvider, AddonModWorkshopAutoSyncData } from '../../services/workshop-sync';
|
||||
|
||||
/**
|
||||
* Page that displays a workshop submission.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'page-addon-mod-workshop-submission-page',
|
||||
templateUrl: 'submission.html',
|
||||
})
|
||||
export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy, CanLeave {
|
||||
|
||||
@ViewChild(AddonModWorkshopAssessmentStrategyComponent) assessmentStrategy?: AddonModWorkshopAssessmentStrategyComponent;
|
||||
|
||||
@ViewChild('feedbackFormEl') formElement?: ElementRef;
|
||||
|
||||
module!: CoreCourseModule;
|
||||
workshop!: AddonModWorkshopData;
|
||||
access!: AddonModWorkshopGetWorkshopAccessInformationWSResponse;
|
||||
assessment?: AddonModWorkshopSubmissionAssessmentWithFormData;
|
||||
submissionInfo!: AddonModWorkshopSubmissionDataWithOfflineData;
|
||||
profile?: CoreUserProfile;
|
||||
courseId!: number;
|
||||
|
||||
submission?: AddonModWorkshopSubmissionDataWithOfflineData;
|
||||
title?: string;
|
||||
loaded = false;
|
||||
ownAssessment?: AddonModWorkshopSubmissionAssessmentWithFormData;
|
||||
strategy?: string;
|
||||
assessmentId?: number;
|
||||
assessmentUserId?: number;
|
||||
evaluate?: AddonWorkshopSubmissionEvaluateData;
|
||||
canAddFeedback = false;
|
||||
canEdit = false;
|
||||
canDelete = false;
|
||||
evaluationGrades: CoreGradesMenuItem[] = [];
|
||||
evaluateGradingByProfile?: CoreUserProfile;
|
||||
evaluateByProfile?: CoreUserProfile;
|
||||
feedbackForm: FormGroup; // The form group.
|
||||
submissionId!: number;
|
||||
|
||||
protected workshopId!: number;
|
||||
protected currentUserId: number;
|
||||
protected userId?: number;
|
||||
protected siteId: string;
|
||||
protected originalEvaluation: Omit<AddonWorkshopSubmissionEvaluateData, 'grade'> & { grade: number | string} = {
|
||||
published: false,
|
||||
text: '',
|
||||
grade: '',
|
||||
};
|
||||
|
||||
protected hasOffline = false;
|
||||
protected component = AddonModWorkshopProvider.COMPONENT;
|
||||
protected forceLeave = false;
|
||||
protected obsAssessmentSaved: CoreEventObserver;
|
||||
protected syncObserver: CoreEventObserver;
|
||||
protected isDestroyed = false;
|
||||
|
||||
constructor(
|
||||
protected fb: FormBuilder,
|
||||
@Optional() protected content: IonContent,
|
||||
) {
|
||||
this.currentUserId = CoreSites.getCurrentSiteUserId();
|
||||
this.siteId = CoreSites.getCurrentSiteId();
|
||||
|
||||
this.feedbackForm = new FormGroup({});
|
||||
this.feedbackForm.addControl('published', this.fb.control(''));
|
||||
this.feedbackForm.addControl('grade', this.fb.control(''));
|
||||
this.feedbackForm.addControl('text', this.fb.control(''));
|
||||
|
||||
this.obsAssessmentSaved = CoreEvents.on(AddonModWorkshopProvider.ASSESSMENT_SAVED, (data) => {
|
||||
this.eventReceived(data);
|
||||
}, this.siteId);
|
||||
|
||||
// Refresh workshop on sync.
|
||||
this.syncObserver = CoreEvents.on(AddonModWorkshopSyncProvider.AUTO_SYNCED, (data) => {
|
||||
// Update just when all database is synced.
|
||||
this.eventReceived(data);
|
||||
}, this.siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
async ngOnInit(): Promise<void> {
|
||||
|
||||
this.submissionId = CoreNavigator.getRouteNumberParam('submissionId')!;
|
||||
this.module = CoreNavigator.getRouteParam<CoreCourseModule>('module')!;
|
||||
this.workshop = CoreNavigator.getRouteParam<AddonModWorkshopData>('workshop')!;
|
||||
this.access = CoreNavigator.getRouteParam<AddonModWorkshopGetWorkshopAccessInformationWSResponse>('access')!;
|
||||
this.courseId = CoreNavigator.getRouteNumberParam('courseId')!;
|
||||
this.profile = CoreNavigator.getRouteParam<CoreUserProfile>('profile');
|
||||
this.submissionInfo = CoreNavigator.getRouteParam<AddonModWorkshopSubmissionDataWithOfflineData>('submission')!;
|
||||
this.assessment = CoreNavigator.getRouteParam<AddonModWorkshopSubmissionAssessmentWithFormData>('assessment');
|
||||
|
||||
this.title = this.module.name;
|
||||
this.workshopId = this.module.instance || this.workshop.id;
|
||||
|
||||
this.userId = this.submissionInfo?.authorid;
|
||||
this.strategy = (this.assessment && this.assessment.strategy) || (this.workshop && this.workshop.strategy);
|
||||
this.assessmentId = this.assessment?.id;
|
||||
this.assessmentUserId = this.assessment?.reviewerid;
|
||||
|
||||
await this.fetchSubmissionData();
|
||||
|
||||
try {
|
||||
await AddonModWorkshop.logViewSubmission(this.submissionId, this.workshopId, this.workshop.name);
|
||||
CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);
|
||||
} catch {
|
||||
// Ignore errors.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we can leave the page or not.
|
||||
*
|
||||
* @return Resolved if we can leave it, rejected if not.
|
||||
*/
|
||||
async canLeave(): Promise<boolean> {
|
||||
const assessmentHasChanged = this.assessmentStrategy?.hasDataChanged();
|
||||
if (this.forceLeave || (!this.hasEvaluationChanged() && !assessmentHasChanged)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Show confirmation if some data has been modified.
|
||||
await CoreDomUtils.showConfirm(Translate.instant('core.confirmcanceledit'));
|
||||
|
||||
CoreForms.triggerFormCancelledEvent(this.formElement, this.siteId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Goto edit submission page.
|
||||
*/
|
||||
editSubmission(): void {
|
||||
const params: Params = {
|
||||
module: module,
|
||||
access: this.access,
|
||||
};
|
||||
|
||||
CoreNavigator.navigate(String(this.submissionId) + '/edit', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called when we receive an event of submission changes.
|
||||
*
|
||||
* @param data Event data received.
|
||||
*/
|
||||
protected eventReceived(data: AddonModWorkshopAutoSyncData |
|
||||
AddonModWorkshopAssessmentSavedChangedEventData): void {
|
||||
if (this.workshopId === data.workshopId) {
|
||||
this.content?.scrollToTop();
|
||||
|
||||
this.loaded = false;
|
||||
this.refreshAllData();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the submission data.
|
||||
*
|
||||
* @return Resolved when done.
|
||||
*/
|
||||
protected async fetchSubmissionData(): Promise<void> {
|
||||
try {
|
||||
this.submission = await AddonModWorkshopHelper.getSubmissionById(this.workshopId, this.submissionId, {
|
||||
cmId: this.module.id,
|
||||
});
|
||||
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
this.submission.grade = this.submissionInfo?.grade;
|
||||
this.submission.gradinggrade = this.submissionInfo?.gradinggrade;
|
||||
this.submission.gradeover = this.submissionInfo?.gradeover;
|
||||
this.userId = this.submission.authorid || this.userId;
|
||||
this.canEdit = this.currentUserId == this.userId && this.access.cansubmit && this.access.modifyingsubmissionallowed;
|
||||
this.canDelete = this.access.candeletesubmissions;
|
||||
|
||||
this.canAddFeedback = !this.assessmentId && this.workshop.phase > AddonModWorkshopPhase.PHASE_ASSESSMENT &&
|
||||
this.workshop.phase < AddonModWorkshopPhase.PHASE_CLOSED && this.access.canoverridegrades;
|
||||
this.ownAssessment = undefined;
|
||||
|
||||
if (this.access.canviewallassessments) {
|
||||
// Get new data, different that came from stateParams.
|
||||
promises.push(AddonModWorkshop.getSubmissionAssessments(this.workshopId, this.submissionId, {
|
||||
cmId: this.module.id,
|
||||
}).then((subAssessments) => {
|
||||
// Only allow the student to delete their own submission if it's still editable and hasn't been assessed.
|
||||
if (this.canDelete) {
|
||||
this.canDelete = !subAssessments.length;
|
||||
}
|
||||
|
||||
this.submissionInfo.reviewedby = subAssessments;
|
||||
|
||||
this.submissionInfo.reviewedby.forEach((assessment) => {
|
||||
assessment = AddonModWorkshopHelper.realGradeValue(this.workshop, assessment);
|
||||
|
||||
if (this.currentUserId == assessment.reviewerid) {
|
||||
this.ownAssessment = assessment;
|
||||
assessment.ownAssessment = true;
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}));
|
||||
} else if (this.currentUserId == this.userId && this.assessmentId) {
|
||||
// Get new data, different that came from stateParams.
|
||||
promises.push(AddonModWorkshop.getAssessment(this.workshopId, this.assessmentId, {
|
||||
cmId: this.module.id,
|
||||
}).then((assessment) => {
|
||||
// Only allow the student to delete their own submission if it's still editable and hasn't been assessed.
|
||||
if (this.canDelete) {
|
||||
this.canDelete = !assessment;
|
||||
}
|
||||
|
||||
this.submissionInfo.reviewedby = [this.parseAssessment(assessment)];
|
||||
|
||||
return;
|
||||
}));
|
||||
} else if (this.workshop.phase == AddonModWorkshopPhase.PHASE_CLOSED && this.userId == this.currentUserId) {
|
||||
const assessments = await AddonModWorkshop.getSubmissionAssessments(this.workshopId, this.submissionId, {
|
||||
cmId: this.module.id,
|
||||
});
|
||||
|
||||
this.submissionInfo.reviewedby = assessments.map((assessment) => this.parseAssessment(assessment));
|
||||
}
|
||||
|
||||
if (this.canAddFeedback || this.workshop.phase == AddonModWorkshopPhase.PHASE_CLOSED) {
|
||||
this.evaluate = {
|
||||
published: this.submission.published,
|
||||
text: this.submission.feedbackauthor || '',
|
||||
};
|
||||
}
|
||||
|
||||
if (this.canAddFeedback) {
|
||||
|
||||
if (!this.isDestroyed) {
|
||||
// Block the workshop.
|
||||
CoreSync.blockOperation(this.component, this.workshopId);
|
||||
}
|
||||
|
||||
const defaultGrade = Translate.instant('addon.mod_workshop.notoverridden');
|
||||
|
||||
promises.push(CoreGradesHelper.makeGradesMenu(this.workshop.grade || 0, undefined, defaultGrade, -1)
|
||||
.then(async (grades) => {
|
||||
this.evaluationGrades = grades;
|
||||
|
||||
this.evaluate!.grade = {
|
||||
label: CoreGradesHelper.getGradeLabelFromValue(grades, this.submissionInfo.gradeover) ||
|
||||
defaultGrade,
|
||||
value: this.submissionInfo.gradeover || -1,
|
||||
};
|
||||
|
||||
try {
|
||||
const offlineSubmission =
|
||||
await AddonModWorkshopOffline.getEvaluateSubmission(this.workshopId, this.submissionId);
|
||||
|
||||
this.hasOffline = true;
|
||||
this.evaluate!.published = offlineSubmission.published;
|
||||
this.evaluate!.text = offlineSubmission.feedbacktext;
|
||||
this.evaluate!.grade = {
|
||||
label: CoreGradesHelper.getGradeLabelFromValue(
|
||||
grades,
|
||||
parseInt(offlineSubmission.gradeover, 10),
|
||||
) || defaultGrade,
|
||||
value: offlineSubmission.gradeover || -1,
|
||||
};
|
||||
} catch {
|
||||
// Ignore errors.
|
||||
this.hasOffline = false;
|
||||
} finally {
|
||||
this.originalEvaluation.published = this.evaluate!.published;
|
||||
this.originalEvaluation.text = this.evaluate!.text;
|
||||
this.originalEvaluation.grade = this.evaluate!.grade.value;
|
||||
|
||||
this.feedbackForm.controls['published'].setValue(this.evaluate!.published);
|
||||
this.feedbackForm.controls['grade'].setValue(this.evaluate!.grade.value);
|
||||
this.feedbackForm.controls['text'].setValue(this.evaluate!.text);
|
||||
}
|
||||
|
||||
return;
|
||||
}));
|
||||
} else if (this.workshop.phase == AddonModWorkshopPhase.PHASE_CLOSED && this.submission.gradeoverby &&
|
||||
this.evaluate && this.evaluate.text) {
|
||||
promises.push(CoreUser.getProfile(this.submission.gradeoverby, this.courseId, true).then((profile) => {
|
||||
this.evaluateByProfile = profile;
|
||||
|
||||
return;
|
||||
}));
|
||||
}
|
||||
|
||||
if (this.assessment && !this.access.assessingallowed && this.assessment.feedbackreviewer &&
|
||||
this.assessment.gradinggradeoverby) {
|
||||
promises.push(CoreUser.getProfile(this.assessment.gradinggradeoverby, this.courseId, true)
|
||||
.then((profile) => {
|
||||
this.evaluateGradingByProfile = profile;
|
||||
|
||||
return;
|
||||
}));
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
const submissionsActions = await AddonModWorkshopOffline.getSubmissions(this.workshopId);
|
||||
|
||||
this.submission = await AddonModWorkshopHelper.applyOfflineData(this.submission, submissionsActions);
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true);
|
||||
} finally {
|
||||
this.loaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse assessment to be shown.
|
||||
*
|
||||
* @param assessment Original assessment.
|
||||
* @return Parsed assessment.
|
||||
*/
|
||||
protected parseAssessment(
|
||||
assessment: AddonModWorkshopSubmissionAssessmentWithFormData,
|
||||
): AddonModWorkshopSubmissionAssessmentWithFormData {
|
||||
assessment = AddonModWorkshopHelper.realGradeValue(this.workshop, assessment);
|
||||
|
||||
if (this.currentUserId == assessment.reviewerid) {
|
||||
this.ownAssessment = assessment;
|
||||
assessment.ownAssessment = true;
|
||||
}
|
||||
|
||||
return assessment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force leaving the page, without checking for changes.
|
||||
*/
|
||||
protected forceLeavePage(): void {
|
||||
this.forceLeave = true;
|
||||
CoreNavigator.back();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if data has changed.
|
||||
*
|
||||
* @return True if changed, false otherwise.
|
||||
*/
|
||||
protected hasEvaluationChanged(): boolean {
|
||||
if (!this.loaded || !this.access.canoverridegrades) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const inputData = this.feedbackForm.value;
|
||||
|
||||
if (this.originalEvaluation.published != inputData.published) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.originalEvaluation.text != inputData.text) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.originalEvaluation.grade != inputData.grade) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to refresh all the data.
|
||||
*
|
||||
* @return Resolved when done.
|
||||
*/
|
||||
protected async refreshAllData(): Promise<void> {
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
promises.push(AddonModWorkshop.invalidateSubmissionData(this.workshopId, this.submissionId));
|
||||
promises.push(AddonModWorkshop.invalidateSubmissionsData(this.workshopId));
|
||||
promises.push(AddonModWorkshop.invalidateSubmissionAssesmentsData(this.workshopId, this.submissionId));
|
||||
|
||||
if (this.assessmentId) {
|
||||
promises.push(AddonModWorkshop.invalidateAssessmentFormData(this.workshopId, this.assessmentId));
|
||||
promises.push(AddonModWorkshop.invalidateAssessmentData(this.workshopId, this.assessmentId));
|
||||
}
|
||||
|
||||
if (this.assessmentUserId) {
|
||||
promises.push(AddonModWorkshop.invalidateReviewerAssesmentsData(this.workshopId, this.assessmentId));
|
||||
}
|
||||
|
||||
try {
|
||||
await Promise.all(promises);
|
||||
} finally {
|
||||
CoreEvents.trigger(AddonModWorkshopProvider.ASSESSMENT_INVALIDATED, null, this.siteId);
|
||||
|
||||
await this.fetchSubmissionData();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull to refresh.
|
||||
*
|
||||
* @param refresher Refresher.
|
||||
*/
|
||||
refreshSubmission(refresher: IonRefresher): void {
|
||||
if (this.loaded) {
|
||||
this.refreshAllData().finally(() => {
|
||||
refresher?.complete();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the assessment.
|
||||
*/
|
||||
async saveAssessment(): Promise<void> {
|
||||
if (this.assessmentStrategy?.hasDataChanged()) {
|
||||
try {
|
||||
await this.assessmentStrategy.saveAssessment();
|
||||
this.forceLeavePage();
|
||||
} catch {
|
||||
// Error, stay on the page.
|
||||
}
|
||||
} else {
|
||||
// Nothing to save, just go back.
|
||||
this.forceLeavePage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the submission evaluation.
|
||||
*/
|
||||
async saveEvaluation(): Promise<void> {
|
||||
// Check if data has changed.
|
||||
if (this.hasEvaluationChanged()) {
|
||||
await this.sendEvaluation();
|
||||
this.forceLeavePage();
|
||||
} else {
|
||||
// Nothing to save, just go back.
|
||||
this.forceLeavePage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the evaluation to be saved on the server.
|
||||
*
|
||||
* @return Resolved when done.
|
||||
*/
|
||||
protected async sendEvaluation(): Promise<void> {
|
||||
const modal = await CoreDomUtils.showModalLoading('core.sending', true);
|
||||
|
||||
const inputData: {
|
||||
grade: number | string;
|
||||
text: string;
|
||||
published: boolean;
|
||||
} = this.feedbackForm.value;
|
||||
|
||||
inputData.grade = inputData.grade >= 0 ? inputData.grade : '';
|
||||
// Add some HTML to the message if needed.
|
||||
inputData.text = CoreTextUtils.formatHtmlLines(inputData.text);
|
||||
|
||||
// Try to send it to server.
|
||||
try {
|
||||
const result = await AddonModWorkshop.evaluateSubmission(
|
||||
this.workshopId,
|
||||
this.submissionId,
|
||||
this.courseId,
|
||||
inputData.text,
|
||||
inputData.published,
|
||||
String(inputData.grade),
|
||||
);
|
||||
CoreForms.triggerFormSubmittedEvent(this.formElement, !!result, this.siteId);
|
||||
|
||||
await AddonModWorkshop.invalidateSubmissionData(this.workshopId, this.submissionId).finally(() => {
|
||||
const data: AddonModWorkshopSubmissionChangedEventData = {
|
||||
workshopId: this.workshopId,
|
||||
submissionId: this.submissionId,
|
||||
};
|
||||
|
||||
CoreEvents.trigger(AddonModWorkshopProvider.SUBMISSION_CHANGED, data, this.siteId);
|
||||
});
|
||||
} catch (message) {
|
||||
CoreDomUtils.showErrorModalDefault(message, 'Cannot save submission evaluation');
|
||||
} finally {
|
||||
modal.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the submission delete action.
|
||||
*/
|
||||
async deleteSubmission(): Promise<void> {
|
||||
try {
|
||||
await CoreDomUtils.showDeleteConfirm('addon.mod_workshop.submissiondeleteconfirm');
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
const modal = await CoreDomUtils.showModalLoading('core.deleting', true);
|
||||
|
||||
let success = false;
|
||||
try {
|
||||
await AddonModWorkshop.deleteSubmission(this.workshopId, this.submissionId, this.courseId);
|
||||
success = true;
|
||||
|
||||
await AddonModWorkshop.invalidateSubmissionData(this.workshopId, this.submissionId);
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'Cannot delete submission');
|
||||
} finally {
|
||||
modal.dismiss();
|
||||
if (success) {
|
||||
const data: AddonModWorkshopSubmissionChangedEventData = {
|
||||
workshopId: this.workshopId,
|
||||
submissionId: this.submissionId,
|
||||
};
|
||||
|
||||
CoreEvents.trigger(AddonModWorkshopProvider.SUBMISSION_CHANGED, data, this.siteId);
|
||||
|
||||
this.forceLeavePage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo the submission delete action.
|
||||
*
|
||||
* @return Resolved when done.
|
||||
*/
|
||||
async undoDeleteSubmission(): Promise<void> {
|
||||
await AddonModWorkshopOffline.deleteSubmissionAction(
|
||||
this.workshopId,
|
||||
AddonModWorkshopAction.DELETE,
|
||||
).finally(async () => {
|
||||
|
||||
const data: AddonModWorkshopSubmissionChangedEventData = {
|
||||
workshopId: this.workshopId,
|
||||
submissionId: this.submissionId,
|
||||
};
|
||||
|
||||
CoreEvents.trigger(AddonModWorkshopProvider.SUBMISSION_CHANGED, data, this.siteId);
|
||||
|
||||
await this.refreshAllData();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.isDestroyed = true;
|
||||
|
||||
this.syncObserver?.off();
|
||||
this.obsAssessmentSaved?.off();
|
||||
// Restore original back functions.
|
||||
CoreSync.unblockOperation(this.component, this.workshopId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type AddonWorkshopSubmissionEvaluateData = {
|
||||
published: boolean;
|
||||
text: string;
|
||||
grade?: CoreGradesMenuItem;
|
||||
};
|
|
@ -0,0 +1,159 @@
|
|||
// (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 { Injectable, Type } from '@angular/core';
|
||||
import { CoreDelegateHandler, CoreDelegate } from '@classes/delegate';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { CoreFormFields } from '@singletons/form';
|
||||
import { AddonModWorkshopGetAssessmentFormDefinitionData, AddonModWorkshopGetAssessmentFormFieldsParsedData } from './workshop';
|
||||
|
||||
/**
|
||||
* Interface that all assessment strategy handlers must implement.
|
||||
*/
|
||||
export interface AddonWorkshopAssessmentStrategyHandler extends CoreDelegateHandler {
|
||||
/**
|
||||
* The name of the assessment strategy. E.g. 'accumulative'.
|
||||
*/
|
||||
strategyName: string;
|
||||
|
||||
/**
|
||||
* Return the Component to render the plugin.
|
||||
* It's recommended to return the class of the component, but you can also return an instance of the component.
|
||||
*
|
||||
* @param injector Injector.
|
||||
* @return The component (or promise resolved with component) to use, undefined if not found.
|
||||
*/
|
||||
getComponent?(): Type<unknown>;
|
||||
|
||||
/**
|
||||
* Prepare original values to be shown and compared.
|
||||
*
|
||||
* @param form Original data of the form.
|
||||
* @param workshopId WorkShop Id
|
||||
* @return Promise resolved with original values sorted.
|
||||
*/
|
||||
getOriginalValues?(
|
||||
form: AddonModWorkshopGetAssessmentFormDefinitionData,
|
||||
workshopId: number,
|
||||
): Promise<AddonModWorkshopGetAssessmentFormFieldsParsedData[]>;
|
||||
|
||||
/**
|
||||
* Check if the assessment data has changed for a certain submission and workshop for a this strategy plugin.
|
||||
*
|
||||
* @param originalValues Original values of the form.
|
||||
* @param currentValues Current values of the form.
|
||||
* @return True if data has changed, false otherwise.
|
||||
*/
|
||||
hasDataChanged?(
|
||||
originalValues: AddonModWorkshopGetAssessmentFormFieldsParsedData[],
|
||||
currentValues: AddonModWorkshopGetAssessmentFormFieldsParsedData[],
|
||||
): boolean;
|
||||
|
||||
/**
|
||||
* Prepare assessment data to be sent to the server depending on the strategy selected.
|
||||
*
|
||||
* @param currentValues Current values of the form.
|
||||
* @param form Assessment form data.
|
||||
* @return Promise resolved with the data to be sent. Or rejected with the input errors object.
|
||||
*/
|
||||
prepareAssessmentData(
|
||||
currentValues: AddonModWorkshopGetAssessmentFormFieldsParsedData[],
|
||||
form: AddonModWorkshopGetAssessmentFormDefinitionData,
|
||||
): Promise<CoreFormFields<unknown>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate to register workshop assessment strategy handlers.
|
||||
* You can use this service to register your own assessment strategy handlers to be used in a workshop.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonWorkshopAssessmentStrategyDelegateService extends CoreDelegate<AddonWorkshopAssessmentStrategyHandler> {
|
||||
|
||||
protected handlerNameProperty = 'strategyName';
|
||||
|
||||
constructor() {
|
||||
super('AddonWorkshopAssessmentStrategyDelegate', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an assessment strategy plugin is supported.
|
||||
*
|
||||
* @param workshopStrategy Assessment strategy name.
|
||||
* @return True if supported, false otherwise.
|
||||
*/
|
||||
isPluginSupported(workshopStrategy: string): boolean {
|
||||
return this.hasHandler(workshopStrategy, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the directive to use for a certain assessment strategy plugin.
|
||||
*
|
||||
* @param injector Injector.
|
||||
* @param workshopStrategy Assessment strategy name.
|
||||
* @return The component, undefined if not found.
|
||||
*/
|
||||
getComponentForPlugin(workshopStrategy: string): Type<unknown> | undefined {
|
||||
return this.executeFunctionOnEnabled(workshopStrategy, 'getComponent');
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare original values to be shown and compared depending on the strategy selected.
|
||||
*
|
||||
* @param workshopStrategy Workshop strategy.
|
||||
* @param form Original data of the form.
|
||||
* @param workshopId Workshop ID.
|
||||
* @return Resolved with original values sorted.
|
||||
*/
|
||||
getOriginalValues(
|
||||
workshopStrategy: string,
|
||||
form: AddonModWorkshopGetAssessmentFormDefinitionData,
|
||||
workshopId: number,
|
||||
): Promise<AddonModWorkshopGetAssessmentFormFieldsParsedData[]> {
|
||||
return Promise.resolve(this.executeFunctionOnEnabled(workshopStrategy, 'getOriginalValues', [form, workshopId]) || []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the assessment data has changed for a certain submission and workshop for a this strategy plugin.
|
||||
*
|
||||
* @param workshopStrategy Workshop strategy.
|
||||
* @param originalValues Original values of the form.
|
||||
* @param currentValues Current values of the form.
|
||||
* @return True if data has changed, false otherwise.
|
||||
*/
|
||||
hasDataChanged(
|
||||
workshopStrategy: string,
|
||||
originalValues: AddonModWorkshopGetAssessmentFormFieldsParsedData[],
|
||||
currentValues: AddonModWorkshopGetAssessmentFormFieldsParsedData[],
|
||||
): boolean {
|
||||
return this.executeFunctionOnEnabled(workshopStrategy, 'hasDataChanged', [originalValues, currentValues]) || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare assessment data to be sent to the server depending on the strategy selected.
|
||||
*
|
||||
* @param workshopStrategy Workshop strategy to follow.
|
||||
* @param currentValues Current values of the form.
|
||||
* @param form Assessment form data.
|
||||
* @return Promise resolved with the data to be sent. Or rejected with the input errors object.
|
||||
*/
|
||||
prepareAssessmentData(
|
||||
workshopStrategy: string,
|
||||
currentValues: AddonModWorkshopGetAssessmentFormFieldsParsedData[],
|
||||
form: AddonModWorkshopGetAssessmentFormDefinitionData,
|
||||
): Promise<CoreFormFields<unknown> | undefined> {
|
||||
return Promise.resolve(this.executeFunctionOnEnabled(workshopStrategy, 'prepareAssessmentData', [currentValues, form]));
|
||||
}
|
||||
|
||||
}
|
||||
export const AddonWorkshopAssessmentStrategyDelegate = makeSingleton(AddonWorkshopAssessmentStrategyDelegateService);
|
Loading…
Reference in New Issue