MOBILE-3636 assign: Feedback plugins

main
Pau Ferrer Ocaña 2021-02-18 09:55:59 +01:00
parent 85f79bb944
commit c4d37b0074
19 changed files with 954 additions and 10 deletions

View File

@ -13,8 +13,11 @@
// limitations under the License. // limitations under the License.
import { Component, Input, OnInit, ViewChild, Type } from '@angular/core'; import { Component, Input, OnInit, ViewChild, Type } from '@angular/core';
import { CoreError } from '@classes/errors/error';
import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-component'; import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-component';
import { CoreWSExternalFile } from '@services/ws'; import { CoreWSExternalFile } from '@services/ws';
import { ModalController } from '@singletons';
import { AddonModAssignFeedbackCommentsPluginData } from '../../feedback/comments/services/handler';
import { import {
AddonModAssignAssign, AddonModAssignAssign,
AddonModAssignSubmission, AddonModAssignSubmission,
@ -24,6 +27,7 @@ import {
} from '../../services/assign'; } from '../../services/assign';
import { AddonModAssignHelper, AddonModAssignPluginConfig } from '../../services/assign-helper'; import { AddonModAssignHelper, AddonModAssignPluginConfig } from '../../services/assign-helper';
import { AddonModAssignFeedbackDelegate } from '../../services/feedback-delegate'; import { AddonModAssignFeedbackDelegate } from '../../services/feedback-delegate';
import { AddonModAssignEditFeedbackModalComponent } from '../edit-feedback-modal/edit-feedback-modal';
/** /**
* Component that displays an assignment feedback plugin. * Component that displays an assignment feedback plugin.
@ -95,6 +99,47 @@ export class AddonModAssignFeedbackPluginComponent implements OnInit {
} }
} }
/**
* Open a modal to edit the feedback plugin.
*
* @return Promise resolved with the input data, rejected if cancelled.
*/
editFeedback(): Promise<AddonModAssignFeedbackCommentsPluginData> {
if (!this.canEdit) {
throw new CoreError('Cannot edit feedback');
}
return new Promise((resolve, reject): void => {
this.showEditFeedbackModal(resolve, reject);
});
}
protected async showEditFeedbackModal(
resolve: (value: AddonModAssignFeedbackCommentsPluginData | PromiseLike<AddonModAssignFeedbackCommentsPluginData>) => void,
reject: () => void,
): Promise < void> {
// Create the navigation modal.
const modal = await ModalController.instance.create({
component: AddonModAssignEditFeedbackModalComponent,
componentProps: {
assign: this.assign,
submission: this.submission,
plugin: this.plugin,
userId: this.userId,
},
});
await modal.present();
const result = await modal.onDidDismiss();
if (typeof result.data == 'undefined') {
reject();
} else {
resolve(result.data);
}
}
/** /**
* Invalidate the plugin data. * Invalidate the plugin data.
* *

View File

@ -0,0 +1,49 @@
// (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 { AddonModAssignFeedbackCommentsHandler } from './services/handler';
import { AddonModAssignFeedbackCommentsComponent } from './component/comments';
import { CoreSharedModule } from '@/core/shared.module';
import { CoreEditorComponentsModule } from '@features/editor/components/components.module';
import { AddonModAssignFeedbackDelegate } from '../../services/feedback-delegate';
import { AddonModAssignSubmissionFileHandler } from '../../submission/file/services/handler';
@NgModule({
declarations: [
AddonModAssignFeedbackCommentsComponent,
],
imports: [
CoreSharedModule,
CoreEditorComponentsModule,
],
providers: [
AddonModAssignFeedbackCommentsHandler,
{
provide: APP_INITIALIZER,
multi: true,
deps: [],
useFactory: () => () => {
AddonModAssignFeedbackDelegate.instance.registerHandler(AddonModAssignSubmissionFileHandler.instance);
},
},
],
exports: [
AddonModAssignFeedbackCommentsComponent,
],
entryComponents: [
AddonModAssignFeedbackCommentsComponent,
],
})
export class AddonModAssignFeedbackCommentsModule {}

View File

@ -0,0 +1,34 @@
<!-- Read only. -->
<ion-item class="ion-text-wrap" *ngIf="(text || canEdit) && !edit">
<ion-label>
<h2>{{ plugin.name }}</h2>
<p>
<core-format-text [component]="component" [componentId]="assign.cmid" [maxHeight]="80" [fullOnClick]="true"
[fullTitle]="plugin.name" [text]="text" contextLevel="module" [contextInstanceId]="assign.cmid"
[courseId]="assign.course">
</core-format-text>
</p>
</ion-label>
<div slot="end">
<div class="ion-text-end">
<ion-button fill="clear" *ngIf="canEdit" (click)="editComment()" color="dark">
<ion-icon name="fas-pen" slot="icon-only"></ion-icon>
</ion-button>
</div>
<ion-note *ngIf="!isSent" color="dark">
<ion-icon name="far-clock"></ion-icon>
{{ 'core.notsent' | translate }}
</ion-note>
</div>
</ion-item>
<!-- Edit -->
<ion-item class="ion-text-wrap" *ngIf="edit && loaded">
<ion-label>
<core-rich-text-editor item-content [control]="control" [placeholder]="plugin.name"
name="assignfeedbackcomments_editor" [component]="component" [componentId]="assign.cmid" [autoSave]="true"
contextLevel="module" [contextInstanceId]="assign.cmid" elementId="assignfeedbackcomments_editor"
[draftExtraParams]="{userid: userId, action: 'grade'}">
</core-rich-text-editor>
</ion-label>
</ion-item>

View File

@ -0,0 +1,161 @@
// (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, ElementRef } from '@angular/core';
import { FormBuilder, FormControl } from '@angular/forms';
import { AddonModAssignFeedbackPluginComponent } from '@addons/mod/assign/components/feedback-plugin/feedback-plugin';
import { AddonModAssign, AddonModAssignProvider } from '@addons/mod/assign/services/assign';
import { CoreTextUtils } from '@services/utils/text';
import {
AddonModAssignFeedbackCommentsDraftData,
AddonModAssignFeedbackCommentsHandler,
AddonModAssignFeedbackCommentsPluginData,
} from '../services/handler';
import { AddonModAssignFeedbackDelegate } from '@addons/mod/assign/services/feedback-delegate';
import { AddonModAssignOffline } from '@addons/mod/assign/services/assign-offline';
import { CoreUtils } from '@services/utils/utils';
/**
* Component to render a comments feedback plugin.
*/
@Component({
selector: 'addon-mod-assign-feedback-comments',
templateUrl: 'addon-mod-assign-feedback-comments.html',
})
export class AddonModAssignFeedbackCommentsComponent extends AddonModAssignFeedbackPluginComponent implements OnInit {
control?: FormControl;
component = AddonModAssignProvider.COMPONENT;
text = '';
isSent = false;
loaded = false;
protected element: HTMLElement;
constructor(
element: ElementRef,
protected fb: FormBuilder,
) {
super();
this.element = element.nativeElement;
}
/**
* Component being initialized.
*/
async ngOnInit(): Promise<void> {
try {
this.text = await this.getText();
if (!this.canEdit && !this.edit) {
// User cannot edit the comment. Show it full when clicked.
this.element.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
if (this.text) {
// Open a new state with the text.
CoreTextUtils.instance.viewText(this.plugin.name, this.text, {
component: this.component,
componentId: this.assign.cmid,
filter: true,
contextLevel: 'module',
instanceId: this.assign.cmid,
courseId: this.assign.course,
});
}
});
} else if (this.edit) {
this.control = this.fb.control(this.text);
}
} finally {
this.loaded = true;
}
}
/**
* Edit the comment.
*/
async editComment(): Promise<void> {
try {
const inputData = await this.editFeedback();
const text = AddonModAssignFeedbackCommentsHandler.instance.getTextFromInputData(this.plugin, inputData);
// Update the text and save it as draft.
this.isSent = false;
this.text = this.replacePluginfileUrls(text);
AddonModAssignFeedbackDelegate.instance.saveFeedbackDraft(this.assign.id, this.userId, this.plugin, {
text: text,
format: 1,
});
} catch {
// User cancelled, nothing to do.
}
}
/**
* Get the text for the plugin.
*
* @return Promise resolved with the text.
*/
protected async getText(): Promise<string> {
// Check if the user already modified the comment.
const draft: AddonModAssignFeedbackCommentsDraftData | undefined =
await AddonModAssignFeedbackDelegate.instance.getPluginDraftData(this.assign.id, this.userId, this.plugin);
if (draft) {
this.isSent = false;
return this.replacePluginfileUrls(draft.text);
}
// There is no draft saved. Check if we have anything offline.
const offlineData = await CoreUtils.instance.ignoreErrors(
AddonModAssignOffline.instance.getSubmissionGrade(this.assign.id, this.userId),
undefined,
);
if (offlineData && offlineData.plugindata && offlineData.plugindata.assignfeedbackcomments_editor) {
const pluginData = <AddonModAssignFeedbackCommentsPluginData>offlineData.plugindata;
// Save offline as draft.
this.isSent = false;
AddonModAssignFeedbackDelegate.instance.saveFeedbackDraft(
this.assign.id,
this.userId,
this.plugin,
pluginData.assignfeedbackcomments_editor,
);
return this.replacePluginfileUrls(pluginData.assignfeedbackcomments_editor.text);
}
// No offline data found, return online text.
this.isSent = true;
return AddonModAssign.instance.getSubmissionPluginText(this.plugin);
}
/**
* Replace @@PLUGINFILE@@ wildcards with the real URL of embedded files.
*
* @param Text to treat.
* @return Treated text.
*/
replacePluginfileUrls(text: string): string {
const files = this.plugin.fileareas && this.plugin.fileareas[0] && this.plugin.fileareas[0].files;
return CoreTextUtils.instance.replacePluginfileUrls(text, files || []);
}
}

