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