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