View File

@ -0,0 +1,3 @@
{
"pluginname": "Feedback comments"
}

View File

@ -0,0 +1,266 @@
// (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 {
AddonModAssignPlugin,
AddonModAssignAssign,
AddonModAssignSubmission,
AddonModAssign,
AddonModAssignSavePluginData,
} from '@addons/mod/assign/services/assign';
import { AddonModAssignOffline } from '@addons/mod/assign/services/assign-offline';
import { AddonModAssignFeedbackHandler } from '@addons/mod/assign/services/feedback-delegate';
import { Injectable, Type } from '@angular/core';
import { CoreSites } from '@services/sites';
import { CoreTextUtils } from '@services/utils/text';
import { CoreUtils } from '@services/utils/utils';
import { CoreWSExternalFile } from '@services/ws';
import { makeSingleton } from '@singletons';
import { AddonModAssignFeedbackCommentsComponent } from '../component/comments';
/**
* Handler for comments feedback plugin.
*/
@Injectable( { providedIn: 'root' })
export class AddonModAssignFeedbackCommentsHandlerService implements AddonModAssignFeedbackHandler {
name = 'AddonModAssignFeedbackCommentsHandler';
type = 'comments';
// Store the data in this service so it isn't lost if the user performs a PTR in the page.
protected drafts: { [draftId: string]: AddonModAssignFeedbackCommentsDraftData } = {};
/**
* Get the text to submit.
*
* @param textUtils Text utils instance.
* @param plugin Plugin.
* @param inputData Data entered in the feedback edit form.
* @return Text to submit.
*/
getTextFromInputData(plugin: AddonModAssignPlugin, inputData: AddonModAssignFeedbackCommentsPluginData): string {
const files = plugin.fileareas && plugin.fileareas[0] ? plugin.fileareas[0].files : [];
let text = '';
// The input data can have a string or an object with text and format. Get the text.
if (inputData.assignfeedbackcomments_editor && inputData.assignfeedbackcomments_editor.text) {
text = inputData.assignfeedbackcomments_editor.text;
}
return CoreTextUtils.instance.restorePluginfileUrls(text, files || []);
}
/**
* Discard the draft data of the feedback plugin.
*
* @param assignId The assignment ID.
* @param userId User ID.
* @param siteId Site ID. If not defined, current site.
* @return If the function is async, it should return a Promise resolved when done.
*/
discardDraft(assignId: number, userId: number, siteId?: string): void {
const id = this.getDraftId(assignId, userId, siteId);
if (typeof this.drafts[id] != 'undefined') {
delete this.drafts[id];
}
}
/**
* Return the Component to use to display the plugin data.
* It's recommended to return the class of the component, but you can also return an instance of the component.
*
* @return The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent(): Type<unknown> {
return AddonModAssignFeedbackCommentsComponent;
}
/**
* Return the draft saved data of the feedback plugin.
*
* @param assignId The assignment ID.
* @param userId User ID.
* @param siteId Site ID. If not defined, current site.
* @return Data (or promise resolved with the data).
*/
getDraft(assignId: number, userId: number, siteId?: string): AddonModAssignFeedbackCommentsDraftData | undefined {
const id = this.getDraftId(assignId, userId, siteId);
if (typeof this.drafts[id] != 'undefined') {
return this.drafts[id];
}
}
/**
* Get a draft ID.
*
* @param assignId The assignment ID.
* @param userId User ID.
* @param siteId Site ID. If not defined, current site.
* @return Draft ID.
*/
protected getDraftId(assignId: number, userId: number, siteId?: string): string {
siteId = siteId || CoreSites.instance.getCurrentSiteId();
return siteId + '#' + assignId + '#' + userId;
}
/**
* Get files used by this plugin.
* The files returned by this function will be prefetched when the user prefetches the assign.
*
* @param assign The assignment.
* @param submission The submission.
* @param plugin The plugin object.
* @return The files (or promise resolved with the files).
*/
getPluginFiles(
assign: AddonModAssignAssign,
submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin,
): CoreWSExternalFile[] {
return AddonModAssign.instance.getSubmissionPluginAttachments(plugin);
}
/**
* Check if the feedback data has changed for this plugin.
*
* @param assign The assignment.
* @param submission The submission.
* @param plugin The plugin object.
* @param inputData Data entered by the user for the feedback.
* @param userId User ID of the submission.
* @return Boolean (or promise resolved with boolean): whether the data has changed.
*/
async hasDataChanged(
assign: AddonModAssignAssign,
submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin,
inputData: AddonModAssignFeedbackCommentsPluginData,
userId: number,
): Promise<boolean> {
// Get it from plugin or offline.
const offlineData = await CoreUtils.instance.ignoreErrors(
AddonModAssignOffline.instance.getSubmissionGrade(assign.id, userId),
undefined,
);
if (offlineData?.plugindata?.assignfeedbackcomments_editor) {
const pluginData = <AddonModAssignFeedbackCommentsPluginData>offlineData.plugindata;
return !!pluginData.assignfeedbackcomments_editor.text;
}
// No offline data found, get text from plugin.
const initialText = AddonModAssign.instance.getSubmissionPluginText(plugin);
const newText = AddonModAssignFeedbackCommentsHandler.instance.getTextFromInputData(plugin, inputData);
if (typeof newText == 'undefined') {
return false;
}
// Check if text has changed.
return initialText != newText;
}
/**
* Check whether the plugin has draft data stored.
*
* @param assignId The assignment ID.
* @param userId User ID.
* @param siteId Site ID. If not defined, current site.
* @return Boolean or promise resolved with boolean: whether the plugin has draft data.
*/
hasDraftData(assignId: number, userId: number, siteId?: string): boolean | Promise<boolean> {
const draft = this.getDraft(assignId, userId, siteId);
return !!draft;
}
/**
* Whether or not the handler is enabled on a site level.
*
* @return True or promise resolved with true if enabled.
*/
async isEnabled(): Promise<boolean> {
// In here we should check if comments is not disabled in site.
// But due to this is not a common comments place and it can be disabled separately into Moodle (disabling the plugin).
// We are leaving it always enabled. It's also a teacher's feature.
return true;
}
/**
* Prepare and add to pluginData the data to send to the server based on the draft data saved.
*
* @param assignId The assignment ID.
* @param userId User ID.
* @param plugin The plugin object.
* @param pluginData Object where to store the data to send.
* @param siteId Site ID. If not defined, current site.
* @return If the function is async, it should return a Promise resolved when done.
*/
prepareFeedbackData(
assignId: number,
userId: number,
plugin: AddonModAssignPlugin,
pluginData: AddonModAssignSavePluginData,
siteId?: string,
): void {
const draft = this.getDraft(assignId, userId, siteId);
if (draft) {
// Add some HTML to the text if needed.
draft.text = CoreTextUtils.instance.formatHtmlLines(draft.text);
pluginData.assignfeedbackcomments_editor = draft;
}
}
/**
* Save draft data of the feedback plugin.
*
* @param assignId The assignment ID.
* @param userId User ID.
* @param plugin The plugin object.
* @param data The data to save.
* @param siteId Site ID. If not defined, current site.
* @return If the function is async, it should return a Promise resolved when done.
*/
saveDraft(
assignId: number,
userId: number,
plugin: AddonModAssignPlugin,
data: AddonModAssignFeedbackCommentsDraftData,
siteId?: string,
): void {
if (data) {
this.drafts[this.getDraftId(assignId, userId, siteId)] = data;
}
}
}
export const AddonModAssignFeedbackCommentsHandler = makeSingleton(AddonModAssignFeedbackCommentsHandlerService);
export type AddonModAssignFeedbackCommentsDraftData = {
text: string; // The text for this feedback.
format: number; // The format for this feedback.
};
export type AddonModAssignFeedbackCommentsPluginData = {
// Editor structure.
// eslint-disable-next-line @typescript-eslint/naming-convention
assignfeedbackcomments_editor: AddonModAssignFeedbackCommentsDraftData;
};

View File

@ -0,0 +1,11 @@
<!-- Read only. -->
<ion-item class="ion-text-wrap" *ngIf="files && files.length">
<ion-label>
<h2>{{plugin.name}}</h2>
<ng-container>
<core-file *ngFor="let file of files" [file]="file" [component]="component" [componentId]="assign.cmid"
[alwaysDownload]="true">
</core-file>
</ng-container>
</ion-label>
</ion-item>

View File

@ -0,0 +1,41 @@
// (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 { AddonModAssignFeedbackPluginComponent } from '@addons/mod/assign/components/feedback-plugin/feedback-plugin';
import { AddonModAssignProvider, AddonModAssign } from '@addons/mod/assign/services/assign';
import { Component, OnInit } from '@angular/core';
import { CoreWSExternalFile } from '@services/ws';
/**
* Component to render a edit pdf feedback plugin.
*/
@Component({
selector: 'addon-mod-assign-feedback-edit-pdf',
templateUrl: 'addon-mod-assign-feedback-editpdf.html',
})
export class AddonModAssignFeedbackEditPdfComponent extends AddonModAssignFeedbackPluginComponent implements OnInit {
component = AddonModAssignProvider.COMPONENT;
files: CoreWSExternalFile[] = [];
/**
* Component being initialized.
*/
async ngOnInit(): Promise<void> {
if (this.plugin) {
this.files = AddonModAssign.instance.getSubmissionPluginAttachments(this.plugin);
}
}
}

