Robmoen has uploaded a new change for review. https://gerrit.wikimedia.org/r/74082
Change subject: WIP: Add size widget to media edit dialog ...................................................................... WIP: Add size widget to media edit dialog TODO: Documentation! Change-Id: Id4e0953ce7de991f94ffefa39f311fbcdd283d29 --- M VisualEditor.php M modules/ve-mw/dm/nodes/ve.dm.MWBlockImageNode.js M modules/ve-mw/dm/nodes/ve.dm.MWInlineImageNode.js M modules/ve-mw/ui/dialogs/ve.ui.MWMediaEditDialog.js M modules/ve/ui/styles/ve.ui.Widget.css M modules/ve/ui/widgets/ve.ui.InputWidget.js A modules/ve/ui/widgets/ve.ui.MediaSizeWidget.js A modules/ve/ui/widgets/ve.ui.NumberInputWidget.js 8 files changed, 283 insertions(+), 35 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/VisualEditor refs/changes/82/74082/1 diff --git a/VisualEditor.php b/VisualEditor.php index 4edcb4c..3a85d8c 100644 --- a/VisualEditor.php +++ b/VisualEditor.php @@ -477,6 +477,8 @@ 've/ui/widgets/ve.ui.MenuWidget.js', 've/ui/widgets/ve.ui.LookupInputWidget.js', 've/ui/widgets/ve.ui.TextInputMenuWidget.js', + 've/ui/widgets/ve.ui.NumberInputWidget.js', + 've/ui/widgets/ve.ui.MediaSizeWidget.js', 've/ui/widgets/ve.ui.LinkTargetInputWidget.js', 've-mw/ui/widgets/ve.ui.MWLinkTargetInputWidget.js', 've-mw/ui/widgets/ve.ui.MWCategoryInputWidget.js', diff --git a/modules/ve-mw/dm/nodes/ve.dm.MWBlockImageNode.js b/modules/ve-mw/dm/nodes/ve.dm.MWBlockImageNode.js index 3bd67f5..8aff454 100644 --- a/modules/ve-mw/dm/nodes/ve.dm.MWBlockImageNode.js +++ b/modules/ve-mw/dm/nodes/ve.dm.MWBlockImageNode.js @@ -57,6 +57,8 @@ width: $img.attr( 'width' ), height: $img.attr( 'height' ), resource: $img.attr( 'resource' ), + originalWidth: $img.attr( 'width' ), + originalHeight: $img.attr( 'height' ), originalClasses: classes }; diff --git a/modules/ve-mw/dm/nodes/ve.dm.MWInlineImageNode.js b/modules/ve-mw/dm/nodes/ve.dm.MWInlineImageNode.js index 923f52e..1d179d7 100644 --- a/modules/ve-mw/dm/nodes/ve.dm.MWInlineImageNode.js +++ b/modules/ve-mw/dm/nodes/ve.dm.MWInlineImageNode.js @@ -56,6 +56,8 @@ attributes.width = width !== undefined && width !== '' ? Number( width ) : null; attributes.height = height !== undefined && height !== '' ? Number( height ) : null; + attributes.originalWidth = attributes.width; + attributes.originalHeight = attributes.height; attributes.isLinked = $firstChild.is( 'a' ); if ( attributes.isLinked ) { diff --git a/modules/ve-mw/ui/dialogs/ve.ui.MWMediaEditDialog.js b/modules/ve-mw/ui/dialogs/ve.ui.MWMediaEditDialog.js index 3cb3d63..fe38fff 100644 --- a/modules/ve-mw/ui/dialogs/ve.ui.MWMediaEditDialog.js +++ b/modules/ve-mw/ui/dialogs/ve.ui.MWMediaEditDialog.js @@ -20,6 +20,7 @@ ve.ui.MWDialog.call( this, surface, config ); // Properties + this.node = null; this.captionNode = null; }; @@ -48,7 +49,17 @@ // Parent method ve.ui.MWDialog.prototype.initialize.call( this ); - // Properties + // Media size + this.sizeFieldset = new ve.ui.FieldsetLayout( { + '$$': this.frame.$$, + //'label': ve.msg( 'visualeditor-dialog-media-size-content-section' ), + 'label': 'Size', + 'icon': 'parameter' + } ); + + this.mediaSizeWidget = new ve.ui.MediaSizeWidget( { '$$': this.frame.$$ } ); + this.sizeFieldset.$.append( this.mediaSizeWidget.$ ); + this.contentFieldset = new ve.ui.FieldsetLayout( { '$$': this.frame.$$, 'label': ve.msg( 'visualeditor-dialog-media-content-section' ), @@ -57,17 +68,32 @@ // Initialization this.$body.addClass( 've-ui-mwMediaEditDialog-body' ); - this.$body.append( this.contentFieldset.$ ); + this.$body.append( this.sizeFieldset.$, this.contentFieldset.$ ); }; ve.ui.MWMediaEditDialog.prototype.onOpen = function () { - var data, doc = this.surface.getModel().getDocument(); + var data, + doc = this.surface.getModel().getDocument(), + node = this.surface.getView().getFocusedNode(), + model = node.getModel(); // Parent method ve.ui.MWDialog.prototype.onOpen.call( this ); + // Set node + this.node = node; // Get caption content - this.captionNode = this.surface.getView().getFocusedNode().getModel().getCaptionNode(); + this.captionNode = model.getCaptionNode(); + + // Init size widget + this.mediaSizeWidget.initialize( { + 'originalHeight': model.getAttribute( 'originalHeight' ), + 'originalWidth': model.getAttribute( 'originalWidth' ), + 'height': model.getAttribute( 'height' ), + 'width': model.getAttribute( 'width' ) + } ); + + // Init caption if ( this.captionNode && this.captionNode.getLength() > 0 ) { data = doc.getData( this.captionNode.getRange(), true ); } else { @@ -91,14 +117,28 @@ }; ve.ui.MWMediaEditDialog.prototype.onClose = function ( action ) { - var data, doc, surfaceModel = this.surface.getModel(); + var data, + surfaceModel = this.surface.getModel(), + doc = surfaceModel.getDocument(), + dimensions = {}; // Parent method ve.ui.MWDialog.prototype.onClose.call( this ); if ( action === 'apply' ) { + // Did the image size change + if ( this.mediaSizeWidget.changed ) { + dimensions = this.mediaSizeWidget.getDimensions(); + // Change size. + surfaceModel.change( + ve.dm.Transaction.newFromAttributeChanges( + doc, this.node.getModel().getOffset(), dimensions ), surfaceModel.getSelection() + ); + // Is this needed? + this.surface.getView().getFocusedNode().emit( 'resize' ); + } + // Get caption data data = this.captionSurface.getModel().getDocument().getData(); - doc = surfaceModel.getDocument(); if ( this.captionNode ) { // Replace the contents of the caption surfaceModel.getFragment( this.captionNode.getRange(), true ).insertContent( data ); diff --git a/modules/ve/ui/styles/ve.ui.Widget.css b/modules/ve/ui/styles/ve.ui.Widget.css index d41ddae..22d044c 100644 --- a/modules/ve/ui/styles/ve.ui.Widget.css +++ b/modules/ve/ui/styles/ve.ui.Widget.css @@ -284,22 +284,14 @@ padding: 0.5em 0; } -/* ve.ui.TextInputWidget */ +/* ve-ui-inputWidget */ -.ve-ui-textInputWidget { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - width: 20em; - position: relative; -} - -.ve-ui-textInputWidget input, -.ve-ui-textInputWidget input:focus[readonly], -.ve-ui-widget-disabled.ve-ui-textInputWidget input:focus, -.ve-ui-textInputWidget textarea, -.ve-ui-textInputWidget textarea:focus[readonly], -.ve-ui-widget-disabled.ve-ui-textInputWidget textarea:focus { +.ve-ui-inputWidget input, +.ve-ui-inputWidget input:focus[readonly], +.ve-ui-widget-disabled.ve-ui-inputWidget input:focus, +.ve-ui-inputWidget textarea, +.ve-ui-inputWidget textarea:focus[readonly], +.ve-ui-widget-disabled.ve-ui-inputWidget textarea:focus { display: inline-block; font-size: 1em; font-family: sans-serif; @@ -322,31 +314,38 @@ transition: border-color 200ms, box-shadow 200ms, background-color 200ms; } -.ve-ui-textInputWidget-pending input, -.ve-ui-textInputWidget-pending textarea { - background-color: transparent; -} - -.ve-ui-textInputWidget input:focus, -.ve-ui-textInputWidget textarea:focus { +.ve-ui-inputWidget input:focus, +.ve-ui-inputWidget textarea:focus { outline: none; border-color: #a7dcff; box-shadow: 0 0 0.3em #a7dcff, 0 0 0 white; background-color: #fff; } -.ve-ui-textInputWidget input[readonly], -.ve-ui-textInputWidget textarea[readonly] { +.ve-ui-inputWidget input[readonly], +.ve-ui-inputWidget textarea[readonly] { color: #777; text-shadow: 0 1px 1px #fff; } -.ve-ui-widget-disabled.ve-ui-textInputWidget input, -.ve-ui-widget-disabled.ve-ui-textInputWidget input:focus, -.ve-ui-widget-disabled.ve-ui-textInputWidget textarea, -.ve-ui-widget-disabled.ve-ui-textInputWidget textarea:focus { +.ve-ui-widget-disabled.ve-ui-inputWidget input, +.ve-ui-widget-disabled.ve-ui-inputWidget input:focus, +.ve-ui-widget-disabled.ve-ui-inputWidget textarea, +.ve-ui-widget-disabled.ve-ui-inputWidget textarea:focus { color: #ccc; text-shadow: 0 1px 1px #fff; +} + +/* ve.ui.TextInputWidget */ + +.ve-ui-textInputWidget { + position: relative; + width: 20em; +} + +.ve-ui-textInputWidget-pending input, +.ve-ui-textInputWidget-pending textarea { + background-color: transparent; } .ve-ui-textInputWidget-decorated input, @@ -477,3 +476,20 @@ overflow-y: auto; line-height: 0; } + +/* ve.ui.MediaSizeWidget */ + +.ve-ui-mediaSizeWidget > div { + float: left; + margin: 0 0.25em; +} + +.ve-ui-mediaSizeWidget label { + float: left; +} + +.ve-ui-mediaSizeWidget .ve-ui-numberInputWidget { + float: left; + margin-left: 0.5em; + margin-right: 1em; +} diff --git a/modules/ve/ui/widgets/ve.ui.InputWidget.js b/modules/ve/ui/widgets/ve.ui.InputWidget.js index a5e76df..0270ebb 100644 --- a/modules/ve/ui/widgets/ve.ui.InputWidget.js +++ b/modules/ve/ui/widgets/ve.ui.InputWidget.js @@ -116,7 +116,7 @@ ve.ui.InputWidget.prototype.setValue = function ( value ) { var domValue = this.$input.val(); value = this.sanitizeValue( value ); - if ( this.value !== value ) { + if ( this.value !== value || this.value !== domValue ) { this.value = value; // Only update the DOM if we must if ( domValue !== this.value ) { diff --git a/modules/ve/ui/widgets/ve.ui.MediaSizeWidget.js b/modules/ve/ui/widgets/ve.ui.MediaSizeWidget.js new file mode 100644 index 0000000..93cec31 --- /dev/null +++ b/modules/ve/ui/widgets/ve.ui.MediaSizeWidget.js @@ -0,0 +1,89 @@ +ve.ui.MediaSizeWidget = function VeUiMediaSizeWidget( config ) { + config = ve.extendObject( {}, config ); + // Parent constructor + ve.ui.Widget.call( this, config ); + + // Properties + this.initialDimensions = {}; + this.changed = false; + this.ratio = null; + + this.widthInput = new ve.ui.NumberInputWidget( { '$$': this.$$ } ); + this.widthLabel = new ve.ui.InputLabelWidget( + { '$$': this.$$, 'input': this.widthInput, 'label': 'Width' } + ); + this.$width = this.$$( '<div>' ).append( this.widthLabel.$, this.widthInput.$ ); + + this.heightInput = new ve.ui.NumberInputWidget( { '$$': this.$$ } ); + this.heightLabel = new ve.ui.InputLabelWidget( + { '$$': this.$$, 'input': this.heightInput, 'label': 'Height' } + ); + + this.$height = this.$$( '<div>' ).append( this.heightLabel.$, this.heightInput.$ ); + this.$layout = this.$$( '<div>' ).append( this.$width, this.$height ) + .addClass( 've-ui-mediaSizeWidget' ); + + this.$.append( this.$layout ); + + // Events + this.widthInput.$input.on( 'input change cut paste keyup blur', ve.bind( this.onWidthInput, this ) ); + this.heightInput.$input.on( 'input change cut paste keyup blur', ve.bind( this.onHeightInput, this ) ); +}; + +/* Inheritance */ + +ve.inheritClass( ve.ui.MediaSizeWidget, ve.ui.Widget ); + +/* Methods */ + +ve.ui.MediaSizeWidget.prototype.initialize = function ( dimensions ) { + this.widthInput.setValue( dimensions.width ); + this.heightInput.setValue( dimensions.height ); + // Store original dimensions + this.initialDimensions = { + 'height': parseInt( dimensions.height, 10 ), + 'width': parseInt( dimensions.width, 10 ) + }; + // Set ratio from original dimensions + this.ratio = dimensions.originalWidth / dimensions.originalHeight; +}; + +ve.ui.MediaSizeWidget.prototype.getDimensions = function () { + return { + 'width': this.widthInput.getValue(), + 'height': this.heightInput.getValue() + }; +}; + +ve.ui.MediaSizeWidget.prototype.onWidthInput = function () { + var changeWidth = this.widthInput.getValue(), + changeHeight = Math.round( changeWidth / this.ratio ); + if ( !this.widthInput.isValid() ) { + this.resetDimensions(); + return; + } + this.heightInput.setValue( changeHeight ); + this.setChangedFlag(); +}; + +ve.ui.MediaSizeWidget.prototype.onHeightInput = function () { + var changeHeight = this.heightInput.getValue(), + changeWidth = Math.round( changeHeight * this.ratio ); + if ( !this.heightInput.isValid() ) { + this.resetDimensions(); + return; + } + this.widthInput.setValue( changeWidth ); + this.setChangedFlag(); +}; + +ve.ui.MediaSizeWidget.prototype.setChangedFlag = function () { + this.changed = this.initialDimensions.height !== this.heightInput.getValue() || + this.initialDimensions.width !== this.widthInput.getValue(); +}; + +ve.ui.MediaSizeWidget.prototype.resetDimensions = function () { + this.widthInput.setValue( this.initialDimensions.width ); + this.heightInput.setValue( this.initialDimensions.height ); + this.changed = false; +}; diff --git a/modules/ve/ui/widgets/ve.ui.NumberInputWidget.js b/modules/ve/ui/widgets/ve.ui.NumberInputWidget.js new file mode 100644 index 0000000..7e85fc9 --- /dev/null +++ b/modules/ve/ui/widgets/ve.ui.NumberInputWidget.js @@ -0,0 +1,97 @@ +/*! + * VisualEditor UserInterface NumberInputWidget class. + * + * @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt + * @license The MIT License (MIT); see LICENSE.txt + */ + +/** + * Creates an ve.ui.NumberInputWidget object. + * + * @class + * @extends ve.ui.InputWidget + * + * @constructor + * @param {Object} [config] Config options + */ +ve.ui.NumberInputWidget = function VeUiTextInputWidget( config ) { + config = config || {}; + + // Parent constructor + ve.ui.InputWidget.call( this, config ); + + // Events + this.$input.on( 'keypress', ve.bind( this.onKeyPress, this ) ); + + // Initialization + this.$.addClass( 've-ui-numberInputWidget' ); +}; + +/* Inheritance */ + +ve.inheritClass( ve.ui.NumberInputWidget, ve.ui.InputWidget ); + +/* Events */ + +/** + * User presses enter inside the text box. + * + * Not called if input is multiline. + * + * @event enter + */ + +/* Methods */ + +/** + * Handles key press events. + * + * @param {jQuery.Event} e Key press event + * @emits enter If enter key is pressed and input is not multiline + */ +ve.ui.NumberInputWidget.prototype.onKeyPress = function ( e ) { + if ( e.which === ve.Keys.ENTER && !this.multiline ) { + this.emit( 'enter' ); + } +}; + +/* Methods */ + +/** + * Get input element. + * + * @method + * @param {Object} [config] Config options + * @returns {jQuery} Input element + */ +ve.ui.NumberInputWidget.prototype.getInputElement = function () { + // Using text as Chrome auto formats input=number + return this.$$( '<input>' ).attr( 'type', 'text' ); +}; + +/** + * Get the value of the input. + * + * @method + * @returns {string} Input value + */ +ve.ui.NumberInputWidget.prototype.getValue = function () { + return parseInt( this.value, 10 ); +}; + +ve.ui.NumberInputWidget.prototype.isValid = function () { + return !( isNaN( this.getValue() ) || this.getValue() <= 0 ); +}; + +/** + * Sanitize incoming value. + * + * Ensures value is a number, and converts undefined and null to empty strings. + * + * @method + * @param {string} value Original value + * @returns {string} Sanitized value + */ +ve.ui.NumberInputWidget.prototype.sanitizeValue = function ( value ) { + return value === undefined || value === null ? '' : String( value ).replace(/[^0-9.]/g, ''); +}; -- To view, visit https://gerrit.wikimedia.org/r/74082 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Id4e0953ce7de991f94ffefa39f311fbcdd283d29 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/VisualEditor Gerrit-Branch: master Gerrit-Owner: Robmoen <rm...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits