MOBILE-2334 assign: Implement feedback plugins

main
Dani Palou 2018-04-18 13:00:29 +02:00
parent 2aa4a55d17
commit dc74546f3d
25 changed files with 1171 additions and 4 deletions

View File

@ -28,12 +28,14 @@ import { AddonModAssignModuleHandler } from './providers/module-handler';
import { AddonModAssignPrefetchHandler } from './providers/prefetch-handler';
import { AddonModAssignSyncCronHandler } from './providers/sync-cron-handler';
import { AddonModAssignSubmissionModule } from './submission/submission.module';
import { AddonModAssignFeedbackModule } from './feedback/feedback.module';
@NgModule({
declarations: [
],
imports: [
AddonModAssignSubmissionModule
AddonModAssignSubmissionModule,
AddonModAssignFeedbackModule
],
providers: [
AddonModAssignProvider,

View File

@ -0,0 +1,70 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { Input } from '@angular/core';
import { ModalController } from 'ionic-angular';
/**
* Base class for component to render a feedback plugin.
*/
export class AddonModAssignFeedbackPluginComponent {
@Input() assign: any; // The assignment.
@Input() submission: any; // The submission.
@Input() plugin: any; // The plugin object.
@Input() userId: number; // The user ID of the submission.
@Input() configs: any; // The configs for the plugin.
@Input() canEdit: boolean; // Whether the user can edit.
@Input() edit: boolean; // Whether the user is editing.
constructor(protected modalCtrl: ModalController) { }
/**
* Open a modal to edit the feedback plugin.
*
* @return {Promise<any>} Promise resolved with the input data, rejected if cancelled.
*/
editFeedback(): Promise<any> {
if (this.canEdit) {
return new Promise((resolve, reject): void => {
// Create the navigation modal.
const modal = this.modalCtrl.create('AddonModAssignEditFeedbackModalPage', {
assign: this.assign,
submission: this.submission,
plugin: this.plugin,
userId: this.userId
});
modal.present();
modal.onDidDismiss((data) => {
if (typeof data == 'undefined') {
reject();
} else {
resolve(data);
}
});
});
} else {
return Promise.reject(null);
}
}
/**
* Invalidate the data.
*
* @return {Promise<any>} Promise resolved when done.
*/
invalidate(): Promise<any> {
return Promise.resolve();
}
}

View File

@ -23,12 +23,14 @@ import { CoreCourseComponentsModule } from '@core/course/components/components.m
import { AddonModAssignIndexComponent } from './index/index';
import { AddonModAssignSubmissionComponent } from './submission/submission';
import { AddonModAssignSubmissionPluginComponent } from './submission-plugin/submission-plugin';
import { AddonModAssignFeedbackPluginComponent } from './feedback-plugin/feedback-plugin';
@NgModule({
declarations: [
AddonModAssignIndexComponent,
AddonModAssignSubmissionComponent,
AddonModAssignSubmissionPluginComponent
AddonModAssignSubmissionPluginComponent,
AddonModAssignFeedbackPluginComponent
],
imports: [
CommonModule,
@ -44,7 +46,8 @@ import { AddonModAssignSubmissionPluginComponent } from './submission-plugin/sub
exports: [
AddonModAssignIndexComponent,
AddonModAssignSubmissionComponent,
AddonModAssignSubmissionPluginComponent
AddonModAssignSubmissionPluginComponent,
AddonModAssignFeedbackPluginComponent
],
entryComponents: [
AddonModAssignIndexComponent

View File

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

View File

@ -0,0 +1,104 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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, Injector, ViewChild } from '@angular/core';
import { AddonModAssignProvider } from '../../providers/assign';
import { AddonModAssignHelperProvider } from '../../providers/helper';
import { AddonModAssignFeedbackDelegate } from '../../providers/feedback-delegate';
import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-component';
/**
* Component that displays an assignment feedback plugin.
*/
@Component({
selector: 'addon-mod-assign-feedback-plugin',
templateUrl: 'feedback-plugin.html',
})
export class AddonModAssignFeedbackPluginComponent implements OnInit {
@ViewChild(CoreDynamicComponent) dynamicComponent: CoreDynamicComponent;
@Input() assign: any; // The assignment.
@Input() submission: any; // The submission.
@Input() plugin: any; // The plugin object.
@Input() userId: number; // The user ID of the submission.
@Input() canEdit: boolean | string; // Whether the user can edit.
@Input() edit: boolean | string; // Whether the user is editing.
pluginComponent: any; // Component to render the plugin.
data: any; // Data to pass to the component.
// Data to render the plugin if it isn't supported.
component = AddonModAssignProvider.COMPONENT;
text = '';
files = [];
notSupported: boolean;
pluginLoaded: boolean;
constructor(protected injector: Injector, protected feedbackDelegate: AddonModAssignFeedbackDelegate,
protected assignProvider: AddonModAssignProvider, protected assignHelper: AddonModAssignHelperProvider) { }
/**
* Component being initialized.
*/
ngOnInit(): void {
if (!this.plugin) {
this.pluginLoaded = true;
return;
}
this.plugin.name = this.feedbackDelegate.getPluginName(this.plugin);
if (!this.plugin.name) {
this.pluginLoaded = true;
return;
}
this.edit = this.edit && this.edit !== 'false';
this.canEdit = this.canEdit && this.canEdit !== 'false';
// Check if the plugin has defined its own component to render itself.
this.feedbackDelegate.getComponentForPlugin(this.injector, this.plugin).then((component) => {
this.pluginComponent = component;
if (component) {
// Prepare the data to pass to the component.
this.data = {
assign: this.assign,
submission: this.submission,
plugin: this.plugin,
userId: this.userId,
configs: this.assignHelper.getPluginConfig(this.assign, 'assignfeedback', this.plugin.type),
edit: this.edit,
canEdit: this.canEdit
};
} else {
// Data to render the plugin.
this.text = this.assignProvider.getSubmissionPluginText(this.plugin);
this.files = this.assignProvider.getSubmissionPluginAttachments(this.plugin);
this.notSupported = this.feedbackDelegate.isPluginSupported(this.plugin.type);
this.pluginLoaded = true;
}
});
}
/**
* Invalidate the plugin data.
*
* @return {Promise<any>} Promise resolved when done.
*/
invalidate(): Promise<any> {
return Promise.resolve(this.dynamicComponent && this.dynamicComponent.callComponentFunction('invalidate', []));
}
}

View File

@ -176,7 +176,7 @@
<p item-content *ngIf="!canSaveGrades || !outcome.itemNumber">{{ outcome.selected }}</p>
</ion-item>
<!-- @todo <addon-mod-assign-feedback-plugin *ngFor="let plugin of feedback.plugins" assign="assign" submission="userSubmission" userid="submitId" plugin="plugin" can-edit="canSaveGrades"></addon-mod-assign-feedback-plugin> -->
<addon-mod-assign-feedback-plugin *ngFor="let plugin of feedback.plugins" [assign]="assign" [submission]="userSubmission" [userId]="submitId" [plugin]="plugin" [canEdit]="canSaveGrades"></addon-mod-assign-feedback-plugin>
<!-- Workflow status. -->
<ion-item text-wrap *ngIf="workflowStatusTranslationId">

View File

@ -0,0 +1,50 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { CommonModule } from '@angular/common';
import { IonicModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { AddonModAssignFeedbackCommentsHandler } from './providers/handler';
import { AddonModAssignFeedbackCommentsComponent } from './component/comments';
import { AddonModAssignFeedbackDelegate } from '../../providers/feedback-delegate';
import { CoreComponentsModule } from '@components/components.module';
import { CoreDirectivesModule } from '@directives/directives.module';
@NgModule({
declarations: [
AddonModAssignFeedbackCommentsComponent
],
imports: [
CommonModule,
IonicModule,
TranslateModule.forChild(),
CoreComponentsModule,
CoreDirectivesModule
],
providers: [
AddonModAssignFeedbackCommentsHandler
],
exports: [
AddonModAssignFeedbackCommentsComponent
],
entryComponents: [
AddonModAssignFeedbackCommentsComponent
]
})
export class AddonModAssignFeedbackCommentsModule {
constructor(feedbackDelegate: AddonModAssignFeedbackDelegate, handler: AddonModAssignFeedbackCommentsHandler) {
feedbackDelegate.registerHandler(handler);
}
}

View File

@ -0,0 +1,24 @@
<!-- Read only. -->
<ion-item text-wrap *ngIf="(text || canEdit) && !edit">
<h2>{{ plugin.name }}</h2>
<p>
<core-format-text [component]="component" [componentId]="assign.cmid" [maxHeight]="80" [fullOnClick]="true" [fullTitle]="plugin.name" [text]="text"></core-format-text>
</p>
<div item-end>
<div text-right>
<a ion-button *ngIf="canEdit" (click)="editComment()" color="light">
<ion-icon name="create"></ion-icon>
</a>
</div>
<ion-note *ngIf="!isSent">
<ion-icon name="clock"></ion-icon>
{{ 'core.notsent' | translate }}
</ion-note>
</div>
</ion-item>
<!-- Edit -->
<ion-item text-wrap *ngIf="edit && loaded">
<!-- @todo: [component]="component" [componentId]="assign.cmid" -->
<core-rich-text-editor item-content [control]="control" [placeholder]="plugin.name" name="assignfeedbackcomments_editor"></core-rich-text-editor>
</ion-item>

View File

@ -0,0 +1,148 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { ModalController } from 'ionic-angular';
import { FormBuilder, FormControl } from '@angular/forms';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreTextUtilsProvider } from '@providers/utils/text';
import { AddonModAssignProvider } from '../../../providers/assign';
import { AddonModAssignOfflineProvider } from '../../../providers/assign-offline';
import { AddonModAssignFeedbackDelegate } from '../../../providers/feedback-delegate';
import { AddonModAssignFeedbackPluginComponent } from '../../../classes/feedback-plugin-component';
import { AddonModAssignFeedbackCommentsHandler } from '../providers/handler';
/**
* Component to render a comments feedback plugin.
*/
@Component({
selector: 'addon-mod-assign-feedback-comments',
templateUrl: 'comments.html'
})
export class AddonModAssignFeedbackCommentsComponent extends AddonModAssignFeedbackPluginComponent implements OnInit {
control: FormControl;
component = AddonModAssignProvider.COMPONENT;
text: string;
isSent: boolean;
loaded: boolean;
protected element: HTMLElement;
constructor(modalCtrl: ModalController, element: ElementRef, protected domUtils: CoreDomUtilsProvider,
protected textUtils: CoreTextUtilsProvider, protected assignOfflineProvider: AddonModAssignOfflineProvider,
protected assignProvider: AddonModAssignProvider, protected fb: FormBuilder,
protected feedbackDelegate: AddonModAssignFeedbackDelegate) {
super(modalCtrl);
this.element = element.nativeElement;
}
/**
* Component being initialized.
*/
ngOnInit(): void {
let promise,
rteEnabled;
// Check if rich text editor is enabled.
if (this.edit) {
promise = this.domUtils.isRichTextEditorEnabled();
} else {
// We aren't editing, so no rich text editor.
promise = Promise.resolve(false);
}
promise.then((enabled) => {
rteEnabled = enabled;
return this.getText(rteEnabled);
}).then((text) => {
this.text = text;
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.
this.textUtils.expandText(this.plugin.name, this.text, this.component, this.assign.cmid);
}
});
} else if (this.edit) {
this.control = this.fb.control(text);
}
}).finally(() => {
this.loaded = true;
});
}
/**
* Edit the comment.
*/
editComment(): void {
this.editFeedback().then((inputData) => {
const text = AddonModAssignFeedbackCommentsHandler.getTextFromInputData(this.textUtils, this.plugin, inputData);
// Update the text and save it as draft.
this.isSent = false;
this.text = text;
this.feedbackDelegate.saveFeedbackDraft(this.assign.id, this.userId, this.plugin, {
text: text,
format: 1
});
}).catch(() => {
// User cancelled, nothing to do.
});
}
/**
* Get the text for the plugin.
*
* @param {boolean} rteEnabled Whether Rich Text Editor is enabled.
* @return {Promise<string>} Promise resolved with the text.
*/
protected getText(rteEnabled: boolean): Promise<string> {
// Check if the user already modified the comment.
return this.feedbackDelegate.getPluginDraftData(this.assign.id, this.userId, this.plugin).then((draft) => {
if (draft) {
this.isSent = false;
return draft.text;
} else {
// There is no draft saved. Check if we have anything offline.
return this.assignOfflineProvider.getSubmissionGrade(this.assign.id, this.userId).catch(() => {
// No offline data found.
}).then((offlineData) => {
if (offlineData && offlineData.plugindata && offlineData.plugindata.assignfeedbackcomments_editor) {
// Save offline as draft.
this.isSent = false;
this.feedbackDelegate.saveFeedbackDraft(this.assign.id, this.userId, this.plugin,
offlineData.plugindata.assignfeedbackcomments_editor);
return offlineData.plugindata.assignfeedbackcomments_editor.text;
}
// No offline data found, return online text.
this.isSent = true;
return this.assignProvider.getSubmissionPluginText(this.plugin, this.edit && !rteEnabled);
});
}
});
}
}

View File

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

View File

@ -0,0 +1,224 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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, Injector } from '@angular/core';
import { CoreSitesProvider } from '@providers/sites';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreTextUtilsProvider } from '@providers/utils/text';
import { AddonModAssignProvider } from '../../../providers/assign';
import { AddonModAssignOfflineProvider } from '../../../providers/assign-offline';
import { AddonModAssignFeedbackHandler } from '../../../providers/feedback-delegate';
import { AddonModAssignFeedbackCommentsComponent } from '../component/comments';
/**
* Handler for comments feedback plugin.
*/
@Injectable()
export class AddonModAssignFeedbackCommentsHandler implements AddonModAssignFeedbackHandler {
name = 'AddonModAssignFeedbackCommentsHandler';
type = 'comments';
protected drafts = {}; // Store the data in this service so it isn't lost if the user performs a PTR in the page.
constructor(private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider,
private textUtils: CoreTextUtilsProvider, private assignProvider: AddonModAssignProvider,
private assignOfflineProvider: AddonModAssignOfflineProvider) { }
/**
* Discard the draft data of the feedback plugin.
*
* @param {number} assignId The assignment ID.
* @param {number} userId User ID.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {void|Promise<any>} If the function is async, it should return a Promise resolved when done.
*/
discardDraft(assignId: number, userId: number, siteId?: string): void | Promise<any> {
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, either in read or in edit mode.
* It's recommended to return the class of the component, but you can also return an instance of the component.
*
* @param {Injector} injector Injector.
* @param {any} plugin The plugin object.
* @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent(injector: Injector, plugin: any): any | Promise<any> {
return AddonModAssignFeedbackCommentsComponent;
}
/**
* Return the draft saved data of the feedback plugin.
*
* @param {number} assignId The assignment ID.
* @param {number} userId User ID.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {any|Promise<any>} Data (or promise resolved with the data).
*/
getDraft(assignId: number, userId: number, siteId?: string): any | Promise<any> {
const id = this.getDraftId(assignId, userId, siteId);
if (typeof this.drafts[id] != 'undefined') {
return this.drafts[id];
}
}
/**
* Get a draft ID.
*
* @param {number} assignId The assignment ID.
* @param {number} userId User ID.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {string} Draft ID.
*/
protected getDraftId(assignId: number, userId: number, siteId?: string): string {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
return siteId + '#' + assignId + '#' + userId;
}
/**
* Get the text to submit.
*
* @param {CoreTextUtilsProvider} textUtils Text utils instance.
* @param {any} plugin Plugin.
* @param {any} inputData Data entered in the feedback edit form.
* @return {string} Text to submit.
*/
static getTextFromInputData(textUtils: CoreTextUtilsProvider, plugin: any, inputData: any): string {
const files = plugin.fileareas && plugin.fileareas[0] ? plugin.fileareas[0].files : [];
let text = inputData.assignfeedbackcomments_editor;
// The input data can have a string or an object with text and format. Get the text.
if (text && text.text) {
text = text.text;
}
return textUtils.restorePluginfileUrls(text, files);
}
/**
* Check if the feedback data has changed for this plugin.
*
* @param {any} assign The assignment.
* @param {any} submission The submission.
* @param {any} plugin The plugin object.
* @param {any} inputData Data entered by the user for the feedback.
* @param {number} userId User ID of the submission.
* @return {boolean|Promise<boolean>} Boolean (or promise resolved with boolean): whether the data has changed.
*/
hasDataChanged(assign: any, submission: any, plugin: any, inputData: any, userId: number): boolean | Promise<boolean> {
// Get it from plugin or offline.
return this.assignOfflineProvider.getSubmissionGrade(assign.id, userId).catch(() => {
// No offline data found.
}).then((data) => {
if (data && data.plugindata && data.plugindata.assignfeedbackcomments_editor) {
return data.plugindata.assignfeedbackcomments_editor.text;
}
// No offline data found, get text from plugin.
return this.domUtils.isRichTextEditorEnabled().then((enabled) => {
return this.assignProvider.getSubmissionPluginText(plugin, !enabled);
});
}).then((initialText) => {
const newText = AddonModAssignFeedbackCommentsHandler.getTextFromInputData(this.textUtils, 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 {number} assignId The assignment ID.
* @param {number} userId User ID.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {boolean|Promise<boolean>} 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 {boolean|Promise<boolean>} True or promise resolved with true if enabled.
*/
isEnabled(): boolean | Promise<boolean> {
return true;
}
/**
* Whether or not the handler is enabled for edit on a site level.
*
* @return {boolean|Promise<boolean>} Whether or not the handler is enabled for edit on a site level.
*/
isEnabledForEdit(): boolean | Promise<boolean> {
return true;
}
/**
* Prepare and add to pluginData the data to send to the server based on the draft data saved.
*
* @param {number} assignId The assignment ID.
* @param {number} userId User ID.
* @param {any} plugin The plugin object.
* @param {any} pluginData Object where to store the data to send.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {void|Promise<any>} If the function is async, it should return a Promise resolved when done.
*/
prepareFeedbackData(assignId: number, userId: number, plugin: any, pluginData: any, siteId?: string): void | Promise<any> {
const draft = this.getDraft(assignId, userId, siteId);
if (draft) {
return this.domUtils.isRichTextEditorEnabled().then((enabled) => {
if (!enabled) {
// Rich text editor not enabled, add some HTML to the text if needed.
draft.text = this.textUtils.formatHtmlLines(draft.text);
}
pluginData.assignfeedbackcomments_editor = draft;
});
}
}
/**
* Save draft data of the feedback plugin.
*
* @param {number} assignId The assignment ID.
* @param {number} userId User ID.
* @param {any} plugin The plugin object.
* @param {any} data The data to save.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {void|Promise<any>} If the function is async, it should return a Promise resolved when done.
*/
saveDraft(assignId: number, userId: number, plugin: any, data: any, siteId?: string): void | Promise<any> {
if (data) {
this.drafts[this.getDraftId(assignId, userId, siteId)] = data;
}
}
}

View File

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

View File

@ -0,0 +1,44 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 } from '@angular/core';
import { ModalController } from 'ionic-angular';
import { AddonModAssignProvider } from '../../../providers/assign';
import { AddonModAssignFeedbackPluginComponent } from '../../../classes/feedback-plugin-component';
/**
* Component to render a edit pdf feedback plugin.
*/
@Component({
selector: 'addon-mod-assign-feedback-edit-pdf',
templateUrl: 'editpdf.html'
})
export class AddonModAssignFeedbackEditPdfComponent extends AddonModAssignFeedbackPluginComponent implements OnInit {
component = AddonModAssignProvider.COMPONENT;
files: any[];
constructor(modalCtrl: ModalController, protected assignProvider: AddonModAssignProvider) {
super(modalCtrl);
}
/**
* Component being initialized.
*/
ngOnInit(): void {
if (this.plugin) {
this.files = this.assignProvider.getSubmissionPluginAttachments(this.plugin);
}
}
}

View File

@ -0,0 +1,50 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { CommonModule } from '@angular/common';
import { IonicModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { AddonModAssignFeedbackEditPdfHandler } from './providers/handler';
import { AddonModAssignFeedbackEditPdfComponent } from './component/editpdf';
import { AddonModAssignFeedbackDelegate } from '../../providers/feedback-delegate';
import { CoreComponentsModule } from '@components/components.module';
import { CoreDirectivesModule } from '@directives/directives.module';
@NgModule({
declarations: [
AddonModAssignFeedbackEditPdfComponent
],
imports: [
CommonModule,
IonicModule,
TranslateModule.forChild(),
CoreComponentsModule,
CoreDirectivesModule
],
providers: [
AddonModAssignFeedbackEditPdfHandler
],
exports: [
AddonModAssignFeedbackEditPdfComponent
],
entryComponents: [
AddonModAssignFeedbackEditPdfComponent
]
})
export class AddonModAssignFeedbackEditPdfModule {
constructor(feedbackDelegate: AddonModAssignFeedbackDelegate, handler: AddonModAssignFeedbackEditPdfHandler) {
feedbackDelegate.registerHandler(handler);
}
}

View File

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

View File

@ -0,0 +1,65 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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, Injector } from '@angular/core';
import { AddonModAssignProvider } from '../../../providers/assign';
import { AddonModAssignFeedbackHandler } from '../../../providers/feedback-delegate';
import { AddonModAssignFeedbackEditPdfComponent } from '../component/editpdf';
/**
* Handler for edit pdf feedback plugin.
*/
@Injectable()
export class AddonModAssignFeedbackEditPdfHandler implements AddonModAssignFeedbackHandler {
name = 'AddonModAssignFeedbackEditPdfHandler';
type = 'editpdf';
constructor(private assignProvider: AddonModAssignProvider) { }
/**
* Return the Component to use to display the plugin data, either in read or in edit mode.
* It's recommended to return the class of the component, but you can also return an instance of the component.
*
* @param {Injector} injector Injector.
* @param {any} plugin The plugin object.
* @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent(injector: Injector, plugin: any): any | Promise<any> {
return AddonModAssignFeedbackEditPdfComponent;
}
/**
* Get files used by this plugin.
* The files returned by this function will be prefetched when the user prefetches the assign.
*
* @param {any} assign The assignment.
* @param {any} submission The submission.
* @param {any} plugin The plugin object.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {any[]|Promise<any[]>} The files (or promise resolved with the files).
*/
getPluginFiles(assign: any, submission: any, plugin: any, siteId?: string): any[] | Promise<any[]> {
return this.assignProvider.getSubmissionPluginAttachments(plugin);
}
/**
* Whether or not the handler is enabled on a site level.
*
* @return {boolean|Promise<boolean>} True or promise resolved with true if enabled.
*/
isEnabled(): boolean | Promise<boolean> {
return true;
}
}

View File

@ -0,0 +1,31 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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({
declarations: [],
imports: [
AddonModAssignFeedbackCommentsModule,
AddonModAssignFeedbackEditPdfModule,
AddonModAssignFeedbackFileModule
],
providers: [
],
exports: []
})
export class AddonModAssignFeedbackModule { }

View File

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

View File

@ -0,0 +1,44 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 } from '@angular/core';
import { ModalController } from 'ionic-angular';
import { AddonModAssignProvider } from '../../../providers/assign';
import { AddonModAssignFeedbackPluginComponent } from '../../../classes/feedback-plugin-component';
/**
* Component to render a file feedback plugin.
*/
@Component({
selector: 'addon-mod-assign-feedback-file',
templateUrl: 'file.html'
})
export class AddonModAssignFeedbackFileComponent extends AddonModAssignFeedbackPluginComponent implements OnInit {
component = AddonModAssignProvider.COMPONENT;
files: any[];
constructor(modalCtrl: ModalController, protected assignProvider: AddonModAssignProvider) {
super(modalCtrl);
}
/**
* Component being initialized.
*/
ngOnInit(): void {
if (this.plugin) {
this.files = this.assignProvider.getSubmissionPluginAttachments(this.plugin);
}
}
}

View File

@ -0,0 +1,50 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { CommonModule } from '@angular/common';
import { IonicModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { AddonModAssignFeedbackFileHandler } from './providers/handler';
import { AddonModAssignFeedbackFileComponent } from './component/file';
import { AddonModAssignFeedbackDelegate } from '../../providers/feedback-delegate';
import { CoreComponentsModule } from '@components/components.module';
import { CoreDirectivesModule } from '@directives/directives.module';
@NgModule({
declarations: [
AddonModAssignFeedbackFileComponent
],
imports: [
CommonModule,
IonicModule,
TranslateModule.forChild(),
CoreComponentsModule,
CoreDirectivesModule
],
providers: [
AddonModAssignFeedbackFileHandler
],
exports: [
AddonModAssignFeedbackFileComponent
],
entryComponents: [
AddonModAssignFeedbackFileComponent
]
})
export class AddonModAssignFeedbackFileModule {
constructor(feedbackDelegate: AddonModAssignFeedbackDelegate, handler: AddonModAssignFeedbackFileHandler) {
feedbackDelegate.registerHandler(handler);
}
}

View File

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

View File

@ -0,0 +1,65 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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, Injector } from '@angular/core';
import { AddonModAssignProvider } from '../../../providers/assign';
import { AddonModAssignFeedbackHandler } from '../../../providers/feedback-delegate';
import { AddonModAssignFeedbackFileComponent } from '../component/file';
/**
* Handler for file feedback plugin.
*/
@Injectable()
export class AddonModAssignFeedbackFileHandler implements AddonModAssignFeedbackHandler {
name = 'AddonModAssignFeedbackFileHandler';
type = 'file';
constructor(private assignProvider: AddonModAssignProvider) { }
/**
* Return the Component to use to display the plugin data, either in read or in edit mode.
* It's recommended to return the class of the component, but you can also return an instance of the component.
*
* @param {Injector} injector Injector.
* @param {any} plugin The plugin object.
* @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent(injector: Injector, plugin: any): any | Promise<any> {
return AddonModAssignFeedbackFileComponent;
}
/**
* Get files used by this plugin.
* The files returned by this function will be prefetched when the user prefetches the assign.
*
* @param {any} assign The assignment.
* @param {any} submission The submission.
* @param {any} plugin The plugin object.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {any[]|Promise<any[]>} The files (or promise resolved with the files).
*/
getPluginFiles(assign: any, submission: any, plugin: any, siteId?: string): any[] | Promise<any[]> {
return this.assignProvider.getSubmissionPluginAttachments(plugin);
}
/**
* Whether or not the handler is enabled on a site level.
*
* @return {boolean|Promise<boolean>} True or promise resolved with true if enabled.
*/
isEnabled(): boolean | Promise<boolean> {
return true;
}
}

View File

@ -0,0 +1,16 @@
<ion-header>
<ion-navbar>
<ion-title><core-format-text [text]="plugin.name"></core-format-text></ion-title>
<ion-buttons end>
<button ion-button icon-only (click)="closeModal()" [attr.aria-label]="'core.close' | translate">
<ion-icon name="close"></ion-icon>
</button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content>
<form name="addon-mod_assign-edit-feedback-form" *ngIf="userId && plugin">
<addon-mod-assign-feedback-plugin [assign]="assign" [submission]="submission" [userId]="userId" [plugin]="plugin" [edit]="true"></addon-mod-assign-feedback-plugin>
<button ion-button block (click)="done()">{{ 'core.done' | translate }}</button>
</form>
</ion-content>

View File

@ -0,0 +1,35 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { IonicPageModule } from 'ionic-angular';
import { AddonModAssignEditFeedbackModalPage } from './edit-feedback-modal';
import { TranslateModule } from '@ngx-translate/core';
import { CoreComponentsModule } from '@components/components.module';
import { CoreDirectivesModule } from '@directives/directives.module';
import { AddonModAssignComponentsModule } from '../../components/components.module';
@NgModule({
declarations: [
AddonModAssignEditFeedbackModalPage
],
imports: [
CoreComponentsModule,
CoreDirectivesModule,
AddonModAssignComponentsModule,
IonicPageModule.forChild(AddonModAssignEditFeedbackModalPage),
TranslateModule.forChild()
]
})
export class AddonModAssignEditFeedbackModalPageModule {}

View File

@ -0,0 +1,103 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { IonicPage, ViewController, NavParams } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { AddonModAssignFeedbackDelegate } from '../../providers/feedback-delegate';
/**
* Modal that allows editing a feedback plugin.
*/
@IonicPage({ segment: 'addon-mod-assign-edit-feedback-modal' })
@Component({
selector: 'page-addon-mod-assign-edit-feedback-modal',
templateUrl: 'edit-feedback-modal.html',
})
export class AddonModAssignEditFeedbackModalPage {
@Input() assign: any; // The assignment.
@Input() submission: any; // The submission.
@Input() plugin: any; // The plugin object.
@Input() userId: number; // The user ID of the submission.
protected forceLeave = false; // To allow leaving the page without checking for changes.
constructor(params: NavParams, protected viewCtrl: ViewController, protected domUtils: CoreDomUtilsProvider,
protected translate: TranslateService, protected feedbackDelegate: AddonModAssignFeedbackDelegate) {
this.assign = params.get('assign');
this.submission = params.get('submission');
this.plugin = params.get('plugin');
this.userId = params.get('userId');
}
/**
* Check if we can leave the page or not.
*
* @return {boolean|Promise<void>} Resolved if we can leave it, rejected if not.
*/
ionViewCanLeave(): boolean | Promise<void> {
if (this.forceLeave) {
return true;
}
return this.hasDataChanged().then((changed) => {
if (changed) {
return this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit'));
}
});
}
/**
* Close modal.
*
* @param {any} data Data to return to the page.
*/
closeModal(data: any): void {
this.viewCtrl.dismiss(data);
}
/**
* Done editing.
*/
done(): void {
// Close the modal, sending the input data.
this.forceLeave = true;
this.closeModal(this.getInputData());
}
/**
* Get the input data.
*
* @return {any} Object with the data.
*/
protected getInputData(): any {
return this.domUtils.getDataFromForm(document.forms['addon-mod_assign-edit-feedback-form']);
}
/**
* Check if data has changed.
*
* @return {Promise<boolean>} Promise resolved with boolean: whether the data has changed.
*/
protected hasDataChanged(): Promise<boolean> {
return this.feedbackDelegate.hasPluginDataChanged(this.assign, this.userId, this.plugin, this.getInputData(), this.userId)
.catch(() => {
// Ignore errors.
return true;
});
}
}