View File

@ -0,0 +1,46 @@
// (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 { AddonModAssignFeedbackEditPdfHandler } from './services/handler';
import { AddonModAssignFeedbackEditPdfComponent } from './component/editpdf';
import { CoreSharedModule } from '@/core/shared.module';
import { AddonModAssignFeedbackDelegate } from '../../services/feedback-delegate';
@NgModule({
declarations: [
AddonModAssignFeedbackEditPdfComponent,
],
imports: [
CoreSharedModule,
],
providers: [
AddonModAssignFeedbackEditPdfHandler,
{
provide: APP_INITIALIZER,
multi: true,
deps: [],
useFactory: () => () => {
AddonModAssignFeedbackDelegate.instance.registerHandler(AddonModAssignFeedbackEditPdfHandler.instance);
},
},
],
exports: [
AddonModAssignFeedbackEditPdfComponent,
],
entryComponents: [
AddonModAssignFeedbackEditPdfComponent,
],
})
export class AddonModAssignFeedbackEditPdfModule {}

View File

@ -0,0 +1,3 @@
{
"pluginname": "Annotate PDF"
}

View File

@ -0,0 +1,73 @@
// (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 {
AddonModAssignPlugin,
AddonModAssignAssign,
AddonModAssignSubmission,
AddonModAssign,
} from '@addons/mod/assign/services/assign';
import { AddonModAssignFeedbackHandler } from '@addons/mod/assign/services/feedback-delegate';
import { Injectable, Type } from '@angular/core';
import { CoreWSExternalFile } from '@services/ws';
import { makeSingleton } from '@singletons';
import { AddonModAssignFeedbackEditPdfComponent } from '../component/editpdf';
/**
* Handler for edit pdf feedback plugin.
*/
@Injectable( { providedIn: 'root' })
export class AddonModAssignFeedbackEditPdfHandlerService implements AddonModAssignFeedbackHandler {
name = 'AddonModAssignFeedbackEditPdfHandler';
type = 'editpdf';
/**
* Return the Component to use to display the plugin data.
* It's recommended to return the class of the component, but you can also return an instance of the component.
*
* @return The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent(): Type<unknown> {
return AddonModAssignFeedbackEditPdfComponent;
}
/**
* Get files used by this plugin.
* The files returned by this function will be prefetched when the user prefetches the assign.
*
* @param assign The assignment.
* @param submission The submission.
* @param plugin The plugin object.
* @return The files (or promise resolved with the files).
*/
getPluginFiles(
assign: AddonModAssignAssign,
submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin,
): CoreWSExternalFile[] {
return AddonModAssign.instance.getSubmissionPluginAttachments(plugin);
}
/**
* Whether or not the handler is enabled on a site level.
*
* @return True or promise resolved with true if enabled.
*/
async isEnabled(): Promise<boolean> {
return true;
}
}
export const AddonModAssignFeedbackEditPdfHandler = makeSingleton(AddonModAssignFeedbackEditPdfHandlerService);

