jenkins-bot has submitted this change and it was merged.

Change subject: Allow resizing nodes
......................................................................


Allow resizing nodes

Actually really resizing the image

Show bounding box on mouseover with 4 handles. Bounding box is resizable. Image 
resizes to match bounding box on mouse up.

Change-Id: I1f3dac64eb86dd1f258937e4915af101b3ac19d8
---
M VisualEditor.php
M demos/ve/index.php
M modules/ve/ce/nodes/ve.ce.ImageNode.js
M modules/ve/ce/nodes/ve.ce.MWImageNode.js
M modules/ve/ce/styles/ve.ce.Node.css
A modules/ve/ce/ve.ce.ResizableNode.js
M modules/ve/test/index.php
7 files changed, 299 insertions(+), 7 deletions(-)

Approvals:
  Trevor Parscal: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/VisualEditor.php b/VisualEditor.php
index 0f43909..fc5be11 100644
--- a/VisualEditor.php
+++ b/VisualEditor.php
@@ -304,6 +304,7 @@
                        've/ce/ve.ce.LeafNode.js',
                        've/ce/ve.ce.FocusableNode.js',
                        've/ce/ve.ce.RelocatableNode.js',
+                       've/ce/ve.ce.ResizableNode.js',
                        've/ce/ve.ce.Surface.js',
                        've/ce/ve.ce.SurfaceObserver.js',
 
diff --git a/demos/ve/index.php b/demos/ve/index.php
index ee66e05..427cdd2 100644
--- a/demos/ve/index.php
+++ b/demos/ve/index.php
@@ -183,6 +183,7 @@
                <script src="../../modules/ve/ce/ve.ce.LeafNode.js"></script>
                <script 
src="../../modules/ve/ce/ve.ce.FocusableNode.js"></script>
                <script 
src="../../modules/ve/ce/ve.ce.RelocatableNode.js"></script>
+               <script 
src="../../modules/ve/ce/ve.ce.ResizableNode.js"></script>
                <script src="../../modules/ve/ce/ve.ce.Surface.js"></script>
                <script 
src="../../modules/ve/ce/ve.ce.SurfaceObserver.js"></script>
                <script 
src="../../modules/ve/ce/nodes/ve.ce.GeneratedContentNode.js"></script>
diff --git a/modules/ve/ce/nodes/ve.ce.ImageNode.js 
b/modules/ve/ce/nodes/ve.ce.ImageNode.js
index 6c32e9c..30dd1a9 100644
--- a/modules/ve/ce/nodes/ve.ce.ImageNode.js
+++ b/modules/ve/ce/nodes/ve.ce.ImageNode.js
@@ -12,6 +12,7 @@
  * @extends ve.ce.LeafNode
  * @mixins ve.ce.FocusableNode
  * @mixins ve.ce.RelocatableNode
+ * @mixins ve.ce.ResizableNode
  *
  * @constructor
  * @param {ve.dm.ImageNode} model Model to observe
@@ -23,13 +24,14 @@
        // Mixin constructors
        ve.ce.FocusableNode.call( this );
        ve.ce.RelocatableNode.call( this );
+       ve.ce.ResizableNode.call( this );
 
        // Properties
        this.$image = this.$;
 
        // Events
        this.model.addListenerMethod( this, 'update', 'onUpdate' );
-       this.$image.on( 'click', ve.bind( this.onClick, this ) );
+       this.$.on( 'click', ve.bind( this.onClick, this ) );
 
        // Initialization
        ve.setDomAttributes( this.$image[0], this.model.getAttributes(), 
['src', 'width', 'height'] );
@@ -42,12 +44,38 @@
 
 ve.mixinClass( ve.ce.ImageNode, ve.ce.FocusableNode );
 ve.mixinClass( ve.ce.ImageNode, ve.ce.RelocatableNode );
+ve.mixinClass( ve.ce.ImageNode, ve.ce.ResizableNode );
 
 /* Static Properties */
 
 ve.ce.ImageNode.static.name = 'image';
 
 /* Methods */
