[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/webroot/rsrc/js/application/pholio/ -> behavior-pholio-mock-view.js (source)

   1  /**
   2   * @provides javelin-behavior-pholio-mock-view
   3   * @requires javelin-behavior
   4   *           javelin-util
   5   *           javelin-stratcom
   6   *           javelin-dom
   7   *           javelin-vector
   8   *           javelin-magical-init
   9   *           javelin-request
  10   *           javelin-history
  11   *           javelin-workflow
  12   *           javelin-mask
  13   *           javelin-behavior-device
  14   *           phabricator-keyboard-shortcut
  15   */
  16  JX.behavior('pholio-mock-view', function(config) {
  17    var is_dragging = false;
  18  
  19    var drag_begin;
  20    var drag_end;
  21    var panel = JX.$(config.panelID);
  22    var viewport = JX.$(config.viewportID);
  23  
  24    var selection_reticle;
  25    var active_image;
  26  
  27    var inline_comments = {};
  28  
  29  
  30  /* -(  Stage  )-------------------------------------------------------------- */
  31  
  32  
  33    var stage = (function() {
  34      var loading = false;
  35      var stageElement = JX.$(config.panelID);
  36      var viewElement = JX.$(config.viewportID);
  37      var reticles = [];
  38  
  39      function begin_load() {
  40        if (loading) {
  41          return;
  42        }
  43        loading = true;
  44        clear_stage();
  45        draw_loading();
  46      }
  47  
  48      function end_load() {
  49        if (!loading) {
  50          return;
  51        }
  52        loading = false;
  53        draw_loading();
  54      }
  55  
  56      function draw_loading() {
  57        JX.DOM.alterClass(stageElement, 'pholio-image-loading', loading);
  58      }
  59  
  60      function add_reticle(reticle, id) {
  61        mark_ref(reticle, id);
  62        reticles.push(reticle);
  63        viewElement.appendChild(reticle);
  64      }
  65  
  66      function clear_stage() {
  67        var ii;
  68        for (ii = 0; ii < reticles.length; ii++) {
  69          JX.DOM.remove(reticles[ii]);
  70        }
  71        reticles = [];
  72      }
  73  
  74      function mark_ref(node, id) {
  75        JX.Stratcom.addSigil(node, 'pholio-inline-ref');
  76        JX.Stratcom.addData(node, {inlineID: id});
  77      }
  78  
  79      return {
  80        beginLoad: begin_load,
  81        endLoad: end_load,
  82        addReticle: add_reticle,
  83        clearStage: clear_stage
  84      };
  85    })();
  86  
  87    JX.enableDispatch(document.body, 'mouseenter');
  88    JX.enableDispatch(document.body, 'mouseleave');
  89  
  90    JX.Stratcom.listen(
  91      ['mouseenter', 'mouseover'],
  92      'mock-panel',
  93      function(e) {
  94        JX.DOM.alterClass(e.getNode('mock-panel'), 'mock-has-cursor', true);
  95      });
  96  
  97    JX.Stratcom.listen('mouseleave', 'mock-panel', function(e) {
  98      var node = e.getNode('mock-panel');
  99      if (e.getTarget() == node) {
 100        JX.DOM.alterClass(node, 'mock-has-cursor', false);
 101      }
 102    });
 103  
 104    function get_image_index(id) {
 105      for (var ii = 0; ii < config.images.length; ii++) {
 106        if (config.images[ii].id == id) {
 107          return ii;
 108        }
 109      }
 110      return null;
 111    }
 112  
 113    function get_image_navindex(id) {
 114      for (var ii = 0; ii < config.navsequence.length; ii++) {
 115        if (config.navsequence[ii] == id) {
 116          return ii;
 117        }
 118      }
 119      return null;
 120    }
 121  
 122    function get_image(id) {
 123      var idx = get_image_index(id);
 124      if (idx === null) {
 125        return idx;
 126      }
 127      return config.images[idx];
 128    }
 129  
 130    function onload_image(id) {
 131      if (active_image.id != id) {
 132        // The user has clicked another image before this one loaded, so just
 133        // bail.
 134        return;
 135      }
 136  
 137      active_image.tag = this;
 138      redraw_image();
 139    }
 140  
 141    function switch_image(delta) {
 142      if (!active_image) {
 143        return;
 144      }
 145      var idx = get_image_navindex(active_image.id);
 146      if (idx === null) {
 147        return;
 148      }
 149      idx = (idx + delta + config.navsequence.length) % config.navsequence.length;
 150      select_image(config.navsequence[idx]);
 151    }
 152  
 153    function redraw_image() {
 154      var new_y;
 155  
 156      // If we don't have an image yet, just scale the stage relative to the
 157      // entire viewport height so the jump isn't too jumpy when the image loads.
 158      if (!active_image || !active_image.tag) {
 159        new_y = (JX.Vector.getViewport().y * 0.80);
 160        new_y = Math.max(320, new_y);
 161        panel.style.height = new_y + 'px';
 162  
 163        return;
 164      }
 165  
 166      var tag = active_image.tag;
 167  
 168      // If the image is too wide for the viewport, scale it down so it fits.
 169      // If it is too tall, just let the viewport scroll.
 170      var w = JX.Vector.getDim(panel);
 171  
 172      // Leave 24px margins on either side of the image.
 173      w.x -= 48;
 174  
 175      var scale = 1;
 176      if (w.x < tag.naturalWidth) {
 177        scale = Math.min(scale, w.x / tag.naturalWidth);
 178      }
 179  
 180      if (scale < 1) {
 181        tag.width = Math.floor(scale * tag.naturalWidth);
 182        tag.height = Math.floor(scale * tag.naturalHeight);
 183      } else {
 184        tag.width = tag.naturalWidth;
 185        tag.height = tag.naturalHeight;
 186      }
 187  
 188      // Scale the viewport's vertical size to the image's adjusted size.
 189      new_y = Math.max(320, tag.height + 48);
 190      panel.style.height = new_y + 'px';
 191  
 192      viewport.style.top = Math.floor((new_y - tag.height) / 2) + 'px';
 193  
 194      stage.endLoad();
 195  
 196      JX.DOM.setContent(viewport, tag);
 197  
 198      redraw_inlines(active_image.id);
 199    }
 200  
 201    function select_image(image_id) {
 202      active_image = get_image(image_id);
 203      active_image.tag = null;
 204  
 205      stage.beginLoad();
 206  
 207      var img = JX.$N('img', {className: 'pholio-mock-image'});
 208      img.onload = JX.bind(img, onload_image, active_image.id);
 209      img.src = active_image.stageURI;
 210  
 211      var thumbs = JX.DOM.scry(
 212        JX.$('pholio-mock-thumb-grid'),
 213        'a',
 214        'mock-thumbnail');
 215  
 216      for(var k in thumbs) {
 217        var thumb_meta = JX.Stratcom.getData(thumbs[k]);
 218  
 219        JX.DOM.alterClass(
 220          thumbs[k],
 221          'pholio-mock-thumb-grid-current',
 222          (active_image.id == thumb_meta.imageID));
 223      }
 224  
 225      load_inline_comments();
 226      if (image_id != config.selectedID) {
 227        JX.History.replace(active_image.pageURI);
 228      }
 229    }
 230  
 231    JX.Stratcom.listen(
 232      'click',
 233      'mock-thumbnail',
 234      function(e) {
 235        if (!e.isNormalMouseEvent()) {
 236          return;
 237        }
 238        e.kill();
 239        select_image(e.getNodeData('mock-thumbnail').imageID);
 240      });
 241  
 242    select_image(config.selectedID);
 243  
 244    JX.Stratcom.listen('mousedown', 'mock-viewport', function(e) {
 245      if (!e.isNormalMouseEvent()) {
 246        return;
 247      }
 248  
 249      if (JX.Device.getDevice() != 'desktop') {
 250        return;
 251      }
 252  
 253      if (JX.Stratcom.pass()) {
 254        return;
 255      }
 256  
 257      if (is_dragging) {
 258        return;
 259      }
 260  
 261      e.kill();
 262  
 263      if (!active_image.isImage) {
 264        // If this is a PDF or something like that, we eat the event but we
 265        // don't let users add inlines to the thumbnail.
 266        return;
 267      }
 268  
 269      is_dragging = true;
 270      drag_begin = get_image_xy(JX.$V(e));
 271      drag_end = drag_begin;
 272  
 273      redraw_selection();
 274    });
 275  
 276  
 277    JX.enableDispatch(document.body, 'mousemove');
 278    JX.Stratcom.listen('mousemove', null, function(e) {
 279      if (!is_dragging) {
 280        return;
 281      }
 282      drag_end = get_image_xy(JX.$V(e));
 283      redraw_selection();
 284    });
 285  
 286    JX.Stratcom.listen(
 287      'mousedown',
 288      'pholio-inline-ref',
 289      function(e) {
 290        e.kill();
 291  
 292        var id = e.getNodeData('pholio-inline-ref').inlineID;
 293  
 294        var active_id = active_image.id;
 295        var handler = function(r) {
 296          var inlines = inline_comments[active_id];
 297  
 298          for (var ii = 0; ii < inlines.length; ii++) {
 299            if (inlines[ii].id == id) {
 300              if (r.id) {
 301                inlines[ii] = r;
 302              } else {
 303                inlines.splice(ii, 1);
 304              }
 305              break;
 306            }
 307          }
 308  
 309          redraw_inlines(active_id);
 310          JX.DOM.invoke(JX.$(config.commentFormID), 'shouldRefresh');
 311        };
 312  
 313        new JX.Workflow('/pholio/inline/' + id + '/')
 314          .setHandler(handler)
 315          .start();
 316      });
 317  
 318    JX.Stratcom.listen(
 319      'mouseup',
 320      null,
 321      function(e) {
 322        if (!is_dragging) {
 323          return;
 324        }
 325  
 326        is_dragging = false;
 327        if (!config.loggedIn) {
 328          new JX.Workflow(config.logInLink).start();
 329          return;
 330        }
 331  
 332        drag_end = get_image_xy(JX.$V(e));
 333  
 334        resize_selection(16);
 335  
 336        var data = {
 337          mockID: config.mockID,
 338          imageID: active_image.id,
 339          startX: Math.min(drag_begin.x, drag_end.x),
 340          startY: Math.min(drag_begin.y, drag_end.y),
 341          endX: Math.max(drag_begin.x, drag_end.x),
 342          endY: Math.max(drag_begin.y, drag_end.y)
 343        };
 344  
 345        var handler = function(r) {
 346          if (!inline_comments[active_image.id]) {
 347            inline_comments[active_image.id] = [];
 348          }
 349          inline_comments[active_image.id].push(r);
 350  
 351          redraw_inlines(active_image.id);
 352          JX.DOM.invoke(JX.$(config.commentFormID), 'shouldRefresh');
 353        };
 354  
 355        clear_selection();
 356  
 357        new JX.Workflow('/pholio/inline/', data)
 358          .setHandler(handler)
 359          .start();
 360      });
 361  
 362    function resize_selection(min_size) {
 363      var start = {
 364        x: Math.min(drag_begin.x, drag_end.x),
 365        y: Math.min(drag_begin.y, drag_end.y)
 366      };
 367      var end = {
 368        x: Math.max(drag_begin.x, drag_end.x),
 369        y: Math.max(drag_begin.y, drag_end.y)
 370      };
 371  
 372      var width = end.x - start.x;
 373      var height = end.y - start.y;
 374      var addon;
 375  
 376      if (width < min_size) {
 377        addon = (min_size-width)/2;
 378  
 379        start.x = Math.max(0, start.x - addon);
 380        end.x = Math.min(active_image.tag.naturalWidth, end.x + addon);
 381  
 382        if (start.x === 0) {
 383          end.x = Math.min(min_size, active_image.tag.naturalWidth);
 384        } else if (end.x == active_image.tag.naturalWidth) {
 385          start.x = Math.max(0, active_image.tag.naturalWidth - min_size);
 386        }
 387      }
 388  
 389      if (height < min_size) {
 390        addon = (min_size-height)/2;
 391  
 392        start.y = Math.max(0, start.y - addon);
 393        end.y = Math.min(active_image.tag.naturalHeight, end.y + addon);
 394  
 395        if (start.y === 0) {
 396          end.y = Math.min(min_size, active_image.tag.naturalHeight);
 397        } else if (end.y == active_image.tag.naturalHeight) {
 398          start.y = Math.max(0, active_image.tag.naturalHeight - min_size);
 399        }
 400      }
 401  
 402      drag_begin = start;
 403      drag_end = end;
 404      redraw_selection();
 405    }
 406  
 407    function render_image_header(image) {
 408      // Render image dimensions and visible size. If we have this infomation
 409      // from the server we can display some of it immediately; otherwise, we need
 410      // to wait for the image to load so we can read dimension information from
 411      // it.
 412  
 413      var image_x = image.width;
 414      var image_y = image.height;
 415      var display_x = null;
 416      if (image.tag) {
 417        image_x = image.tag.naturalWidth;
 418        image_y = image.tag.naturalHeight;
 419        display_x = image.tag.width;
 420      }
 421  
 422      var visible = [];
 423      if (image_x) {
 424        if (display_x) {
 425          var area = Math.round(100 * (display_x / image_x));
 426          visible.push(
 427            JX.$N(
 428              'span',
 429              {className: 'pholio-visible-size'},
 430              [area, '%']));
 431          visible.push(' ');
 432        }
 433        visible.push(['(', image_x, ' \u00d7 ', image_y, ')']);
 434      }
 435  
 436      return visible;
 437    }
 438  
 439    function redraw_inlines(id) {
 440      if (!active_image) {
 441        return;
 442      }
 443  
 444      if (active_image.id != id) {
 445        return;
 446      }
 447  
 448      stage.clearStage();
 449      var comment_holder = JX.$('mock-image-description');
 450      JX.DOM.setContent(comment_holder, render_image_info(active_image));
 451  
 452      var image_header = JX.$('mock-image-header');
 453      JX.DOM.setContent(image_header, render_image_header(active_image));
 454  
 455      var inlines = inline_comments[active_image.id];
 456      if (!inlines || !inlines.length) {
 457        return;
 458      }
 459  
 460      for (var ii = 0; ii < inlines.length; ii++) {
 461        var inline = inlines[ii];
 462  
 463        if (!active_image.tag) {
 464          // The image itself hasn't loaded yet, so we can't draw the inline
 465          // reticles.
 466          continue;
 467        }
 468  
 469        var classes = [];
 470        if (!inline.transactionPHID) {
 471          classes.push('pholio-mock-reticle-draft');
 472        } else {
 473          classes.push('pholio-mock-reticle-final');
 474        }
 475  
 476        var inline_selection = render_reticle(classes,
 477          'pholio-mock-comment-icon phui-font-fa fa-comment');
 478        stage.addReticle(inline_selection, inline.id);
 479        position_inline_rectangle(inline, inline_selection);
 480      }
 481    }
 482  
 483    function position_inline_rectangle(inline, rect) {
 484      var scale = get_image_scale();
 485  
 486      JX.$V(scale * inline.x, scale * inline.y).setPos(rect);
 487      JX.$V(scale * inline.width, scale * inline.height).setDim(rect);
 488    }
 489  
 490    function get_image_xy(p) {
 491      var img = active_image.tag;
 492      var imgp = JX.$V(img);
 493  
 494      var scale = 1 / get_image_scale();
 495  
 496      var x = scale * Math.max(0, Math.min(p.x - imgp.x, img.width));
 497      var y = scale * Math.max(0, Math.min(p.y - imgp.y, img.height));
 498  
 499      return {
 500        x: x,
 501        y: y
 502      };
 503    }
 504  
 505    function get_image_scale() {
 506      var img = active_image.tag;
 507      return Math.min(
 508        img.width / img.naturalWidth,
 509        img.height / img.naturalHeight);
 510    }
 511  
 512    function redraw_selection() {
 513      var classes = ['pholio-mock-reticle-selection'];
 514      selection_reticle = selection_reticle || render_reticle(classes, '');
 515  
 516      var p = JX.$V(
 517        Math.min(drag_begin.x, drag_end.x),
 518        Math.min(drag_begin.y, drag_end.y));
 519  
 520      var d = JX.$V(
 521        Math.max(drag_begin.x, drag_end.x) - p.x,
 522        Math.max(drag_begin.y, drag_end.y) - p.y);
 523  
 524      var scale = get_image_scale();
 525  
 526      p.x *= scale;
 527      p.y *= scale;
 528      d.x *= scale;
 529      d.y *= scale;
 530  
 531      viewport.appendChild(selection_reticle);
 532      p.setPos(selection_reticle);
 533      d.setDim(selection_reticle);
 534    }
 535  
 536    function clear_selection() {
 537      selection_reticle && JX.DOM.remove(selection_reticle);
 538      selection_reticle = null;
 539    }
 540  
 541    function load_inline_comments() {
 542      var id = active_image.id;
 543      var inline_comments_uri = '/pholio/inline/list/' + id + '/';
 544  
 545      new JX.Request(inline_comments_uri, function(r) {
 546        inline_comments[id] = r;
 547        redraw_inlines(id);
 548      }).send();
 549    }
 550  
 551    load_inline_comments();
 552    if (config.loggedIn && config.commentFormID) {
 553      JX.DOM.invoke(JX.$(config.commentFormID), 'shouldRefresh');
 554    }
 555  
 556    JX.Stratcom.listen('resize', null, redraw_image);
 557    redraw_image();
 558  
 559  
 560  /* -(  Keyboard Shortcuts  )------------------------------------------------- */
 561  
 562  
 563    new JX.KeyboardShortcut(['j', 'right'], 'Show next image.')
 564      .setHandler(function() {
 565        switch_image(1);
 566      })
 567      .register();
 568  
 569    new JX.KeyboardShortcut(['k', 'left'], 'Show previous image.')
 570      .setHandler(function() {
 571        switch_image(-1);
 572      })
 573      .register();
 574  
 575    JX.DOM.listen(panel, 'gesture.swipe.end', null, function(e) {
 576      var data = e.getData();
 577  
 578      if (data.length <= (JX.Vector.getDim(panel) / 2)) {
 579        // If the user didn't move their finger far enough, don't switch.
 580        return;
 581      }
 582  
 583      switch_image(data.direction == 'right' ? -1 : 1);
 584    });
 585  
 586  /* -(  Render  )------------------------------------------------------------- */
 587  
 588  
 589    function render_image_info(image) {
 590      var info = [];
 591  
 592      var buttons = [];
 593  
 594      var classes = ['pholio-image-button'];
 595  
 596      if (image.isViewable) {
 597        classes.push('pholio-image-button-active');
 598      } else {
 599        classes.push('pholio-image-button-disabled');
 600      }
 601  
 602      buttons.push(
 603        JX.$N(
 604          'div',
 605          {
 606            className: classes.join(' ')
 607          },
 608          JX.$N(
 609            image.isViewable ? 'a' : 'span',
 610            {
 611              href: image.fullURI,
 612              target: '_blank',
 613              className: 'pholio-image-button-link'
 614            },
 615            JX.$H(config.fullIcon))));
 616  
 617      classes = ['pholio-image-button', 'pholio-image-button-active'];
 618  
 619      buttons.push(
 620        JX.$N(
 621          'form',
 622          {
 623            className: classes.join(' '),
 624            action: image.downloadURI,
 625            method: 'POST',
 626            sigil: 'download'
 627          },
 628          JX.$N(
 629            'button',
 630            {
 631              href: image.downloadURI,
 632              className: 'pholio-image-button-link'
 633            },
 634            JX.$H(config.downloadIcon))));
 635  
 636      if (image.title === '') {
 637        image.title = 'Untitled Masterpiece';
 638      }
 639      var title = JX.$N(
 640        'div',
 641        {className: 'pholio-image-title'},
 642        image.title);
 643      info.push(title);
 644  
 645      if (!image.isObsolete) {
 646        var img_len = config.currentSetSize;
 647        var rev = JX.$N(
 648          'div',
 649          {className: 'pholio-image-revision'},
 650          JX.$H('Current Revision (' + img_len + ' images)'));
 651        info.push(rev);
 652      } else {
 653        var prev = JX.$N(
 654          'div',
 655          {className: 'pholio-image-revision'},
 656          JX.$H('(Previous Revision)'));
 657        info.push(prev);
 658      }
 659  
 660      for (var ii = 0; ii < info.length; ii++) {
 661        info[ii] = JX.$N('div', {className: 'pholio-image-info-item'}, info[ii]);
 662      }
 663      info = JX.$N('div', {className: 'pholio-image-info'}, info);
 664  
 665      if (image.descriptionMarkup === '') {
 666        return [buttons, info];
 667      } else {
 668        var desc = JX.$N(
 669          'div',
 670          {className: 'pholio-image-description'},
 671          JX.$H(image.descriptionMarkup));
 672        return [buttons, info, desc];
 673      }
 674    }
 675  
 676    function render_reticle(classes, inner_classes) {
 677      var inner = JX.$N('div', {className: inner_classes});
 678      var outer = JX.$N(
 679        'div',
 680        {className: ['pholio-mock-reticle'].concat(classes).join(' ')}, inner);
 681      return outer;
 682    }
 683  
 684  
 685  /* -(  Device Lightbox  )---------------------------------------------------- */
 686  
 687    // On devices, we show images full-size when the user taps them instead of
 688    // attempting to implement inlines.
 689  
 690    var lightbox = null;
 691  
 692    JX.Stratcom.listen('click', 'mock-viewport', function(e) {
 693      if (!e.isNormalMouseEvent()) {
 694        return;
 695      }
 696      if (JX.Device.getDevice() == 'desktop') {
 697        return;
 698      }
 699      lightbox_attach();
 700      e.kill();
 701    });
 702  
 703    JX.Stratcom.listen('click', 'pholio-device-lightbox', lightbox_detach);
 704    JX.Stratcom.listen('resize', null, lightbox_resize);
 705  
 706    function lightbox_attach() {
 707      JX.DOM.alterClass(document.body, 'lightbox-attached', true);
 708      JX.Mask.show('jx-dark-mask');
 709  
 710      lightbox = lightbox_render();
 711      var image = JX.$N('img');
 712      image.onload = lightbox_loaded;
 713      setTimeout(function() {
 714        image.src = active_image.stageURI;
 715      }, 1000);
 716      JX.DOM.setContent(lightbox, image);
 717      JX.DOM.alterClass(lightbox, 'pholio-device-lightbox-loading', true);
 718  
 719      lightbox_resize();
 720  
 721      document.body.appendChild(lightbox);
 722    }
 723  
 724    function lightbox_detach() {
 725      JX.DOM.remove(lightbox);
 726      JX.Mask.hide();
 727      JX.DOM.alterClass(document.body, 'lightbox-attached', false);
 728      lightbox = null;
 729    }
 730  
 731    function lightbox_resize() {
 732      if (!lightbox) {
 733        return;
 734      }
 735      JX.Vector.getScroll().setPos(lightbox);
 736      JX.Vector.getViewport().setDim(lightbox);
 737    }
 738  
 739    function lightbox_loaded() {
 740      JX.DOM.alterClass(lightbox, 'pholio-device-lightbox-loading', false);
 741    }
 742  
 743    function lightbox_render() {
 744      var el = JX.$N('div', {className: 'pholio-device-lightbox'});
 745      JX.Stratcom.addSigil(el, 'pholio-device-lightbox');
 746      return el;
 747    }
 748  
 749  
 750  /* -(  Preload  )------------------------------------------------------------ */
 751  
 752    var preload = [];
 753    for (var ii = 0; ii < config.images.length; ii++) {
 754      preload.push(config.images[ii].stageURI);
 755    }
 756  
 757    function preload_next() {
 758      next_src = preload[0];
 759      if (!next_src) {
 760        return;
 761      }
 762      preload.splice(0, 1);
 763  
 764      var img = JX.$N('img');
 765      img.onload = preload_next;
 766      img.onerror = preload_next;
 767      img.src = next_src;
 768    }
 769  
 770    preload_next();
 771  
 772  
 773  });


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1