View File

@ -0,0 +1,27 @@
// (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 { AddonModAssignFeedbackCommentsModule } from './comments/comments.module';
import { AddonModAssignFeedbackEditPdfModule } from './editpdf/editpdf.module';
import { AddonModAssignFeedbackFileModule } from './file/file.module';
@NgModule({
imports: [
AddonModAssignFeedbackCommentsModule,
AddonModAssignFeedbackEditPdfModule,
AddonModAssignFeedbackFileModule,
],
})
export class AddonModAssignFeedbackModule { }

View File

@ -0,0 +1,11 @@
<!-- Read only. -->
<ion-item class="ion-text-wrap" *ngIf="files && files.length">
<ion-label>
<h2>{{plugin.name}}</h2>
<ng-container>
<core-file *ngFor="let file of files" [file]="file" [component]="component" [componentId]="assign.cmid"
[alwaysDownload]="true">
</core-file>
</ng-container>
</ion-label>
</ion-item>

View File

@ -0,0 +1,41 @@
// (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 { AddonModAssignFeedbackPluginComponent } from '@addons/mod/assign/components/feedback-plugin/feedback-plugin';
import { AddonModAssign, AddonModAssignProvider } from '@addons/mod/assign/services/assign';
import { Component, OnInit } from '@angular/core';
import { CoreWSExternalFile } from '@services/ws';
/**
* Component to render a file feedback plugin.
*/
@Component({
selector: 'addon-mod-assign-feedback-file',
templateUrl: 'addon-mod-assign-feedback-file.html',
})
export class AddonModAssignFeedbackFileComponent extends AddonModAssignFeedbackPluginComponent implements OnInit {
component = AddonModAssignProvider.COMPONENT;
files: CoreWSExternalFile[] = [];
/**
* Component being initialized.
*/
async ngOnInit(): Promise<void> {
if (this.plugin) {
this.files = AddonModAssign.instance.getSubmissionPluginAttachments(this.plugin);
}
}
}

View File

@ -0,0 +1,46 @@
// (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 { AddonModAssignFeedbackFileHandler } from './services/handler';
import { AddonModAssignFeedbackFileComponent } from './component/file';
import { CoreSharedModule } from '@/core/shared.module';
import { AddonModAssignFeedbackDelegate } from '../../services/feedback-delegate';
@NgModule({
declarations: [
AddonModAssignFeedbackFileComponent,
],
imports: [
CoreSharedModule,
],
providers: [
AddonModAssignFeedbackFileHandler,
{
provide: APP_INITIALIZER,
multi: true,
deps: [],
useFactory: () => () => {
AddonModAssignFeedbackDelegate.instance.registerHandler(AddonModAssignFeedbackFileHandler.instance);
},
},
],
exports: [
AddonModAssignFeedbackFileComponent,
],
entryComponents: [
AddonModAssignFeedbackFileComponent,
],
})
export class AddonModAssignFeedbackFileModule {}

View File

@ -0,0 +1,3 @@
{
"pluginname": "File feedback"
}

View File

@ -0,0 +1,73 @@
// (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 {
AddonModAssignPlugin,
AddonModAssignAssign,
AddonModAssignSubmission,
AddonModAssign,
} from '@addons/mod/assign/services/assign';
import { AddonModAssignFeedbackHandler } from '@addons/mod/assign/services/feedback-delegate';
import { Injectable, Type } from '@angular/core';
import { CoreWSExternalFile } from '@services/ws';
import { makeSingleton } from '@singletons';
import { AddonModAssignFeedbackFileComponent } from '../component/file';
/**
* Handler for file feedback plugin.
*/
@Injectable( { providedIn: 'root' })
export class AddonModAssignFeedbackFileHandlerService implements AddonModAssignFeedbackHandler {
name = 'AddonModAssignFeedbackFileHandler';
type = 'file';
/**
* Return the Component to use to display the plugin data.
* It's recommended to return the class of the component, but you can also return an instance of the component.
*
* @return The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent(): Type<unknown> {
return AddonModAssignFeedbackFileComponent;
}
/**
* Get files used by this plugin.
* The files returned by this function will be prefetched when the user prefetches the assign.
*
* @param assign The assignment.
* @param submission The submission.
* @param plugin The plugin object.
* @return The files (or promise resolved with the files).
*/
getPluginFiles(
assign: AddonModAssignAssign,
submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin,
): CoreWSExternalFile[] {
return AddonModAssign.instance.getSubmissionPluginAttachments(plugin);
}
/**
* Whether or not the handler is enabled on a site level.
*
* @return True or promise resolved with true if enabled.
*/
async isEnabled(): Promise<boolean> {
return true;
}
}
export const AddonModAssignFeedbackFileHandler = makeSingleton(AddonModAssignFeedbackFileHandlerService);

View File

@ -58,7 +58,11 @@ export interface AddonModAssignFeedbackHandler extends CoreDelegateHandler {
* @param siteId Site ID. If not defined, current site. * @param siteId Site ID. If not defined, current site.
* @return Data (or promise resolved with the data). * @return Data (or promise resolved with the data).
*/ */
getDraft?(assignId: number, userId: number, siteId?: string): any | Promise<any>; getDraft?(
assignId: number,
userId: number,
siteId?: string,
): Record<string, unknown> | Promise<Record<string, unknown> | undefined> | undefined;
/** /**
* Get files used by this plugin. * Get files used by this plugin.
@ -99,7 +103,7 @@ export interface AddonModAssignFeedbackHandler extends CoreDelegateHandler {
assign: AddonModAssignAssign, assign: AddonModAssignAssign,
submission: AddonModAssignSubmission, submission: AddonModAssignSubmission,
plugin: AddonModAssignPlugin, plugin: AddonModAssignPlugin,
inputData: any, inputData: Record<string, unknown>,
userId: number, userId: number,
): boolean | Promise<boolean>; ): boolean | Promise<boolean>;
@ -158,7 +162,13 @@ export interface AddonModAssignFeedbackHandler extends CoreDelegateHandler {
* @param siteId Site ID. If not defined, current site. * @param siteId Site ID. If not defined, current site.
* @return If the function is async, it should return a Promise resolved when done. * @return If the function is async, it should return a Promise resolved when done.
*/ */
saveDraft?(assignId: number, userId: number, plugin: AddonModAssignPlugin, data: any, siteId?: string): void | Promise<any>; saveDraft?(
assignId: number,
userId: number,
plugin: AddonModAssignPlugin,
data: Record<string, unknown>,
siteId?: string,
): void | Promise<void>;
} }
/** /**
@ -189,7 +199,7 @@ export class AddonModAssignFeedbackDelegateService extends CoreDelegate<AddonMod
userId: number, userId: number,
plugin: AddonModAssignPlugin, plugin: AddonModAssignPlugin,
siteId?: string, siteId?: string,
): Promise<any | undefined> { ): Promise<void> {
return await this.executeFunctionOnEnabled(plugin.type, 'discardDraft', [assignId, userId, siteId]); return await this.executeFunctionOnEnabled(plugin.type, 'discardDraft', [assignId, userId, siteId]);
} }
@ -212,12 +222,12 @@ export class AddonModAssignFeedbackDelegateService extends CoreDelegate<AddonMod
* @param siteId Site ID. If not defined, current site. * @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the draft data. * @return Promise resolved with the draft data.
*/ */
async getPluginDraftData( async getPluginDraftData<T>(
assignId: number, assignId: number,
userId: number, userId: number,
plugin: AddonModAssignPlugin, plugin: AddonModAssignPlugin,
siteId?: string, siteId?: string,
): Promise<any | undefined> { ): Promise<T | undefined> {
return await this.executeFunctionOnEnabled(plugin.type, 'getDraft', [assignId, userId, siteId]); return await this.executeFunctionOnEnabled(plugin.type, 'getDraft', [assignId, userId, siteId]);
} }
@ -267,7 +277,7 @@ export class AddonModAssignFeedbackDelegateService extends CoreDelegate<AddonMod
assign: AddonModAssignAssign, assign: AddonModAssignAssign,
submission: AddonModAssignSubmission | AddonModAssignSubmissionFormatted, submission: AddonModAssignSubmission | AddonModAssignSubmissionFormatted,
plugin: AddonModAssignPlugin, plugin: AddonModAssignPlugin,
inputData: any, inputData: Record<string, unknown>,
userId: number, userId: number,
): Promise<boolean | undefined> { ): Promise<boolean | undefined> {
return await this.executeFunctionOnEnabled( return await this.executeFunctionOnEnabled(
@ -362,9 +372,9 @@ export class AddonModAssignFeedbackDelegateService extends CoreDelegate<AddonMod
assignId: number, assignId: number,
userId: number, userId: number,
plugin: AddonModAssignPlugin, plugin: AddonModAssignPlugin,
inputData: any, inputData: Record<string, unknown>,
siteId?: string, siteId?: string,
): Promise<any> { ): Promise<void> {
return await this.executeFunctionOnEnabled( return await this.executeFunctionOnEnabled(
plugin.type, plugin.type,
'saveDraft', 'saveDraft',

View File

@ -41,8 +41,9 @@ export class AddonModAssignDefaultFeedbackHandler implements AddonModAssignFeedb
* *
* @return Data (or promise resolved with the data). * @return Data (or promise resolved with the data).
*/ */
getDraft(): void { getDraft(): undefined {
// Nothing to do. // Nothing to do.
return;
} }
/** /**