[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 YUI.add('moodle-assignfeedback_editpdf-editor', function (Y, NAME) { 2 3 // This file is part of Moodle - http://moodle.org/ 4 // 5 // Moodle is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // Moodle is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU General Public License for more details. 14 // 15 // You should have received a copy of the GNU General Public License 16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 17 /* eslint-disable no-unused-vars */ 18 19 /** 20 * A list of globals used by this module. 21 * 22 * @module moodle-assignfeedback_editpdf-editor 23 */ 24 var AJAXBASE = M.cfg.wwwroot + '/mod/assign/feedback/editpdf/ajax.php', 25 AJAXBASEPROGRESS = M.cfg.wwwroot + '/mod/assign/feedback/editpdf/ajax_progress.php', 26 CSS = { 27 DIALOGUE: 'assignfeedback_editpdf_widget' 28 }, 29 SELECTOR = { 30 PREVIOUSBUTTON: '.navigate-previous-button', 31 NEXTBUTTON: ' .navigate-next-button', 32 SEARCHCOMMENTSBUTTON: '.searchcommentsbutton', 33 SEARCHFILTER: '.assignfeedback_editpdf_commentsearch input', 34 SEARCHCOMMENTSLIST: '.assignfeedback_editpdf_commentsearch ul', 35 PAGESELECT: '.navigate-page-select', 36 LOADINGICON: '.loading', 37 PROGRESSBARCONTAINER: '.progress-info.progress-striped', 38 DRAWINGREGION: '.drawingregion', 39 DRAWINGCANVAS: '.drawingcanvas', 40 SAVE: '.savebutton', 41 COMMENTCOLOURBUTTON: '.commentcolourbutton', 42 COMMENTMENU: '.commentdrawable a', 43 ANNOTATIONCOLOURBUTTON: '.annotationcolourbutton', 44 DELETEANNOTATIONBUTTON: '.deleteannotationbutton', 45 UNSAVEDCHANGESDIV: '.assignfeedback_editpdf_unsavedchanges', 46 UNSAVEDCHANGESINPUT: 'input[name="assignfeedback_editpdf_haschanges"]', 47 STAMPSBUTTON: '.currentstampbutton', 48 DIALOGUE: '.' + CSS.DIALOGUE 49 }, 50 SELECTEDBORDERCOLOUR = 'rgba(200, 200, 255, 0.9)', 51 SELECTEDFILLCOLOUR = 'rgba(200, 200, 255, 0.5)', 52 COMMENTTEXTCOLOUR = 'rgb(51, 51, 51)', 53 COMMENTCOLOUR = { 54 'white': 'rgb(255,255,255)', 55 'yellow': 'rgb(255,236,174)', 56 'red': 'rgb(249,181,179)', 57 'green': 'rgb(214,234,178)', 58 'blue': 'rgb(203,217,237)', 59 'clear': 'rgba(255,255,255, 0)' 60 }, 61 ANNOTATIONCOLOUR = { 62 'white': 'rgb(255,255,255)', 63 'yellow': 'rgb(255,207,53)', 64 'red': 'rgb(239,69,64)', 65 'green': 'rgb(152,202,62)', 66 'blue': 'rgb(125,159,211)', 67 'black': 'rgb(51,51,51)' 68 }, 69 CLICKTIMEOUT = 300, 70 TOOLSELECTOR = { 71 'comment': '.commentbutton', 72 'pen': '.penbutton', 73 'line': '.linebutton', 74 'rectangle': '.rectanglebutton', 75 'oval': '.ovalbutton', 76 'stamp': '.stampbutton', 77 'select': '.selectbutton', 78 'drag': '.dragbutton', 79 'highlight': '.highlightbutton' 80 }, 81 STROKEWEIGHT = 4; 82 // This file is part of Moodle - http://moodle.org/ 83 // 84 // Moodle is free software: you can redistribute it and/or modify 85 // it under the terms of the GNU General Public License as published by 86 // the Free Software Foundation, either version 3 of the License, or 87 // (at your option) any later version. 88 // 89 // Moodle is distributed in the hope that it will be useful, 90 // but WITHOUT ANY WARRANTY; without even the implied warranty of 91 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 92 // GNU General Public License for more details. 93 // 94 // You should have received a copy of the GNU General Public License 95 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 96 97 /** 98 * Provides an in browser PDF editor. 99 * 100 * @module moodle-assignfeedback_editpdf-editor 101 */ 102 103 /** 104 * Class representing a 2d point. 105 * 106 * @namespace M.assignfeedback_editpdf 107 * @param Number x 108 * @param Number y 109 * @class point 110 */ 111 var POINT = function(x, y) { 112 113 /** 114 * X coordinate. 115 * @property x 116 * @type int 117 * @public 118 */ 119 this.x = parseInt(x, 10); 120 121 /** 122 * Y coordinate. 123 * @property y 124 * @type int 125 * @public 126 */ 127 this.y = parseInt(y, 10); 128 129 /** 130 * Clip this point to the rect 131 * @method clip 132 * @param M.assignfeedback_editpdf.point 133 * @public 134 */ 135 this.clip = function(bounds) { 136 if (this.x < bounds.x) { 137 this.x = bounds.x; 138 } 139 if (this.x > (bounds.x + bounds.width)) { 140 this.x = bounds.x + bounds.width; 141 } 142 if (this.y < bounds.y) { 143 this.y = bounds.y; 144 } 145 if (this.y > (bounds.y + bounds.height)) { 146 this.y = bounds.y + bounds.height; 147 } 148 // For chaining. 149 return this; 150 }; 151 }; 152 153 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {}; 154 M.assignfeedback_editpdf.point = POINT; 155 // This file is part of Moodle - http://moodle.org/ 156 // 157 // Moodle is free software: you can redistribute it and/or modify 158 // it under the terms of the GNU General Public License as published by 159 // the Free Software Foundation, either version 3 of the License, or 160 // (at your option) any later version. 161 // 162 // Moodle is distributed in the hope that it will be useful, 163 // but WITHOUT ANY WARRANTY; without even the implied warranty of 164 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 165 // GNU General Public License for more details. 166 // 167 // You should have received a copy of the GNU General Public License 168 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 169 170 /** 171 * Provides an in browser PDF editor. 172 * 173 * @module moodle-assignfeedback_editpdf-editor 174 */ 175 176 /** 177 * Class representing a 2d rect. 178 * 179 * @namespace M.assignfeedback_editpdf 180 * @param int x 181 * @param int y 182 * @param int width 183 * @param int height 184 * @class rect 185 */ 186 var RECT = function(x, y, width, height) { 187 188 /** 189 * X coordinate. 190 * @property x 191 * @type int 192 * @public 193 */ 194 this.x = x; 195 196 /** 197 * Y coordinate. 198 * @property y 199 * @type int 200 * @public 201 */ 202 this.y = y; 203 204 /** 205 * Width 206 * @property width 207 * @type int 208 * @public 209 */ 210 this.width = width; 211 212 /** 213 * Height 214 * @property height 215 * @type int 216 * @public 217 */ 218 this.height = height; 219 220 /** 221 * Set this rect to represent the smallest possible rectangle containing this list of points. 222 * @method bounds 223 * @param M.assignfeedback_editpdf.point[] 224 * @public 225 */ 226 this.bound = function(points) { 227 var minx = 0, 228 maxx = 0, 229 miny = 0, 230 maxy = 0, 231 i = 0, 232 point; 233 234 for (i = 0; i < points.length; i++) { 235 point = points[i]; 236 if (point.x < minx || i === 0) { 237 minx = point.x; 238 } 239 if (point.x > maxx || i === 0) { 240 maxx = point.x; 241 } 242 if (point.y < miny || i === 0) { 243 miny = point.y; 244 } 245 if (point.y > maxy || i === 0) { 246 maxy = point.y; 247 } 248 } 249 this.x = minx; 250 this.y = miny; 251 this.width = maxx - minx; 252 this.height = maxy - miny; 253 // Allow chaining. 254 return this; 255 }; 256 257 /** 258 * Checks if rect has min width. 259 * @method has_min_width 260 * @return bool true if width is more than 5px. 261 * @public 262 */ 263 this.has_min_width = function() { 264 return (this.width >= 5); 265 }; 266 267 /** 268 * Checks if rect has min height. 269 * @method has_min_height 270 * @return bool true if height is more than 5px. 271 * @public 272 */ 273 this.has_min_height = function() { 274 return (this.height >= 5); 275 }; 276 277 /** 278 * Set min. width of annotation bound. 279 * @method set_min_width 280 * @public 281 */ 282 this.set_min_width = function() { 283 this.width = 5; 284 }; 285 286 /** 287 * Set min. height of annotation bound. 288 * @method set_min_height 289 * @public 290 */ 291 this.set_min_height = function() { 292 this.height = 5; 293 }; 294 }; 295 296 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {}; 297 M.assignfeedback_editpdf.rect = RECT; 298 // This file is part of Moodle - http://moodle.org/ 299 // 300 // Moodle is free software: you can redistribute it and/or modify 301 // it under the terms of the GNU General Public License as published by 302 // the Free Software Foundation, either version 3 of the License, or 303 // (at your option) any later version. 304 // 305 // Moodle is distributed in the hope that it will be useful, 306 // but WITHOUT ANY WARRANTY; without even the implied warranty of 307 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 308 // GNU General Public License for more details. 309 // 310 // You should have received a copy of the GNU General Public License 311 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 312 313 /** 314 * Provides an in browser PDF editor. 315 * 316 * @module moodle-assignfeedback_editpdf-editor 317 */ 318 319 /** 320 * EDIT 321 * 322 * @namespace M.assignfeedback_editpdf 323 * @class edit 324 */ 325 var EDIT = function() { 326 327 /** 328 * Starting point for the edit. 329 * @property start 330 * @type M.assignfeedback_editpdf.point|false 331 * @public 332 */ 333 this.start = false; 334 335 /** 336 * Finishing point for the edit. 337 * @property end 338 * @type M.assignfeedback_editpdf.point|false 339 * @public 340 */ 341 this.end = false; 342 343 /** 344 * Starting time for the edit. 345 * @property starttime 346 * @type int 347 * @public 348 */ 349 this.starttime = 0; 350 351 /** 352 * Starting point for the currently selected annotation. 353 * @property annotationstart 354 * @type M.assignfeedback_editpdf.point|false 355 * @public 356 */ 357 this.annotationstart = false; 358 359 /** 360 * The currently selected tool 361 * @property tool 362 * @type String 363 * @public 364 */ 365 this.tool = "drag"; 366 367 /** 368 * The currently comment colour 369 * @property commentcolour 370 * @type String 371 * @public 372 */ 373 this.commentcolour = 'yellow'; 374 375 /** 376 * The currently annotation colour 377 * @property annotationcolour 378 * @type String 379 * @public 380 */ 381 this.annotationcolour = 'red'; 382 383 /** 384 * The current stamp image. 385 * @property stamp 386 * @type String 387 * @public 388 */ 389 this.stamp = ''; 390 391 /** 392 * List of points the the current drawing path. 393 * @property path 394 * @type M.assignfeedback_editpdf.point[] 395 * @public 396 */ 397 this.path = []; 398 }; 399 400 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {}; 401 M.assignfeedback_editpdf.edit = EDIT; 402 // This file is part of Moodle - http://moodle.org/ 403 // 404 // Moodle is free software: you can redistribute it and/or modify 405 // it under the terms of the GNU General Public License as published by 406 // the Free Software Foundation, either version 3 of the License, or 407 // (at your option) any later version. 408 // 409 // Moodle is distributed in the hope that it will be useful, 410 // but WITHOUT ANY WARRANTY; without even the implied warranty of 411 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 412 // GNU General Public License for more details. 413 // 414 // You should have received a copy of the GNU General Public License 415 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 416 /* global SELECTOR */ 417 418 /** 419 * Provides an in browser PDF editor. 420 * 421 * @module moodle-assignfeedback_editpdf-editor 422 */ 423 424 /** 425 * Class representing a drawable thing which contains both Y.Nodes, and Y.Shapes. 426 * 427 * @namespace M.assignfeedback_editpdf 428 * @param M.assignfeedback_editpdf.editor editor 429 * @class drawable 430 */ 431 var DRAWABLE = function(editor) { 432 433 /** 434 * Reference to M.assignfeedback_editpdf.editor. 435 * @property editor 436 * @type M.assignfeedback_editpdf.editor 437 * @public 438 */ 439 this.editor = editor; 440 441 /** 442 * Array of Y.Shape 443 * @property shapes 444 * @type Y.Shape[] 445 * @public 446 */ 447 this.shapes = []; 448 449 /** 450 * Array of Y.Node 451 * @property nodes 452 * @type Y.Node[] 453 * @public 454 */ 455 this.nodes = []; 456 457 /** 458 * Delete the shapes from the drawable. 459 * @protected 460 * @method erase_drawable 461 */ 462 this.erase = function() { 463 if (this.shapes) { 464 while (this.shapes.length > 0) { 465 this.editor.graphic.removeShape(this.shapes.pop()); 466 } 467 } 468 if (this.nodes) { 469 while (this.nodes.length > 0) { 470 this.nodes.pop().remove(); 471 } 472 } 473 }; 474 475 /** 476 * Update the positions of all absolutely positioned nodes, when the drawing canvas is scrolled 477 * @public 478 * @method scroll_update 479 * @param scrollx int 480 * @param scrolly int 481 */ 482 this.scroll_update = function(scrollx, scrolly) { 483 var i, x, y; 484 for (i = 0; i < this.nodes.length; i++) { 485 x = this.nodes[i].getData('x'); 486 y = this.nodes[i].getData('y'); 487 if (x !== undefined && y !== undefined) { 488 this.nodes[i].setX(parseInt(x, 10) - scrollx); 489 this.nodes[i].setY(parseInt(y, 10) - scrolly); 490 } 491 } 492 }; 493 494 /** 495 * Store the initial position of the node, so it can be updated when the drawing canvas is scrolled 496 * @public 497 * @method store_position 498 * @param container 499 * @param x 500 * @param y 501 */ 502 this.store_position = function(container, x, y) { 503 var drawingregion, scrollx, scrolly; 504 505 drawingregion = this.editor.get_dialogue_element(SELECTOR.DRAWINGREGION); 506 scrollx = parseInt(drawingregion.get('scrollLeft'), 10); 507 scrolly = parseInt(drawingregion.get('scrollTop'), 10); 508 container.setData('x', x + scrollx); 509 container.setData('y', y + scrolly); 510 }; 511 }; 512 513 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {}; 514 M.assignfeedback_editpdf.drawable = DRAWABLE; 515 // This file is part of Moodle - http://moodle.org/ 516 // 517 // Moodle is free software: you can redistribute it and/or modify 518 // it under the terms of the GNU General Public License as published by 519 // the Free Software Foundation, either version 3 of the License, or 520 // (at your option) any later version. 521 // 522 // Moodle is distributed in the hope that it will be useful, 523 // but WITHOUT ANY WARRANTY; without even the implied warranty of 524 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 525 // GNU General Public License for more details. 526 // 527 // You should have received a copy of the GNU General Public License 528 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 529 /* global STROKEWEIGHT, SELECTOR, SELECTEDBORDERCOLOUR, SELECTEDFILLCOLOUR */ 530 531 /** 532 * Provides an in browser PDF editor. 533 * 534 * @module moodle-assignfeedback_editpdf-editor 535 */ 536 537 /** 538 * Class representing a highlight. 539 * 540 * @namespace M.assignfeedback_editpdf 541 * @class annotation 542 * @constructor 543 */ 544 var ANNOTATION = function(config) { 545 ANNOTATION.superclass.constructor.apply(this, [config]); 546 }; 547 548 ANNOTATION.NAME = "annotation"; 549 ANNOTATION.ATTRS = {}; 550 551 Y.extend(ANNOTATION, Y.Base, { 552 /** 553 * Reference to M.assignfeedback_editpdf.editor. 554 * @property editor 555 * @type M.assignfeedback_editpdf.editor 556 * @public 557 */ 558 editor: null, 559 560 /** 561 * Grade id 562 * @property gradeid 563 * @type Int 564 * @public 565 */ 566 gradeid: 0, 567 568 /** 569 * Comment page number 570 * @property pageno 571 * @type Int 572 * @public 573 */ 574 pageno: 0, 575 576 /** 577 * X position 578 * @property x 579 * @type Int 580 * @public 581 */ 582 x: 0, 583 584 /** 585 * Y position 586 * @property y 587 * @type Int 588 * @public 589 */ 590 y: 0, 591 592 /** 593 * Ending x position 594 * @property endx 595 * @type Int 596 * @public 597 */ 598 endx: 0, 599 600 /** 601 * Ending y position 602 * @property endy 603 * @type Int 604 * @public 605 */ 606 endy: 0, 607 608 /** 609 * Path 610 * @property path 611 * @type String - list of points like x1,y1:x2,y2 612 * @public 613 */ 614 path: '', 615 616 /** 617 * Tool. 618 * @property type 619 * @type String 620 * @public 621 */ 622 type: 'rect', 623 624 /** 625 * Annotation colour. 626 * @property colour 627 * @type String 628 * @public 629 */ 630 colour: 'red', 631 632 /** 633 * Reference to M.assignfeedback_editpdf.drawable 634 * @property drawable 635 * @type M.assignfeedback_editpdf.drawable 636 * @public 637 */ 638 drawable: false, 639 640 /** 641 * Initialise the annotation. 642 * 643 * @method initializer 644 * @return void 645 */ 646 initializer: function(config) { 647 this.editor = config.editor || null; 648 this.gradeid = parseInt(config.gradeid, 10) || 0; 649 this.pageno = parseInt(config.pageno, 10) || 0; 650 this.x = parseInt(config.x, 10) || 0; 651 this.y = parseInt(config.y, 10) || 0; 652 this.endx = parseInt(config.endx, 10) || 0; 653 this.endy = parseInt(config.endy, 10) || 0; 654 this.path = config.path || ''; 655 this.type = config.type || 'rect'; 656 this.colour = config.colour || 'red'; 657 this.drawable = false; 658 }, 659 660 /** 661 * Clean a comment record, returning an oject with only fields that are valid. 662 * @public 663 * @method clean 664 * @return {} 665 */ 666 clean: function() { 667 return { 668 gradeid: this.gradeid, 669 x: parseInt(this.x, 10), 670 y: parseInt(this.y, 10), 671 endx: parseInt(this.endx, 10), 672 endy: parseInt(this.endy, 10), 673 type: this.type, 674 path: this.path, 675 pageno: this.pageno, 676 colour: this.colour 677 }; 678 }, 679 680 /** 681 * Draw a selection around this annotation if it is selected. 682 * @public 683 * @method draw_highlight 684 * @return M.assignfeedback_editpdf.drawable 685 */ 686 draw_highlight: function() { 687 var bounds, 688 drawingregion = this.editor.get_dialogue_element(SELECTOR.DRAWINGREGION), 689 offsetcanvas = this.editor.get_dialogue_element(SELECTOR.DRAWINGCANVAS).getXY(), 690 shape; 691 692 if (this.editor.currentannotation === this) { 693 // Draw a highlight around the annotation. 694 bounds = new M.assignfeedback_editpdf.rect(); 695 bounds.bound([new M.assignfeedback_editpdf.point(this.x, this.y), 696 new M.assignfeedback_editpdf.point(this.endx, this.endy)]); 697 698 shape = this.editor.graphic.addShape({ 699 type: Y.Rect, 700 width: bounds.width, 701 height: bounds.height, 702 stroke: { 703 weight: STROKEWEIGHT, 704 color: SELECTEDBORDERCOLOUR 705 }, 706 fill: { 707 color: SELECTEDFILLCOLOUR 708 }, 709 x: bounds.x, 710 y: bounds.y 711 }); 712 this.drawable.shapes.push(shape); 713 714 // Add a delete X to the annotation. 715 var deleteicon = Y.Node.create('<img src="' + M.util.image_url('trash', 'assignfeedback_editpdf') + '"/>'), 716 deletelink = Y.Node.create('<a href="#" role="button"></a>'); 717 718 deleteicon.setAttrs({ 719 'alt': M.util.get_string('deleteannotation', 'assignfeedback_editpdf') 720 }); 721 deleteicon.setStyles({ 722 'backgroundColor': 'white' 723 }); 724 deletelink.addClass('deleteannotationbutton'); 725 deletelink.append(deleteicon); 726 727 drawingregion.append(deletelink); 728 deletelink.setData('annotation', this); 729 deletelink.setStyle('zIndex', '200'); 730 731 deletelink.on('click', this.remove, this); 732 deletelink.on('key', this.remove, 'space,enter', this); 733 734 deletelink.setX(offsetcanvas[0] + bounds.x + bounds.width - 18); 735 deletelink.setY(offsetcanvas[1] + bounds.y + 6); 736 this.drawable.nodes.push(deletelink); 737 } 738 return this.drawable; 739 }, 740 741 /** 742 * Draw an annotation 743 * @public 744 * @method draw 745 * @return M.assignfeedback_editpdf.drawable|false 746 */ 747 draw: function() { 748 // Should be overridden by the subclass. 749 this.draw_highlight(); 750 return this.drawable; 751 }, 752 753 /** 754 * Delete an annotation 755 * @protected 756 * @method remove 757 * @param event 758 */ 759 remove: function(e) { 760 var annotations, 761 i; 762 763 e.preventDefault(); 764 765 annotations = this.editor.pages[this.editor.currentpage].annotations; 766 for (i = 0; i < annotations.length; i++) { 767 if (annotations[i] === this) { 768 annotations.splice(i, 1); 769 if (this.drawable) { 770 this.drawable.erase(); 771 } 772 this.editor.currentannotation = false; 773 this.editor.save_current_page(); 774 return; 775 } 776 } 777 }, 778 779 /** 780 * Move an annotation to a new location. 781 * @public 782 * @param int newx 783 * @param int newy 784 * @method move_annotation 785 */ 786 move: function(newx, newy) { 787 var diffx = newx - this.x, 788 diffy = newy - this.y, 789 newpath, oldpath, xy, 790 x, y; 791 792 this.x += diffx; 793 this.y += diffy; 794 this.endx += diffx; 795 this.endy += diffy; 796 797 if (this.path) { 798 newpath = []; 799 oldpath = this.path.split(':'); 800 Y.each(oldpath, function(position) { 801 xy = position.split(','); 802 x = parseInt(xy[0], 10); 803 y = parseInt(xy[1], 10); 804 newpath.push((x + diffx) + ',' + (y + diffy)); 805 }); 806 807 this.path = newpath.join(':'); 808 809 } 810 if (this.drawable) { 811 this.drawable.erase(); 812 } 813 this.editor.drawables.push(this.draw()); 814 }, 815 816 /** 817 * Draw the in progress edit. 818 * 819 * @public 820 * @method draw_current_edit 821 * @param M.assignfeedback_editpdf.edit edit 822 */ 823 draw_current_edit: function(edit) { 824 var noop = edit && false; 825 // Override me please. 826 return noop; 827 }, 828 829 /** 830 * Promote the current edit to a real annotation. 831 * 832 * @public 833 * @method init_from_edit 834 * @param M.assignfeedback_editpdf.edit edit 835 * @return bool if width/height is more than min. required. 836 */ 837 init_from_edit: function(edit) { 838 var bounds = new M.assignfeedback_editpdf.rect(); 839 bounds.bound([edit.start, edit.end]); 840 841 this.gradeid = this.editor.get('gradeid'); 842 this.pageno = this.editor.currentpage; 843 this.x = bounds.x; 844 this.y = bounds.y; 845 this.endx = bounds.x + bounds.width; 846 this.endy = bounds.y + bounds.height; 847 this.colour = edit.annotationcolour; 848 this.path = ''; 849 return (bounds.has_min_width() && bounds.has_min_height()); 850 } 851 852 }); 853 854 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {}; 855 M.assignfeedback_editpdf.annotation = ANNOTATION; 856 // This file is part of Moodle - http://moodle.org/ 857 // 858 // Moodle is free software: you can redistribute it and/or modify 859 // it under the terms of the GNU General Public License as published by 860 // the Free Software Foundation, either version 3 of the License, or 861 // (at your option) any later version. 862 // 863 // Moodle is distributed in the hope that it will be useful, 864 // but WITHOUT ANY WARRANTY; without even the implied warranty of 865 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 866 // GNU General Public License for more details. 867 // 868 // You should have received a copy of the GNU General Public License 869 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 870 /* global STROKEWEIGHT, ANNOTATIONCOLOUR */ 871 872 /** 873 * Provides an in browser PDF editor. 874 * 875 * @module moodle-assignfeedback_editpdf-editor 876 */ 877 878 /** 879 * Class representing a line. 880 * 881 * @namespace M.assignfeedback_editpdf 882 * @class annotationline 883 * @extends M.assignfeedback_editpdf.annotation 884 */ 885 var ANNOTATIONLINE = function(config) { 886 ANNOTATIONLINE.superclass.constructor.apply(this, [config]); 887 }; 888 889 ANNOTATIONLINE.NAME = "annotationline"; 890 ANNOTATIONLINE.ATTRS = {}; 891 892 Y.extend(ANNOTATIONLINE, M.assignfeedback_editpdf.annotation, { 893 /** 894 * Draw a line annotation 895 * @protected 896 * @method draw 897 * @return M.assignfeedback_editpdf.drawable 898 */ 899 draw: function() { 900 var drawable, 901 shape; 902 903 drawable = new M.assignfeedback_editpdf.drawable(this.editor); 904 905 shape = this.editor.graphic.addShape({ 906 type: Y.Path, 907 fill: false, 908 stroke: { 909 weight: STROKEWEIGHT, 910 color: ANNOTATIONCOLOUR[this.colour] 911 } 912 }); 913 914 shape.moveTo(this.x, this.y); 915 shape.lineTo(this.endx, this.endy); 916 shape.end(); 917 drawable.shapes.push(shape); 918 this.drawable = drawable; 919 920 return ANNOTATIONLINE.superclass.draw.apply(this); 921 }, 922 923 /** 924 * Draw the in progress edit. 925 * 926 * @public 927 * @method draw_current_edit 928 * @param M.assignfeedback_editpdf.edit edit 929 */ 930 draw_current_edit: function(edit) { 931 var drawable = new M.assignfeedback_editpdf.drawable(this.editor), 932 shape; 933 934 shape = this.editor.graphic.addShape({ 935 type: Y.Path, 936 fill: false, 937 stroke: { 938 weight: STROKEWEIGHT, 939 color: ANNOTATIONCOLOUR[edit.annotationcolour] 940 } 941 }); 942 943 shape.moveTo(edit.start.x, edit.start.y); 944 shape.lineTo(edit.end.x, edit.end.y); 945 shape.end(); 946 947 drawable.shapes.push(shape); 948 949 return drawable; 950 }, 951 952 /** 953 * Promote the current edit to a real annotation. 954 * 955 * @public 956 * @method init_from_edit 957 * @param M.assignfeedback_editpdf.edit edit 958 * @return bool true if line bound is more than min width/height, else false. 959 */ 960 init_from_edit: function(edit) { 961 this.gradeid = this.editor.get('gradeid'); 962 this.pageno = this.editor.currentpage; 963 this.x = edit.start.x; 964 this.y = edit.start.y; 965 this.endx = edit.end.x; 966 this.endy = edit.end.y; 967 this.colour = edit.annotationcolour; 968 this.path = ''; 969 970 return !(((this.endx - this.x) === 0) && ((this.endy - this.y) === 0)); 971 } 972 973 }); 974 975 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {}; 976 M.assignfeedback_editpdf.annotationline = ANNOTATIONLINE; 977 // This file is part of Moodle - http://moodle.org/ 978 // 979 // Moodle is free software: you can redistribute it and/or modify 980 // it under the terms of the GNU General Public License as published by 981 // the Free Software Foundation, either version 3 of the License, or 982 // (at your option) any later version. 983 // 984 // Moodle is distributed in the hope that it will be useful, 985 // but WITHOUT ANY WARRANTY; without even the implied warranty of 986 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 987 // GNU General Public License for more details. 988 // 989 // You should have received a copy of the GNU General Public License 990 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 991 /* global STROKEWEIGHT, ANNOTATIONCOLOUR */ 992 993 /** 994 * Provides an in browser PDF editor. 995 * 996 * @module moodle-assignfeedback_editpdf-editor 997 */ 998 999 /** 1000 * Class representing a rectangle. 1001 * 1002 * @namespace M.assignfeedback_editpdf 1003 * @class annotationrectangle 1004 * @extends M.assignfeedback_editpdf.annotation 1005 */ 1006 var ANNOTATIONRECTANGLE = function(config) { 1007 ANNOTATIONRECTANGLE.superclass.constructor.apply(this, [config]); 1008 }; 1009 1010 ANNOTATIONRECTANGLE.NAME = "annotationrectangle"; 1011 ANNOTATIONRECTANGLE.ATTRS = {}; 1012 1013 Y.extend(ANNOTATIONRECTANGLE, M.assignfeedback_editpdf.annotation, { 1014 /** 1015 * Draw a rectangle annotation 1016 * @protected 1017 * @method draw 1018 * @return M.assignfeedback_editpdf.drawable 1019 */ 1020 draw: function() { 1021 var drawable, 1022 bounds, 1023 shape; 1024 1025 drawable = new M.assignfeedback_editpdf.drawable(this.editor); 1026 1027 bounds = new M.assignfeedback_editpdf.rect(); 1028 bounds.bound([new M.assignfeedback_editpdf.point(this.x, this.y), 1029 new M.assignfeedback_editpdf.point(this.endx, this.endy)]); 1030 1031 shape = this.editor.graphic.addShape({ 1032 type: Y.Rect, 1033 width: bounds.width, 1034 height: bounds.height, 1035 stroke: { 1036 weight: STROKEWEIGHT, 1037 color: ANNOTATIONCOLOUR[this.colour] 1038 }, 1039 x: bounds.x, 1040 y: bounds.y 1041 }); 1042 drawable.shapes.push(shape); 1043 this.drawable = drawable; 1044 1045 return ANNOTATIONRECTANGLE.superclass.draw.apply(this); 1046 }, 1047 1048 /** 1049 * Draw the in progress edit. 1050 * 1051 * @public 1052 * @method draw_current_edit 1053 * @param M.assignfeedback_editpdf.edit edit 1054 */ 1055 draw_current_edit: function(edit) { 1056 var drawable = new M.assignfeedback_editpdf.drawable(this.editor), 1057 shape, 1058 bounds; 1059 1060 bounds = new M.assignfeedback_editpdf.rect(); 1061 bounds.bound([new M.assignfeedback_editpdf.point(edit.start.x, edit.start.y), 1062 new M.assignfeedback_editpdf.point(edit.end.x, edit.end.y)]); 1063 1064 // Set min. width and height of rectangle. 1065 if (!bounds.has_min_width()) { 1066 bounds.set_min_width(); 1067 } 1068 if (!bounds.has_min_height()) { 1069 bounds.set_min_height(); 1070 } 1071 1072 shape = this.editor.graphic.addShape({ 1073 type: Y.Rect, 1074 width: bounds.width, 1075 height: bounds.height, 1076 stroke: { 1077 weight: STROKEWEIGHT, 1078 color: ANNOTATIONCOLOUR[edit.annotationcolour] 1079 }, 1080 x: bounds.x, 1081 y: bounds.y 1082 }); 1083 1084 drawable.shapes.push(shape); 1085 1086 return drawable; 1087 } 1088 }); 1089 1090 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {}; 1091 M.assignfeedback_editpdf.annotationrectangle = ANNOTATIONRECTANGLE; 1092 // This file is part of Moodle - http://moodle.org/ 1093 // 1094 // Moodle is free software: you can redistribute it and/or modify 1095 // it under the terms of the GNU General Public License as published by 1096 // the Free Software Foundation, either version 3 of the License, or 1097 // (at your option) any later version. 1098 // 1099 // Moodle is distributed in the hope that it will be useful, 1100 // but WITHOUT ANY WARRANTY; without even the implied warranty of 1101 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1102 // GNU General Public License for more details. 1103 // 1104 // You should have received a copy of the GNU General Public License 1105 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 1106 /* global STROKEWEIGHT, ANNOTATIONCOLOUR */ 1107 1108 /** 1109 * Provides an in browser PDF editor. 1110 * 1111 * @module moodle-assignfeedback_editpdf-editor 1112 */ 1113 1114 /** 1115 * Class representing a oval. 1116 * 1117 * @namespace M.assignfeedback_editpdf 1118 * @class annotationoval 1119 * @extends M.assignfeedback_editpdf.annotation 1120 */ 1121 var ANNOTATIONOVAL = function(config) { 1122 ANNOTATIONOVAL.superclass.constructor.apply(this, [config]); 1123 }; 1124 1125 ANNOTATIONOVAL.NAME = "annotationoval"; 1126 ANNOTATIONOVAL.ATTRS = {}; 1127 1128 Y.extend(ANNOTATIONOVAL, M.assignfeedback_editpdf.annotation, { 1129 /** 1130 * Draw a oval annotation 1131 * @protected 1132 * @method draw 1133 * @return M.assignfeedback_editpdf.drawable 1134 */ 1135 draw: function() { 1136 var drawable, 1137 bounds, 1138 shape; 1139 1140 drawable = new M.assignfeedback_editpdf.drawable(this.editor); 1141 1142 bounds = new M.assignfeedback_editpdf.rect(); 1143 bounds.bound([new M.assignfeedback_editpdf.point(this.x, this.y), 1144 new M.assignfeedback_editpdf.point(this.endx, this.endy)]); 1145 1146 shape = this.editor.graphic.addShape({ 1147 type: Y.Ellipse, 1148 width: bounds.width, 1149 height: bounds.height, 1150 stroke: { 1151 weight: STROKEWEIGHT, 1152 color: ANNOTATIONCOLOUR[this.colour] 1153 }, 1154 x: bounds.x, 1155 y: bounds.y 1156 }); 1157 drawable.shapes.push(shape); 1158 this.drawable = drawable; 1159 1160 return ANNOTATIONOVAL.superclass.draw.apply(this); 1161 }, 1162 1163 /** 1164 * Draw the in progress edit. 1165 * 1166 * @public 1167 * @method draw_current_edit 1168 * @param M.assignfeedback_editpdf.edit edit 1169 */ 1170 draw_current_edit: function(edit) { 1171 var drawable = new M.assignfeedback_editpdf.drawable(this.editor), 1172 shape, 1173 bounds; 1174 1175 bounds = new M.assignfeedback_editpdf.rect(); 1176 bounds.bound([new M.assignfeedback_editpdf.point(edit.start.x, edit.start.y), 1177 new M.assignfeedback_editpdf.point(edit.end.x, edit.end.y)]); 1178 1179 // Set min. width and height of oval. 1180 if (!bounds.has_min_width()) { 1181 bounds.set_min_width(); 1182 } 1183 if (!bounds.has_min_height()) { 1184 bounds.set_min_height(); 1185 } 1186 1187 shape = this.editor.graphic.addShape({ 1188 type: Y.Ellipse, 1189 width: bounds.width, 1190 height: bounds.height, 1191 stroke: { 1192 weight: STROKEWEIGHT, 1193 color: ANNOTATIONCOLOUR[edit.annotationcolour] 1194 }, 1195 x: bounds.x, 1196 y: bounds.y 1197 }); 1198 1199 drawable.shapes.push(shape); 1200 1201 return drawable; 1202 } 1203 }); 1204 1205 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {}; 1206 M.assignfeedback_editpdf.annotationoval = ANNOTATIONOVAL; 1207 // This file is part of Moodle - http://moodle.org/ 1208 // 1209 // Moodle is free software: you can redistribute it and/or modify 1210 // it under the terms of the GNU General Public License as published by 1211 // the Free Software Foundation, either version 3 of the License, or 1212 // (at your option) any later version. 1213 // 1214 // Moodle is distributed in the hope that it will be useful, 1215 // but WITHOUT ANY WARRANTY; without even the implied warranty of 1216 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1217 // GNU General Public License for more details. 1218 // 1219 // You should have received a copy of the GNU General Public License 1220 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 1221 /* global STROKEWEIGHT, ANNOTATIONCOLOUR */ 1222 1223 /** 1224 * Provides an in browser PDF editor. 1225 * 1226 * @module moodle-assignfeedback_editpdf-editor 1227 */ 1228 1229 /** 1230 * Class representing a pen. 1231 * 1232 * @namespace M.assignfeedback_editpdf 1233 * @class annotationpen 1234 * @extends M.assignfeedback_editpdf.annotation 1235 */ 1236 var ANNOTATIONPEN = function(config) { 1237 ANNOTATIONPEN.superclass.constructor.apply(this, [config]); 1238 }; 1239 1240 ANNOTATIONPEN.NAME = "annotationpen"; 1241 ANNOTATIONPEN.ATTRS = {}; 1242 1243 Y.extend(ANNOTATIONPEN, M.assignfeedback_editpdf.annotation, { 1244 /** 1245 * Draw a pen annotation 1246 * @protected 1247 * @method draw 1248 * @return M.assignfeedback_editpdf.drawable 1249 */ 1250 draw: function() { 1251 var drawable, 1252 shape, 1253 first, 1254 positions, 1255 xy; 1256 1257 drawable = new M.assignfeedback_editpdf.drawable(this.editor); 1258 1259 shape = this.editor.graphic.addShape({ 1260 type: Y.Path, 1261 fill: false, 1262 stroke: { 1263 weight: STROKEWEIGHT, 1264 color: ANNOTATIONCOLOUR[this.colour] 1265 } 1266 }); 1267 1268 first = true; 1269 // Recreate the pen path array. 1270 positions = this.path.split(':'); 1271 // Redraw all the lines. 1272 Y.each(positions, function(position) { 1273 xy = position.split(','); 1274 if (first) { 1275 shape.moveTo(xy[0], xy[1]); 1276 first = false; 1277 } else { 1278 shape.lineTo(xy[0], xy[1]); 1279 } 1280 }, this); 1281 1282 shape.end(); 1283 1284 drawable.shapes.push(shape); 1285 this.drawable = drawable; 1286 1287 return ANNOTATIONPEN.superclass.draw.apply(this); 1288 }, 1289 1290 /** 1291 * Draw the in progress edit. 1292 * 1293 * @public 1294 * @method draw_current_edit 1295 * @param M.assignfeedback_editpdf.edit edit 1296 */ 1297 draw_current_edit: function(edit) { 1298 var drawable = new M.assignfeedback_editpdf.drawable(this.editor), 1299 shape, 1300 first; 1301 1302 shape = this.editor.graphic.addShape({ 1303 type: Y.Path, 1304 fill: false, 1305 stroke: { 1306 weight: STROKEWEIGHT, 1307 color: ANNOTATIONCOLOUR[edit.annotationcolour] 1308 } 1309 }); 1310 1311 first = true; 1312 // Recreate the pen path array. 1313 // Redraw all the lines. 1314 Y.each(edit.path, function(position) { 1315 if (first) { 1316 shape.moveTo(position.x, position.y); 1317 first = false; 1318 } else { 1319 shape.lineTo(position.x, position.y); 1320 } 1321 }, this); 1322 1323 shape.end(); 1324 1325 drawable.shapes.push(shape); 1326 1327 return drawable; 1328 }, 1329 1330 1331 /** 1332 * Promote the current edit to a real annotation. 1333 * 1334 * @public 1335 * @method init_from_edit 1336 * @param M.assignfeedback_editpdf.edit edit 1337 * @return bool true if pen bound is more than min width/height, else false. 1338 */ 1339 init_from_edit: function(edit) { 1340 var bounds = new M.assignfeedback_editpdf.rect(), 1341 pathlist = [], 1342 i = 0; 1343 1344 // This will get the boundaries of all points in the path. 1345 bounds.bound(edit.path); 1346 1347 for (i = 0; i < edit.path.length; i++) { 1348 pathlist.push(parseInt(edit.path[i].x, 10) + ',' + parseInt(edit.path[i].y, 10)); 1349 } 1350 1351 this.gradeid = this.editor.get('gradeid'); 1352 this.pageno = this.editor.currentpage; 1353 this.x = bounds.x; 1354 this.y = bounds.y; 1355 this.endx = bounds.x + bounds.width; 1356 this.endy = bounds.y + bounds.height; 1357 this.colour = edit.annotationcolour; 1358 this.path = pathlist.join(':'); 1359 1360 return (bounds.has_min_width() || bounds.has_min_height()); 1361 } 1362 1363 1364 }); 1365 1366 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {}; 1367 M.assignfeedback_editpdf.annotationpen = ANNOTATIONPEN; 1368 // This file is part of Moodle - http://moodle.org/ 1369 // 1370 // Moodle is free software: you can redistribute it and/or modify 1371 // it under the terms of the GNU General Public License as published by 1372 // the Free Software Foundation, either version 3 of the License, or 1373 // (at your option) any later version. 1374 // 1375 // Moodle is distributed in the hope that it will be useful, 1376 // but WITHOUT ANY WARRANTY; without even the implied warranty of 1377 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1378 // GNU General Public License for more details. 1379 // 1380 // You should have received a copy of the GNU General Public License 1381 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 1382 /* global ANNOTATIONCOLOUR */ 1383 1384 /** 1385 * Provides an in browser PDF editor. 1386 * 1387 * @module moodle-assignfeedback_editpdf-editor 1388 */ 1389 1390 /** 1391 * Class representing a highlight. 1392 * 1393 * @namespace M.assignfeedback_editpdf 1394 * @class annotationhighlight 1395 * @extends M.assignfeedback_editpdf.annotation 1396 * @module moodle-assignfeedback_editpdf-editor 1397 */ 1398 var ANNOTATIONHIGHLIGHT = function(config) { 1399 ANNOTATIONHIGHLIGHT.superclass.constructor.apply(this, [config]); 1400 }; 1401 1402 ANNOTATIONHIGHLIGHT.NAME = "annotationhighlight"; 1403 ANNOTATIONHIGHLIGHT.ATTRS = {}; 1404 1405 Y.extend(ANNOTATIONHIGHLIGHT, M.assignfeedback_editpdf.annotation, { 1406 /** 1407 * Draw a highlight annotation 1408 * @protected 1409 * @method draw 1410 * @return M.assignfeedback_editpdf.drawable 1411 */ 1412 draw: function() { 1413 var drawable, 1414 shape, 1415 bounds, 1416 highlightcolour; 1417 1418 drawable = new M.assignfeedback_editpdf.drawable(this.editor); 1419 bounds = new M.assignfeedback_editpdf.rect(); 1420 bounds.bound([new M.assignfeedback_editpdf.point(this.x, this.y), 1421 new M.assignfeedback_editpdf.point(this.endx, this.endy)]); 1422 1423 highlightcolour = ANNOTATIONCOLOUR[this.colour]; 1424 1425 // Add an alpha channel to the rgb colour. 1426 1427 highlightcolour = highlightcolour.replace('rgb', 'rgba'); 1428 highlightcolour = highlightcolour.replace(')', ',0.5)'); 1429 1430 shape = this.editor.graphic.addShape({ 1431 type: Y.Rect, 1432 width: bounds.width, 1433 height: bounds.height, 1434 stroke: false, 1435 fill: { 1436 color: highlightcolour 1437 }, 1438 x: bounds.x, 1439 y: bounds.y 1440 }); 1441 1442 drawable.shapes.push(shape); 1443 this.drawable = drawable; 1444 1445 return ANNOTATIONHIGHLIGHT.superclass.draw.apply(this); 1446 }, 1447 1448 /** 1449 * Draw the in progress edit. 1450 * 1451 * @public 1452 * @method draw_current_edit 1453 * @param M.assignfeedback_editpdf.edit edit 1454 */ 1455 draw_current_edit: function(edit) { 1456 var drawable = new M.assignfeedback_editpdf.drawable(this.editor), 1457 shape, 1458 bounds, 1459 highlightcolour; 1460 1461 bounds = new M.assignfeedback_editpdf.rect(); 1462 bounds.bound([new M.assignfeedback_editpdf.point(edit.start.x, edit.start.y), 1463 new M.assignfeedback_editpdf.point(edit.end.x, edit.end.y)]); 1464 1465 // Set min. width of highlight. 1466 if (!bounds.has_min_width()) { 1467 bounds.set_min_width(); 1468 } 1469 1470 highlightcolour = ANNOTATIONCOLOUR[edit.annotationcolour]; 1471 // Add an alpha channel to the rgb colour. 1472 1473 highlightcolour = highlightcolour.replace('rgb', 'rgba'); 1474 highlightcolour = highlightcolour.replace(')', ',0.5)'); 1475 1476 // We will draw a box with the current background colour. 1477 shape = this.editor.graphic.addShape({ 1478 type: Y.Rect, 1479 width: bounds.width, 1480 height: 16, 1481 stroke: false, 1482 fill: { 1483 color: highlightcolour 1484 }, 1485 x: bounds.x, 1486 y: edit.start.y 1487 }); 1488 1489 drawable.shapes.push(shape); 1490 1491 return drawable; 1492 }, 1493 1494 /** 1495 * Promote the current edit to a real annotation. 1496 * 1497 * @public 1498 * @method init_from_edit 1499 * @param M.assignfeedback_editpdf.edit edit 1500 * @return bool true if highlight bound is more than min width/height, else false. 1501 */ 1502 init_from_edit: function(edit) { 1503 var bounds = new M.assignfeedback_editpdf.rect(); 1504 bounds.bound([edit.start, edit.end]); 1505 1506 this.gradeid = this.editor.get('gradeid'); 1507 this.pageno = this.editor.currentpage; 1508 this.x = bounds.x; 1509 this.y = edit.start.y; 1510 this.endx = bounds.x + bounds.width; 1511 this.endy = edit.start.y + 16; 1512 this.colour = edit.annotationcolour; 1513 this.page = ''; 1514 1515 return (bounds.has_min_width()); 1516 } 1517 1518 }); 1519 1520 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {}; 1521 M.assignfeedback_editpdf.annotationhighlight = ANNOTATIONHIGHLIGHT; 1522 // This file is part of Moodle - http://moodle.org/ 1523 // 1524 // Moodle is free software: you can redistribute it and/or modify 1525 // it under the terms of the GNU General Public License as published by 1526 // the Free Software Foundation, either version 3 of the License, or 1527 // (at your option) any later version. 1528 // 1529 // Moodle is distributed in the hope that it will be useful, 1530 // but WITHOUT ANY WARRANTY; without even the implied warranty of 1531 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1532 // GNU General Public License for more details. 1533 // 1534 // You should have received a copy of the GNU General Public License 1535 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 1536 /* global SELECTOR */ 1537 1538 /** 1539 * Provides an in browser PDF editor. 1540 * 1541 * @module moodle-assignfeedback_editpdf-editor 1542 */ 1543 1544 /** 1545 * Class representing a stamp. 1546 * 1547 * @namespace M.assignfeedback_editpdf 1548 * @class annotationstamp 1549 * @extends M.assignfeedback_editpdf.annotation 1550 */ 1551 var ANNOTATIONSTAMP = function(config) { 1552 ANNOTATIONSTAMP.superclass.constructor.apply(this, [config]); 1553 }; 1554 1555 ANNOTATIONSTAMP.NAME = "annotationstamp"; 1556 ANNOTATIONSTAMP.ATTRS = {}; 1557 1558 Y.extend(ANNOTATIONSTAMP, M.assignfeedback_editpdf.annotation, { 1559 /** 1560 * Draw a stamp annotation 1561 * @protected 1562 * @method draw 1563 * @return M.assignfeedback_editpdf.drawable 1564 */ 1565 draw: function() { 1566 var drawable = new M.assignfeedback_editpdf.drawable(this.editor), 1567 drawingregion = this.editor.get_dialogue_element(SELECTOR.DRAWINGREGION), 1568 node, 1569 position; 1570 1571 position = this.editor.get_window_coordinates(new M.assignfeedback_editpdf.point(this.x, this.y)); 1572 node = Y.Node.create('<div/>'); 1573 node.setStyles({ 1574 'position': 'absolute', 1575 'display': 'inline-block', 1576 'backgroundImage': 'url(' + this.editor.get_stamp_image_url(this.path) + ')', 1577 'width': (this.endx - this.x), 1578 'height': (this.endy - this.y), 1579 'backgroundSize': '100% 100%', 1580 'zIndex': 50 1581 }); 1582 1583 drawingregion.append(node); 1584 node.setX(position.x); 1585 node.setY(position.y); 1586 drawable.store_position(node, position.x, position.y); 1587 1588 // Bind events only when editing. 1589 if (!this.editor.get('readonly')) { 1590 // Pass through the event handlers on the div. 1591 node.on('gesturemovestart', this.editor.edit_start, null, this.editor); 1592 node.on('gesturemove', this.editor.edit_move, null, this.editor); 1593 node.on('gesturemoveend', this.editor.edit_end, null, this.editor); 1594 } 1595 1596 drawable.nodes.push(node); 1597 1598 this.drawable = drawable; 1599 return ANNOTATIONSTAMP.superclass.draw.apply(this); 1600 }, 1601 1602 /** 1603 * Draw the in progress edit. 1604 * 1605 * @public 1606 * @method draw_current_edit 1607 * @param M.assignfeedback_editpdf.edit edit 1608 */ 1609 draw_current_edit: function(edit) { 1610 var bounds = new M.assignfeedback_editpdf.rect(), 1611 drawable = new M.assignfeedback_editpdf.drawable(this.editor), 1612 drawingregion = this.editor.get_dialogue_element(SELECTOR.DRAWINGREGION), 1613 node, 1614 position; 1615 1616 bounds.bound([edit.start, edit.end]); 1617 position = this.editor.get_window_coordinates(new M.assignfeedback_editpdf.point(bounds.x, bounds.y)); 1618 1619 node = Y.Node.create('<div/>'); 1620 node.setStyles({ 1621 'position': 'absolute', 1622 'display': 'inline-block', 1623 'backgroundImage': 'url(' + this.editor.get_stamp_image_url(edit.stamp) + ')', 1624 'width': bounds.width, 1625 'height': bounds.height, 1626 'backgroundSize': '100% 100%', 1627 'zIndex': 50 1628 }); 1629 1630 drawingregion.append(node); 1631 node.setX(position.x); 1632 node.setY(position.y); 1633 drawable.store_position(node, position.x, position.y); 1634 1635 drawable.nodes.push(node); 1636 1637 return drawable; 1638 }, 1639 1640 /** 1641 * Promote the current edit to a real annotation. 1642 * 1643 * @public 1644 * @method init_from_edit 1645 * @param M.assignfeedback_editpdf.edit edit 1646 * @return bool if width/height is more than min. required. 1647 */ 1648 init_from_edit: function(edit) { 1649 var bounds = new M.assignfeedback_editpdf.rect(); 1650 bounds.bound([edit.start, edit.end]); 1651 1652 if (bounds.width < 40) { 1653 bounds.width = 40; 1654 } 1655 if (bounds.height < 40) { 1656 bounds.height = 40; 1657 } 1658 this.gradeid = this.editor.get('gradeid'); 1659 this.pageno = this.editor.currentpage; 1660 this.x = bounds.x; 1661 this.y = bounds.y; 1662 this.endx = bounds.x + bounds.width; 1663 this.endy = bounds.y + bounds.height; 1664 this.colour = edit.annotationcolour; 1665 this.path = edit.stamp; 1666 1667 // Min width and height is always more than 40px. 1668 return true; 1669 }, 1670 1671 /** 1672 * Move an annotation to a new location. 1673 * @public 1674 * @param int newx 1675 * @param int newy 1676 * @method move_annotation 1677 */ 1678 move: function(newx, newy) { 1679 var diffx = newx - this.x, 1680 diffy = newy - this.y; 1681 1682 this.x += diffx; 1683 this.y += diffy; 1684 this.endx += diffx; 1685 this.endy += diffy; 1686 1687 if (this.drawable) { 1688 this.drawable.erase(); 1689 } 1690 this.editor.drawables.push(this.draw()); 1691 } 1692 1693 }); 1694 1695 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {}; 1696 M.assignfeedback_editpdf.annotationstamp = ANNOTATIONSTAMP; 1697 var DROPDOWN_NAME = "Dropdown menu", 1698 DROPDOWN; 1699 1700 /** 1701 * Provides an in browser PDF editor. 1702 * 1703 * @module moodle-assignfeedback_editpdf-editor 1704 */ 1705 1706 /** 1707 * This is a drop down list of buttons triggered (and aligned to) a button. 1708 * 1709 * @namespace M.assignfeedback_editpdf 1710 * @class dropdown 1711 * @constructor 1712 * @extends M.core.dialogue 1713 */ 1714 DROPDOWN = function(config) { 1715 config.draggable = false; 1716 config.centered = false; 1717 config.width = 'auto'; 1718 config.visible = false; 1719 config.footerContent = ''; 1720 DROPDOWN.superclass.constructor.apply(this, [config]); 1721 }; 1722 1723 Y.extend(DROPDOWN, M.core.dialogue, { 1724 /** 1725 * Initialise the menu. 1726 * 1727 * @method initializer 1728 * @return void 1729 */ 1730 initializer: function(config) { 1731 var button, body, headertext, bb; 1732 DROPDOWN.superclass.initializer.call(this, config); 1733 1734 bb = this.get('boundingBox'); 1735 bb.addClass('assignfeedback_editpdf_dropdown'); 1736 1737 // Align the menu to the button that opens it. 1738 button = this.get('buttonNode'); 1739 1740 // Close the menu when clicked outside (excluding the button that opened the menu). 1741 body = this.bodyNode; 1742 1743 headertext = Y.Node.create('<h3/>'); 1744 headertext.addClass('accesshide'); 1745 headertext.setHTML(this.get('headerText')); 1746 body.prepend(headertext); 1747 1748 body.on('clickoutside', function(e) { 1749 if (this.get('visible')) { 1750 // Note: we need to compare ids because for some reason - sometimes button is an Object, not a Y.Node. 1751 if (e.target.get('id') !== button.get('id') && e.target.ancestor().get('id') !== button.get('id')) { 1752 e.preventDefault(); 1753 this.hide(); 1754 } 1755 } 1756 }, this); 1757 1758 button.on('click', function(e) { 1759 e.preventDefault(); this.show(); 1760 }, this); 1761 button.on('key', this.show, 'enter,space', this); 1762 }, 1763 1764 /** 1765 * Override the show method to align to the button. 1766 * 1767 * @method show 1768 * @return void 1769 */ 1770 show: function() { 1771 var button = this.get('buttonNode'), 1772 result = DROPDOWN.superclass.show.call(this); 1773 this.align(button, [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]); 1774 1775 return result; 1776 } 1777 }, { 1778 NAME: DROPDOWN_NAME, 1779 ATTRS: { 1780 /** 1781 * The header for the drop down (only accessible to screen readers). 1782 * 1783 * @attribute headerText 1784 * @type String 1785 * @default '' 1786 */ 1787 headerText: { 1788 value: '' 1789 }, 1790 1791 /** 1792 * The button used to show/hide this drop down menu. 1793 * 1794 * @attribute buttonNode 1795 * @type Y.Node 1796 * @default null 1797 */ 1798 buttonNode: { 1799 value: null 1800 } 1801 } 1802 }); 1803 1804 Y.Base.modifyAttrs(DROPDOWN, { 1805 /** 1806 * Whether the widget should be modal or not. 1807 * 1808 * Moodle override: We override this for commentsearch to force it always false. 1809 * 1810 * @attribute Modal 1811 * @type Boolean 1812 * @default false 1813 */ 1814 modal: { 1815 getter: function() { 1816 return false; 1817 } 1818 } 1819 }); 1820 1821 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {}; 1822 M.assignfeedback_editpdf.dropdown = DROPDOWN; 1823 var COLOURPICKER_NAME = "Colourpicker", 1824 COLOURPICKER; 1825 1826 /** 1827 * Provides an in browser PDF editor. 1828 * 1829 * @module moodle-assignfeedback_editpdf-editor 1830 */ 1831 1832 /** 1833 * COLOURPICKER 1834 * This is a drop down list of colours. 1835 * 1836 * @namespace M.assignfeedback_editpdf 1837 * @class colourpicker 1838 * @constructor 1839 * @extends M.assignfeedback_editpdf.dropdown 1840 */ 1841 COLOURPICKER = function(config) { 1842 COLOURPICKER.superclass.constructor.apply(this, [config]); 1843 }; 1844 1845 Y.extend(COLOURPICKER, M.assignfeedback_editpdf.dropdown, { 1846 1847 /** 1848 * Initialise the menu. 1849 * 1850 * @method initializer 1851 * @return void 1852 */ 1853 initializer: function(config) { 1854 var colourlist = Y.Node.create('<ul role="menu" class="assignfeedback_editpdf_menu"/>'), 1855 body; 1856 1857 // Build a list of coloured buttons. 1858 Y.each(this.get('colours'), function(rgb, colour) { 1859 var button, listitem, title, img, iconname; 1860 1861 title = M.util.get_string(colour, 'assignfeedback_editpdf'); 1862 iconname = this.get('iconprefix') + colour; 1863 img = M.util.image_url(iconname, 'assignfeedback_editpdf'); 1864 button = Y.Node.create('<button><img alt="' + title + '" src="' + img + '"/></button>'); 1865 button.setAttribute('data-colour', colour); 1866 button.setAttribute('data-rgb', rgb); 1867 button.setStyle('backgroundImage', 'none'); 1868 listitem = Y.Node.create('<li/>'); 1869 listitem.append(button); 1870 colourlist.append(listitem); 1871 }, this); 1872 1873 body = Y.Node.create('<div/>'); 1874 1875 // Set the call back. 1876 colourlist.delegate('click', this.callback_handler, 'button', this); 1877 colourlist.delegate('key', this.callback_handler, 'down:13', 'button', this); 1878 1879 // Set the accessible header text. 1880 this.set('headerText', M.util.get_string('colourpicker', 'assignfeedback_editpdf')); 1881 1882 // Set the body content. 1883 body.append(colourlist); 1884 this.set('bodyContent', body); 1885 1886 COLOURPICKER.superclass.initializer.call(this, config); 1887 }, 1888 callback_handler: function(e) { 1889 e.preventDefault(); 1890 1891 var callback = this.get('callback'), 1892 callbackcontext = this.get('context'), 1893 bind; 1894 1895 this.hide(); 1896 1897 // Call the callback with the specified context. 1898 bind = Y.bind(callback, callbackcontext, e); 1899 1900 bind(); 1901 } 1902 }, { 1903 NAME: COLOURPICKER_NAME, 1904 ATTRS: { 1905 /** 1906 * The list of colours this colour picker supports. 1907 * 1908 * @attribute colours 1909 * @type {String: String} (The keys of the array are the colour names and the values are localized strings) 1910 * @default {} 1911 */ 1912 colours: { 1913 value: {} 1914 }, 1915 1916 /** 1917 * The function called when a new colour is chosen. 1918 * 1919 * @attribute callback 1920 * @type function 1921 * @default null 1922 */ 1923 callback: { 1924 value: null 1925 }, 1926 1927 /** 1928 * The context passed to the callback when a colour is chosen. 1929 * 1930 * @attribute context 1931 * @type Y.Node 1932 * @default null 1933 */ 1934 context: { 1935 value: null 1936 }, 1937 1938 /** 1939 * The prefix for the icon image names. 1940 * 1941 * @attribute iconprefix 1942 * @type String 1943 * @default 'colour_' 1944 */ 1945 iconprefix: { 1946 value: 'colour_' 1947 } 1948 } 1949 }); 1950 1951 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {}; 1952 M.assignfeedback_editpdf.colourpicker = COLOURPICKER; 1953 var STAMPPICKER_NAME = "Colourpicker", 1954 STAMPPICKER; 1955 1956 /** 1957 * Provides an in browser PDF editor. 1958 * 1959 * @module moodle-assignfeedback_editpdf-editor 1960 */ 1961 1962 /** 1963 * This is a drop down list of stamps. 1964 * 1965 * @namespace M.assignfeedback_editpdf 1966 * @class stamppicker 1967 * @constructor 1968 * @extends M.assignfeedback_editpdf.dropdown 1969 */ 1970 STAMPPICKER = function(config) { 1971 STAMPPICKER.superclass.constructor.apply(this, [config]); 1972 }; 1973 1974 Y.extend(STAMPPICKER, M.assignfeedback_editpdf.dropdown, { 1975 1976 /** 1977 * Initialise the menu. 1978 * 1979 * @method initializer 1980 * @return void 1981 */ 1982 initializer: function(config) { 1983 var stamplist = Y.Node.create('<ul role="menu" class="assignfeedback_editpdf_menu"/>'); 1984 1985 // Build a list of stamped buttons. 1986 Y.each(this.get('stamps'), function(stamp) { 1987 var button, listitem, title; 1988 1989 title = M.util.get_string('stamp', 'assignfeedback_editpdf'); 1990 button = Y.Node.create('<button><img height="16" width="16" alt="' + title + '" src="' + stamp + '"/></button>'); 1991 button.setAttribute('data-stamp', stamp); 1992 button.setStyle('backgroundImage', 'none'); 1993 listitem = Y.Node.create('<li/>'); 1994 listitem.append(button); 1995 stamplist.append(listitem); 1996 }, this); 1997 1998 1999 // Set the call back. 2000 stamplist.delegate('click', this.callback_handler, 'button', this); 2001 stamplist.delegate('key', this.callback_handler, 'down:13', 'button', this); 2002 2003 // Set the accessible header text. 2004 this.set('headerText', M.util.get_string('stamppicker', 'assignfeedback_editpdf')); 2005 2006 // Set the body content. 2007 this.set('bodyContent', stamplist); 2008 2009 STAMPPICKER.superclass.initializer.call(this, config); 2010 }, 2011 callback_handler: function(e) { 2012 e.preventDefault(); 2013 var callback = this.get('callback'), 2014 callbackcontext = this.get('context'), 2015 bind; 2016 2017 this.hide(); 2018 2019 // Call the callback with the specified context. 2020 bind = Y.bind(callback, callbackcontext, e); 2021 2022 bind(); 2023 } 2024 }, { 2025 NAME: STAMPPICKER_NAME, 2026 ATTRS: { 2027 /** 2028 * The list of stamps this stamp picker supports. 2029 * 2030 * @attribute stamps 2031 * @type String[] - the stamp filenames. 2032 * @default {} 2033 */ 2034 stamps: { 2035 value: [] 2036 }, 2037 2038 /** 2039 * The function called when a new stamp is chosen. 2040 * 2041 * @attribute callback 2042 * @type function 2043 * @default null 2044 */ 2045 callback: { 2046 value: null 2047 }, 2048 2049 /** 2050 * The context passed to the callback when a stamp is chosen. 2051 * 2052 * @attribute context 2053 * @type Y.Node 2054 * @default null 2055 */ 2056 context: { 2057 value: null 2058 } 2059 } 2060 }); 2061 2062 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {}; 2063 M.assignfeedback_editpdf.stamppicker = STAMPPICKER; 2064 var COMMENTMENUNAME = "Commentmenu", 2065 COMMENTMENU; 2066 2067 /** 2068 * Provides an in browser PDF editor. 2069 * 2070 * @module moodle-assignfeedback_editpdf-editor 2071 */ 2072 2073 /** 2074 * COMMENTMENU 2075 * This is a drop down list of comment context functions. 2076 * 2077 * @namespace M.assignfeedback_editpdf 2078 * @class commentmenu 2079 * @constructor 2080 * @extends M.assignfeedback_editpdf.dropdown 2081 */ 2082 COMMENTMENU = function(config) { 2083 COMMENTMENU.superclass.constructor.apply(this, [config]); 2084 }; 2085 2086 Y.extend(COMMENTMENU, M.assignfeedback_editpdf.dropdown, { 2087 2088 /** 2089 * Initialise the menu. 2090 * 2091 * @method initializer 2092 * @return void 2093 */ 2094 initializer: function(config) { 2095 var commentlinks, 2096 link, 2097 body, 2098 comment; 2099 2100 comment = this.get('comment'); 2101 // Build the list of menu items. 2102 commentlinks = Y.Node.create('<ul role="menu" class="assignfeedback_editpdf_menu"/>'); 2103 2104 link = Y.Node.create('<li><a tabindex="-1" href="#">' + 2105 M.util.get_string('addtoquicklist', 'assignfeedback_editpdf') + 2106 '</a></li>'); 2107 link.on('click', comment.add_to_quicklist, comment); 2108 link.on('key', comment.add_to_quicklist, 'enter,space', comment); 2109 2110 commentlinks.append(link); 2111 2112 link = Y.Node.create('<li><a tabindex="-1" href="#">' + 2113 M.util.get_string('deletecomment', 'assignfeedback_editpdf') + 2114 '</a></li>'); 2115 link.on('click', function(e) { 2116 e.preventDefault(); 2117 this.menu.hide(); 2118 this.remove(); 2119 }, comment); 2120 2121 link.on('key', function() { 2122 comment.menu.hide(); 2123 comment.remove(); 2124 }, 'enter,space', comment); 2125 2126 commentlinks.append(link); 2127 2128 link = Y.Node.create('<li><hr/></li>'); 2129 commentlinks.append(link); 2130 2131 // Set the accessible header text. 2132 this.set('headerText', M.util.get_string('commentcontextmenu', 'assignfeedback_editpdf')); 2133 2134 body = Y.Node.create('<div/>'); 2135 2136 // Set the body content. 2137 body.append(commentlinks); 2138 this.set('bodyContent', body); 2139 2140 COMMENTMENU.superclass.initializer.call(this, config); 2141 }, 2142 2143 /** 2144 * Show the menu. 2145 * 2146 * @method show 2147 * @return void 2148 */ 2149 show: function() { 2150 var commentlinks = this.get('boundingBox').one('ul'); 2151 commentlinks.all('.quicklist_comment').remove(true); 2152 var comment = this.get('comment'); 2153 2154 comment.deleteme = false; // Cancel the deleting of blank comments. 2155 2156 // Now build the list of quicklist comments. 2157 Y.each(comment.editor.quicklist.comments, function(quickcomment) { 2158 var listitem = Y.Node.create('<li class="quicklist_comment"></li>'), 2159 linkitem = Y.Node.create('<a href="#" tabindex="-1">' + quickcomment.rawtext + '</a>'), 2160 deletelinkitem = Y.Node.create('<a href="#" tabindex="-1" class="delete_quicklist_comment">' + 2161 '<img src="' + M.util.image_url('t/delete', 'core') + '" ' + 2162 'alt="' + M.util.get_string('deletecomment', 'assignfeedback_editpdf') + '"/>' + 2163 '</a>'); 2164 listitem.append(linkitem); 2165 listitem.append(deletelinkitem); 2166 2167 commentlinks.append(listitem); 2168 2169 linkitem.on('click', comment.set_from_quick_comment, comment, quickcomment); 2170 linkitem.on('key', comment.set_from_quick_comment, 'space,enter', comment, quickcomment); 2171 2172 deletelinkitem.on('click', comment.remove_from_quicklist, comment, quickcomment); 2173 deletelinkitem.on('key', comment.remove_from_quicklist, 'space,enter', comment, quickcomment); 2174 }, this); 2175 2176 COMMENTMENU.superclass.show.call(this); 2177 } 2178 }, { 2179 NAME: COMMENTMENUNAME, 2180 ATTRS: { 2181 /** 2182 * The comment this menu is attached to. 2183 * 2184 * @attribute comment 2185 * @type M.assignfeedback_editpdf.comment 2186 * @default null 2187 */ 2188 comment: { 2189 value: null 2190 } 2191 2192 } 2193 }); 2194 2195 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {}; 2196 M.assignfeedback_editpdf.commentmenu = COMMENTMENU; 2197 /* eslint-disable no-unused-vars */ 2198 /* global SELECTOR */ 2199 var COMMENTSEARCHNAME = "commentsearch", 2200 COMMENTSEARCH; 2201 2202 /** 2203 * Provides an in browser PDF editor. 2204 * 2205 * @module moodle-assignfeedback_editpdf-editor 2206 */ 2207 2208 /** 2209 * This is a searchable dialogue of comments. 2210 * 2211 * @namespace M.assignfeedback_editpdf 2212 * @class commentsearch 2213 * @constructor 2214 * @extends M.core.dialogue 2215 */ 2216 COMMENTSEARCH = function(config) { 2217 config.draggable = false; 2218 config.centered = true; 2219 config.width = '400px'; 2220 config.visible = false; 2221 config.headerContent = M.util.get_string('searchcomments', 'assignfeedback_editpdf'); 2222 config.footerContent = ''; 2223 COMMENTSEARCH.superclass.constructor.apply(this, [config]); 2224 }; 2225 2226 Y.extend(COMMENTSEARCH, M.core.dialogue, { 2227 /** 2228 * Initialise the menu. 2229 * 2230 * @method initializer 2231 * @return void 2232 */ 2233 initializer: function(config) { 2234 var editor, 2235 container, 2236 placeholder, 2237 commentfilter, 2238 commentlist, 2239 bb; 2240 2241 bb = this.get('boundingBox'); 2242 bb.addClass('assignfeedback_editpdf_commentsearch'); 2243 2244 editor = this.get('editor'); 2245 container = Y.Node.create('<div/>'); 2246 2247 placeholder = M.util.get_string('filter', 'assignfeedback_editpdf'); 2248 commentfilter = Y.Node.create('<input type="text" size="20" placeholder="' + placeholder + '"/>'); 2249 container.append(commentfilter); 2250 commentlist = Y.Node.create('<ul role="menu" class="assignfeedback_editpdf_menu"/>'); 2251 container.append(commentlist); 2252 2253 commentfilter.on('keyup', this.filter_search_comments, this); 2254 commentlist.delegate('click', this.focus_on_comment, 'a', this); 2255 commentlist.delegate('key', this.focus_on_comment, 'enter,space', 'a', this); 2256 2257 // Set the body content. 2258 this.set('bodyContent', container); 2259 2260 COMMENTSEARCH.superclass.initializer.call(this, config); 2261 }, 2262 2263 /** 2264 * Event handler to filter the list of comments. 2265 * 2266 * @protected 2267 * @method filter_search_comments 2268 */ 2269 filter_search_comments: function() { 2270 var filternode, 2271 commentslist, 2272 filtertext, 2273 dialogueid; 2274 2275 dialogueid = this.get('id'); 2276 filternode = Y.one('#' + dialogueid + SELECTOR.SEARCHFILTER); 2277 commentslist = Y.one('#' + dialogueid + SELECTOR.SEARCHCOMMENTSLIST); 2278 2279 filtertext = filternode.get('value'); 2280 2281 commentslist.all('li').each(function(node) { 2282 if (node.get('text').indexOf(filtertext) !== -1) { 2283 node.show(); 2284 } else { 2285 node.hide(); 2286 } 2287 }); 2288 }, 2289 2290 /** 2291 * Event handler to focus on a selected comment. 2292 * 2293 * @param Event e 2294 * @protected 2295 * @method focus_on_comment 2296 */ 2297 focus_on_comment: function(e) { 2298 e.preventDefault(); 2299 var target = e.target.ancestor('li'), 2300 comment = target.getData('comment'), 2301 editor = this.get('editor'); 2302 2303 this.hide(); 2304 2305 if (comment.pageno === editor.currentpage) { 2306 comment.drawable.nodes[0].one('textarea').focus(); 2307 } else { 2308 // Comment is on a different page. 2309 editor.currentpage = comment.pageno; 2310 editor.change_page(); 2311 comment.drawable.nodes[0].one('textarea').focus(); 2312 } 2313 }, 2314 2315 /** 2316 * Show the menu. 2317 * 2318 * @method show 2319 * @return void 2320 */ 2321 show: function() { 2322 var commentlist = this.get('boundingBox').one('ul'), 2323 editor = this.get('editor'); 2324 2325 commentlist.all('li').remove(true); 2326 2327 // Rebuild the latest list of comments. 2328 Y.each(editor.pages, function(page) { 2329 Y.each(page.comments, function(comment) { 2330 var commentnode = Y.Node.create('<li><a href="#" tabindex="-1"><pre>' + comment.rawtext + '</pre></a></li>'); 2331 commentlist.append(commentnode); 2332 commentnode.setData('comment', comment); 2333 }, this); 2334 }, this); 2335 2336 this.centerDialogue(); 2337 COMMENTSEARCH.superclass.show.call(this); 2338 } 2339 }, { 2340 NAME: COMMENTSEARCHNAME, 2341 ATTRS: { 2342 /** 2343 * The editor this search window is attached to. 2344 * 2345 * @attribute editor 2346 * @type M.assignfeedback_editpdf.editor 2347 * @default null 2348 */ 2349 editor: { 2350 value: null 2351 } 2352 2353 } 2354 }); 2355 2356 Y.Base.modifyAttrs(COMMENTSEARCH, { 2357 /** 2358 * Whether the widget should be modal or not. 2359 * 2360 * Moodle override: We override this for commentsearch to force it always true. 2361 * 2362 * @attribute Modal 2363 * @type Boolean 2364 * @default true 2365 */ 2366 modal: { 2367 getter: function() { 2368 return true; 2369 } 2370 } 2371 }); 2372 2373 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {}; 2374 M.assignfeedback_editpdf.commentsearch = COMMENTSEARCH; 2375 // This file is part of Moodle - http://moodle.org/ 2376 // 2377 // Moodle is free software: you can redistribute it and/or modify 2378 // it under the terms of the GNU General Public License as published by 2379 // the Free Software Foundation, either version 3 of the License, or 2380 // (at your option) any later version. 2381 // 2382 // Moodle is distributed in the hope that it will be useful, 2383 // but WITHOUT ANY WARRANTY; without even the implied warranty of 2384 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 2385 // GNU General Public License for more details. 2386 // 2387 // You should have received a copy of the GNU General Public License 2388 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 2389 /* global SELECTOR, COMMENTCOLOUR, COMMENTTEXTCOLOUR */ 2390 2391 /** 2392 * Provides an in browser PDF editor. 2393 * 2394 * @module moodle-assignfeedback_editpdf-editor 2395 */ 2396 2397 /** 2398 * Class representing a list of comments. 2399 * 2400 * @namespace M.assignfeedback_editpdf 2401 * @class comment 2402 * @param M.assignfeedback_editpdf.editor editor 2403 * @param Int gradeid 2404 * @param Int pageno 2405 * @param Int x 2406 * @param Int y 2407 * @param Int width 2408 * @param String colour 2409 * @param String rawtext 2410 */ 2411 var COMMENT = function(editor, gradeid, pageno, x, y, width, colour, rawtext) { 2412 2413 /** 2414 * Reference to M.assignfeedback_editpdf.editor. 2415 * @property editor 2416 * @type M.assignfeedback_editpdf.editor 2417 * @public 2418 */ 2419 this.editor = editor; 2420 2421 /** 2422 * Grade id 2423 * @property gradeid 2424 * @type Int 2425 * @public 2426 */ 2427 this.gradeid = gradeid || 0; 2428 2429 /** 2430 * X position 2431 * @property x 2432 * @type Int 2433 * @public 2434 */ 2435 this.x = parseInt(x, 10) || 0; 2436 2437 /** 2438 * Y position 2439 * @property y 2440 * @type Int 2441 * @public 2442 */ 2443 this.y = parseInt(y, 10) || 0; 2444 2445 /** 2446 * Comment width 2447 * @property width 2448 * @type Int 2449 * @public 2450 */ 2451 this.width = parseInt(width, 10) || 0; 2452 2453 /** 2454 * Comment rawtext 2455 * @property rawtext 2456 * @type String 2457 * @public 2458 */ 2459 this.rawtext = rawtext || ''; 2460 2461 /** 2462 * Comment page number 2463 * @property pageno 2464 * @type Int 2465 * @public 2466 */ 2467 this.pageno = pageno || 0; 2468 2469 /** 2470 * Comment background colour. 2471 * @property colour 2472 * @type String 2473 * @public 2474 */ 2475 this.colour = colour || 'yellow'; 2476 2477 /** 2478 * Reference to M.assignfeedback_editpdf.drawable 2479 * @property drawable 2480 * @type M.assignfeedback_editpdf.drawable 2481 * @public 2482 */ 2483 this.drawable = false; 2484 2485 /** 2486 * Boolean used by a timeout to delete empty comments after a short delay. 2487 * @property deleteme 2488 * @type Boolean 2489 * @public 2490 */ 2491 this.deleteme = false; 2492 2493 /** 2494 * Reference to the link that opens the menu. 2495 * @property menulink 2496 * @type Y.Node 2497 * @public 2498 */ 2499 this.menulink = null; 2500 2501 /** 2502 * Reference to the dialogue that is the context menu. 2503 * @property menu 2504 * @type M.assignfeedback_editpdf.dropdown 2505 * @public 2506 */ 2507 this.menu = null; 2508 2509 /** 2510 * Clean a comment record, returning an oject with only fields that are valid. 2511 * @public 2512 * @method clean 2513 * @return {} 2514 */ 2515 this.clean = function() { 2516 return { 2517 gradeid: this.gradeid, 2518 x: parseInt(this.x, 10), 2519 y: parseInt(this.y, 10), 2520 width: parseInt(this.width, 10), 2521 rawtext: this.rawtext, 2522 pageno: this.currentpage, 2523 colour: this.colour 2524 }; 2525 }; 2526 2527 /** 2528 * Draw a comment. 2529 * @public 2530 * @method draw_comment 2531 * @param boolean focus - Set the keyboard focus to the new comment if true 2532 * @return M.assignfeedback_editpdf.drawable 2533 */ 2534 this.draw = function(focus) { 2535 var drawable = new M.assignfeedback_editpdf.drawable(this.editor), 2536 node, 2537 drawingregion = this.editor.get_dialogue_element(SELECTOR.DRAWINGREGION), 2538 container, 2539 menu, 2540 position, 2541 scrollheight; 2542 2543 // Lets add a contenteditable div. 2544 node = Y.Node.create('<textarea/>'); 2545 container = Y.Node.create('<div class="commentdrawable"/>'); 2546 menu = Y.Node.create('<a href="#"><img src="' + M.util.image_url('t/contextmenu', 'core') + '"/></a>'); 2547 2548 this.menulink = menu; 2549 container.append(node); 2550 2551 if (!this.editor.get('readonly')) { 2552 container.append(menu); 2553 } else { 2554 node.setAttribute('readonly', 'readonly'); 2555 } 2556 if (this.width < 100) { 2557 this.width = 100; 2558 } 2559 2560 position = this.editor.get_window_coordinates(new M.assignfeedback_editpdf.point(this.x, this.y)); 2561 node.setStyles({ 2562 width: this.width + 'px', 2563 backgroundColor: COMMENTCOLOUR[this.colour], 2564 color: COMMENTTEXTCOLOUR 2565 }); 2566 2567 drawingregion.append(container); 2568 container.setStyle('position', 'absolute'); 2569 container.setX(position.x); 2570 container.setY(position.y); 2571 drawable.store_position(container, position.x, position.y); 2572 drawable.nodes.push(container); 2573 node.set('value', this.rawtext); 2574 scrollheight = node.get('scrollHeight'); 2575 node.setStyles({ 2576 'height': scrollheight + 'px', 2577 'overflow': 'hidden' 2578 }); 2579 if (!this.editor.get('readonly')) { 2580 this.attach_events(node, menu); 2581 } 2582 if (focus) { 2583 node.focus(); 2584 } 2585 this.drawable = drawable; 2586 2587 2588 return drawable; 2589 }; 2590 2591 /** 2592 * Delete an empty comment if it's menu hasn't been opened in time. 2593 * @method delete_comment_later 2594 */ 2595 this.delete_comment_later = function() { 2596 if (this.deleteme) { 2597 this.remove(); 2598 } 2599 }; 2600 2601 /** 2602 * Comment nodes have a bunch of event handlers attached to them directly. 2603 * This is all done here for neatness. 2604 * 2605 * @protected 2606 * @method attach_comment_events 2607 * @param node - The Y.Node representing the comment. 2608 * @param menu - The Y.Node representing the menu. 2609 */ 2610 this.attach_events = function(node, menu) { 2611 // Save the text on blur. 2612 node.on('blur', function() { 2613 // Save the changes back to the comment. 2614 this.rawtext = node.get('value'); 2615 this.width = parseInt(node.getStyle('width'), 10); 2616 2617 // Trim. 2618 if (this.rawtext.replace(/^\s+|\s+$/g, "") === '') { 2619 // Delete empty comments. 2620 this.deleteme = true; 2621 Y.later(400, this, this.delete_comment_later); 2622 } 2623 this.editor.save_current_page(); 2624 this.editor.editingcomment = false; 2625 }, this); 2626 2627 // For delegated event handler. 2628 menu.setData('comment', this); 2629 2630 node.on('keyup', function() { 2631 var scrollheight = node.get('scrollHeight'), 2632 height = parseInt(node.getStyle('height'), 10); 2633 2634 // Webkit scrollheight fix. 2635 if (scrollheight === height + 8) { 2636 scrollheight -= 8; 2637 } 2638 node.setStyle('height', scrollheight + 'px'); 2639 2640 }); 2641 2642 node.on('gesturemovestart', function(e) { 2643 if (editor.currentedit.tool === 'select') { 2644 e.preventDefault(); 2645 node.setData('dragging', true); 2646 node.setData('offsetx', e.clientX - node.getX()); 2647 node.setData('offsety', e.clientY - node.getY()); 2648 } 2649 }); 2650 node.on('gesturemoveend', function() { 2651 if (editor.currentedit.tool === 'select') { 2652 node.setData('dragging', false); 2653 this.editor.save_current_page(); 2654 } 2655 }, null, this); 2656 node.on('gesturemove', function(e) { 2657 if (editor.currentedit.tool === 'select') { 2658 var x = e.clientX - node.getData('offsetx'), 2659 y = e.clientY - node.getData('offsety'), 2660 nodewidth, 2661 nodeheight, 2662 newlocation, 2663 windowlocation, 2664 bounds; 2665 2666 nodewidth = parseInt(node.getStyle('width'), 10); 2667 nodeheight = parseInt(node.getStyle('height'), 10); 2668 2669 newlocation = this.editor.get_canvas_coordinates(new M.assignfeedback_editpdf.point(x, y)); 2670 bounds = this.editor.get_canvas_bounds(true); 2671 bounds.x = 0; 2672 bounds.y = 0; 2673 2674 bounds.width -= nodewidth + 42; 2675 bounds.height -= nodeheight + 8; 2676 // Clip to the window size - the comment size. 2677 newlocation.clip(bounds); 2678 2679 this.x = newlocation.x; 2680 this.y = newlocation.y; 2681 2682 windowlocation = this.editor.get_window_coordinates(newlocation); 2683 node.ancestor().setX(windowlocation.x); 2684 node.ancestor().setY(windowlocation.y); 2685 this.drawable.store_position(node.ancestor(), windowlocation.x, windowlocation.y); 2686 } 2687 }, null, this); 2688 2689 this.menu = new M.assignfeedback_editpdf.commentmenu({ 2690 buttonNode: this.menulink, 2691 comment: this 2692 }); 2693 }; 2694 2695 /** 2696 * Delete a comment. 2697 * @method remove 2698 */ 2699 this.remove = function() { 2700 var i = 0; 2701 var comments; 2702 2703 comments = this.editor.pages[this.editor.currentpage].comments; 2704 for (i = 0; i < comments.length; i++) { 2705 if (comments[i] === this) { 2706 comments.splice(i, 1); 2707 this.drawable.erase(); 2708 this.editor.save_current_page(); 2709 return; 2710 } 2711 } 2712 }; 2713 2714 /** 2715 * Event handler to remove a comment from the users quicklist. 2716 * 2717 * @protected 2718 * @method remove_from_quicklist 2719 */ 2720 this.remove_from_quicklist = function(e, quickcomment) { 2721 e.preventDefault(); 2722 2723 this.menu.hide(); 2724 2725 this.editor.quicklist.remove(quickcomment); 2726 }; 2727 2728 /** 2729 * A quick comment was selected in the list, update the active comment and redraw the page. 2730 * 2731 * @param Event e 2732 * @protected 2733 * @method set_from_quick_comment 2734 */ 2735 this.set_from_quick_comment = function(e, quickcomment) { 2736 e.preventDefault(); 2737 2738 this.menu.hide(); 2739 2740 this.rawtext = quickcomment.rawtext; 2741 this.width = quickcomment.width; 2742 this.colour = quickcomment.colour; 2743 2744 this.editor.save_current_page(); 2745 2746 this.editor.redraw(); 2747 }; 2748 2749 /** 2750 * Event handler to add a comment to the users quicklist. 2751 * 2752 * @protected 2753 * @method add_to_quicklist 2754 */ 2755 this.add_to_quicklist = function(e) { 2756 e.preventDefault(); 2757 this.menu.hide(); 2758 this.editor.quicklist.add(this); 2759 }; 2760 2761 /** 2762 * Draw the in progress edit. 2763 * 2764 * @public 2765 * @method draw_current_edit 2766 * @param M.assignfeedback_editpdf.edit edit 2767 */ 2768 this.draw_current_edit = function(edit) { 2769 var drawable = new M.assignfeedback_editpdf.drawable(this.editor), 2770 shape, 2771 bounds; 2772 2773 bounds = new M.assignfeedback_editpdf.rect(); 2774 bounds.bound([edit.start, edit.end]); 2775 2776 // We will draw a box with the current background colour. 2777 shape = this.editor.graphic.addShape({ 2778 type: Y.Rect, 2779 width: bounds.width, 2780 height: bounds.height, 2781 fill: { 2782 color: COMMENTCOLOUR[edit.commentcolour] 2783 }, 2784 x: bounds.x, 2785 y: bounds.y 2786 }); 2787 2788 drawable.shapes.push(shape); 2789 2790 return drawable; 2791 }; 2792 2793 /** 2794 * Promote the current edit to a real comment. 2795 * 2796 * @public 2797 * @method init_from_edit 2798 * @param M.assignfeedback_editpdf.edit edit 2799 * @return bool true if comment bound is more than min width/height, else false. 2800 */ 2801 this.init_from_edit = function(edit) { 2802 var bounds = new M.assignfeedback_editpdf.rect(); 2803 bounds.bound([edit.start, edit.end]); 2804 2805 // Minimum comment width. 2806 if (bounds.width < 100) { 2807 bounds.width = 100; 2808 } 2809 2810 // Save the current edit to the server and the current page list. 2811 2812 this.gradeid = this.editor.get('gradeid'); 2813 this.pageno = this.editor.currentpage; 2814 this.x = bounds.x; 2815 this.y = bounds.y; 2816 this.width = bounds.width; 2817 this.colour = edit.commentcolour; 2818 this.rawtext = ''; 2819 2820 return (bounds.has_min_width() && bounds.has_min_height()); 2821 }; 2822 2823 }; 2824 2825 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {}; 2826 M.assignfeedback_editpdf.comment = COMMENT; 2827 // This file is part of Moodle - http://moodle.org/ 2828 // 2829 // Moodle is free software: you can redistribute it and/or modify 2830 // it under the terms of the GNU General Public License as published by 2831 // the Free Software Foundation, either version 3 of the License, or 2832 // (at your option) any later version. 2833 // 2834 // Moodle is distributed in the hope that it will be useful, 2835 // but WITHOUT ANY WARRANTY; without even the implied warranty of 2836 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 2837 // GNU General Public License for more details. 2838 // 2839 // You should have received a copy of the GNU General Public License 2840 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 2841 2842 /** 2843 * Provides an in browser PDF editor. 2844 * 2845 * @module moodle-assignfeedback_editpdf-editor 2846 */ 2847 2848 /** 2849 * Class representing a users quick comment. 2850 * 2851 * @namespace M.assignfeedback_editpdf 2852 * @class quickcomment 2853 */ 2854 var QUICKCOMMENT = function(id, rawtext, width, colour) { 2855 2856 /** 2857 * Quick comment text. 2858 * @property rawtext 2859 * @type String 2860 * @public 2861 */ 2862 this.rawtext = rawtext || ''; 2863 2864 /** 2865 * ID of the comment 2866 * @property id 2867 * @type Int 2868 * @public 2869 */ 2870 this.id = id || 0; 2871 2872 /** 2873 * Width of the comment 2874 * @property width 2875 * @type Int 2876 * @public 2877 */ 2878 this.width = width || 100; 2879 2880 /** 2881 * Colour of the comment. 2882 * @property colour 2883 * @type String 2884 * @public 2885 */ 2886 this.colour = colour || "yellow"; 2887 }; 2888 2889 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {}; 2890 M.assignfeedback_editpdf.quickcomment = QUICKCOMMENT; 2891 // This file is part of Moodle - http://moodle.org/ 2892 // 2893 // Moodle is free software: you can redistribute it and/or modify 2894 // it under the terms of the GNU General Public License as published by 2895 // the Free Software Foundation, either version 3 of the License, or 2896 // (at your option) any later version. 2897 // 2898 // Moodle is distributed in the hope that it will be useful, 2899 // but WITHOUT ANY WARRANTY; without even the implied warranty of 2900 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 2901 // GNU General Public License for more details. 2902 // 2903 // You should have received a copy of the GNU General Public License 2904 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 2905 /* global AJAXBASE */ 2906 2907 /** 2908 * Provides an in browser PDF editor. 2909 * 2910 * @module moodle-assignfeedback_editpdf-editor 2911 */ 2912 2913 /** 2914 * Class representing a users list of quick comments. 2915 * 2916 * @namespace M.assignfeedback_editpdf 2917 * @class quickcommentlist 2918 */ 2919 var QUICKCOMMENTLIST = function(editor) { 2920 2921 /** 2922 * Reference to M.assignfeedback_editpdf.editor. 2923 * @property editor 2924 * @type M.assignfeedback_editpdf.editor 2925 * @public 2926 */ 2927 this.editor = editor; 2928 2929 /** 2930 * Array of Comments 2931 * @property shapes 2932 * @type M.assignfeedback_editpdf.quickcomment[] 2933 * @public 2934 */ 2935 this.comments = []; 2936 2937 /** 2938 * Add a comment to the users quicklist. 2939 * 2940 * @protected 2941 * @method add 2942 */ 2943 this.add = function(comment) { 2944 var ajaxurl = AJAXBASE, 2945 config; 2946 2947 // Do not save empty comments. 2948 if (comment.rawtext === '') { 2949 return; 2950 } 2951 2952 config = { 2953 method: 'post', 2954 context: this, 2955 sync: false, 2956 data: { 2957 'sesskey': M.cfg.sesskey, 2958 'action': 'addtoquicklist', 2959 'userid': this.editor.get('userid'), 2960 'commenttext': comment.rawtext, 2961 'width': comment.width, 2962 'colour': comment.colour, 2963 'attemptnumber': this.editor.get('attemptnumber'), 2964 'assignmentid': this.editor.get('assignmentid') 2965 }, 2966 on: { 2967 success: function(tid, response) { 2968 var jsondata, quickcomment; 2969 try { 2970 jsondata = Y.JSON.parse(response.responseText); 2971 if (jsondata.error) { 2972 return new M.core.ajaxException(jsondata); 2973 } else { 2974 quickcomment = new M.assignfeedback_editpdf.quickcomment(jsondata.id, 2975 jsondata.rawtext, 2976 jsondata.width, 2977 jsondata.colour); 2978 this.comments.push(quickcomment); 2979 } 2980 } catch (e) { 2981 return new M.core.exception(e); 2982 } 2983 }, 2984 failure: function(tid, response) { 2985 return M.core.exception(response.responseText); 2986 } 2987 } 2988 }; 2989 2990 Y.io(ajaxurl, config); 2991 }; 2992 2993 /** 2994 * Remove a comment from the users quicklist. 2995 * 2996 * @public 2997 * @method remove 2998 */ 2999 this.remove = function(comment) { 3000 var ajaxurl = AJAXBASE, 3001 config; 3002 3003 // Should not happen. 3004 if (!comment) { 3005 return; 3006 } 3007 3008 config = { 3009 method: 'post', 3010 context: this, 3011 sync: false, 3012 data: { 3013 'sesskey': M.cfg.sesskey, 3014 'action': 'removefromquicklist', 3015 'userid': this.editor.get('userid'), 3016 'commentid': comment.id, 3017 'attemptnumber': this.editor.get('attemptnumber'), 3018 'assignmentid': this.editor.get('assignmentid') 3019 }, 3020 on: { 3021 success: function() { 3022 var i; 3023 3024 // Find and remove the comment from the quicklist. 3025 i = this.comments.indexOf(comment); 3026 if (i >= 0) { 3027 this.comments.splice(i, 1); 3028 } 3029 }, 3030 failure: function(tid, response) { 3031 return M.core.exception(response.responseText); 3032 } 3033 } 3034 }; 3035 3036 Y.io(ajaxurl, config); 3037 }; 3038 3039 /** 3040 * Load the users quick comments list. 3041 * 3042 * @protected 3043 * @method load_quicklist 3044 */ 3045 this.load = function() { 3046 var ajaxurl = AJAXBASE, 3047 config; 3048 3049 config = { 3050 method: 'get', 3051 context: this, 3052 sync: false, 3053 data: { 3054 'sesskey': M.cfg.sesskey, 3055 'action': 'loadquicklist', 3056 'userid': this.editor.get('userid'), 3057 'attemptnumber': this.editor.get('attemptnumber'), 3058 'assignmentid': this.editor.get('assignmentid') 3059 }, 3060 on: { 3061 success: function(tid, response) { 3062 var jsondata; 3063 try { 3064 jsondata = Y.JSON.parse(response.responseText); 3065 if (jsondata.error) { 3066 return new M.core.ajaxException(jsondata); 3067 } else { 3068 Y.each(jsondata, function(comment) { 3069 var quickcomment = new M.assignfeedback_editpdf.quickcomment(comment.id, 3070 comment.rawtext, 3071 comment.width, 3072 comment.colour); 3073 this.comments.push(quickcomment); 3074 }, this); 3075 } 3076 } catch (e) { 3077 return new M.core.exception(e); 3078 } 3079 }, 3080 failure: function(tid, response) { 3081 return M.core.exception(response.responseText); 3082 } 3083 } 3084 }; 3085 3086 Y.io(ajaxurl, config); 3087 }; 3088 }; 3089 3090 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {}; 3091 M.assignfeedback_editpdf.quickcommentlist = QUICKCOMMENTLIST; 3092 // This file is part of Moodle - http://moodle.org/ 3093 // 3094 // Moodle is free software: you can redistribute it and/or modify 3095 // it under the terms of the GNU General Public License as published by 3096 // the Free Software Foundation, either version 3 of the License, or 3097 // (at your option) any later version. 3098 // 3099 // Moodle is distributed in the hope that it will be useful, 3100 // but WITHOUT ANY WARRANTY; without even the implied warranty of 3101 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 3102 // GNU General Public License for more details. 3103 // 3104 // You should have received a copy of the GNU General Public License 3105 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 3106 /* eslint-disable no-unused-vars */ 3107 /* global SELECTOR, TOOLSELECTOR, AJAXBASE, COMMENTCOLOUR, ANNOTATIONCOLOUR, AJAXBASEPROGRESS, CLICKTIMEOUT */ 3108 3109 /** 3110 * Provides an in browser PDF editor. 3111 * 3112 * @module moodle-assignfeedback_editpdf-editor 3113 */ 3114 3115 /** 3116 * EDITOR 3117 * This is an in browser PDF editor. 3118 * 3119 * @namespace M.assignfeedback_editpdf 3120 * @class editor 3121 * @constructor 3122 * @extends Y.Base 3123 */ 3124 var EDITOR = function() { 3125 EDITOR.superclass.constructor.apply(this, arguments); 3126 }; 3127 EDITOR.prototype = { 3128 3129 /** 3130 * The dialogue used for all action menu displays. 3131 * 3132 * @property type 3133 * @type M.core.dialogue 3134 * @protected 3135 */ 3136 dialogue: null, 3137 3138 /** 3139 * The panel used for all action menu displays. 3140 * 3141 * @property type 3142 * @type Y.Node 3143 * @protected 3144 */ 3145 panel: null, 3146 3147 /** 3148 * The number of pages in the pdf. 3149 * 3150 * @property pagecount 3151 * @type Number 3152 * @protected 3153 */ 3154 pagecount: 0, 3155 3156 /** 3157 * The active page in the editor. 3158 * 3159 * @property currentpage 3160 * @type Number 3161 * @protected 3162 */ 3163 currentpage: 0, 3164 3165 /** 3166 * A list of page objects. Each page has a list of comments and annotations. 3167 * 3168 * @property pages 3169 * @type array 3170 * @protected 3171 */ 3172 pages: [], 3173 3174 /** 3175 * The yui node for the loading icon. 3176 * 3177 * @property loadingicon 3178 * @type Node 3179 * @protected 3180 */ 3181 loadingicon: null, 3182 3183 /** 3184 * Image object of the current page image. 3185 * 3186 * @property pageimage 3187 * @type Image 3188 * @protected 3189 */ 3190 pageimage: null, 3191 3192 /** 3193 * YUI Graphic class for drawing shapes. 3194 * 3195 * @property graphic 3196 * @type Graphic 3197 * @protected 3198 */ 3199 graphic: null, 3200 3201 /** 3202 * Info about the current edit operation. 3203 * 3204 * @property currentedit 3205 * @type M.assignfeedback_editpdf.edit 3206 * @protected 3207 */ 3208 currentedit: new M.assignfeedback_editpdf.edit(), 3209 3210 /** 3211 * Current drawable. 3212 * 3213 * @property currentdrawable 3214 * @type M.assignfeedback_editpdf.drawable|false 3215 * @protected 3216 */ 3217 currentdrawable: false, 3218 3219 /** 3220 * Current drawables. 3221 * 3222 * @property drawables 3223 * @type array(M.assignfeedback_editpdf.drawable) 3224 * @protected 3225 */ 3226 drawables: [], 3227 3228 /** 3229 * Current comment when the comment menu is open. 3230 * @property currentcomment 3231 * @type M.assignfeedback_editpdf.comment 3232 * @protected 3233 */ 3234 currentcomment: null, 3235 3236 /** 3237 * Current annotation when the select tool is used. 3238 * @property currentannotation 3239 * @type M.assignfeedback_editpdf.annotation 3240 * @protected 3241 */ 3242 currentannotation: null, 3243 3244 /** 3245 * Last selected annotation tool 3246 * @property lastannotationtool 3247 * @type String 3248 * @protected 3249 */ 3250 lastanntationtool: "pen", 3251 3252 /** 3253 * The users comments quick list 3254 * @property quicklist 3255 * @type M.assignfeedback_editpdf.quickcommentlist 3256 * @protected 3257 */ 3258 quicklist: null, 3259 3260 /** 3261 * The search comments window. 3262 * @property searchcommentswindow 3263 * @type M.core.dialogue 3264 * @protected 3265 */ 3266 searchcommentswindow: null, 3267 3268 3269 /** 3270 * The selected stamp picture. 3271 * @property currentstamp 3272 * @type String 3273 * @protected 3274 */ 3275 currentstamp: null, 3276 3277 /** 3278 * The stamps. 3279 * @property stamps 3280 * @type Array 3281 * @protected 3282 */ 3283 stamps: [], 3284 3285 /** 3286 * Prevent new comments from appearing 3287 * immediately after clicking off a current 3288 * comment 3289 * @property editingcomment 3290 * @type Boolean 3291 * @public 3292 */ 3293 editingcomment: false, 3294 3295 /** 3296 * Called during the initialisation process of the object. 3297 * @method initializer 3298 */ 3299 initializer: function() { 3300 var link; 3301 3302 link = Y.one('#' + this.get('linkid')); 3303 3304 if (link) { 3305 link.on('click', this.link_handler, this); 3306 link.on('key', this.link_handler, 'down:13', this); 3307 3308 // We call the amd module to see if we can take control of the review panel. 3309 require(['mod_assign/grading_review_panel'], function(ReviewPanelManager) { 3310 var panelManager = new ReviewPanelManager(); 3311 3312 var panel = panelManager.getReviewPanel('assignfeedback_editpdf'); 3313 if (panel) { 3314 panel = Y.one(panel); 3315 panel.empty(); 3316 link.ancestor('.fitem').hide(); 3317 this.open_in_panel(panel); 3318 } 3319 this.currentedit.start = false; 3320 this.currentedit.end = false; 3321 if (!this.get('readonly')) { 3322 this.quicklist = new M.assignfeedback_editpdf.quickcommentlist(this); 3323 } 3324 }.bind(this)); 3325 3326 } 3327 }, 3328 3329 /** 3330 * Called to show/hide buttons and set the current colours/stamps. 3331 * @method refresh_button_state 3332 */ 3333 refresh_button_state: function() { 3334 var button, currenttoolnode, imgurl, drawingregion; 3335 3336 // Initalise the colour buttons. 3337 button = this.get_dialogue_element(SELECTOR.COMMENTCOLOURBUTTON); 3338 3339 imgurl = M.util.image_url('background_colour_' + this.currentedit.commentcolour, 'assignfeedback_editpdf'); 3340 button.one('img').setAttribute('src', imgurl); 3341 3342 if (this.currentedit.commentcolour === 'clear') { 3343 button.one('img').setStyle('borderStyle', 'dashed'); 3344 } else { 3345 button.one('img').setStyle('borderStyle', 'solid'); 3346 } 3347 3348 button = this.get_dialogue_element(SELECTOR.ANNOTATIONCOLOURBUTTON); 3349 imgurl = M.util.image_url('colour_' + this.currentedit.annotationcolour, 'assignfeedback_editpdf'); 3350 button.one('img').setAttribute('src', imgurl); 3351 3352 currenttoolnode = this.get_dialogue_element(TOOLSELECTOR[this.currentedit.tool]); 3353 currenttoolnode.addClass('assignfeedback_editpdf_selectedbutton'); 3354 currenttoolnode.setAttribute('aria-pressed', 'true'); 3355 drawingregion = this.get_dialogue_element(SELECTOR.DRAWINGREGION); 3356 drawingregion.setAttribute('data-currenttool', this.currentedit.tool); 3357 3358 button = this.get_dialogue_element(SELECTOR.STAMPSBUTTON); 3359 button.one('img').setAttrs({'src': this.get_stamp_image_url(this.currentedit.stamp), 3360 'height': '16', 3361 'width': '16'}); 3362 }, 3363 3364 /** 3365 * Called to get the bounds of the drawing region. 3366 * @method get_canvas_bounds 3367 */ 3368 get_canvas_bounds: function() { 3369 var canvas = this.get_dialogue_element(SELECTOR.DRAWINGCANVAS), 3370 offsetcanvas = canvas.getXY(), 3371 offsetleft = offsetcanvas[0], 3372 offsettop = offsetcanvas[1], 3373 width = parseInt(canvas.getStyle('width'), 10), 3374 height = parseInt(canvas.getStyle('height'), 10); 3375 3376 return new M.assignfeedback_editpdf.rect(offsetleft, offsettop, width, height); 3377 }, 3378 3379 /** 3380 * Called to translate from window coordinates to canvas coordinates. 3381 * @method get_canvas_coordinates 3382 * @param M.assignfeedback_editpdf.point point in window coordinats. 3383 */ 3384 get_canvas_coordinates: function(point) { 3385 var bounds = this.get_canvas_bounds(), 3386 newpoint = new M.assignfeedback_editpdf.point(point.x - bounds.x, point.y - bounds.y); 3387 3388 bounds.x = bounds.y = 0; 3389 3390 newpoint.clip(bounds); 3391 return newpoint; 3392 }, 3393 3394 /** 3395 * Called to translate from canvas coordinates to window coordinates. 3396 * @method get_window_coordinates 3397 * @param M.assignfeedback_editpdf.point point in window coordinats. 3398 */ 3399 get_window_coordinates: function(point) { 3400 var bounds = this.get_canvas_bounds(), 3401 newpoint = new M.assignfeedback_editpdf.point(point.x + bounds.x, point.y + bounds.y); 3402 3403 return newpoint; 3404 }, 3405 3406 /** 3407 * Open the edit-pdf editor in the panel in the page instead of a popup. 3408 * @method open_in_panel 3409 */ 3410 open_in_panel: function(panel) { 3411 var drawingcanvas, drawingregion; 3412 3413 this.panel = panel; 3414 panel.append(this.get('body')); 3415 panel.addClass(CSS.DIALOGUE); 3416 3417 this.loadingicon = this.get_dialogue_element(SELECTOR.LOADINGICON); 3418 3419 drawingcanvas = this.get_dialogue_element(SELECTOR.DRAWINGCANVAS); 3420 this.graphic = new Y.Graphic({render: drawingcanvas}); 3421 3422 drawingregion = this.get_dialogue_element(SELECTOR.DRAWINGREGION); 3423 drawingregion.on('scroll', this.move_canvas, this); 3424 3425 if (!this.get('readonly')) { 3426 drawingcanvas.on('gesturemovestart', this.edit_start, null, this); 3427 drawingcanvas.on('gesturemove', this.edit_move, null, this); 3428 drawingcanvas.on('gesturemoveend', this.edit_end, null, this); 3429 3430 this.refresh_button_state(); 3431 } 3432 3433 this.load_all_pages(); 3434 }, 3435 3436 /** 3437 * Called to open the pdf editing dialogue. 3438 * @method link_handler 3439 */ 3440 link_handler: function(e) { 3441 var drawingcanvas, drawingregion; 3442 var resize = true; 3443 e.preventDefault(); 3444 3445 if (!this.dialogue) { 3446 this.dialogue = new M.core.dialogue({ 3447 headerContent: this.get('header'), 3448 bodyContent: this.get('body'), 3449 footerContent: this.get('footer'), 3450 modal: true, 3451 width: '840px', 3452 visible: false, 3453 draggable: true 3454 }); 3455 3456 // Add custom class for styling. 3457 this.dialogue.get('boundingBox').addClass(CSS.DIALOGUE); 3458 3459 this.loadingicon = this.get_dialogue_element(SELECTOR.LOADINGICON); 3460 3461 drawingcanvas = this.get_dialogue_element(SELECTOR.DRAWINGCANVAS); 3462 this.graphic = new Y.Graphic({render: drawingcanvas}); 3463 3464 drawingregion = this.get_dialogue_element(SELECTOR.DRAWINGREGION); 3465 drawingregion.on('scroll', this.move_canvas, this); 3466 3467 if (!this.get('readonly')) { 3468 drawingcanvas.on('gesturemovestart', this.edit_start, null, this); 3469 drawingcanvas.on('gesturemove', this.edit_move, null, this); 3470 drawingcanvas.on('gesturemoveend', this.edit_end, null, this); 3471 3472 this.refresh_button_state(); 3473 } 3474 3475 this.load_all_pages(); 3476 drawingcanvas.on('windowresize', this.resize, this); 3477 3478 resize = false; 3479 } 3480 this.dialogue.centerDialogue(); 3481 this.dialogue.show(); 3482 3483 // Redraw when the dialogue is moved, to ensure the absolute elements are all positioned correctly. 3484 this.dialogue.dd.on('drag:end', this.redraw, this); 3485 if (resize) { 3486 this.resize(); // When re-opening the dialog call redraw, to make sure the size + layout is correct. 3487 } 3488 }, 3489 3490 /** 3491 * Called to load the information and annotations for all pages. 3492 * @method load_all_pages 3493 */ 3494 load_all_pages: function() { 3495 var ajaxurl = AJAXBASE, 3496 config, 3497 checkconversionstatus, 3498 ajax_error_total; 3499 3500 config = { 3501 method: 'get', 3502 context: this, 3503 sync: false, 3504 data: { 3505 sesskey: M.cfg.sesskey, 3506 action: 'loadallpages', 3507 userid: this.get('userid'), 3508 attemptnumber: this.get('attemptnumber'), 3509 assignmentid: this.get('assignmentid'), 3510 readonly: this.get('readonly') ? 1 : 0 3511 }, 3512 on: { 3513 success: function(tid, response) { 3514 this.all_pages_loaded(response.responseText); 3515 }, 3516 failure: function(tid, response) { 3517 return new M.core.exception(response.responseText); 3518 } 3519 } 3520 }; 3521 3522 Y.io(ajaxurl, config); 3523 3524 // If pages are not loaded, check PDF conversion status for the progress bar. 3525 if (this.pagecount <= 0) { 3526 checkconversionstatus = { 3527 method: 'get', 3528 context: this, 3529 sync: false, 3530 data: { 3531 sesskey: M.cfg.sesskey, 3532 action: 'conversionstatus', 3533 userid: this.get('userid'), 3534 attemptnumber: this.get('attemptnumber'), 3535 assignmentid: this.get('assignmentid') 3536 }, 3537 on: { 3538 success: function(tid, response) { 3539 ajax_error_total = 0; 3540 if (this.pagecount === 0) { 3541 var pagetotal = this.get('pagetotal'); 3542 3543 // Update the progress bar. 3544 var progressbarcontainer = this.get_dialogue_element(SELECTOR.PROGRESSBARCONTAINER); 3545 var progressbar = progressbarcontainer.one('.bar'); 3546 if (progressbar) { 3547 // Calculate progress. 3548 var progress = (response.response / pagetotal) * 100; 3549 progressbar.setStyle('width', progress + '%'); 3550 progressbarcontainer.setAttribute('aria-valuenow', progress); 3551 } 3552 3553 // New ajax request delayed of a second. 3554 Y.later(1000, this, function() { 3555 Y.io(AJAXBASEPROGRESS, checkconversionstatus); 3556 }); 3557 } 3558 }, 3559 failure: function(tid, response) { 3560 ajax_error_total = ajax_error_total + 1; 3561 // We only continue on error if the all pages were not generated, 3562 // and if the ajax call did not produce 5 errors in the row. 3563 if (this.pagecount === 0 && ajax_error_total < 5) { 3564 Y.later(1000, this, function() { 3565 Y.io(AJAXBASEPROGRESS, checkconversionstatus); 3566 }); 3567 } 3568 return new M.core.exception(response.responseText); 3569 } 3570 } 3571 }; 3572 // We start the AJAX "generated page total number" call a second later to give a chance to 3573 // the AJAX "combined pdf generation" call to clean the previous submission images. 3574 Y.later(1000, this, function() { 3575 ajax_error_total = 0; 3576 Y.io(AJAXBASEPROGRESS, checkconversionstatus); 3577 }); 3578 } 3579 }, 3580 3581 /** 3582 * The info about all pages in the pdf has been returned. 3583 * @param string The ajax response as text. 3584 * @protected 3585 * @method all_pages_loaded 3586 */ 3587 all_pages_loaded: function(responsetext) { 3588 var data, i, j, comment, error; 3589 try { 3590 data = Y.JSON.parse(responsetext); 3591 if (data.error || !data.pagecount) { 3592 if (this.dialogue) { 3593 this.dialogue.hide(); 3594 } 3595 // Display alert dialogue. 3596 error = new M.core.alert({message: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf')}); 3597 error.show(); 3598 return; 3599 } 3600 } catch (e) { 3601 if (this.dialogue) { 3602 this.dialogue.hide(); 3603 } 3604 // Display alert dialogue. 3605 error = new M.core.alert({title: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf')}); 3606 error.show(); 3607 return; 3608 } 3609 3610 this.pagecount = data.pagecount; 3611 this.pages = data.pages; 3612 3613 for (i = 0; i < this.pages.length; i++) { 3614 for (j = 0; j < this.pages[i].comments.length; j++) { 3615 comment = this.pages[i].comments[j]; 3616 this.pages[i].comments[j] = new M.assignfeedback_editpdf.comment(this, 3617 comment.gradeid, 3618 comment.pageno, 3619 comment.x, 3620 comment.y, 3621 comment.width, 3622 comment.colour, 3623 comment.rawtext); 3624 } 3625 for (j = 0; j < this.pages[i].annotations.length; j++) { 3626 data = this.pages[i].annotations[j]; 3627 this.pages[i].annotations[j] = this.create_annotation(data.type, data); 3628 } 3629 } 3630 3631 // Update the ui. 3632 if (this.quicklist) { 3633 this.quicklist.load(); 3634 } 3635 this.setup_navigation(); 3636 this.setup_toolbar(); 3637 this.change_page(); 3638 }, 3639 3640 /** 3641 * Get the full pluginfile url for an image file - just given the filename. 3642 * 3643 * @public 3644 * @method get_stamp_image_url 3645 * @param string filename 3646 */ 3647 get_stamp_image_url: function(filename) { 3648 var urls = this.get('stampfiles'), 3649 fullurl = ''; 3650 3651 Y.Array.each(urls, function(url) { 3652 if (url.indexOf(filename) > 0) { 3653 fullurl = url; 3654 } 3655 }, this); 3656 3657 return fullurl; 3658 }, 3659 3660 /** 3661 * Attach listeners and enable the color picker buttons. 3662 * @protected 3663 * @method setup_toolbar 3664 */ 3665 setup_toolbar: function() { 3666 var toolnode, 3667 commentcolourbutton, 3668 annotationcolourbutton, 3669 searchcommentsbutton, 3670 currentstampbutton, 3671 stampfiles, 3672 picker, 3673 filename; 3674 3675 searchcommentsbutton = this.get_dialogue_element(SELECTOR.SEARCHCOMMENTSBUTTON); 3676 searchcommentsbutton.on('click', this.open_search_comments, this); 3677 searchcommentsbutton.on('key', this.open_search_comments, 'down:13', this); 3678 3679 if (this.get('readonly')) { 3680 return; 3681 } 3682 // Setup the tool buttons. 3683 Y.each(TOOLSELECTOR, function(selector, tool) { 3684 toolnode = this.get_dialogue_element(selector); 3685 toolnode.on('click', this.handle_tool_button, this, tool); 3686 toolnode.on('key', this.handle_tool_button, 'down:13', this, tool); 3687 toolnode.setAttribute('aria-pressed', 'false'); 3688 }, this); 3689 3690 // Set the default tool. 3691 3692 commentcolourbutton = this.get_dialogue_element(SELECTOR.COMMENTCOLOURBUTTON); 3693 picker = new M.assignfeedback_editpdf.colourpicker({ 3694 buttonNode: commentcolourbutton, 3695 colours: COMMENTCOLOUR, 3696 iconprefix: 'background_colour_', 3697 callback: function(e) { 3698 var colour = e.target.getAttribute('data-colour'); 3699 if (!colour) { 3700 colour = e.target.ancestor().getAttribute('data-colour'); 3701 } 3702 this.currentedit.commentcolour = colour; 3703 this.handle_tool_button(e, "comment"); 3704 }, 3705 context: this 3706 }); 3707 3708 annotationcolourbutton = this.get_dialogue_element(SELECTOR.ANNOTATIONCOLOURBUTTON); 3709 picker = new M.assignfeedback_editpdf.colourpicker({ 3710 buttonNode: annotationcolourbutton, 3711 iconprefix: 'colour_', 3712 colours: ANNOTATIONCOLOUR, 3713 callback: function(e) { 3714 var colour = e.target.getAttribute('data-colour'); 3715 if (!colour) { 3716 colour = e.target.ancestor().getAttribute('data-colour'); 3717 } 3718 this.currentedit.annotationcolour = colour; 3719 if (this.lastannotationtool) { 3720 this.handle_tool_button(e, this.lastannotationtool); 3721 } else { 3722 this.handle_tool_button(e, "pen"); 3723 } 3724 }, 3725 context: this 3726 }); 3727 3728 stampfiles = this.get('stampfiles'); 3729 if (stampfiles.length <= 0) { 3730 this.get_dialogue_element(TOOLSELECTOR.stamp).ancestor().hide(); 3731 } else { 3732 filename = stampfiles[0].substr(stampfiles[0].lastIndexOf('/') + 1); 3733 this.currentedit.stamp = filename; 3734 currentstampbutton = this.get_dialogue_element(SELECTOR.STAMPSBUTTON); 3735 3736 picker = new M.assignfeedback_editpdf.stamppicker({ 3737 buttonNode: currentstampbutton, 3738 stamps: stampfiles, 3739 callback: function(e) { 3740 var stamp = e.target.getAttribute('data-stamp'), 3741 filename; 3742 3743 if (!stamp) { 3744 stamp = e.target.ancestor().getAttribute('data-stamp'); 3745 } 3746 filename = stamp.substr(stamp.lastIndexOf('/')); 3747 this.currentedit.stamp = filename; 3748 this.handle_tool_button(e, "stamp"); 3749 }, 3750 context: this 3751 }); 3752 this.refresh_button_state(); 3753 } 3754 }, 3755 3756 /** 3757 * Change the current tool. 3758 * @protected 3759 * @method handle_tool_button 3760 */ 3761 handle_tool_button: function(e, tool) { 3762 var currenttoolnode; 3763 3764 e.preventDefault(); 3765 3766 // Change style of the pressed button. 3767 currenttoolnode = this.get_dialogue_element(TOOLSELECTOR[this.currentedit.tool]); 3768 currenttoolnode.removeClass('assignfeedback_editpdf_selectedbutton'); 3769 currenttoolnode.setAttribute('aria-pressed', 'false'); 3770 this.currentedit.tool = tool; 3771 3772 if (tool !== "comment" && tool !== "select" && tool !== "drag" && tool !== "stamp") { 3773 this.lastannotationtool = tool; 3774 } 3775 this.refresh_button_state(); 3776 }, 3777 3778 /** 3779 * JSON encode the current page data - stripping out drawable references which cannot be encoded. 3780 * @protected 3781 * @method stringify_current_page 3782 * @return string 3783 */ 3784 stringify_current_page: function() { 3785 var comments = [], 3786 annotations = [], 3787 page, 3788 i = 0; 3789 3790 for (i = 0; i < this.pages[this.currentpage].comments.length; i++) { 3791 comments[i] = this.pages[this.currentpage].comments[i].clean(); 3792 } 3793 for (i = 0; i < this.pages[this.currentpage].annotations.length; i++) { 3794 annotations[i] = this.pages[this.currentpage].annotations[i].clean(); 3795 } 3796 3797 page = {comments: comments, annotations: annotations}; 3798 3799 return Y.JSON.stringify(page); 3800 }, 3801 3802 /** 3803 * Generate a drawable from the current in progress edit. 3804 * @protected 3805 * @method get_current_drawable 3806 */ 3807 get_current_drawable: function() { 3808 var comment, 3809 annotation, 3810 drawable = false; 3811 3812 if (!this.currentedit.start || !this.currentedit.end) { 3813 return false; 3814 } 3815 3816 if (this.currentedit.tool === 'comment') { 3817 comment = new M.assignfeedback_editpdf.comment(this); 3818 drawable = comment.draw_current_edit(this.currentedit); 3819 } else { 3820 annotation = this.create_annotation(this.currentedit.tool, {}); 3821 if (annotation) { 3822 drawable = annotation.draw_current_edit(this.currentedit); 3823 } 3824 } 3825 3826 return drawable; 3827 }, 3828 3829 /** 3830 * Find an element within the dialogue. 3831 * @protected 3832 * @method get_dialogue_element 3833 */ 3834 get_dialogue_element: function(selector) { 3835 if (this.panel) { 3836 return this.panel.one(selector); 3837 } else { 3838 return this.dialogue.get('boundingBox').one(selector); 3839 } 3840 }, 3841 3842 /** 3843 * Redraw the active edit. 3844 * @protected 3845 * @method redraw_active_edit 3846 */ 3847 redraw_current_edit: function() { 3848 if (this.currentdrawable) { 3849 this.currentdrawable.erase(); 3850 } 3851 this.currentdrawable = this.get_current_drawable(); 3852 }, 3853 3854 /** 3855 * Event handler for mousedown or touchstart. 3856 * @protected 3857 * @param Event 3858 * @method edit_start 3859 */ 3860 edit_start: function(e) { 3861 e.preventDefault(); 3862 var canvas = this.get_dialogue_element(SELECTOR.DRAWINGCANVAS), 3863 offset = canvas.getXY(), 3864 scrolltop = canvas.get('docScrollY'), 3865 scrollleft = canvas.get('docScrollX'), 3866 point = {x: e.clientX - offset[0] + scrollleft, 3867 y: e.clientY - offset[1] + scrolltop}, 3868 selected = false, 3869 lastannotation; 3870 3871 // Ignore right mouse click. 3872 if (e.button === 3) { 3873 return; 3874 } 3875 3876 if (this.currentedit.starttime) { 3877 return; 3878 } 3879 3880 if (this.editingcomment) { 3881 return; 3882 } 3883 3884 this.currentedit.starttime = new Date().getTime(); 3885 this.currentedit.start = point; 3886 this.currentedit.end = {x: point.x, y: point.y}; 3887 3888 if (this.currentedit.tool === 'select') { 3889 var x = this.currentedit.end.x, 3890 y = this.currentedit.end.y, 3891 annotations = this.pages[this.currentpage].annotations; 3892 // Find the first annotation whose bounds encompass the click. 3893 Y.each(annotations, function(annotation) { 3894 if (((x - annotation.x) * (x - annotation.endx)) <= 0 && 3895 ((y - annotation.y) * (y - annotation.endy)) <= 0) { 3896 selected = annotation; 3897 } 3898 }); 3899 3900 if (selected) { 3901 lastannotation = this.currentannotation; 3902 this.currentannotation = selected; 3903 if (lastannotation && lastannotation !== selected) { 3904 // Redraw the last selected annotation to remove the highlight. 3905 if (lastannotation.drawable) { 3906 lastannotation.drawable.erase(); 3907 this.drawables.push(lastannotation.draw()); 3908 } 3909 } 3910 // Redraw the newly selected annotation to show the highlight. 3911 if (this.currentannotation.drawable) { 3912 this.currentannotation.drawable.erase(); 3913 } 3914 this.drawables.push(this.currentannotation.draw()); 3915 } 3916 } 3917 if (this.currentannotation) { 3918 // Used to calculate drag offset. 3919 this.currentedit.annotationstart = {x: this.currentannotation.x, 3920 y: this.currentannotation.y}; 3921 } 3922 }, 3923 3924 /** 3925 * Event handler for mousemove. 3926 * @protected 3927 * @param Event 3928 * @method edit_move 3929 */ 3930 edit_move: function(e) { 3931 e.preventDefault(); 3932 var bounds = this.get_canvas_bounds(), 3933 canvas = this.get_dialogue_element(SELECTOR.DRAWINGCANVAS), 3934 drawingregion = this.get_dialogue_element(SELECTOR.DRAWINGREGION), 3935 clientpoint = new M.assignfeedback_editpdf.point(e.clientX + canvas.get('docScrollX'), 3936 e.clientY + canvas.get('docScrollY')), 3937 point = this.get_canvas_coordinates(clientpoint), 3938 diffX, 3939 diffY; 3940 3941 // Ignore events out of the canvas area. 3942 if (point.x < 0 || point.x > bounds.width || point.y < 0 || point.y > bounds.height) { 3943 return; 3944 } 3945 3946 if (this.currentedit.tool === 'pen') { 3947 this.currentedit.path.push(point); 3948 } 3949 3950 if (this.currentedit.tool === 'select') { 3951 if (this.currentannotation && this.currentedit) { 3952 this.currentannotation.move(this.currentedit.annotationstart.x + point.x - this.currentedit.start.x, 3953 this.currentedit.annotationstart.y + point.y - this.currentedit.start.y); 3954 } 3955 } else if (this.currentedit.tool === 'drag') { 3956 diffX = point.x - this.currentedit.start.x; 3957 diffY = point.y - this.currentedit.start.y; 3958 3959 drawingregion.getDOMNode().scrollLeft -= diffX; 3960 drawingregion.getDOMNode().scrollTop -= diffY; 3961 3962 } else { 3963 if (this.currentedit.start) { 3964 this.currentedit.end = point; 3965 this.redraw_current_edit(); 3966 } 3967 } 3968 }, 3969 3970 /** 3971 * Event handler for mouseup or touchend. 3972 * @protected 3973 * @param Event 3974 * @method edit_end 3975 */ 3976 edit_end: function() { 3977 var duration, 3978 comment, 3979 annotation; 3980 3981 duration = new Date().getTime() - this.currentedit.start; 3982 3983 if (duration < CLICKTIMEOUT || this.currentedit.start === false) { 3984 return; 3985 } 3986 3987 if (this.currentedit.tool === 'comment') { 3988 if (this.currentdrawable) { 3989 this.currentdrawable.erase(); 3990 } 3991 this.currentdrawable = false; 3992 comment = new M.assignfeedback_editpdf.comment(this); 3993 if (comment.init_from_edit(this.currentedit)) { 3994 this.pages[this.currentpage].comments.push(comment); 3995 this.drawables.push(comment.draw(true)); 3996 this.editingcomment = true; 3997 } 3998 } else { 3999 annotation = this.create_annotation(this.currentedit.tool, {}); 4000 if (annotation) { 4001 if (this.currentdrawable) { 4002 this.currentdrawable.erase(); 4003 } 4004 this.currentdrawable = false; 4005 if (annotation.init_from_edit(this.currentedit)) { 4006 this.pages[this.currentpage].annotations.push(annotation); 4007 this.drawables.push(annotation.draw()); 4008 } 4009 } 4010 } 4011 4012 // Save the changes. 4013 this.save_current_page(); 4014 4015 // Reset the current edit. 4016 this.currentedit.starttime = 0; 4017 this.currentedit.start = false; 4018 this.currentedit.end = false; 4019 this.currentedit.path = []; 4020 }, 4021 4022 /** 4023 * Resize the dialogue window when the browser is resized. 4024 * @public 4025 * @method resize 4026 */ 4027 resize: function() { 4028 var drawingregion, drawregionheight; 4029 4030 if (this.dialogue) { 4031 if (!this.dialogue.get('visible')) { 4032 return; 4033 } 4034 this.dialogue.centerDialogue(); 4035 } 4036 4037 // Make sure the dialogue box is not bigger than the max height of the viewport. 4038 drawregionheight = Y.one('body').get('winHeight') - 120; // Space for toolbar + titlebar. 4039 if (drawregionheight < 100) { 4040 drawregionheight = 100; 4041 } 4042 drawingregion = this.get_dialogue_element(SELECTOR.DRAWINGREGION); 4043 if (this.dialogue) { 4044 drawingregion.setStyle('maxHeight', drawregionheight + 'px'); 4045 } 4046 this.redraw(); 4047 return true; 4048 }, 4049 4050 /** 4051 * Factory method for creating annotations of the correct subclass. 4052 * @public 4053 * @method create_annotation 4054 */ 4055 create_annotation: function(type, data) { 4056 data.type = type; 4057 data.editor = this; 4058 if (type === "line") { 4059 return new M.assignfeedback_editpdf.annotationline(data); 4060 } else if (type === "rectangle") { 4061 return new M.assignfeedback_editpdf.annotationrectangle(data); 4062 } else if (type === "oval") { 4063 return new M.assignfeedback_editpdf.annotationoval(data); 4064 } else if (type === "pen") { 4065 return new M.assignfeedback_editpdf.annotationpen(data); 4066 } else if (type === "highlight") { 4067 return new M.assignfeedback_editpdf.annotationhighlight(data); 4068 } else if (type === "stamp") { 4069 return new M.assignfeedback_editpdf.annotationstamp(data); 4070 } 4071 return false; 4072 }, 4073 4074 /** 4075 * Save all the annotations and comments for the current page. 4076 * @protected 4077 * @method save_current_page 4078 */ 4079 save_current_page: function() { 4080 var ajaxurl = AJAXBASE, 4081 config; 4082 4083 config = { 4084 method: 'post', 4085 context: this, 4086 sync: false, 4087 data: { 4088 'sesskey': M.cfg.sesskey, 4089 'action': 'savepage', 4090 'index': this.currentpage, 4091 'userid': this.get('userid'), 4092 'attemptnumber': this.get('attemptnumber'), 4093 'assignmentid': this.get('assignmentid'), 4094 'page': this.stringify_current_page() 4095 }, 4096 on: { 4097 success: function(tid, response) { 4098 var jsondata; 4099 try { 4100 jsondata = Y.JSON.parse(response.responseText); 4101 if (jsondata.error) { 4102 return new M.core.ajaxException(jsondata); 4103 } 4104 Y.one(SELECTOR.UNSAVEDCHANGESINPUT).set('value', 'true'); 4105 Y.one(SELECTOR.UNSAVEDCHANGESDIV).setStyle('opacity', 1); 4106 Y.one(SELECTOR.UNSAVEDCHANGESDIV).setStyle('display', 'inline-block'); 4107 Y.one(SELECTOR.UNSAVEDCHANGESDIV).transition({ 4108 duration: 1, 4109 delay: 2, 4110 opacity: 0 4111 }, function() { 4112 Y.one(SELECTOR.UNSAVEDCHANGESDIV).setStyle('display', 'none'); 4113 }); 4114 } catch (e) { 4115 return new M.core.exception(e); 4116 } 4117 }, 4118 failure: function(tid, response) { 4119 return new M.core.exception(response.responseText); 4120 } 4121 } 4122 }; 4123 4124 Y.io(ajaxurl, config); 4125 4126 }, 4127 4128 /** 4129 * Event handler to open the comment search interface. 4130 * 4131 * @param Event e 4132 * @protected 4133 * @method open_search_comments 4134 */ 4135 open_search_comments: function(e) { 4136 if (!this.searchcommentswindow) { 4137 this.searchcommentswindow = new M.assignfeedback_editpdf.commentsearch({ 4138 editor: this 4139 }); 4140 } 4141 4142 this.searchcommentswindow.show(); 4143 e.preventDefault(); 4144 }, 4145 4146 /** 4147 * Redraw all the comments and annotations. 4148 * @protected 4149 * @method redraw 4150 */ 4151 redraw: function() { 4152 var i, 4153 page; 4154 4155 page = this.pages[this.currentpage]; 4156 if (page === undefined) { 4157 return; // Can happen if a redraw is triggered by an event, before the page has been selected. 4158 } 4159 while (this.drawables.length > 0) { 4160 this.drawables.pop().erase(); 4161 } 4162 4163 for (i = 0; i < page.annotations.length; i++) { 4164 this.drawables.push(page.annotations[i].draw()); 4165 } 4166 for (i = 0; i < page.comments.length; i++) { 4167 this.drawables.push(page.comments[i].draw(false)); 4168 } 4169 }, 4170 4171 /** 4172 * Load the image for this pdf page and remove the loading icon (if there). 4173 * @protected 4174 * @method change_page 4175 */ 4176 change_page: function() { 4177 var drawingcanvas = this.get_dialogue_element(SELECTOR.DRAWINGCANVAS), 4178 page, 4179 previousbutton, 4180 nextbutton; 4181 4182 previousbutton = this.get_dialogue_element(SELECTOR.PREVIOUSBUTTON); 4183 nextbutton = this.get_dialogue_element(SELECTOR.NEXTBUTTON); 4184 4185 if (this.currentpage > 0) { 4186 previousbutton.removeAttribute('disabled'); 4187 } else { 4188 previousbutton.setAttribute('disabled', 'true'); 4189 } 4190 if (this.currentpage < (this.pagecount - 1)) { 4191 nextbutton.removeAttribute('disabled'); 4192 } else { 4193 nextbutton.setAttribute('disabled', 'true'); 4194 } 4195 4196 page = this.pages[this.currentpage]; 4197 this.loadingicon.hide(); 4198 drawingcanvas.setStyle('backgroundImage', 'url("' + page.url + '")'); 4199 drawingcanvas.setStyle('width', page.width + 'px'); 4200 drawingcanvas.setStyle('height', page.height + 'px'); 4201 4202 // Update page select. 4203 this.get_dialogue_element(SELECTOR.PAGESELECT).set('selectedIndex', this.currentpage); 4204 4205 this.resize(); // Internally will call 'redraw', after checking the dialogue size. 4206 }, 4207 4208 /** 4209 * Now we know how many pages there are, 4210 * we can enable the navigation controls. 4211 * @protected 4212 * @method setup_navigation 4213 */ 4214 setup_navigation: function() { 4215 var pageselect, 4216 i, 4217 strinfo, 4218 option, 4219 previousbutton, 4220 nextbutton; 4221 4222 pageselect = this.get_dialogue_element(SELECTOR.PAGESELECT); 4223 4224 var options = pageselect.all('option'); 4225 if (options.size() <= 1) { 4226 for (i = 0; i < this.pages.length; i++) { 4227 option = Y.Node.create('<option/>'); 4228 option.setAttribute('value', i); 4229 strinfo = {page: i + 1, total: this.pages.length}; 4230 option.setHTML(M.util.get_string('pagexofy', 'assignfeedback_editpdf', strinfo)); 4231 pageselect.append(option); 4232 } 4233 } 4234 pageselect.removeAttribute('disabled'); 4235 pageselect.on('change', function() { 4236 this.currentpage = pageselect.get('value'); 4237 this.change_page(); 4238 }, this); 4239 4240 previousbutton = this.get_dialogue_element(SELECTOR.PREVIOUSBUTTON); 4241 nextbutton = this.get_dialogue_element(SELECTOR.NEXTBUTTON); 4242 4243 previousbutton.on('click', this.previous_page, this); 4244 previousbutton.on('key', this.previous_page, 'down:13', this); 4245 nextbutton.on('click', this.next_page, this); 4246 nextbutton.on('key', this.next_page, 'down:13', this); 4247 }, 4248 4249 /** 4250 * Navigate to the previous page. 4251 * @protected 4252 * @method previous_page 4253 */ 4254 previous_page: function(e) { 4255 e.preventDefault(); 4256 this.currentpage--; 4257 if (this.currentpage < 0) { 4258 this.currentpage = 0; 4259 } 4260 this.change_page(); 4261 }, 4262 4263 /** 4264 * Navigate to the next page. 4265 * @protected 4266 * @method next_page 4267 */ 4268 next_page: function(e) { 4269 e.preventDefault(); 4270 this.currentpage++; 4271 if (this.currentpage >= this.pages.length) { 4272 this.currentpage = this.pages.length - 1; 4273 } 4274 this.change_page(); 4275 }, 4276 4277 /** 4278 * Update any absolutely positioned nodes, within each drawable, when the drawing canvas is scrolled 4279 * @protected 4280 * @method move_canvas 4281 */ 4282 move_canvas: function() { 4283 var drawingregion, x, y, i; 4284 4285 drawingregion = this.get_dialogue_element(SELECTOR.DRAWINGREGION); 4286 x = parseInt(drawingregion.get('scrollLeft'), 10); 4287 y = parseInt(drawingregion.get('scrollTop'), 10); 4288 4289 for (i = 0; i < this.drawables.length; i++) { 4290 this.drawables[i].scroll_update(x, y); 4291 } 4292 } 4293 4294 }; 4295 4296 Y.extend(EDITOR, Y.Base, EDITOR.prototype, { 4297 NAME: 'moodle-assignfeedback_editpdf-editor', 4298 ATTRS: { 4299 userid: { 4300 validator: Y.Lang.isInteger, 4301 value: 0 4302 }, 4303 assignmentid: { 4304 validator: Y.Lang.isInteger, 4305 value: 0 4306 }, 4307 attemptnumber: { 4308 validator: Y.Lang.isInteger, 4309 value: 0 4310 }, 4311 header: { 4312 validator: Y.Lang.isString, 4313 value: '' 4314 }, 4315 body: { 4316 validator: Y.Lang.isString, 4317 value: '' 4318 }, 4319 footer: { 4320 validator: Y.Lang.isString, 4321 value: '' 4322 }, 4323 linkid: { 4324 validator: Y.Lang.isString, 4325 value: '' 4326 }, 4327 deletelinkid: { 4328 validator: Y.Lang.isString, 4329 value: '' 4330 }, 4331 readonly: { 4332 validator: Y.Lang.isBoolean, 4333 value: true 4334 }, 4335 stampfiles: { 4336 validator: Y.Lang.isArray, 4337 value: '' 4338 }, 4339 pagetotal: { 4340 validator: Y.Lang.isInteger, 4341 value: 0 4342 } 4343 } 4344 }); 4345 4346 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {}; 4347 M.assignfeedback_editpdf.editor = M.assignfeedback_editpdf.editor || {}; 4348 4349 /** 4350 * Init function - will create a new instance every time. 4351 * @method editor.init 4352 * @static 4353 * @param {Object} params 4354 */ 4355 M.assignfeedback_editpdf.editor.init = M.assignfeedback_editpdf.editor.init || function(params) { 4356 M.assignfeedback_editpdf.instance = new EDITOR(params); 4357 return M.assignfeedback_editpdf.instance; 4358 }; 4359 4360 4361 }, '@VERSION@', { 4362 "requires": [ 4363 "base", 4364 "event", 4365 "node", 4366 "io", 4367 "graphics", 4368 "json", 4369 "event-move", 4370 "event-resize", 4371 "transition", 4372 "querystring-stringify-simple", 4373 "moodle-core-notification-dialog", 4374 "moodle-core-notification-exception", 4375 "moodle-core-notification-ajaxexception" 4376 ] 4377 });
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |