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