mediawiki-extensions-Visual.../modules/ve-mw/dm/nodes/ve.dm.MWImageNode.js
Moriel Schottlender f0f8f52080 Refactor MWMediaResultWidget to resize better
For the masonry view, the result widget resizes itself according to
the rows and other images in the row. Up until now the resize was
a bit buggy, occasionally missing height/width values. This is
mostly due to rounding errors, but also due to a small double
calculation that is now fixed with this code.

Change-Id: If18adc5280e4bdbb9174b7c7e6e4eadf3c1ab26d
2015-02-26 16:42:14 -08:00

283 lines
8.2 KiB
JavaScript

/*!
* VisualEditor DataModel MWImageNode class.
*
* @copyright 2011-2015 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* DataModel MediaWiki image node.
*
* @class
* @abstract
* @extends ve.dm.GeneratedContentNode
* @mixins ve.dm.FocusableNode
* @mixins ve.dm.ResizableNode
*
* @constructor
*/
ve.dm.MWImageNode = function VeDmMWImageNode() {
// Parent constructor
ve.dm.GeneratedContentNode.call( this );
// Mixin constructors
ve.dm.ResizableNode.call( this );
ve.dm.FocusableNode.call( this );
this.scalablePromise = null;
// Use 'bitmap' as default media type until we can
// fetch the actual media type from the API
this.mediaType = 'BITMAP';
// Get wiki defaults
this.svgMaxSize = mw.config.get( 'wgVisualEditorConfig' ).svgMaxSize;
// Initialize
this.constructor.static.syncScalableToType(
this.getAttribute( 'type' ),
this.mediaType,
this.getScalable()
);
// Events
this.connect( this, { attributeChange: 'onAttributeChange' } );
};
/* Inheritance */
OO.inheritClass( ve.dm.MWImageNode, ve.dm.GeneratedContentNode );
OO.mixinClass( ve.dm.MWImageNode, ve.dm.FocusableNode );
OO.mixinClass( ve.dm.MWImageNode, ve.dm.ResizableNode );
/* Static methods */
ve.dm.MWImageNode.static.getHashObject = function ( dataElement ) {
return {
type: dataElement.type,
resource: dataElement.attributes.resource,
width: dataElement.attributes.width,
height: dataElement.attributes.height
};
};
/**
* Take the given dimensions and scale them to thumbnail size.
* @param {Object} dimensions Width and height of the image
* @param {string} [mediaType] Media type 'DRAWING' or 'BITMAP'
* @return {Object} The new width and height of the scaled image
*/
ve.dm.MWImageNode.static.scaleToThumbnailSize = function ( dimensions, mediaType ) {
var defaultThumbSize = mw.config.get( 'wgVisualEditorConfig' ).defaultUserOptions.defaultthumbsize;
mediaType = mediaType || 'BITMAP';
if ( dimensions.width && dimensions.height ) {
// Use dimensions
// Resize to default thumbnail size, but only if the image itself
// isn't smaller than the default size
// For svg/drawings, the default wiki size is always applied
if ( dimensions.width > defaultThumbSize || mediaType === 'DRAWING' ) {
return ve.dm.Scalable.static.getDimensionsFromValue( {
width: defaultThumbSize
}, dimensions.width / dimensions.height );
}
}
return dimensions;
};
/**
* Translate the image dimensions into new ones according to the bounding box.
* @param {Object} imageDimension Width and height of the image
* @param {Object} boundingBox The limit of the bounding box
* @returns {Object} The new width and height of the scaled image.
*/
ve.dm.MWImageNode.static.resizeToBoundingBox = function ( imageDimensions, boundingBox ) {
var newDimensions = ve.copy( imageDimensions ),
scale = Math.min(
boundingBox.height / imageDimensions.height,
boundingBox.width / imageDimensions.width
);
if ( scale < 1 ) {
// Scale down
newDimensions = {
width: Math.floor( newDimensions.width * scale ),
height: Math.floor( newDimensions.height * scale )
};
}
return newDimensions;
};
/**
* Update image scalable properties according to the image type.
*
* @param {string} type The new image type
* @param {string} mediaType Image media type 'DRAWING' or 'BITMAP'
* @param {ve.dm.Scalable} scalable The scalable object to update
*/
ve.dm.MWImageNode.static.syncScalableToType = function ( type, mediaType, scalable ) {
var originalDimensions, dimensions,
defaultThumbSize = mw.config.get( 'wgVisualEditorConfig' ).defaultUserOptions.defaultthumbsize;
originalDimensions = scalable.getOriginalDimensions();
// We can only set default dimensions if we have the original ones
if ( originalDimensions ) {
if ( type === 'thumb' || type === 'frameless' ) {
// Set the default size to that in the wiki configuration if
// 1. The original image width is not smaller than the default
// 2. If the image is an SVG drawing
if ( originalDimensions.width >= defaultThumbSize || mediaType === 'DRAWING' ) {
dimensions = ve.dm.Scalable.static.getDimensionsFromValue( {
width: defaultThumbSize
}, scalable.getRatio() );
} else {
dimensions = ve.dm.Scalable.static.getDimensionsFromValue(
originalDimensions,
scalable.getRatio()
);
}
scalable.setDefaultDimensions( dimensions );
} else {
scalable.setDefaultDimensions( originalDimensions );
}
}
// Deal with maximum dimensions for images and drawings
if ( mediaType !== 'DRAWING' ) {
if ( originalDimensions ) {
scalable.setMaxDimensions( originalDimensions );
scalable.setEnforcedMax( true );
} else {
scalable.setEnforcedMax( false );
}
}
// TODO: Some day, when svgMaxSize works properly in MediaWiki
// we can add it back as max dimension consideration.
};
/**
* Get the scalable promise which fetches original dimensions from the API
* @param {string} filename The image filename whose details the scalable will represent
* @returns {jQuery.Promise} Promise which resolves after the image size details are fetched from the API
*/
ve.dm.MWImageNode.static.getScalablePromise = function ( filename ) {
// On the first call set off an async call to update the scalable's
// original dimensions from the API.
if ( ve.init.platform.imageInfoCache ) {
return ve.init.platform.imageInfoCache.get( filename ).then( function ( info ) {
if ( !info ) {
return $.Deferred().reject().promise();
}
return info;
} );
} else {
return $.Deferred().reject().promise();
}
};
/* Methods */
/**
* Respond to attribute change.
* Update the rendering of the 'align', src', 'width' and 'height' attributes
* when they change in the model.
*
* @method
* @param {string} key Attribute key
* @param {string} from Old value
* @param {string} to New value
*/
ve.dm.MWImageNode.prototype.onAttributeChange = function ( key, from, to ) {
if ( key === 'type' ) {
this.constructor.static.syncScalableToType( to, this.mediaType, this.getScalable() );
}
};
/**
* Get the normalised filename of the image
*
* @returns {string} Filename
*/
ve.dm.MWImageNode.prototype.getFilename = function () {
// Strip ./ stuff and decode URI encoding
var resource = this.getAttribute( 'resource' ) || '',
filename = resource.replace( /^(\.+\/)*/, '' );
// Protect against decodeURIComponent() throwing exceptions
try {
filename = decodeURIComponent( filename );
} catch ( e ) {
ve.log( 'URI decoding exception', e );
}
return filename;
};
/**
* Get the store hash for the original dimensions of the image
*
* @returns {string} Store hash
*/
ve.dm.MWImageNode.prototype.getSizeHash = function () {
return 'MWImageOriginalSize:' + this.getFilename();
};
/**
* @inheritdoc
*/
ve.dm.MWImageNode.prototype.getScalable = function () {
var imageNode = this;
if ( !this.scalablePromise ) {
this.scalablePromise = ve.dm.MWImageNode.static.getScalablePromise( this.getFilename() );
// If the promise was already resolved before getScalablePromise returned, then jQuery will execute the done straight away.
// So don't just do getScalablePromise( ... ).done because we need to make sure that this.scalablePromise gets set first.
this.scalablePromise.done( function ( info ) {
if ( info ) {
imageNode.getScalable().setOriginalDimensions( {
width: info.width,
height: info.height
} );
// Update media type
imageNode.mediaType = info.mediatype;
// Update according to type
imageNode.constructor.static.syncScalableToType(
imageNode.getAttribute( 'type' ),
imageNode.mediaType,
imageNode.getScalable()
);
}
} );
}
// Parent method
return ve.dm.ResizableNode.prototype.getScalable.call( this );
};
/**
* @inheritdoc
*/
ve.dm.MWImageNode.prototype.createScalable = function () {
return new ve.dm.Scalable( {
currentDimensions: {
width: this.getAttribute( 'width' ),
height: this.getAttribute( 'height' )
},
minDimensions: {
width: 1,
height: 1
}
} );
};
/**
* Get symbolic name of media type.
*
* Example values: "BITMAP" for JPEG or PNG images; "DRAWING" for SVG graphics
*
* @return {string|undefined} Symbolic media type name, or undefined if empty
*/
ve.dm.MWImageNode.prototype.getMediaType = function () {
return this.mediaType;
};