+
+/**
+ * Handle attribute change events.
+ *
+ * Whitelisted attributes will be added or removed in sync with the DOM. They 
are initially set in
+ * the constructor.
+ *
+ * @method
+ * @param {string} key Attribute key
+ * @param {string} from Old value
+ * @param {string} to New value
+ */
+ve.ce.ImageNode.prototype.onAttributeChange = function ( key, from, to ) {
+       if ( ( key === 'width' || key === 'height' ) && from !== to ) {
+               this.$image.attr( key, to );
+       }
+};
+
+/**
+ * Update method
+ *
+ * @method
+ */
+ve.ce.ImageNode.prototype.onUpdate = function () {
+};
 
 /**
  * Handle the mouse click.
@@ -66,7 +94,7 @@
                                [ selectionRange, nodeRange ], 
selectionRange.from > nodeRange.from
                        ) :
                        nodeRange
-       ).select();
+       ).select().destroy();
 };
 
 /* Registration */
diff --git a/modules/ve/ce/nodes/ve.ce.MWImageNode.js 
b/modules/ve/ce/nodes/ve.ce.MWImageNode.js
index 0fea716..0e77b76 100644
--- a/modules/ve/ce/nodes/ve.ce.MWImageNode.js
+++ b/modules/ve/ce/nodes/ve.ce.MWImageNode.js
@@ -18,7 +18,6 @@
        ve.ce.ImageNode.call( this, model );
 
        // Properties
-       this.$image = this.$;
        this.$ = $( '<' + ( model.getAttribute( 'isLinked' ) ? 'a' : 'span' ) + 
'>' );
 
        // Initialization
@@ -39,10 +38,6 @@
 ve.ce.MWImageNode.static.name = 'MWimage';
 
 /* Methods */
-
-ve.ce.MWImageNode.prototype.onUpdate = function () {
-       // ...
-};
 
 /* Registration */
 
diff --git a/modules/ve/ce/styles/ve.ce.Node.css 
b/modules/ve/ce/styles/ve.ce.Node.css
index 37f3010..a6936cb 100644
--- a/modules/ve/ce/styles/ve.ce.Node.css
+++ b/modules/ve/ce/styles/ve.ce.Node.css
@@ -85,3 +85,45 @@
        margin: 0;
        padding: 0;
 }
+
+.ve-ce-resizableNode-handles {
+       position: absolute;
+       border: solid 1px rgba(0,0,0,0.25);
+       z-index: 10000;
+       box-sizing: border-box;
+}
+
+.ve-ce-resizableNode-handles div {
+       position: absolute;
+       width: 0.66em;
+       height: 0.66em;
+       border: solid 0.125em #fff;
+       background-color: #333;
+       box-shadow: 0 0.1em 0.2em rgba(0,0,0,0.75);
+       border-radius: 1em;
+       box-sizing: border-box;
+}
+
+.ve-ce-resizableNode-nwHandle {
+       cursor: nw-resize;
+       left: -0.33em;
+       top: -0.33em;
+}
+
+.ve-ce-resizableNode-neHandle {
+       cursor: ne-resize;
+       right: -0.33em;
+       top: -0.33em;
+}
+
+.ve-ce-resizableNode-swHandle {
+       cursor: sw-resize;
+       bottom: -0.33em;
+       left: -0.33em;
+}
+
+.ve-ce-resizableNode-seHandle {
+       cursor: se-resize;
+       bottom: -0.33em;
+       right: -0.33em;
+}
diff --git a/modules/ve/ce/ve.ce.ResizableNode.js 
b/modules/ve/ce/ve.ce.ResizableNode.js
new file mode 100644
index 0000000..b502ad8
--- /dev/null
+++ b/modules/ve/ce/ve.ce.ResizableNode.js
@@ -0,0 +1,224 @@
+/*!
+ * VisualEditor ContentEditable ResizableNode class.
+ *
+ * @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+
+/**
+ * ContentEditable resizable node.
+ *
+ * @class
+ * @abstract
+ *
+ * @constructor
+ * @param {jQuery} [$resizable=this.$] Resizable DOM element
+ */
+ve.ce.ResizableNode = function VeCeResizableNode( $resizable ) {
+       // Properties
+       this.$resizable = $resizable || this.$;
+       this.ratio = this.model.getAttribute( 'width' ) / 
this.model.getAttribute( 'height' );
+       this.resizing = false;
+       this.$resizeHandles = $( '<div>' );
+
+       // Events
+       this.addListenerMethods( this, {
+               'focus': 'onResizableFocus',
+               'blur': 'onResizableBlur'
+       } );
+
+       // Initialization
+       this.$resizeHandles
+               .addClass( 've-ce-resizableNode-handles' )
+               .append( $( '<div>' ).addClass( 've-ce-resizableNode-nwHandle' 
) )
+               .append( $( '<div>' ).addClass( 've-ce-resizableNode-neHandle' 
) )
+               .append( $( '<div>' ).addClass( 've-ce-resizableNode-seHandle' 
) )
+               .append( $( '<div>' ).addClass( 've-ce-resizableNode-swHandle' 
) )
+               .children()
+                       .on( {
+                               'mousedown': ve.bind( 
this.onResizeHandlesCornerMouseDown, this )
+                       } );
+};
+
+/* Methods */
+
+/**
+ * Handle node focus.
+ *
+ * @method
+ */
+ve.ce.ResizableNode.prototype.onResizableFocus = function () {
+       var offset = this.$image.offset();
+
+       this.$resizeHandles
+               .css( {
+                       'width': 0,
+                       'height': 0,
+                       'top': offset.top,
+                       'left': offset.left
+               } )
+               .appendTo( $( 'body' ) );
+
+       this.$resizeHandles
+               .find('.ve-ce-resizableNode-neHandle').css( {
+                       'margin-right': -this.$image.width()
+               } ).end()
+               .find('.ve-ce-resizableNode-swHandle').css( {
+                       'margin-bottom': -this.$image.height()
+               } ).end()
+               .find('.ve-ce-resizableNode-seHandle').css( {
+                       'margin-right': -this.$image.width(),
+                       'margin-bottom': -this.$image.height()
+               } );
+};
+
+/**
+ * Handle node blur.
+ *
+ * @method
+ */
+ve.ce.ResizableNode.prototype.onResizableBlur = function () {
+       this.$resizeHandles.detach();
+};
+
+/**
+ * Handle bounding box handle mousedown.
+ *
+ * @method
+ * @param {jQuery.Event} e Click event
+ */
+ve.ce.ResizableNode.prototype.onResizeHandlesCornerMouseDown = function ( e ) {
+
+       // Set bounding box width and undo the handle margins
+       this.$resizeHandles
+               .css( {
+                       'width': this.$image.width(),
+                       'height': this.$image.height()
+               } );
+
+       this.$resizeHandles.children().css( 'margin', 0 );
+
+       // Values to calculate adjusted bounding box size
+       this.resizeInfo = {
+               'mouseX': e.screenX,
+               'mouseY': e.screenY,
+               'top': this.$resizeHandles.position().top,
+               'left': this.$resizeHandles.position().left,
+               'height': this.$resizeHandles.height(),
+               'width': this.$resizeHandles.width(),
+               'handle': e.target.className,
+       };
+
+       // Bind resize events
+       this.resizing = true;
+       $( document ).on( {
+               'mousemove.ve-ce-resizableNode': ve.bind( 
this.onDocumentMouseMove, this ),
+               'mouseup.ve-ce-resizableNode': ve.bind( this.onDocumentMouseUp, 
this )
+       } );
+
+       return false;
+};
+
+/**
+ * Handle body mousemove.
+ *
+ * @method
+ * @param {jQuery.Event} e Click event
+ */
+ve.ce.ResizableNode.prototype.onDocumentMouseMove = function ( e ) {
+       var newWidth, newHeight, newRatio,
+               // TODO: Make these configurable
+               min = 1,
+               max = 1000,
+               diff = {},
+               dimensions = {
+                       'width': 0,
+                       'height': 0,
+                       'top': this.resizeInfo.top,
+                       'left': this.resizeInfo.left
+               };
+
+       if ( this.resizing ) {
+               // X and Y diff
+               switch ( this.resizeInfo.handle ) {
+                       case 've-ce-resizableNode-seHandle':
+                               diff.x = e.screenX - this.resizeInfo.mouseX;
+                               diff.y = e.screenY - this.resizeInfo.mouseY;
+                               break;
+                       case 've-ce-resizableNode-nwHandle':
+                               diff.x = this.resizeInfo.mouseX - e.screenX;
+                               diff.y = this.resizeInfo.mouseY - e.screenY;
+                               break;
+                       case 've-ce-resizableNode-neHandle':
+                               diff.x = e.screenX - this.resizeInfo.mouseX;
+                               diff.y = this.resizeInfo.mouseY - e.screenY;
+                               break;
+                       case 've-ce-resizableNode-swHandle':
+                               diff.x = this.resizeInfo.mouseX - e.screenX;
+                               diff.y = e.screenY - this.resizeInfo.mouseY;
+                               break;
+               }
+
+               // Unconstrained dimensions and ratio
+               newWidth = Math.max( Math.min( this.$image.width() + diff.x, 
max ), min );
+               newHeight = Math.max( Math.min( this.$image.height() + diff.y, 
max ), min );
+               newRatio = newWidth / newHeight;
+
+               // Fix the ratio
+               if ( this.ratio > newRatio ) {
+                       dimensions.width = newWidth;
+                       dimensions.height = this.$image.height() +
+                               ( newWidth - this.$image.width() ) / this.ratio;
+               } else {
+                       dimensions.width = this.$image.width() +
+                               ( newHeight - this.$image.height() ) * 
this.ratio;
+                       dimensions.height = newHeight;
+               }
+
+               // Fix the position
+               switch ( this.resizeInfo.handle ) {
+                       case 've-ce-resizableNode-neHandle':
+                               dimensions.top = this.resizeInfo.top +
+                                       ( this.resizeInfo.height - 
dimensions.height );
+                               break;
+                       case 've-ce-resizableNode-swHandle':
+                               dimensions.left = this.resizeInfo.left +
+                                       ( this.resizeInfo.width - 
dimensions.width );
+                               break;
+                       case 've-ce-resizableNode-nwHandle':
+                               dimensions.top = this.resizeInfo.top +
+                                       ( this.resizeInfo.height - 
dimensions.height );
+                               dimensions.left = this.resizeInfo.left +
+                                       ( this.resizeInfo.width - 
dimensions.width );
+                               break;
+               }
+
+               // Update bounding box
+               this.$resizeHandles.css( dimensions );
+       }
+};
+
+/**
+ * Handle body mouseup.
+ *
+ * @method
+ */
+ve.ce.ResizableNode.prototype.onDocumentMouseUp = function () {
+       var offset = this.model.getOffset(),
+               width = this.$resizeHandles.outerWidth(),
+               height = this.$resizeHandles.outerHeight(),
+               surfaceModel = this.getRoot().getSurface().getModel(),
+               documentModel = surfaceModel.getDocument(),
+               selection = surfaceModel.getSelection(),
+               txs = [];
+
+       $( document ).off( '.ve-ce-resizableNode' );
+       this.resizing = false;
+
+       txs.push( ve.dm.Transaction.newFromAttributeChange( documentModel, 
offset, 'width', width ) );
+       txs.push( ve.dm.Transaction.newFromAttributeChange( documentModel, 
offset, 'height', height ) );
+       surfaceModel.change( txs, selection );
+
+       // HACK: Update bounding box
+       this.onResizableFocus();
+};
diff --git a/modules/ve/test/index.php b/modules/ve/test/index.php
index 5eb2d91..068dabd 100644
--- a/modules/ve/test/index.php
+++ b/modules/ve/test/index.php
@@ -126,6 +126,7 @@
                <script src="../../ve/ce/ve.ce.LeafNode.js"></script>
                <script src="../../ve/ce/ve.ce.FocusableNode.js"></script>
                <script src="../../ve/ce/ve.ce.RelocatableNode.js"></script>
+               <script src="../../ve/ce/ve.ce.ResizableNode.js"></script>
                <script src="../../ve/ce/ve.ce.Surface.js"></script>
                <script src="../../ve/ce/ve.ce.SurfaceObserver.js"></script>
                <script 
src="../../ve/ce/nodes/ve.ce.GeneratedContentNode.js"></script>

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

Gerrit-MessageType: merged
Gerrit-Change-Id: I1f3dac64eb86dd1f258937e4915af101b3ac19d8
Gerrit-PatchSet: 14
Gerrit-Project: mediawiki/extensions/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Christian <[email protected]>
Gerrit-Reviewer: Catrope <[email protected]>
Gerrit-Reviewer: Christian <[email protected]>
Gerrit-Reviewer: Inez <[email protected]>
Gerrit-Reviewer: Jforrester <[email protected]>
Gerrit-Reviewer: Krinkle <[email protected]>
Gerrit-Reviewer: Trevor Parscal <[email protected]>
Gerrit-Reviewer: jenkins-bot

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to