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