Mooeypoo has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/119435

Change subject: [WIP^2] Refactor Scalable and MediaSizeWidget
......................................................................

[WIP^2] Refactor Scalable and MediaSizeWidget

Scalable is now a dm calculation engine, responsible for all the
size and constraint calculations of a scalable object. MediaSizeWidget
takes a scalable object to interact with the user and process the values
supplied.

**
This commit is up on gerrit for backup purposes since my VM is
unpredictable. The commit requests you not look at it. It's hedious.
Seriously. Move along. Nothing to see here just yet.
***

Change-Id: Ie7ca3bbbe5d0287c7ce17d296c1827b8213ea950
---
M modules/ve/ce/ve.ce.ResizableNode.js
A modules/ve/dm/ve.dm.ResizableNode.js
R modules/ve/dm/ve.dm.Scalable.js
A modules/ve/ui/widgets/ve.ui.DimensionsWidget.js
M modules/ve/ui/widgets/ve.ui.MediaSizeWidget.js
5 files changed, 505 insertions(+), 154 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/VisualEditor/VisualEditor 
refs/changes/35/119435/1

diff --git a/modules/ve/ce/ve.ce.ResizableNode.js 
b/modules/ve/ce/ve.ce.ResizableNode.js
index 89372e5..6c31ab1 100644
--- a/modules/ve/ce/ve.ce.ResizableNode.js
+++ b/modules/ve/ce/ve.ce.ResizableNode.js
@@ -23,9 +23,6 @@
 ve.ce.ResizableNode = function VeCeResizableNode( $resizable, config ) {
        config = config || {};
 
-       // Mixin constructors
-       ve.Scalable.call( this, config );
-
        // Properties
        this.$resizable = $resizable || this.$element;
        this.resizing = false;
@@ -68,15 +65,15 @@
                        .addClass( 've-ce-resizableNode-swHandle 
ve-ui-icon-resize-ne-sw' )
                        .data( 'handle', 'sw' ) );
 
-       this.setCurrentDimensions( {
-               'width': this.model.getAttribute( 'width' ),
-               'height': this.model.getAttribute( 'height' )
-       } );
+       // Scalable object ftw
+       this.scalable = this.model.getScalable();
+//     this.setCurrentDimensions( {
+//             'width': this.model.getAttribute( 'width' ),
+//             'height': this.model.getAttribute( 'height' )
+//     } );
 };
 
 /* Inheritance */
-
-OO.mixinClass( ve.ce.ResizableNode, ve.Scalable );
 
 /* Events */
 
@@ -115,12 +112,12 @@
 
 /** */
 ve.ce.ResizableNode.prototype.setOriginalDimensions = function ( dimensions ) {
-       // Parent method
-       ve.Scalable.prototype.setOriginalDimensions.call( this, dimensions );
+       this.scalable.setOriginalDimensions( dimensions );
+//     ve.Scalable.prototype.setOriginalDimensions.call( this, dimensions );
        // If dimensions are valid and the scale label is desired, enable it
        this.canShowScaleLabel = this.showScaleLabel &&
-               this.getOriginalDimensions().width &&
-               this.getOriginalDimensions().height;
+               this.scalable.getOriginalDimensions().width &&
+               this.scalable.getOriginalDimensions().height;
 };
 
 /**
diff --git a/modules/ve/dm/ve.dm.ResizableNode.js 
b/modules/ve/dm/ve.dm.ResizableNode.js
new file mode 100644
index 0000000..198ca53
--- /dev/null
+++ b/modules/ve/dm/ve.dm.ResizableNode.js
@@ -0,0 +1,36 @@
+/*!
+ * VisualEditor DataModel Resizable node.
+ *
+ * @copyright 2011-2014 VisualEditor Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+
+/**
+ * A mixin class for resizable nodes.
+ *
+ * @class
+ * @abstract
+ * @constructor
+ */
+ve.dm.ResizableNode = function VeDmResizableNode( config ) {
+       config = config || {};
+};
+
+/**
+ * Produce a scalable object based on the current object's
+ * properties. This should be overriden in the specific instances
+ * of resizable nodes; the basic operation assumes at least current
+ * width and height.
+ * @returns {ve.dm.Scalable} Scalable object
+ */
+ve.dm.ResizableNode.prototype.getScalable = function() {
+       var width = this.getAttribute( 'width' ),
+       height = this.getAttribute( 'height' );
+
+       return new ve.dm.Scalable( {
+               'currentDimensions': {
+                       'width': width,
+                       'height': height,
+               }
+       } );
+};
diff --git a/modules/ve/ve.Scalable.js b/modules/ve/dm/ve.dm.Scalable.js
similarity index 76%
rename from modules/ve/ve.Scalable.js
rename to modules/ve/dm/ve.dm.Scalable.js
index bc1bf23..e3fe77e 100644
--- a/modules/ve/ve.Scalable.js
+++ b/modules/ve/dm/ve.dm.Scalable.js
@@ -1,5 +1,5 @@
 /*!
- * VisualEditor Scalable class.
+ * VisualEditor DataModel Scalable class.
  *
  * @copyright 2011-2014 VisualEditor Team and others; see AUTHORS.txt
  * @license The MIT License (MIT); see LICENSE.txt
@@ -21,15 +21,21 @@
  * @cfg {boolean} [enforceMin=true] Enforce the minimum dimensions
  * @cfg {boolean} [enforceMax=true] Enforce the maximum dimensions
  */
-ve.Scalable = function VeScalable( config ) {
+ve.dm.Scalable = function VeDmScalable( config ) {
        config = ve.extendObject( {
                'fixedRatio': true
        }, config );
 
        // Properties
        this.fixedRatio = config.fixedRatio;
-       this.currentDimensions = config.currentDimensions || null;
-       this.originalDimensions = config.originalDimensions || null;
+       if ( config.originalDimensions ) {
+               this.setOriginalDimensions( config.originalDimensions );
+       }
+       if ( config.currentDimensions ) {
+               this.setCurrentDimensions( config.currentDimensions );
+       }
+
+
        this.minDimensions = config.minDimensions || null;
        this.maxDimensions = config.maxDimensions || null;
        this.enforceMin = config.enforceMin !== false;
@@ -45,7 +51,7 @@
 /**
  * Call setters with current values so computed values are updated
  */
-ve.Scalable.prototype.init = function () {
+ve.dm.Scalable.prototype.init = function () {
        if ( this.currentDimensions ) {
                this.setCurrentDimensions( this.currentDimensions );
        }
@@ -63,9 +69,9 @@
 /**
  * Set properties from another scalable object
  *
- * @param {ve.Scalable} scalable Scalable object
- */
-ve.Scalable.prototype.setPropertiesFromScalable = function ( scalable ) {
+ * @param {ve.dm.Scalable} scalable Scalable object
+ *
+ve.dm.Scalable.prototype.setPropertiesFromScalable = function ( scalable ) {
        // Properties
        this.fixedRatio = scalable.fixedRatio;
        this.currentDimensions = scalable.currentDimensions;
@@ -87,18 +93,18 @@
  *
  * @param {Object} dimensions Dimensions object with width & height
  */
-ve.Scalable.prototype.setRatioFromDimensions = function ( dimensions ) {
+ve.dm.Scalable.prototype.setRatioFromDimensions = function ( dimensions ) {
        if ( dimensions.width && dimensions.height ) {
                this.ratio = dimensions.width / dimensions.height;
        }
 };
 
 /**
- * Set the original dimensions of an image
+ * Set the current dimensions of an image
  *
  * @param {Object} dimensions Dimensions object with width & height
  */
-ve.Scalable.prototype.setCurrentDimensions = function ( dimensions ) {
+ve.dm.Scalable.prototype.setCurrentDimensions = function ( dimensions ) {
        this.currentDimensions = ve.copy( dimensions );
        // Only use current dimensions for ratio if it isn't set
        if ( this.fixedRatio && !this.ratio ) {
@@ -112,7 +118,7 @@
  *
  * @param {Object} dimensions Dimensions object with width & height
  */
-ve.Scalable.prototype.setOriginalDimensions = function ( dimensions ) {
+ve.dm.Scalable.prototype.setOriginalDimensions = function ( dimensions ) {
        this.originalDimensions = ve.copy( dimensions );
        // Always overwrite ratio
        if ( this.fixedRatio ) {
@@ -125,7 +131,7 @@
  *
  * @param {Object} dimensions Dimensions object with width & height
  */
-ve.Scalable.prototype.setMinDimensions = function ( dimensions ) {
+ve.dm.Scalable.prototype.setMinDimensions = function ( dimensions ) {
        this.minDimensions = ve.copy( dimensions );
        this.valid = null;
 };
@@ -135,7 +141,7 @@
  *
  * @param {Object} dimensions Dimensions object with width & height
  */
-ve.Scalable.prototype.setMaxDimensions = function ( dimensions ) {
+ve.dm.Scalable.prototype.setMaxDimensions = function ( dimensions ) {
        this.maxDimensions = ve.copy( dimensions );
        this.valid = null;
 };
@@ -145,7 +151,7 @@
  *
  * @returns {Object} Dimensions object with width & height
  */
-ve.Scalable.prototype.getCurrentDimensions = function () {
+ve.dm.Scalable.prototype.getCurrentDimensions = function () {
        return this.currentDimensions;
 };
 
@@ -154,7 +160,7 @@
  *
  * @returns {Object} Dimensions object with width & height
  */
-ve.Scalable.prototype.getOriginalDimensions = function () {
+ve.dm.Scalable.prototype.getOriginalDimensions = function () {
        return this.originalDimensions;
 };
 
@@ -163,7 +169,7 @@
  *
  * @returns {Object} Dimensions object with width & height
  */
-ve.Scalable.prototype.getMinDimensions = function () {
+ve.dm.Scalable.prototype.getMinDimensions = function () {
        return this.minDimensions;
 };
 
@@ -172,7 +178,7 @@
  *
  * @returns {Object} Dimensions object with width & height
  */
-ve.Scalable.prototype.getMaxDimensions = function () {
+ve.dm.Scalable.prototype.getMaxDimensions = function () {
        return this.maxDimensions;
 };
 
@@ -181,7 +187,7 @@
  *
  * @returns {boolean} Enforces the minimum dimensions
  */
-ve.Scalable.prototype.isEnforcedMin = function () {
+ve.dm.Scalable.prototype.isEnforcedMin = function () {
        return this.enforceMin;
 };
 
@@ -190,7 +196,7 @@
  *
  * @returns {boolean} Enforces the maximum dimensions
  */
-ve.Scalable.prototype.isEnforcedMax = function () {
+ve.dm.Scalable.prototype.isEnforcedMax = function () {
        return this.enforceMax;
 };
 
@@ -199,7 +205,7 @@
  *
  * @param {boolean} enforceMin Enforces the minimum dimensions
  */
-ve.Scalable.prototype.setEnforcedMin = function ( enforceMin ) {
+ve.dm.Scalable.prototype.setEnforcedMin = function ( enforceMin ) {
        this.valid = null;
        this.enforceMin = enforceMin;
 };
@@ -209,7 +215,7 @@
  *
  * @param {boolean} enforceMax Enforces the maximum dimensions
  */
-ve.Scalable.prototype.setEnforcedMax = function ( enforceMax ) {
+ve.dm.Scalable.prototype.setEnforcedMax = function ( enforceMax ) {
        this.valid = null;
        this.enforceMax = enforceMax;
 };
@@ -219,7 +225,7 @@
  *
  * @returns {number} Aspect ratio
  */
-ve.Scalable.prototype.getRatio = function () {
+ve.dm.Scalable.prototype.getRatio = function () {
        return this.ratio;
 };
 
@@ -228,7 +234,7 @@
  *
  * @returns {boolean} The object has a fixed ratio
  */
-ve.Scalable.prototype.isFixedRatio = function () {
+ve.dm.Scalable.prototype.isFixedRatio = function () {
        return this.fixedRatio;
 };
 
@@ -237,7 +243,7 @@
  *
  * @returns {number|null} A scale (1=100%), or null if not applicable
  */
-ve.Scalable.prototype.getCurrentScale = function () {
+ve.dm.Scalable.prototype.getCurrentScale = function () {
        if ( !this.isFixedRatio() || !this.getCurrentDimensions() || 
!this.getOriginalDimensions() ) {
                return null;
        }
@@ -251,7 +257,7 @@
  *
  * @returns {boolean} Current dimensions are greater than maximum dimensions
  */
-ve.Scalable.prototype.isTooSmall = function () {
+ve.dm.Scalable.prototype.isTooSmall = function () {
        return !!( this.getCurrentDimensions() && this.getMinDimensions() && (
                        this.getCurrentDimensions().width < 
this.getMinDimensions().width ||
                        this.getCurrentDimensions().height < 
this.getMinDimensions().height
@@ -265,11 +271,34 @@
  *
  * @returns {boolean} Current dimensions are greater than maximum dimensions
  */
-ve.Scalable.prototype.isTooLarge = function () {
+ve.dm.Scalable.prototype.isTooLarge = function () {
        return !!( this.getCurrentDimensions() && this.getMaxDimensions() && (
                        this.getCurrentDimensions().width > 
this.getMaxDimensions().width ||
                        this.getCurrentDimensions().height > 
this.getMaxDimensions().height
                ) );
+};
+
+/**
+ * Calculate the dimensions from a given value of either width or height.
+ * This method doesn't take into account any restrictions of minimum or 
maximum,
+ * it simply calculates the new dimensions according to the aspect ratio in 
case
+ * it exists.
+ *
+ * @param {Object} dimensions Dimensions object with either width or height
+ *     if both are given, the object will be returned as-is.
+ * @returns {Object} Dimensions object with width and height
+ */
+ve.dm.Scalable.prototype.getDimensionsFromValue = function ( dimensions ) {
+       dimensions = dimensions || {};
+
+       if ( !dimensions.height && this.getRatio() !== null && $.isNumeric( 
dimensions.width ) ) {
+               dimensions.height = Math.round( dimensions.width / 
this.getRatio() );
+       }
+       if ( !dimensions.width && this.getRatio() !== null && $.isNumeric( 
dimensions.height ) ) {
+               dimensions.width = Math.round( dimensions.height * 
this.getRatio() );
+       }
+
+       return dimensions;
 };
 
 /**
@@ -279,7 +308,7 @@
  * @param {number} [grid] Optional grid size to snap to
  * @returns {Object} Dimensions object with width & height
  */
-ve.Scalable.prototype.getBoundedDimensions = function ( dimensions, grid ) {
+ve.dm.Scalable.prototype.getBoundedDimensions = function ( dimensions, grid ) {
        var ratio, snap, snapMin, snapMax,
                minDimensions = this.isEnforcedMin() && this.getMinDimensions(),
                maxDimensions = this.isEnforcedMax() && this.getMaxDimensions();
@@ -332,7 +361,7 @@
  *
  * @returns {boolean} Current dimensions are valid
  */
-ve.Scalable.prototype.isCurrentDimensionsValid = function () {
+ve.dm.Scalable.prototype.isCurrentDimensionsValid = function () {
        if ( this.valid === null ) {
                var dimensions = this.getCurrentDimensions(),
                        minDimensions = this.isEnforcedMin() && 
this.getMinDimensions(),
diff --git a/modules/ve/ui/widgets/ve.ui.DimensionsWidget.js 
b/modules/ve/ui/widgets/ve.ui.DimensionsWidget.js
new file mode 100644
index 0000000..5c01e7d
--- /dev/null
+++ b/modules/ve/ui/widgets/ve.ui.DimensionsWidget.js
@@ -0,0 +1,154 @@
+/*!
+ * VisualEditor UserInterface DimensionsWidget class.
+ *
+ * @copyright 2011-2014 VisualEditor Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+
+/**
+ * Widget that visually displays width and height inputs.
+ * This widget is for presentation-only, no calculation is done.
+ *
+ * @class
+ * @extends OO.ui.Widget
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+ve.ui.DimensionsWidget = function VeUiDimensionsWidget( config ) {
+       var labelTimes, labelPx;
+
+       // Configuration
+       config = config || {};
+
+       this.placeholders = config.placeholders || {};
+
+       // Parent constructor
+       OO.ui.Widget.call( this, config );
+
+       this.widthInput = new OO.ui.TextInputWidget( {
+               '$': this.$
+       } );
+       this.heightInput = new OO.ui.TextInputWidget( {
+               '$': this.$
+       } );
+
+       labelTimes = new OO.ui.LabelWidget( {}, {
+               '$': this.$,
+               'label': ve.msg( 'visualeditor-dimensionsWidget-times' )
+       } );
+       labelPx = new OO.ui.LabelWidget( {}, {
+               '$': this.$,
+               'label': ve.msg( 'visualeditor-dimensionsWidget-px' )
+       } );
+
+       // Events
+       this.widthInput.connect( this, { 'change': 'onWidthChange' } );
+       this.heightInput.connect( this, { 'change': 'onHeightChange' } );
+
+       // Setup
+
+       this.$element
+               .addClass( 've-ui-dimensionsWidget' )
+               .append( [
+                       this.widthInput.$element,
+                       labelTimes.$element
+                               .addClass( 've-ui-dimensionsWidget-label-times' 
),
+                       this.heightInput.$element,
+                       labelPx.$element
+                               .addClass( 've-ui-dimensionsWidget-label-px' )
+               ] );
+};
+
+/* Inheritance */
+
+OO.inheritClass( ve.ui.DimensionsWidget, OO.ui.Widget );
+
+/* Events */
+
+/**
+ * @event widthChange
+ * @event heightChange
+ */
+
+/* Methods */
+
+/**
+ * Set placeholder dimension values
+ * @param {Object} dimensions Height and width placeholders
+ */
+ve.ui.DimensionsWidget.prototype.setPlaceholders = function ( dimensions ) {
+       if ( dimensions && dimensions.width && dimensions.height ) {
+               this.placeholders = dimensions;
+               this.widthInput.$input.attr( 'placeholder', 
this.placeholders.width );
+               this.heightInput.$input.attr( 'placeholder', 
this.placeholders.height );
+       }
+};
+
+/**
+ * Get the current placeholder values
+ * @returns {Object} Placeholder dimensions height and width values
+ */
+ve.ui.DimensionsWidget.prototype.getPlaceholders = function () {
+       return this.placeholders;
+};
+
+/**
+ * Check whether the widget is empty. This indicates that placeholders,
+ * if they exist, are shown.
+ * @returns {boolean} Both values are empty
+ */
+ve.ui.DimensionsWidget.prototype.isEmpty = function () {
+       return (
+               this.widthInput.getValue() === '' &&
+               this.heightInput.getValue() === ''
+       );l
+};
+
+/**
+ * Respond to width change, propogate the input change event
+ * @emit widthChange
+ */
+ve.ui.DimensionsWidget.prototype.onWidthChange = function () {
+       this.emit( 'widthChange', this.widthInput.getValue() );
+};
+
+/**
+ * Respond to height change, propogate the input change event
+ * @emit heightChange
+ */
+ve.ui.DimensionsWidget.prototype.onHeightChange = function () {
+       this.emit( 'heightChange', this.heightInput.getValue() );
+};
+
+/**
+ * Get the current value in the width input
+ * @returns {String} Input value
+ */
+ve.ui.DimensionsWidget.prototype.getWidth = function () {
+       return this.widthInput.getValue();
+};
+
+/**
+ * Get the current value in the height input
+ * @returns {String} Input value
+ */
+ve.ui.DimensionsWidget.prototype.getHeight = function () {
+       return this.heightInput.getValue();
+};
+
+/**
+ * Set a value for the width input
+ * @param {String} value
+ */
+ve.ui.DimensionsWidget.prototype.setWidth = function ( value ) {
+       this.widthInput.setValue( value );
+};
+
+/**
+ * Set a value for the height input
+ * @param {String} value
+ */
+ve.ui.DimensionsWidget.prototype.setHeight = function ( value ) {
+       this.heightInput.setValue( value );
+};
diff --git a/modules/ve/ui/widgets/ve.ui.MediaSizeWidget.js 
b/modules/ve/ui/widgets/ve.ui.MediaSizeWidget.js
index a282f17..8fee2a1 100644
--- a/modules/ve/ui/widgets/ve.ui.MediaSizeWidget.js
+++ b/modules/ve/ui/widgets/ve.ui.MediaSizeWidget.js
@@ -7,103 +7,118 @@
 
 /**
  * Widget that lets the user edit dimensions (width and height),
- * optionally with a fixed aspect ratio.
- *
- * The widget is designed to work in one of two ways:
- * 1. Instantiated with size configuration already set up
- * 2. Instantiated empty, and size details added when the
- *    data is available.
+ * based on a scalable object.
  *
  * @class
  * @extends OO.ui.Widget
- * @mixins ve.Scalable
  *
  * @constructor
+ * @param {ve.dm.Scalable} scalable A scalable object
  * @param {Object} [config] Configuration options
  */
-ve.ui.MediaSizeWidget = function VeUiMediaSizeWidget( config ) {
-       var heightLabel, widthLabel;
+ve.ui.MediaSizeWidget = function VeUiMediaSizeWidget( scalable, config ) {
+       var fieldScale, fieldCustom, scalePercentLabel;
 
        // Configuration
        config = config || {};
 
-       this.showOriginalDimensionsButton = 
!!config.showOriginalDimensionsButton;
+       this.scalable = scalable || {};
+
        // Parent constructor
        OO.ui.Widget.call( this, config );
 
-       // Mixin constructors
-       ve.Scalable.call( this, config );
+       // Properties
+       this.ratio = {};
+       this.currentDimensions = {};
+       this.maxDimensions = {};
+       this.placeholders = {};
 
-       // Define dimension input widgets
-       this.widthInput = new OO.ui.TextInputWidget( {
+       // Define button select widget
+       this.sizeTypeSelectWidget = new OO.ui.ButtonSelectWidget( {
                '$': this.$
        } );
-       this.heightInput = new OO.ui.TextInputWidget( {
+       this.sizeTypeSelectWidget.addItems( [
+               new OO.ui.ButtonOptionWidget( 'default', {
+                       '$': this.$,
+                       'label': ve.msg( 
'visualeditor-mediasizewidget-sizeoptions-default' )
+               } ),
+               new OO.ui.ButtonOptionWidget( 'scale', {
+                       '$': this.$,
+                       'label': ve.msg( 
'visualeditor-mediasizewidget-sizeoptions-scale' )
+               } ),
+               new OO.ui.ButtonOptionWidget( 'custom', {
+                       '$': this.$,
+                       'label': ve.msg( 
'visualeditor-mediasizewidget-sizeoptions-custom' )
+               } )
+       ] );
+
+       // Define scale
+       this.scaleInput = new OO.ui.TextInputWidget( {
+               '$': this.$
+       } );
+       // TODO: Put the percent label after the scale input
+       scalePercentLabel = new OO.ui.LabelWidget( {
+               '$': this.$,
+               'input': this.scaleInput,
+               'label': ve.msg( 
'visualeditor-mediasizewidget-label-scale-percent' )
+       } );
+
+       this.dimensionsWidget = new ve.ui.DimensionsWidget( {
                '$': this.$
        } );
 
-       // Define dimension labels
-       widthLabel = new OO.ui.LabelWidget( {
-               '$': this.$,
-               'input': this.widthInput,
-               'label': ve.msg( 'visualeditor-mediasizewidget-label-width' )
-       } );
-       heightLabel = new OO.ui.LabelWidget( {
-               '$': this.$,
-               'input': this.heightInput,
-               'label': ve.msg( 'visualeditor-mediasizewidget-label-height' )
-       } );
-       // Error label
+       // Error label is available globally so it can be displayed and
+       // hidden as needed
        this.errorLabel = new OO.ui.LabelWidget( {
                '$': this.$,
                'label': ve.msg( 
'visualeditor-mediasizewidget-label-defaulterror' )
        } );
 
-       // Define buttons
-       this.originalDimensionsButton = new OO.ui.ButtonWidget( {
+       // Field layouts
+       fieldScale = new OO.ui.FieldLayout(
+               this.scaleInput, {
                '$': this.$,
-               'label': ve.msg( 
'visualeditor-mediasizewidget-button-originaldimensions' )
+               'align': 'inline',
+               'label': ve.msg( 'visualeditor-mediasizewidget-label-scale' )
+       } );
+       fieldCustom = new OO.ui.FieldLayout(
+               this.dimensionsWidget, {
+               '$': this.$,
+               'align': 'inline',
+               'label': ve.msg( 'visualeditor-mediasizewidget-label-custom' )
        } );
 
-       // Build the GUI
-       this.$element.append( [
-               this.$( '<div>' )
-                       .addClass( 've-ui-mediaSizeWidget-section-width' )
-                       .append( [
-                               widthLabel.$element,
-                               this.widthInput.$element
-                       ] ),
-               this.$( '<div>' )
-                       .addClass( 've-ui-mediaSizeWidget-section-height' )
-                       .append( [
-                               heightLabel.$element,
-                               this.heightInput.$element
-                       ] )
-       ] );
-       // Optionally append the original size button
-       if ( this.showOriginalDimensionsButton ) {
-               this.$element.append(
+       // Buttons
+       this.fullSizeButton = new OO.ui.ButtonWidget( {
+               '$': this.$,
+               'label': ve.msg( 'visualeditor-mediasizewidget-button-fullsize' 
),
+       } );
+
+       // Build GUI
+       this.$element
+               .addClass( 've-ui-mediaSizeWidget' )
+               .append( [
+                       this.$sizeSelectWidget.$element
+                               .addClass( 
've-ui-mediaSizeWidget-section-sizeSelectwidget' ),
+                       fieldScale.$element
+                               .addClass( 
've-ui-mediaSizeWidget-section--scale' ),
+                       fieldCustom.$element
+                               .addClass( 
've-ui-mediaSizeWidget-section-custom' ),
+                       this.fullSizeButton.$element
+                               .addClass( 
've-ui-mediaSizeWidget-button-fullsize' ),
                        this.$( '<div>' )
-                               .addClass( 
've-ui-mediaSizeWidget-button-originalSize' )
-                               .append( this.originalDimensionsButton.$element 
)
-               );
-               this.originalDimensionsButton.setDisabled( true );
-               // Events
-               this.originalDimensionsButton.connect( this, { 'click': 
'onButtonOriginalDimensionsClick' } );
-       }
+                               .addClass( 've-ui-mediaSizeWidget-label-error' )
+                               .append( this.errorLabel.$element )
+               ] );
 
-       // Append error message
-       this.$element.append(
-               this.$( '<div>' )
-                       .addClass( 've-ui-mediaSizeWidget-label-error' )
-                       .append( this.errorLabel.$element )
-       );
-
-       this.widthInput.connect( this, { 'change': 'onWidthChange' } );
-       this.heightInput.connect( this, { 'change': 'onHeightChange' } );
+       // Events
+       this.dimensionsWidget.connect( this, { 'changeWidth': 'onWidthChange' } 
);
+       this.dimensionsWidget.connect( this, { 'changeHeight': 'onHeightChange' 
} );
+       this.scaleInput.connect( this, { 'change': 'onScaleChange' } );
+       this.sizeTypeSelectWidget.connect( this, { 'select': 'onSizeTypeSelect' 
} );
+       this.fullSizeButton.connect( this, { 'click': 'onFullSizeButtonClick' } 
);
 
        // Initialization
-       this.$element.addClass( 've-ui-mediaSizeWidget' );
        if ( config.originalDimensions ) {
                this.setOriginalDimensions( config.originalDimensions );
        }
@@ -116,8 +131,6 @@
 
 OO.inheritClass( ve.ui.MediaSizeWidget, OO.ui.Widget );
 
-OO.mixinClass( ve.ui.MediaSizeWidget, ve.Scalable );
-
 /* Events */
 
 /**
@@ -127,24 +140,127 @@
 /* Methods */
 
 /**
+ * Respond to width input value change. Only update dimensions if
+ * the value is numeric. Invoke validation for every change.
+ * @param {String} width New width
+ */
+ve.ui.MediaSizeWidget.prototype.onWidthChange = function ( width ) {
+       if ( $.isNumeric( width ) ) {
+               this.setCurrentDimensions( {
+                       'width': width
+               } );
+       }
+       this.validateDimensions();
+};
+
+/**
+ * Respond to height input value change. Only update dimensions if
+ * the value is numeric. Invoke validation for every change.
+ * @param {String} width New width
+ */
+ve.ui.MediaSizeWidget.prototype.onHeightChange = function ( height ) {
+       if ( $.isNumeric( height ) ) {
+               this.setCurrentDimensions( {
+                       'height': height
+               } );
+       }
+       this.validateDimensions();
+};
+
+ve.ui.MediaSizeWidget.prototype.onScaleChange = function ( scale ) {
+};
+
+ve.ui.MediaSizeWidget.prototype.setRatio = function ( ratio ) {
+       this.scalable.setRatio( ratio );
+};
+
+ve.ui.MediaSizeWidget.prototype.getRatio = function ( ratio ) {
+       return this.scalable.getRatio();
+};
+
+ve.ui.MediaSizeWidget.prototype.setMaxDimensions = function ( dimensions ) {
+       var maxDimensions = this.scalable.getDimensionsFromValue( dimensions );
+       this.scalable.setMaxDimensions( maxDimensions );
+};
+ve.ui.MediaSizeWidget.prototype.getMaxDimensions = function ( dimensions ) {
+       // Normalize dimensions
+       return this.scalable.getMaxDimensions();
+};
+
+/**
+ * Updates the current dimensions in the inputs, either one at a time or both
+ *
+ * @fires change
+ */
+ve.ui.MediaSizeWidget.prototype.setCurrentDimensions = function ( 
givenDimensions ) {
+       // Recursion protection
+       if ( this.preventChangeRecursion ) {
+               return;
+       }
+       this.preventChangeRecursion = true;
+
+       // Normalize the new dimensions
+       this.currentDimensions = this.scalable.getDimensionsFromValue( 
givenDimensions );
+
+       if (
+               // If placeholders are set and dimensions are 0x0, erase input 
values
+               // so placeholders are visible
+               this.getPlaceholderDimensions() &&
+               ( this.currentDimensions.height === 0 || 
this.currentDimensions.width === 0 )
+       ) {
+               // Use placeholders
+               this.widthInput.setValue( '' );
+               this.heightInput.setValue( '' );
+       } else {
+               // This will only update if the value has changed
+               this.widthInput.setValue( this.currentDimensions.width );
+               this.heightInput.setValue( this.currentDimensions.height );
+       }
+
+       // Update scalable object
+       this.scalable.setCurrentDimensions( this.currentDimensions );
+
+       this.validateDimensions();
+
+       // Emit change event
+       this.emit( 'change' );
+       this.preventChangeRecursion = false;
+};
+
+/**
+ * Validate current dimensions.
+ * Explicitly call for validating the current dimensions. This is especially
+ * useful if we've changed conditions for the widget, like limiting image
+ * dimensions for thumbnails when the image type changes. Triggers the error
+ * class if needed.
+ *
+ * @returns {boolean} Current dimensions are valid
+ */
+ve.ui.MediaSizeWidget.prototype.validateDimensions = function () {
+       var isValid = this.isValid();
+       this.errorLabel.$element.toggle( !isValid );
+       this.$element.toggleClass( 've-ui-mediaSizeWidget-input-hasError', 
!isValid );
+
+       return isValid;
+};
+
+/**
  * Set placeholder dimensions in case the widget is empty or set to 0 values
  * @param {Object} dimensions Height and width placeholders
  */
 ve.ui.MediaSizeWidget.prototype.setPlaceholderDimensions = function ( 
dimensions ) {
-       dimensions = dimensions || {};
-
-       if ( !dimensions.height && this.getRatio() !== null && $.isNumeric( 
dimensions.width ) ) {
-               dimensions.height = Math.round( dimensions.width / 
this.getRatio() );
-       }
-       if ( !dimensions.width && this.getRatio() !== null && $.isNumeric( 
dimensions.height ) ) {
-               dimensions.width = Math.round( dimensions.height * 
this.getRatio() );
-       }
-
-       this.placeholders = dimensions;
+       this.placeholders = this.scalable.getDimensionsFromValue( dimensions );
 
        // Set the inputs' placeholders
        this.widthInput.$input.attr( 'placeholder', this.placeholders.width );
        this.heightInput.$input.attr( 'placeholder', this.placeholders.height );
+};
+
+/**
+ * Remove the placeholders.
+ */
+ve.ui.MediaSizeWidget.prototype.removePlaceholderDimensions = function () {
+       this.placeholders = {};
 };
 
 /**
@@ -154,6 +270,59 @@
 ve.ui.MediaSizeWidget.prototype.getPlaceholderDimensions = function () {
        return this.placeholders;
 };
+
+/**
+ * Check whether the current value inputs are valid
+ * 1. If placeholders are visible, the input is valid
+ * 2. If inputs have non numeric values, input is invalid
+ * 3. If inputs have numeric values, validate through scalable
+ *    calculations to see if the dimensions follow the rules.
+ * @returns {Boolean} [description]
+ */
+ve.ui.MediaSizeWidget.prototype.isValid = function () {
+       if (
+               this.placeholders &&
+               this.heightInput.getValue() === '' &&
+               this.widthInput.getValue() === ''
+       ) {
+               return true;
+       } else if (
+               $.isNumeric( this.widthInput.getValue() ) &&
+               $.isNumeric( this.heightInput.getValue() )
+       ) {
+               return this.scalable.isCurrentDimensionsValid();
+       } else {
+               return false;
+       }
+};
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
 
 /**
  * Check if both inputs are empty, so to use their placeholders
@@ -168,7 +337,7 @@
  * at a time, write values back to inputs and show any errors.
  *
  * @fires change
- */
+ *
 ve.ui.MediaSizeWidget.prototype.setCurrentDimensions = function ( dimensions ) 
{
        // Recursion protection
        if ( this.preventChangeRecursion ) {
@@ -208,22 +377,6 @@
        this.preventChangeRecursion = false;
 };
 
-/**
- * Validate current dimensions.
- * Explicitly call for validating the current dimensions. This is especially
- * useful if we've changed conditions for the widget, like limiting image
- * dimensions for thumbnails when the image type changes. Triggers the error
- * class if needed.
- *
- * @returns {boolean} Current dimensions are valid
- */
-ve.ui.MediaSizeWidget.prototype.validateDimensions = function () {
-       var isValid = this.isCurrentDimensionsValid();
-       this.errorLabel.$element.toggle( !isValid );
-       this.$element.toggleClass( 've-ui-mediaSizeWidget-input-hasError', 
!isValid );
-
-       return isValid;
-};
 
 /** */
 ve.ui.MediaSizeWidget.prototype.setOriginalDimensions = function ( dimensions 
) {
@@ -259,22 +412,4 @@
  */
 ve.ui.MediaSizeWidget.prototype.onButtonOriginalDimensionsClick = function () {
        this.setCurrentDimensions( this.getOriginalDimensions() );
-};
-
-/**
- * Expand on Scalable's method of checking for valid dimensions. Allow for
- * empty dimensions if the placeholders are set.
- * @returns {boolean}
- */
-ve.ui.MediaSizeWidget.prototype.isCurrentDimensionsValid = function () {
-       if (
-               this.placeholders &&
-               this.heightInput.getValue() === '' &&
-               this.widthInput.getValue() === ''
-       ) {
-               return true;
-       } else {
-               // Parent method
-               return ve.Scalable.prototype.isCurrentDimensionsValid.call( 
this );
-       }
 };

-- 
To view, visit https://gerrit.wikimedia.org/r/119435
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ie7ca3bbbe5d0287c7ce17d296c1827b8213ea950
Gerrit-PatchSet: 1
Gerrit-Project: VisualEditor/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Mooeypoo <mor...@gmail.com>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to