MDL-45119 ajax: Correct use of COUNT in exception dialogue
[moodle.git] / mod / assign / feedback / editpdf / yui / src / editor / js / editor.js
CommitLineData
5c386472
DW
1// This file is part of Moodle - http://moodle.org/
2//
3// Moodle is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or
6// (at your option) any later version.
7//
8// Moodle is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
15
16/**
17 * Provides an in browser PDF editor.
18 *
19 * @module moodle-assignfeedback_editpdf-editor
20 */
21
22/**
23 * EDITOR
24 * This is an in browser PDF editor.
25 *
1f777e5c
AN
26 * @namespace M.assignfeedback_editpdf
27 * @class editor
5c386472
DW
28 * @constructor
29 * @extends Y.Base
30 */
31EDITOR = function() {
32 EDITOR.superclass.constructor.apply(this, arguments);
33};
34EDITOR.prototype = {
35
5c386472
DW
36 /**
37 * The dialogue used for all action menu displays.
1f777e5c 38 *
5c386472
DW
39 * @property type
40 * @type M.core.dialogue
41 * @protected
42 */
43 dialogue : null,
44
45 /**
46 * The number of pages in the pdf.
1f777e5c
AN
47 *
48 * @property pagecount
49 * @type Number
5c386472
DW
50 * @protected
51 */
52 pagecount : 0,
53
54 /**
55 * The active page in the editor.
1f777e5c
AN
56 *
57 * @property currentpage
58 * @type Number
5c386472
DW
59 * @protected
60 */
61 currentpage : 0,
62
63 /**
64 * A list of page objects. Each page has a list of comments and annotations.
1f777e5c
AN
65 *
66 * @property pages
5c386472
DW
67 * @type array
68 * @protected
69 */
70 pages : [],
71
72 /**
73 * The yui node for the loading icon.
1f777e5c
AN
74 *
75 * @property loadingicon
76 * @type Node
5c386472
DW
77 * @protected
78 */
79 loadingicon : null,
80
81 /**
82 * Image object of the current page image.
1f777e5c
AN
83 *
84 * @property pageimage
5c386472
DW
85 * @type Image
86 * @protected
87 */
88 pageimage : null,
89
90 /**
91 * YUI Graphic class for drawing shapes.
1f777e5c
AN
92 *
93 * @property graphic
94 * @type Graphic
5c386472
DW
95 * @protected
96 */
97 graphic : null,
98
99 /**
100 * Info about the current edit operation.
1f777e5c 101 *
5c386472
DW
102 * @property currentedit
103 * @type M.assignfeedback_editpdf.edit
104 * @protected
105 */
106 currentedit : new M.assignfeedback_editpdf.edit(),
107
108 /**
109 * Current drawable.
1f777e5c 110 *
5c386472 111 * @property currentdrawable
1f777e5c 112 * @type M.assignfeedback_editpdf.drawable|false
5c386472
DW
113 * @protected
114 */
115 currentdrawable : false,
116
117 /**
118 * Current drawables.
1f777e5c 119 *
5c386472
DW
120 * @property drawables
121 * @type array(M.assignfeedback_editpdf.drawable)
122 * @protected
123 */
124 drawables : [],
125
126 /**
127 * Current comment when the comment menu is open.
128 * @property currentcomment
129 * @type M.assignfeedback_editpdf.comment
130 * @protected
131 */
132 currentcomment : null,
133
134 /**
135 * Current annotation when the select tool is used.
136 * @property currentannotation
137 * @type M.assignfeedback_editpdf.annotation
138 * @protected
139 */
140 currentannotation : null,
141
142 /**
143 * Last selected annotation tool
144 * @property lastannotationtool
145 * @type String
146 * @protected
147 */
148 lastanntationtool : "pen",
149
150 /**
151 * The users comments quick list
152 * @property quicklist
153 * @type M.assignfeedback_editpdf.quickcommentlist
154 * @protected
155 */
156 quicklist : null,
157
158 /**
159 * The search comments window.
160 * @property searchcommentswindow
161 * @type M.core.dialogue
162 * @protected
163 */
164 searchcommentswindow : null,
165
166
167 /**
168 * The selected stamp picture.
169 * @property currentstamp
170 * @type String
171 * @protected
172 */
173 currentstamp : null,
174
175 /**
176 * The stamps.
177 * @property stamps
178 * @type Array
179 * @protected
180 */
181 stamps : [],
182
60b87080
DS
183 /**
184 * Prevent new comments from appearing
185 * immediately after clicking off a current
186 * comment
187 * @property editingcomment
188 * @type Boolean
189 * @public
190 */
191 editingcomment : false,
192
5c386472
DW
193 /**
194 * Called during the initialisation process of the object.
195 * @method initializer
196 */
197 initializer : function() {
198 var link;
199
200 link = Y.one('#' + this.get('linkid'));
201
202 if (link) {
203 link.on('click', this.link_handler, this);
204 link.on('key', this.link_handler, 'down:13', this);
205
206 this.currentedit.start = false;
207 this.currentedit.end = false;
208 this.quicklist = new M.assignfeedback_editpdf.quickcommentlist(this);
209 }
210 },
211
212 /**
213 * Called to show/hide buttons and set the current colours/stamps.
214 * @method refresh_button_state
215 */
216 refresh_button_state : function() {
217 var button, currenttoolnode, imgurl;
218
219 // Initalise the colour buttons.
220 button = Y.one(SELECTOR.COMMENTCOLOURBUTTON);
221
222 imgurl = M.util.image_url('background_colour_' + this.currentedit.commentcolour, 'assignfeedback_editpdf');
223 button.one('img').setAttribute('src', imgurl);
224
225 if (this.currentedit.commentcolour === 'clear') {
226 button.one('img').setStyle('borderStyle', 'dashed');
227 } else {
228 button.one('img').setStyle('borderStyle', 'solid');
229 }
230
231 button = Y.one(SELECTOR.ANNOTATIONCOLOURBUTTON);
232 imgurl = M.util.image_url('colour_' + this.currentedit.annotationcolour, 'assignfeedback_editpdf');
233 button.one('img').setAttribute('src', imgurl);
234
235 currenttoolnode = Y.one(TOOLSELECTOR[this.currentedit.tool]);
236 currenttoolnode.addClass('assignfeedback_editpdf_selectedbutton');
237 currenttoolnode.setAttribute('aria-pressed', 'true');
238
239 button = Y.one(SELECTOR.STAMPSBUTTON);
240 button.one('img').setAttrs({'src': this.get_stamp_image_url(this.currentedit.stamp),
241 'height': '16',
242 'width': '16'});
243 },
244
245 /**
246 * Called to get the bounds of the drawing region.
247 * @method get_canvas_bounds
248 */
249 get_canvas_bounds : function() {
250 var canvas = Y.one(SELECTOR.DRAWINGCANVAS),
251 offsetcanvas = canvas.getXY(),
252 offsetleft = offsetcanvas[0],
253 offsettop = offsetcanvas[1],
254 width = parseInt(canvas.getStyle('width'), 10),
255 height = parseInt(canvas.getStyle('height'), 10);
256
257 return new M.assignfeedback_editpdf.rect(offsetleft, offsettop, width, height);
258 },
259
260 /**
261 * Called to translate from window coordinates to canvas coordinates.
262 * @method get_canvas_coordinates
263 * @param M.assignfeedback_editpdf.point point in window coordinats.
264 */
265 get_canvas_coordinates : function(point) {
266 var bounds = this.get_canvas_bounds(),
267 newpoint = new M.assignfeedback_editpdf.point(point.x - bounds.x, point.y - bounds.y);
268
269 bounds.x = bounds.y = 0;
270
271 newpoint.clip(bounds);
272 return newpoint;
273 },
274
275 /**
276 * Called to translate from canvas coordinates to window coordinates.
277 * @method get_window_coordinates
278 * @param M.assignfeedback_editpdf.point point in window coordinats.
279 */
280 get_window_coordinates : function(point) {
281 var bounds = this.get_canvas_bounds(),
282 newpoint = new M.assignfeedback_editpdf.point(point.x + bounds.x, point.y + bounds.y);
283
284 return newpoint;
285 },
286
287 /**
288 * Called to open the pdf editing dialogue.
289 * @method link_handler
290 */
291 link_handler : function(e) {
292 var drawingcanvas;
293 e.preventDefault();
294
295 if (!this.dialogue) {
296 this.dialogue = new M.core.dialogue({
297 headerContent: this.get('header'),
298 bodyContent: this.get('body'),
299 footerContent: this.get('footer'),
aebc768f 300 modal: true,
5c386472 301 width: '840px',
6d87dceb
DW
302 visible: false,
303 draggable: true
5c386472
DW
304 });
305
5c386472
DW
306 // Add custom class for styling.
307 this.dialogue.get('boundingBox').addClass(CSS.DIALOGUE);
308
309 this.loadingicon = Y.one(SELECTOR.LOADINGICON);
310
311 drawingcanvas = Y.one(SELECTOR.DRAWINGCANVAS);
312 this.graphic = new Y.Graphic({render : SELECTOR.DRAWINGCANVAS});
313
314 if (!this.get('readonly')) {
315 drawingcanvas.on('gesturemovestart', this.edit_start, null, this);
316 drawingcanvas.on('gesturemove', this.edit_move, null, this);
317 drawingcanvas.on('gesturemoveend', this.edit_end, null, this);
318
319 this.refresh_button_state();
320 }
d40ce26f
JM
321
322 this.load_all_pages();
5c386472 323 }
6d87dceb
DW
324 this.dialogue.centerDialogue();
325 this.dialogue.show();
5c386472
DW
326 },
327
328 /**
329 * Called to load the information and annotations for all pages.
330 * @method load_all_pages
331 */
332 load_all_pages : function() {
333 var ajaxurl = AJAXBASE,
aa3e4bde
JM
334 config,
335 checkconversionstatus,
336 ajax_error_total;
5c386472
DW
337
338 config = {
339 method: 'get',
340 context: this,
341 sync: false,
342 data : {
aa3e4bde
JM
343 sesskey : M.cfg.sesskey,
344 action : 'loadallpages',
345 userid : this.get('userid'),
346 attemptnumber : this.get('attemptnumber'),
347 assignmentid : this.get('assignmentid')
5c386472
DW
348 },
349 on: {
350 success: function(tid, response) {
351 this.all_pages_loaded(response.responseText);
352 },
353 failure: function(tid, response) {
aa3e4bde 354 return new M.core.exception(response.responseText);
5c386472
DW
355 }
356 }
357 };
358
359 Y.io(ajaxurl, config);
aa3e4bde
JM
360
361 // If pages are not loaded, check PDF conversion status for the progress bar.
362 if (this.pagecount <= 0) {
363 checkconversionstatus = {
364 method: 'get',
365 context: this,
366 sync: false,
367 data : {
368 sesskey : M.cfg.sesskey,
369 action : 'conversionstatus',
370 userid : this.get('userid'),
371 attemptnumber : this.get('attemptnumber'),
372 assignmentid : this.get('assignmentid')
373 },
374 on: {
375 success: function(tid, response) {
376 ajax_error_total = 0;
377 if (this.pagecount === 0) {
378 var pagetotal = this.get('pagetotal');
379
380 // Update the progress bar.
381 var progressbarcontainer = Y.one(SELECTOR.PROGRESSBARCONTAINER);
382 var progressbar = progressbarcontainer.one('.bar');
383 if (progressbar) {
384 // Calculate progress.
385 var progress = (response.response / pagetotal) * 100;
386 progressbar.setStyle('width', progress + '%');
387 progressbarcontainer.setAttribute('aria-valuenow', progress);
388 }
389
390 // New ajax request delayed of a second.
391 Y.later(1000, this, function () {
392 Y.io(AJAXBASEPROGRESS, checkconversionstatus);
393 });
394 }
395 },
396 failure: function(tid, response) {
397 ajax_error_total = ajax_error_total + 1;
398 // We only continue on error if the all pages were not generated,
399 // and if the ajax call did not produce 5 errors in the row.
400 if (this.pagecount === 0 && ajax_error_total < 5) {
401 Y.later(1000, this, function () {
402 Y.io(AJAXBASEPROGRESS, checkconversionstatus);
403 });
404 }
405 return new M.core.exception(response.responseText);
406 }
407 }
408 };
409 // We start the AJAX "generated page total number" call a second later to give a chance to
410 // the AJAX "combined pdf generation" call to clean the previous submission images.
411 Y.later(1000, this, function () {
412 ajax_error_total = 0;
413 Y.io(AJAXBASEPROGRESS, checkconversionstatus);
414 });
415 }
5c386472
DW
416 },
417
418 /**
419 * The info about all pages in the pdf has been returned.
420 * @param string The ajax response as text.
421 * @protected
422 * @method all_pages_loaded
423 */
424 all_pages_loaded : function(responsetext) {
425 var data, i, j, comment, error;
426 try {
427 data = Y.JSON.parse(responsetext);
428 if (data.error || !data.pagecount) {
429 this.dialogue.hide();
430 // Display alert dialogue.
431 error = new M.core.alert({ message: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf') });
432 error.show();
433 return;
434 }
435 } catch (e) {
436 this.dialogue.hide();
437 // Display alert dialogue.
438 error = new M.core.alert({ title: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf')});
439 error.show();
440 return;
441 }
442
443 this.pagecount = data.pagecount;
444 this.pages = data.pages;
445
446 for (i = 0; i < this.pages.length; i++) {
447 for (j = 0; j < this.pages[i].comments.length; j++) {
448 comment = this.pages[i].comments[j];
449 this.pages[i].comments[j] = new M.assignfeedback_editpdf.comment(this,
450 comment.gradeid,
451 comment.pageno,
452 comment.x,
453 comment.y,
454 comment.width,
455 comment.colour,
456 comment.rawtext);
457 }
458 for (j = 0; j < this.pages[i].annotations.length; j++) {
459 data = this.pages[i].annotations[j];
460 this.pages[i].annotations[j] = this.create_annotation(data.type, data);
461 }
462 }
463
464 // Update the ui.
465 this.quicklist.load();
466 this.setup_navigation();
467 this.setup_toolbar();
468 this.change_page();
469 },
470
471 /**
472 * Get the full pluginfile url for an image file - just given the filename.
473 *
474 * @public
475 * @method get_stamp_image_url
476 * @param string filename
477 */
478 get_stamp_image_url : function(filename) {
479 var urls = this.get('stampfiles'),
480 fullurl = '';
481
482 Y.Array.each(urls, function(url) {
483 if (url.indexOf(filename) > 0) {
484 fullurl = url;
485 }
486 }, this);
487
488 return fullurl;
489 },
490
491 /**
492 * Attach listeners and enable the color picker buttons.
493 * @protected
494 * @method setup_toolbar
495 */
496 setup_toolbar : function() {
497 var toolnode,
498 commentcolourbutton,
499 annotationcolourbutton,
500 searchcommentsbutton,
501 currentstampbutton,
502 stampfiles,
503 picker,
504 filename;
505
506 searchcommentsbutton = Y.one(SELECTOR.SEARCHCOMMENTSBUTTON);
507 searchcommentsbutton.on('click', this.open_search_comments, this);
508 searchcommentsbutton.on('key', this.open_search_comments, 'down:13', this);
509
510 if (this.get('readonly')) {
511 return;
512 }
513 // Setup the tool buttons.
514 Y.each(TOOLSELECTOR, function(selector, tool) {
515 toolnode = Y.one(selector);
516 toolnode.on('click', this.handle_tool_button, this, tool);
517 toolnode.on('key', this.handle_tool_button, 'down:13', this, tool);
518 toolnode.setAttribute('aria-pressed', 'false');
519 }, this);
520
521 // Set the default tool.
522
523 commentcolourbutton = Y.one(SELECTOR.COMMENTCOLOURBUTTON);
524 picker = new M.assignfeedback_editpdf.colourpicker({
525 buttonNode: commentcolourbutton,
526 colours: COMMENTCOLOUR,
527 iconprefix: 'background_colour_',
528 callback: function (e) {
529 var colour = e.target.getAttribute('data-colour');
530 if (!colour) {
531 colour = e.target.ancestor().getAttribute('data-colour');
532 }
533 this.currentedit.commentcolour = colour;
534 this.handle_tool_button(e, "comment");
535 },
536 context: this
537 });
538
539 annotationcolourbutton = Y.one(SELECTOR.ANNOTATIONCOLOURBUTTON);
540 picker = new M.assignfeedback_editpdf.colourpicker({
541 buttonNode: annotationcolourbutton,
542 iconprefix: 'colour_',
543 colours: ANNOTATIONCOLOUR,
544 callback: function (e) {
545 var colour = e.target.getAttribute('data-colour');
546 if (!colour) {
547 colour = e.target.ancestor().getAttribute('data-colour');
548 }
549 this.currentedit.annotationcolour = colour;
550 if (this.lastannotationtool) {
551 this.handle_tool_button(e, this.lastannotationtool);
552 } else {
553 this.handle_tool_button(e, "pen");
554 }
555 },
556 context: this
557 });
558
559 stampfiles = this.get('stampfiles');
560 if (stampfiles.length <= 0) {
b9e946f8 561 Y.one(TOOLSELECTOR.stamp).ancestor().hide();
5c386472
DW
562 } else {
563 filename = stampfiles[0].substr(stampfiles[0].lastIndexOf('/') + 1);
564 this.currentedit.stamp = filename;
565 currentstampbutton = Y.one(SELECTOR.STAMPSBUTTON);
566
567 picker = new M.assignfeedback_editpdf.stamppicker({
568 buttonNode: currentstampbutton,
569 stamps: stampfiles,
570 callback: function(e) {
571 var stamp = e.target.getAttribute('data-stamp'),
572 filename;
573
574 if (!stamp) {
575 stamp = e.target.ancestor().getAttribute('data-stamp');
576 }
577 filename = stamp.substr(stamp.lastIndexOf('/'));
578 this.currentedit.stamp = filename;
673c4ffe 579 this.handle_tool_button(e, "stamp");
5c386472
DW
580 },
581 context: this
582 });
583 this.refresh_button_state();
584 }
585 },
586
587 /**
588 * Change the current tool.
589 * @protected
590 * @method handle_tool_button
591 */
592 handle_tool_button : function(e, tool) {
593 var currenttoolnode;
594
595 e.preventDefault();
596
597 // Change style of the pressed button.
598 currenttoolnode = Y.one(TOOLSELECTOR[this.currentedit.tool]);
599 currenttoolnode.removeClass('assignfeedback_editpdf_selectedbutton');
600 currenttoolnode.setAttribute('aria-pressed', 'false');
601 this.currentedit.tool = tool;
673c4ffe 602 if (tool !== "comment" && tool !== "select" && tool !== "stamp") {
5c386472
DW
603 this.lastannotationtool = tool;
604 }
605 this.refresh_button_state();
606 },
607
608 /**
609 * JSON encode the current page data - stripping out drawable references which cannot be encoded.
610 * @protected
611 * @method stringify_current_page
612 * @return string
613 */
614 stringify_current_page : function() {
615 var comments = [],
616 annotations = [],
617 page,
618 i = 0;
619
620 for (i = 0; i < this.pages[this.currentpage].comments.length; i++) {
621 comments[i] = this.pages[this.currentpage].comments[i].clean();
622 }
623 for (i = 0; i < this.pages[this.currentpage].annotations.length; i++) {
624 annotations[i] = this.pages[this.currentpage].annotations[i].clean();
625 }
626
627 page = { comments : comments, annotations : annotations };
628
629 return Y.JSON.stringify(page);
630 },
631
632 /**
633 * Generate a drawable from the current in progress edit.
634 * @protected
635 * @method get_current_drawable
636 */
637 get_current_drawable : function() {
638 var comment,
639 annotation,
640 drawable = false;
641
642 if (!this.currentedit.start || !this.currentedit.end) {
643 return false;
644 }
645
646 if (this.currentedit.tool === 'comment') {
647 comment = new M.assignfeedback_editpdf.comment(this);
648 drawable = comment.draw_current_edit(this.currentedit);
649 } else {
650 annotation = this.create_annotation(this.currentedit.tool, {});
651 if (annotation) {
652 drawable = annotation.draw_current_edit(this.currentedit);
653 }
654 }
655
656 return drawable;
657 },
658
659 /**
660 * Redraw the active edit.
661 * @protected
662 * @method redraw_active_edit
663 */
664 redraw_current_edit : function() {
665 if (this.currentdrawable) {
666 this.currentdrawable.erase();
667 }
668 this.currentdrawable = this.get_current_drawable();
669 },
670
671 /**
672 * Event handler for mousedown or touchstart.
673 * @protected
674 * @param Event
675 * @method edit_start
676 */
677 edit_start : function(e) {
678 var canvas = Y.one(SELECTOR.DRAWINGCANVAS),
679 offset = canvas.getXY(),
680 scrolltop = canvas.get('docScrollY'),
681 scrollleft = canvas.get('docScrollX'),
682 point = {x : e.clientX - offset[0] + scrollleft,
683 y : e.clientY - offset[1] + scrolltop},
684 selected = false,
685 lastannotation;
686
687 // Ignore right mouse click.
688 if (e.button === 3) {
689 return;
690 }
691
692 if (this.currentedit.starttime) {
693 return;
694 }
695
60b87080
DS
696 if (this.editingcomment) {
697 return;
698 }
699
5c386472
DW
700 this.currentedit.starttime = new Date().getTime();
701 this.currentedit.start = point;
702 this.currentedit.end = {x : point.x, y : point.y};
703
704 if (this.currentedit.tool === 'select') {
705 x = this.currentedit.end.x;
706 y = this.currentedit.end.y;
707 annotations = this.pages[this.currentpage].annotations;
708 // Find the first annotation whose bounds encompass the click.
709 Y.each(annotations, function(annotation) {
710 if (((x - annotation.x) * (x - annotation.endx)) <= 0 &&
711 ((y - annotation.y) * (y - annotation.endy)) <= 0) {
712 selected = annotation;
713 }
714 });
715
716 if (selected) {
717 lastannotation = this.currentannotation;
718 this.currentannotation = selected;
719 if (lastannotation && lastannotation !== selected) {
720 // Redraw the last selected annotation to remove the highlight.
721 if (lastannotation.drawable) {
722 lastannotation.drawable.erase();
723 this.drawables.push(lastannotation.draw());
724 }
725 }
726 // Redraw the newly selected annotation to show the highlight.
727 if (this.currentannotation.drawable) {
728 this.currentannotation.drawable.erase();
729 }
730 this.drawables.push(this.currentannotation.draw());
731 }
732 }
733 if (this.currentannotation) {
734 // Used to calculate drag offset.
735 this.currentedit.annotationstart = { x : this.currentannotation.x,
736 y : this.currentannotation.y };
737 }
738 },
739
740 /**
741 * Event handler for mousemove.
742 * @protected
743 * @param Event
744 * @method edit_move
745 */
746 edit_move : function(e) {
747 var bounds = this.get_canvas_bounds(),
748 canvas = Y.one(SELECTOR.DRAWINGCANVAS),
749 clientpoint = new M.assignfeedback_editpdf.point(e.clientX + canvas.get('docScrollX'),
750 e.clientY + canvas.get('docScrollY')),
751 point = this.get_canvas_coordinates(clientpoint);
752
753 // Ignore events out of the canvas area.
754 if (point.x < 0 || point.x > bounds.width || point.y < 0 || point.y > bounds.height) {
755 return;
756 }
757
758 if (this.currentedit.tool === 'pen') {
759 this.currentedit.path.push(point);
760 }
761
762 if (this.currentedit.tool === 'select') {
763 if (this.currentannotation && this.currentedit) {
764 this.currentannotation.move( this.currentedit.annotationstart.x + point.x - this.currentedit.start.x,
765 this.currentedit.annotationstart.y + point.y - this.currentedit.start.y);
766 }
767 } else {
768 if (this.currentedit.start) {
769 this.currentedit.end = point;
770 this.redraw_current_edit();
771 }
772 }
773 },
774
775 /**
776 * Event handler for mouseup or touchend.
777 * @protected
778 * @param Event
779 * @method edit_end
780 */
781 edit_end : function() {
782 var duration,
783 comment,
784 annotation;
785
786 duration = new Date().getTime() - this.currentedit.start;
787
788 if (duration < CLICKTIMEOUT || this.currentedit.start === false) {
789 return;
790 }
791
792 if (this.currentedit.tool === 'comment') {
793 if (this.currentdrawable) {
794 this.currentdrawable.erase();
795 }
796 this.currentdrawable = false;
797 comment = new M.assignfeedback_editpdf.comment(this);
baf881b8
RT
798 if (comment.init_from_edit(this.currentedit)) {
799 this.pages[this.currentpage].comments.push(comment);
800 this.drawables.push(comment.draw(true));
801 this.editingcomment = true;
802 }
5c386472
DW
803 } else {
804 annotation = this.create_annotation(this.currentedit.tool, {});
805 if (annotation) {
806 if (this.currentdrawable) {
807 this.currentdrawable.erase();
808 }
809 this.currentdrawable = false;
baf881b8
RT
810 if (annotation.init_from_edit(this.currentedit)) {
811 this.pages[this.currentpage].annotations.push(annotation);
812 this.drawables.push(annotation.draw());
813 }
5c386472
DW
814 }
815 }
816
5c386472
DW
817 // Save the changes.
818 this.save_current_page();
819
820 // Reset the current edit.
821 this.currentedit.starttime = 0;
822 this.currentedit.start = false;
823 this.currentedit.end = false;
824 this.currentedit.path = [];
825 },
826
827 /**
828 * Factory method for creating annotations of the correct subclass.
829 * @public
830 * @method create_annotation
831 */
832 create_annotation : function(type, data) {
833 data.type = type;
834 data.editor = this;
835 if (type === "line") {
836 return new M.assignfeedback_editpdf.annotationline(data);
837 } else if (type === "rectangle") {
838 return new M.assignfeedback_editpdf.annotationrectangle(data);
839 } else if (type === "oval") {
840 return new M.assignfeedback_editpdf.annotationoval(data);
841 } else if (type === "pen") {
842 return new M.assignfeedback_editpdf.annotationpen(data);
843 } else if (type === "highlight") {
844 return new M.assignfeedback_editpdf.annotationhighlight(data);
845 } else if (type === "stamp") {
846 return new M.assignfeedback_editpdf.annotationstamp(data);
847 }
848 return false;
849 },
850
851 /**
852 * Save all the annotations and comments for the current page.
853 * @protected
854 * @method save_current_page
855 */
856 save_current_page : function() {
857 var ajaxurl = AJAXBASE,
858 config;
859
860 config = {
861 method: 'post',
862 context: this,
863 sync: false,
864 data : {
865 'sesskey' : M.cfg.sesskey,
866 'action' : 'savepage',
867 'index' : this.currentpage,
868 'userid' : this.get('userid'),
869 'attemptnumber' : this.get('attemptnumber'),
870 'assignmentid' : this.get('assignmentid'),
871 'page' : this.stringify_current_page()
872 },
873 on: {
874 success: function(tid, response) {
875 var jsondata;
876 try {
877 jsondata = Y.JSON.parse(response.responseText);
878 if (jsondata.error) {
879 return new M.core.ajaxException(jsondata);
880 }
881 Y.one(SELECTOR.UNSAVEDCHANGESDIV).addClass('haschanges');
882 } catch (e) {
883 return new M.core.exception(e);
884 }
885 },
886 failure: function(tid, response) {
aa3e4bde 887 return new M.core.exception(response.responseText);
5c386472
DW
888 }
889 }
890 };
891
892 Y.io(ajaxurl, config);
893
894 },
895
896 /**
897 * Event handler to open the comment search interface.
898 *
899 * @param Event e
900 * @protected
901 * @method open_search_comments
902 */
903 open_search_comments : function(e) {
904 if (!this.searchcommentswindow) {
905 this.searchcommentswindow = new M.assignfeedback_editpdf.commentsearch({
906 editor : this
907 });
908 }
909
910 this.searchcommentswindow.show();
911 e.preventDefault();
912 },
913
914 /**
915 * Redraw all the comments and annotations.
916 * @protected
917 * @method redraw
918 */
919 redraw : function() {
920 var i,
921 page;
922
923 page = this.pages[this.currentpage];
924 while (this.drawables.length > 0) {
925 this.drawables.pop().erase();
926 }
927
928 for (i = 0; i < page.annotations.length; i++) {
929 this.drawables.push(page.annotations[i].draw());
930 }
931 for (i = 0; i < page.comments.length; i++) {
932 this.drawables.push(page.comments[i].draw(false));
933 }
934 },
935
936 /**
937 * Load the image for this pdf page and remove the loading icon (if there).
938 * @protected
939 * @method change_page
940 */
941 change_page : function() {
942 var drawingcanvas = Y.one(SELECTOR.DRAWINGCANVAS),
943 page,
944 previousbutton,
945 nextbutton;
946
947 previousbutton = Y.one(SELECTOR.PREVIOUSBUTTON);
948 nextbutton = Y.one(SELECTOR.NEXTBUTTON);
949
950 if (this.currentpage > 0) {
951 previousbutton.removeAttribute('disabled');
952 } else {
953 previousbutton.setAttribute('disabled', 'true');
954 }
955 if (this.currentpage < (this.pagecount - 1)) {
956 nextbutton.removeAttribute('disabled');
957 } else {
958 nextbutton.setAttribute('disabled', 'true');
959 }
960
961 page = this.pages[this.currentpage];
962 this.loadingicon.hide();
963 drawingcanvas.setStyle('backgroundImage', 'url("' + page.url + '")');
964
ea6d8674
JM
965 // Update page select.
966 Y.one(SELECTOR.PAGESELECT).set('value', this.currentpage);
967
5c386472
DW
968 this.redraw();
969 },
970
971 /**
972 * Now we know how many pages there are,
973 * we can enable the navigation controls.
974 * @protected
975 * @method setup_navigation
976 */
977 setup_navigation : function() {
978 var pageselect,
979 i,
980 option,
981 previousbutton,
982 nextbutton;
983
984 pageselect = Y.one(SELECTOR.PAGESELECT);
985
986 options = pageselect.all('option');
987 if (options.size() <= 1) {
988 for (i = 0; i < this.pages.length; i++) {
989 option = Y.Node.create('<option/>');
990 option.setAttribute('value', i);
ea6d8674 991 option.setHTML(M.util.get_string('pagenumber', 'assignfeedback_editpdf', i+1));
5c386472
DW
992 pageselect.append(option);
993 }
994 }
995 pageselect.removeAttribute('disabled');
996 pageselect.on('change', function() {
997 this.currentpage = pageselect.get('value');
998 this.change_page();
999 }, this);
1000
1001 previousbutton = Y.one(SELECTOR.PREVIOUSBUTTON);
1002 nextbutton = Y.one(SELECTOR.NEXTBUTTON);
1003
1004 previousbutton.on('click', this.previous_page, this);
1005 previousbutton.on('key', this.previous_page, 'down:13', this);
1006 nextbutton.on('click', this.next_page, this);
1007 nextbutton.on('key', this.next_page, 'down:13', this);
1008 },
1009
1010 /**
1011 * Navigate to the previous page.
1012 * @protected
1013 * @method previous_page
1014 */
114913e3
DS
1015 previous_page : function(e) {
1016 e.preventDefault();
5c386472
DW
1017 this.currentpage--;
1018 if (this.currentpage < 0) {
1019 this.currentpage = 0;
1020 }
1021 this.change_page();
1022 },
1023
1024 /**
1025 * Navigate to the next page.
1026 * @protected
1027 * @method next_page
1028 */
114913e3
DS
1029 next_page : function(e) {
1030 e.preventDefault();
5c386472
DW
1031 this.currentpage++;
1032 if (this.currentpage >= this.pages.length) {
1033 this.currentpage = this.pages.length - 1;
1034 }
1035 this.change_page();
1036 }
1037
1038
1039
1040};
1041
1042Y.extend(EDITOR, Y.Base, EDITOR.prototype, {
1043 NAME : 'moodle-assignfeedback_editpdf-editor',
1044 ATTRS : {
1045 userid : {
1046 validator : Y.Lang.isInteger,
1047 value : 0
1048 },
1049 assignmentid : {
1050 validator : Y.Lang.isInteger,
1051 value : 0
1052 },
1053 attemptnumber : {
1054 validator : Y.Lang.isInteger,
1055 value : 0
1056 },
1057 header : {
1058 validator : Y.Lang.isString,
1059 value : ''
1060 },
1061 body : {
1062 validator : Y.Lang.isString,
1063 value : ''
1064 },
1065 footer : {
1066 validator : Y.Lang.isString,
1067 value : ''
1068 },
1069 linkid : {
1070 validator : Y.Lang.isString,
1071 value : ''
1072 },
1073 deletelinkid : {
1074 validator : Y.Lang.isString,
1075 value : ''
1076 },
1077 readonly : {
1078 validator : Y.Lang.isBoolean,
1079 value : true
1080 },
1081 stampfiles : {
1082 validator : Y.Lang.isArray,
1083 value : ''
aa3e4bde
JM
1084 },
1085 pagetotal : {
1086 validator : Y.Lang.isInteger,
1087 value : 0
5c386472
DW
1088 }
1089 }
1090});
1091
5c386472 1092M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
5c386472
DW
1093M.assignfeedback_editpdf.editor = M.assignfeedback_editpdf.editor || {};
1094
1095/**
1096 * Init function - will create a new instance every time.
1f777e5c 1097 * @method editor.init
5c386472
DW
1098 * @static
1099 * @param {Object} params
1100 */
1101M.assignfeedback_editpdf.editor.init = M.assignfeedback_editpdf.editor.init || function(params) {
1102 return new EDITOR(params);
1103};