mediawiki-extensions-Visual.../modules/ve-mw/ui/widgets/ve.ui.MWMediaResultWidget.js
Moriel Schottlender a44e0d2e5d Lazy load the media search results
* Set the src attribute only when the image is actually
  visible in the search results.
* Display the thumbnail image we have from the search
  results and then update with a larger one from the API.
* Request for more media results on a higher threshhold,
  when the user views 2 rows above the last available
  result.
* Correct the resizeToBoundingBox and simplify it to work
  properly for a non-square bounding box regardless of
  constraints.

Change-Id: If024b0335ce6a5d2d0eafdbfdfe1030dcaac3a75
2015-01-28 17:56:54 -08:00

268 lines
7.5 KiB
JavaScript

/*!
* VisualEditor UserInterface MWMediaResultWidget class.
*
* @copyright 2011-2015 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* Creates an ve.ui.MWMediaResultWidget object.
*
* @class
* @extends OO.ui.OptionWidget
*
* @constructor
* @param {Object} [config] Configuration options
* @cfg {number} [size] Media thumbnail size
*/
ve.ui.MWMediaResultWidget = function VeUiMWMediaResultWidget( config ) {
// Configuration initialization
config = config || {};
// Parent constructor
OO.ui.OptionWidget.call( this, config );
// Properties
this.initialSize = config.size || 150;
this.maxSize = config.maxSize || this.initialSize * 2;
this.expanded = false;
this.dimensions = {};
this.$thumb = this.buildThumbnail();
this.$overlay = this.$( '<div>' );
this.row = null;
// Store the thumbnail url
this.thumbUrl = ve.getProp( this.data.imageinfo, 0, 'thumburl' );
// Get wiki default thumbnail size
this.defaultThumbSize = mw.config.get( 'wgVisualEditorConfig' )
.defaultUserOptions.defaultthumbsize;
// Initialization
this.setLabel( new mw.Title( this.data.title ).getNameText() );
this.$label.addClass( 've-ui-mwMediaResultWidget-nameLabel' );
this.$overlay.addClass( 've-ui-mwMediaResultWidget-overlay' );
this.$element
.addClass( 've-ui-mwMediaResultWidget ve-ui-texture-pending' )
.prepend( this.$thumb, this.$overlay );
// Adjust wrapper padding
this.$element.css( $.extend( this.dimensions, this.calculateWrapperPadding( this.dimensions, this.initialSize ) ) );
// Select button
this.selectButton = new OO.ui.ButtonWidget( {
$: this.$,
label: ve.msg( 'visualeditor-dialog-media-searchselect' ),
icon: 'check'
} );
this.selectButton.$element.hide();
this.$element.prepend( this.selectButton.$element );
};
/* Inheritance */
OO.inheritClass( ve.ui.MWMediaResultWidget, OO.ui.OptionWidget );
/* Methods */
/** */
ve.ui.MWMediaResultWidget.prototype.onThumbnailLoad = function () {
this.$thumb.first().addClass( 've-ui-texture-transparency' );
this.$element
.addClass( 've-ui-mwMediaResultWidget-done' )
.removeClass( 've-ui-texture-pending' );
};
/** */
ve.ui.MWMediaResultWidget.prototype.onThumbnailError = function () {
this.$thumb.last()
.css( 'background-image', '' )
.addClass( 've-ui-texture-alert' );
this.$element
.addClass( 've-ui-mwMediaResultWidget-error' )
.removeClass( 've-ui-texture-pending' );
};
/**
* Build a thumbnail.
*
* @method
* @returns {jQuery} Thumbnail element
*/
ve.ui.MWMediaResultWidget.prototype.buildThumbnail = function () {
var imageDimensions,
info = this.data.imageinfo[0],
$thumb = this.$( '<img>' );
// Preload image
$thumb
.addClass( 've-ui-mwMediaResultWidget-thumbnail' )
.on( {
load: this.onThumbnailLoad.bind( this ),
error: this.onThumbnailError.bind( this )
} );
if ( info.mediatype === 'AUDIO' ) {
// HACK: We are getting the wrong information from the
// API about audio files. Set their thumbnail to square
imageDimensions = {
width: this.initialSize,
height: this.initialSize
};
} else {
if ( info.height < this.initialSize && info.width < this.maxSize ) {
// Define dimensions with original size
imageDimensions = {
width: info.width,
height: info.height
};
} else {
// Resize dimensions to be a fixed height
imageDimensions = ve.dm.Scalable.static.getDimensionsFromValue(
{ height: this.initialSize },
info.thumbwidth / info.thumbheight
);
}
}
// Resize the wrapper
this.dimensions = this.calculateThumbDimensions( imageDimensions );
// Resize the image
$thumb.css( this.dimensions );
return $thumb;
};
/**
* Replace the empty .src attribute of the image with the
* actual src.
*/
ve.ui.MWMediaResultWidget.prototype.lazyLoad = function () {
this.$thumb.attr( 'src', this.thumbUrl );
};
/**
* Retrieve the store dimensions object
* @return {Object} Thumb dimensions
*/
ve.ui.MWMediaResultWidget.prototype.getDimensions = function () {
return this.dimensions;
};
/**
* Resize thumbnail and element according to the resize factor
* @param {number} resizeFactor New resizing factor, multiplying the
* current dimensions of the thumbnail
*/
ve.ui.MWMediaResultWidget.prototype.resizeThumb = function ( resizeFactor ) {
var wrapperCss, imageDimensions,
currWidth = this.$thumb.width(),
currHeight = this.$thumb.height();
imageDimensions = {
width: currWidth * resizeFactor,
height: currHeight * resizeFactor
};
// Resize thumb wrapper
this.$thumb.css( imageDimensions );
// Adjust wrapper padding - this is done so the image is placed in the center
wrapperCss = $.extend( imageDimensions, this.calculateWrapperPadding( imageDimensions, imageDimensions.height ) );
this.$element.css( wrapperCss );
};
/**
* Calculate thumbnail dimensions
* @param {Object} imageDimensions Image dimensions
* @return {Object} Thumbnail dimensions
*/
ve.ui.MWMediaResultWidget.prototype.calculateThumbDimensions = function ( imageDimensions ) {
var dimensions,
maxWidth = this.maxSize,
ratio = imageDimensions.width / imageDimensions.height;
// Rules of resizing:
// (1) Images must have height = this.initialSize
// (2) If after resize image width is larger than maxWidth
// the image is scaled down to width = 3*this.width
// (3) Smaller images do not scale up
// * If image height < this.initialSize, add padding and center
// the image vertically
// * If image width < this.initialSize, add a fixed padding to both
// sides.
// This is done in 'calculateWrapperPadding'
// First scale all images based on height = this.initialSize
dimensions = ve.dm.MWImageNode.static.resizeToBoundingBox(
// Image thumb size
imageDimensions,
// Bounding box
{
width: maxWidth,
height: this.initialSize
}
);
// Check if image width is larger than maxWidth
if ( dimensions.width > maxWidth ) {
// Resize again to fit maxWidth
dimensions = ve.dm.Scalable.static.getDimensionsFromValue( { width: maxWidth }, ratio );
}
return dimensions;
};
/**
* Adjust the wrapper padding for small images
* @param {Object} thumbDimensions Thumbnail dimensions
* @return {Object} Left and right padding
*/
ve.ui.MWMediaResultWidget.prototype.calculateWrapperPadding = function ( thumbDimensions, rowHeight ) {
var padding,
minWidthRatioForPadding = 2 / 3 * rowHeight,
paddingWidth = {},
paddingHeight = {},
minWidth = 0.5 * rowHeight;
// Check if the image fits the row height
if ( thumbDimensions.height < rowHeight ) {
// Set up top/bottom padding
paddingHeight = {
'padding-top': ( rowHeight - thumbDimensions.height ) / 2,
'padding-bottom': ( rowHeight - thumbDimensions.height ) / 2
};
}
// Check if the image is too thin so we can make a bit of space around it
if ( thumbDimensions.width < minWidth ) {
// Make the padding so that the total width is a 1/3 of the line height
padding = rowHeight - minWidthRatioForPadding - thumbDimensions.width;
paddingWidth = {
'padding-left': padding / 2,
'padding-right': padding / 2
};
}
return $.extend( {}, paddingWidth, paddingHeight );
};
/**
* Set the row this result is in.
* @param {number} row Row number
*/
ve.ui.MWMediaResultWidget.prototype.setRow = function ( row ) {
this.row = row;
};
/**
* Get the row this result is in.
* @return {number} row Row number
*/
ve.ui.MWMediaResultWidget.prototype.getRow = function () {
return this.row;
};
/**
* Check if the image has a src attribute already
* @returns {boolean} Thumbnail has its source attribute set
*/
ve.ui.MWMediaResultWidget.prototype.hasSrc = function () {
return !!this.$thumb.attr( 'src' );
};