[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/mod/assign/feedback/editpdf/yui/src/editor/js/ -> editor.js (source)

   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  };


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1