mediawiki-extensions-Visual.../modules/ve-mw/dm/nodes/ve.dm.MWImageNode.js

283 lines
8.2 KiB
JavaScript
Raw Normal View History

/*!
* 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: newDimensions.width * scale,
height: 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;
};