mediawiki-extensions-Visual.../modules/ve/ce/ve.ce.ResizableNode.js
Rob Moen c472b2fe4a Make local overlays local to surface and remove insane z-indexes
ve.ui.Surface.js
* Make local overlay a child of ve-ui-surface and a sibling to
  ve-ce-surface elements.
** This keeps local overlays relative to their surface and eliminates the
   need for insane z-indexes.

ve.ui.PopupWidget.js
* PopupWidget boundaries are now relative to ve-ce-surface and no longer
  protrude out

ve.ce.Node.css, ve.ui.Window.css
* Removal or replacement of insane z-indexes.

ve.ce.FocusableNode.js, ve.ce.ProtectedNode.js, ve.ce.ResizableNode.js,
ve.ui.Context.js
* Translate offsets from local overlay

ve.init.mw.ViewPageTarget-monobook.css,
ve.init.mw.ViewPageTarget-vector.css
* Skin specific z-indexes for global overlay

ve.init.mw.ViewPageTarget.js
* Applied direction specific mw class to ce.Surface vs ui.Surface to
  prevent mw content styles from being applied to ui elements.

ve.ui.Dialog.css
* Adjustments to surface inside of dialog so that relative offsets for
  local overlays can be properly calculated.

ve.ui.Surface.css
* Explicitly force .ve-ui-surface to be relative so that it's children can
  be relatively positioned.

ve.ui.Widget.css
* Removal of unnecessary font-size properties now that local overlay is
  sibling of surface.

ve.js
* Added get relative position helper method to translate position offsets
  from target parent

Bug: 50241
Change-Id: Ibadce404a2286bc5dcec48f0d9da89004dbbd867
2013-07-02 19:35:43 +00:00

289 lines
7.7 KiB
JavaScript

/*!
* 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 = this.$$( '<div>' );
// Events
this.connect( this, {
'focus': 'onResizableFocus',
'blur': 'onResizableBlur',
'live': 'onResizableLive',
'resize': 'onResizableFocus'
} );
// Initialization
this.$resizeHandles
.addClass( 've-ce-resizableNode-handles' )
.append( this.$$( '<div>' ).addClass( 've-ce-resizableNode-nwHandle' ) )
.append( this.$$( '<div>' ).addClass( 've-ce-resizableNode-neHandle' ) )
.append( this.$$( '<div>' ).addClass( 've-ce-resizableNode-seHandle' ) )
.append( this.$$( '<div>' ).addClass( 've-ce-resizableNode-swHandle' ) );
};
/* Static Properties */
ve.ce.ResizableNode.static = {};
/* Methods */
/**
* Handle node focus.
*
* @method
*/
ve.ce.ResizableNode.prototype.onResizableFocus = function () {
this.$resizeHandles.appendTo( this.root.getSurface().getSurface().$localOverlayControls );
this.setResizableHandlesSizeAndPosition();
this.$resizeHandles
.find( '.ve-ce-resizableNode-neHandle' )
.css( { 'margin-right': -this.$resizable.width() } )
.end()
.find( '.ve-ce-resizableNode-swHandle' )
.css( { 'margin-bottom': -this.$resizable.height() } )
.end()
.find( '.ve-ce-resizableNode-seHandle' )
.css( {
'margin-right': -this.$resizable.width(),
'margin-bottom': -this.$resizable.height()
} );
this.$resizeHandles.children()
.off( '.ve-ui-resizableNode' )
.on(
'mousedown.ve-ui-resizableNode',
ve.bind( this.onResizeHandlesCornerMouseDown, this )
);
};
/**
* Handle node blur.
*
* @method
*/
ve.ce.ResizableNode.prototype.onResizableBlur = function () {
this.$resizeHandles.detach();
};
/**
* Handle live event.
*
* @method
*/
ve.ce.ResizableNode.prototype.onResizableLive = function () {
var surfaceModel = this.getRoot().getSurface().getModel();
if ( this.live ) {
surfaceModel.connect( this, { 'history': 'setResizableHandlesSizeAndPosition' } );
} else {
surfaceModel.disconnect( this, { 'history': 'setResizableHandlesSizeAndPosition' } );
this.onResizableBlur();
}
};
/**
* Handle bounding box handle mousedown.
*
* @method
* @param {jQuery.Event} e Click event
*/
ve.ce.ResizableNode.prototype.onResizeHandlesCornerMouseDown = function ( e ) {
// Hide context menu
// TODO: Maybe there's a more generic way to handle this sort of thing? For relocation it's
// handled in ve.ce.Surface
this.root.getSurface().getSurface().getContext().hide();
// Set bounding box width and undo the handle margins
this.$resizeHandles
.addClass( 've-ui-resizableNode-handles-resizing' )
.css( {
'width': this.$resizable.width(),
'height': this.$resizable.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;
$( this.getElementDocument() ).on( {
'mousemove.ve-ce-resizableNode': ve.bind( this.onDocumentMouseMove, this ),
'mouseup.ve-ce-resizableNode': ve.bind( this.onDocumentMouseUp, this )
} );
return false;
};
/**
* Set the proper size and position for resize handles
*
* @method
*/
ve.ce.ResizableNode.prototype.setResizableHandlesSizeAndPosition = function () {
var offset = ve.Element.getRelativePosition(
this.$resizable, this.getRoot().getSurface().getSurface().$
);
this.$resizeHandles
.css( {
'width': 0,
'height': 0,
'top': offset.top,
'left': offset.left
} )
.find( '.ve-ce-resizableNode-neHandle' )
.css( { 'margin-right': -this.$resizable.width() } )
.end()
.find( '.ve-ce-resizableNode-swHandle' )
.css( { 'margin-bottom': -this.$resizable.height() } )
.end()
.find( '.ve-ce-resizableNode-seHandle' )
.css( {
'margin-right': -this.$resizable.width(),
'margin-bottom': -this.$resizable.height()
} );
};
/**
* 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.$resizable.width() + diff.x, max ), min );
newHeight = Math.max( Math.min( this.$resizable.height() + diff.y, max ), min );
newRatio = newWidth / newHeight;
// Fix the ratio
if ( this.ratio > newRatio ) {
dimensions.width = newWidth;
dimensions.height = this.$resizable.height() +
( newWidth - this.$resizable.width() ) / this.ratio;
} else {
dimensions.width = this.$resizable.width() +
( newHeight - this.$resizable.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(),
attrChanges = {};
this.$resizeHandles.removeClass( 've-ui-resizableNode-handles-resizing' );
$( this.getElementDocument() ).off( '.ve-ce-resizableNode' );
this.resizing = false;
// Apply changes to the model
if ( this.model.getAttribute( 'width' ) !== width ) {
attrChanges.width = width;
}
if ( this.model.getAttribute( 'height' ) !== height ) {
attrChanges.height = height;
}
if ( !ve.isEmptyObject( attrChanges ) ) {
surfaceModel.change(
ve.dm.Transaction.newFromAttributeChanges( documentModel, offset, attrChanges ),
selection
);
}
this.emit( 'resize' );
};