Note: After saving, changes may not occur immediately. Click here to learn how to bypass your browser's cache.
  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (Cmd-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (Cmd-Shift-R on a Mac)
  • Internet Explorer: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Clear the cache in Tools → Preferences

For details and instructions about other browsers, see Wikipedia:Bypass your cache.

/* global $, mw */
"use strict";

console.log("loupe.js loading");

// Make sure the necessary modules are loaded
mw.loader.using(['mediawiki.util', 'mediawiki.api'], function() {
	console.log("loupe.js in mw.loader.using");

  // Wait for the page to be parsed (new-style $(document).ready())
  $(function() { 
	console.log("loupe.js in $(document).ready()");
    // Only active on Page:-namespace pages.
    if (mw.config.get('wgCanonicalNamespace') !== 'Page') {
      return;
    }
	console.log("loupe.js passed namespace check");

    // Page image is statically positioned in view mode, so force it to relative
    if (mw.config.get('wgAction') == 'view') {
      $('.prp-page-image').css('position', 'relative');
    }

    // Pre-load loading GIF
    var loadingGifUrl = '//upload.wikimedia.org/wikipedia/commons/4/42/Loading.gif';
    $('<img />').attr('src', loadingGifUrl).appendTo('body').hide();

    // Track whether full-sized image has loaded
    var fullwidth_image_loaded = false;

    // Set up the loupe
    var loupe = $('<div></div>').attr('id', 'loupe').css({
      'width': '200px',
      'height': '125px',
      'position': 'absolute',
      'border-radius': '.2ex',
      'box-shadow': '0 0 0 1px rgba(0, 0, 0, 0.85), 0 0 7px 7px rgba(0, 0, 0, 0.25), inset 0 0 40px 2px rgba(0, 0, 0, 0.25)',
      'background-color': 'white',
      'background-image': "url('" + loadingGifUrl + "')",
      'background-repeat': 'no-repeat',
      'background-position': 'center',
      'cursor': 'crosshair',
      'display': 'none'
    }).appendTo('.mw-content-text').hide();

    var fullsize_width  = $('.prp-page-image img').data('fileWidth');

    var match = /(.*?)\/(\d+)/.exec(mw.config.get('wgTitle'));
    var file = match[1];
    var page = match[2];

	console.log("loupe.js about to call API");


    var api = new mw.Api();
    api.get({
      'action': 'query',
      'prop': 'imageinfo',
      'titles': "File:" + file,
      'formatversion': 2,
      'format': 'json',
      'iiprop': 'url',
      'iiurlwidth': fullsize_width,
      'iiurlparam': "page" + page + "-" + fullsize_width + "px"
    })
    .done(function(data) {
      console.log("loupe.js API call called .done(" + data.query.pages[0].imageinfo[0].thumburl + ")");
      var thumburl = data.query.pages[0].imageinfo[0].thumburl;
      $('<img />').on('load', {thumburl: thumburl}, function(e) {
		console.log("loupe.js DOM called .on(load)");
        var thumburl = e.data.thumburl;
        fullwidth_image_loaded = true;
        $('#loupe').css({'background-image': "url('" + thumburl + "')"});
      }).attr('src', thumburl).appendTo('body').hide();
    });


    // Now set up the mouse tracking and loupe display
    $('.prp-page-image').mousemove(function(e) {
      var img = $('.prp-page-image img');
      var img_offset = img.offset();
      var imgX = img_offset.left;
      var imgY = img_offset.top;
      var imgW = img.width();
      var imgH = img.height();


      // Subtract the page image container offsets from the mouse offsets to
      // make the mouse offsets relative to the page image container.
      var mX = e.pageX - imgX;
      var mY = e.pageY - imgY;
    
      // Fade in the loupe if the mouse offsets are within the bounds of the
      // page image container. Otherwise, fade it out.
      //
      // Apply a safety margin since mousemove events are coalesced and there is
      // a window where the mouse can be outside .prp-page-image, so it no
      // longer gets mousemove events, but still inside the image rectangle so
      // it doesn't fade the loupe out.
      // FIXME: Should get clever with .mouseenter here instead.
      var fuzz = 5;
      var insideRightEdge  = mX < (imgW - fuzz) ? true : false;
      var insideLeftEdge   = mX > (   0 + fuzz) ? true : false;
      var insideTopEdge    = mY < (imgH - fuzz) ? true : false;
      var insideBottomEdge = mY > (   0 + fuzz) ? true : false;
      if (insideRightEdge && insideLeftEdge && insideTopEdge && insideBottomEdge) {
        $("#loupe").fadeIn(200);
      } else {
        $("#loupe").fadeOut(200);
      }

      // Position full-resolution image relative to mouse position so that the
      // magnification effect works.
      if ($('#loupe').is(':visible')) {
        // Calculate the position of the loupe relative to the mouse position.
        var loupe_left = mX - ($('#loupe').width()  / 2);
        var loupe_top  = mY - ($('#loupe').height() / 2);

        // Percentage offset of the mouse pointer in the small image, which
        // translates to a percentage offset for the large image.
        // FIXME: Need an "accelleration curve" here, so that the offset between
        // the mouse position (center) and edge of the loupe doesn't cut off the
        // edge of the image (problem on tight margins).
        var bgposition_left = (100 / imgW) * mX;
        var bgposition_top  = (100 / imgH) * mY;
        var bgposition = bgposition_left + "% " + bgposition_top + "%";
        if (!fullwidth_image_loaded) {
          bgposition = 'center';
        }
      
        // Position the loupe and background.
        $("#loupe").css({left: loupe_left, top: loupe_top, backgroundPosition: bgposition});
      }
    });

  }); // END: $(document).ready()
}); // END: mw.loader.using()