Refactor button things into a separate class

https://wikimedia.mingle.thoughtworks.com/projects/multimedia/cards/124

Change-Id: Ib4b54164bccbe17e42b223880179531e8d9e6123
This commit is contained in:
Mark Holmquist 2014-02-04 15:44:36 -08:00 committed by Gilles Dubuc
parent 698f677cd3
commit a62410616d
17 changed files with 307 additions and 238 deletions

View file

@ -88,6 +88,7 @@ call_user_func( function() {
'dependencies' => array(
'oojs',
'multilightbox.interface',
'mmv.ui.buttons',
'mmv.ui.categories',
'mmv.ui.description',
'mmv.ui.fileUsage',
@ -316,6 +317,21 @@ call_user_func( function() {
),
), $moduleInfo( 'mmv/ui' ) );
$wgResourceModules['mmv.ui.buttons'] = array_merge( array(
'scripts' => array(
'mmv.ui.buttons.js',
),
'styles' => array(
'mmv.ui.buttons.less',
),
'dependencies' => array(
'mmv.ui',
'oojs',
),
), $moduleInfo( 'mmv/ui' ) );
$wgResourceModules['mmv.logger'] = array_merge( array(
'scripts' => array(
'mmv.logger.js',

View file

@ -173,10 +173,6 @@
// Register various event hooks. TODO: Make this a function that's only called once.
lightboxHooks.register( 'closeInterface', function () {
if ( this.$nextButton ) {
this.$nextButton.add( this.$prevButton ).css( 'top', '-999px' );
}
$( document.body ).removeClass( 'mw-mlb-lightbox-open' );
if ( comingFromPopstate === false ) {
history.pushState( {}, '', '#' );
@ -283,26 +279,6 @@
this.ui.updateControls( showNextButton, showPreviousButton );
};
MMVP.registerLogging = function () {
var viewer = this;
this.ui.$closeButton.click( function () {
if ( viewer.ui.$dialog ) {
viewer.ui.$dialog.dialog( 'close' );
}
mw.mmv.logger.log( 'close-link-click' );
} );
this.ui.$fullscreenButton.click( function () {
if ( viewer.ui.isFullscreen ) {
mw.mmv.logger.log( 'fullscreen-link-click' );
} else {
mw.mmv.logger.log( 'defullscreen-link-click' );
}
} );
};
/**
* @method
* Loads and sets the image specified in the imageData. It also updates the controls
@ -371,6 +347,14 @@
this.lightbox.iface.load( image );
}
this.lightbox.iface.$imageWrapper.on( 'mmv-next', function () {
viewer.nextImage();
} );
this.lightbox.iface.$imageWrapper.on( 'mmv-prev', function () {
viewer.prevImage();
} );
$( document.body ).addClass( 'mw-mlb-lightbox-open' );
imageWidths = this.ui.getImageSizeApiArgs();

View file

@ -135,50 +135,11 @@ body.mobile .mw-mlb-controls,
background-color: rgb(0,0,0);
}
@buttons-offset: 5px;
.mlb-close,
.mlb-fullscreen {
right: @buttons-offset;
left: auto;
transition: opacity 0.25s;
background-position: center;
}
.mlb-close {
top: @buttons-offset;
/* @embed */
background-image: url(img/mw-close.svg);
}
.mlb-fullscreen {
top: 42px;
/* @embed */
background-image: url(img/mw-fullscreen-ltr.svg);
}
.jq-fullscreened .mlb-fullscreen {
/* @embed */
background-image: url(img/mw-defullscreen-ltr.svg);
}
.mlb-close,
.mlb-fullscreen,
.mw-mlb-next-image,
.mw-mlb-prev-image {
position: fixed;
background-repeat: no-repeat;
opacity: 0.8;
border: none;
z-index: 1003;
}
.mlb-close:hover,
.mw-mlb-next-image:hover,
.mw-mlb-prev-image:hover {
opacity: 1;
}
.mw-mlb-license,
.mw-mlb-title-contain {
vertical-align: middle;
@ -359,44 +320,6 @@ body.mw-mlb-lightbox-open #content {
display: none;
}
.mw-mlb-next-image,
.mw-mlb-prev-image {
top: -999px;
width: 80px;
height: 120px;
cursor: pointer;
transition: opacity 0.25s, margin 0.25s;
&.disabled {
display: none;
cursor: none;
}
}
@nav-offset: 18px;
.mw-mlb-next-image {
/* @embed */
background-image: url(img/next-ltr.svg);
background-position: right;
right: @nav-offset;
&:hover {
margin-right: -4px;
}
}
.mw-mlb-prev-image {
/* @embed */
background-image: url(img/prev-ltr.svg);
background-position: left;
left: @nav-offset;
&:hover {
margin-left: -4px;
}
}
.mw-mlb-drag-affordance {
width: 100%;
height: @drag-height;

View file

@ -109,7 +109,7 @@
// Buttons fading might not had been reset properly after a hard fullscreen exit
// This needs to happen after the parent attach() because the buttons need to be attached
// to the DOM for $.fn.stop() to work
this.stopButtonsFade();
this.buttons.stopFade();
};
LIP.unattach = function () {
@ -120,6 +120,8 @@
$.scrollTo( this.scrollTopBeforeAttach, 0 );
this.scrollTopBeforeAttach = undefined;
}
this.panel.fileReuse.closeDialog();
};
LIP.load = function ( image ) {
@ -127,7 +129,6 @@
ui = this;
this.viewer.ui = this;
this.viewer.registerLogging();
if ( !this.comingFromPopstate ) {
history.pushState( {}, '', hashFragment );
@ -145,48 +146,15 @@
LIP.initializeInterface = function () {
this.panel = new mw.mmv.ui.MetadataPanel( this.$postDiv, this.$controlBar );
this.initializeNavigation();
this.initializeButtons();
this.buttons = new mw.mmv.ui.Buttons( this.$imageWrapper, this.$closeButton, this.$fullscreenButton );
this.initializeImage();
};
LIP.initializeButtons = function () {
// Note we aren't adding the fullscreen button here.
// Fullscreen causes some funky issues with UI redraws,
// and we aren't sure why, but it's not really necessary
// with the new interface anyway - it's basically fullscreen
// already!
this.$buttons = this.$closeButton
.add( this.$fullscreenButton )
.add( this.$nextButton )
.add( this.$prevButton )
.appendTo( this.$imageWrapper );
};
LIP.initializeImage = function () {
this.$imageDiv
.addClass( 'empty' );
};
LIP.initializeNavigation = function () {
var viewer = this.viewer;
this.$nextButton = $( '<div>' )
.addClass( 'mw-mlb-next-image disabled' )
.html( '&nbsp;' )
.click( function () {
viewer.nextImage();
} );
this.$prevButton = $( '<div>' )
.addClass( 'mw-mlb-prev-image disabled' )
.html( '&nbsp;' )
.click( function () {
viewer.prevImage();
} );
};
LIP.replaceImageWith = function ( imageEle ) {
var $image = $( imageEle );
@ -220,7 +188,7 @@
// the fade out really takes place (otherwise it's cancelled
// by updateControls which is called a few times when fullscreen opens)
this.mousePosition = { x: 0, y: 0 };
this.fadeOutButtons();
this.buttons.fadeOut();
}
};
@ -261,86 +229,12 @@
// Saving the mouse position is useful whenever we need to
// run LIP.mousemove manually, such as when going to the next/prev
// element
this.mousePosition = { x: e.pageX, y: e.pageY};
this.mousePosition = { x: e.pageX, y: e.pageY };
}
this.revealButtonsAndFadeIfNeeded();
};
/**
* @method
* Reveals all active buttons and schedule a fade out if needed
*/
LIP.revealButtonsAndFadeIfNeeded = function () {
// Only fullscreen mode sees its buttons fade out when not used
if ( !this.isFullscreen ) {
return;
if ( this.isFullscreen ) {
this.buttons.revealAndFade( this.mousePosition );
}
if ( this.buttonsFadeTimeout ) {
clearTimeout( this.buttonsFadeTimeout );
}
// Stop ongoing animations and make sure the buttons that need to be displayed are displayed
this.stopButtonsFade();
// this.mousePosition can be empty, for instance when we enter fullscreen and haven't
// recorded a real mousemove event yet
if ( !this.mousePosition
|| !this.isAnyActiveButtonHovered( this.mousePosition.x, this.mousePosition.y ) ) {
this.fadeOutButtons();
}
};
/**
* @method
* Fades out the active buttons
*/
LIP.fadeOutButtons = function () {
var ui = this;
// We don't use animation chaining because delay() can't be stop()ed
this.buttonsFadeTimeout = setTimeout( function() {
ui.$buttons.not( '.disabled' ).animate( { opacity: 0 }, 1000 );
}, 1500 );
};
/**
* @method
* Stops the fading animation of the buttons and cancel any opacity value
*/
LIP.stopButtonsFade = function () {
this.$buttons
.stop( true )
.css( 'opacity', '' );
};
/**
* @method
* Checks if any active buttons are currently hovered, given a position
* @param {number} x The horizontal coordinate of the position
* @param {number} y The vertical coordinate of the position
* @return bool
*/
LIP.isAnyActiveButtonHovered = function ( x, y ) {
// We don't use mouseenter/mouseleave events because content is subject
// to change underneath the cursor, eg. when entering fullscreen or
// when going prev/next (the button can disappear when reaching ends)
var hovered = false;
this.$buttons.not( '.disabled' ).each( function( idx, e ) {
var $e = $( e ),
offset = $e.offset();
if ( y >= offset.top
&& y <= offset.top + $e.height()
&& x >= offset.left
&& x <= offset.left + $e.width() ) {
hovered = true;
}
} );
return hovered;
};
/**
@ -358,14 +252,12 @@
this.$postDiv.css( 'top', this.$imageWrapper.height() );
}
this.$nextButton.add( this.$prevButton ).css( {
top: prevNextTop
} );
this.buttons.setOffset( prevNextTop );
this.buttons.toggle( showPrevButton, showNextButton );
this.$nextButton.toggleClass( 'disabled', !showPrevButton );
this.$prevButton.toggleClass( 'disabled', !showNextButton );
this.revealButtonsAndFadeIfNeeded();
if ( this.isFullscreen ) {
this.buttons.revealAndFade( this.mousePosition );
}
};
/**

View file

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View file

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View file

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 2 KiB

View file

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 2 KiB

View file

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1,172 @@
/*
* This file is part of the MediaWiki extension MultimediaViewer.
*
* MultimediaViewer is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* MultimediaViewer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MultimediaViewer. If not, see <http://www.gnu.org/licenses/>.
*/
( function ( mw, $, oo ) {
/**
* @class mw.mmv.ui.Buttons
* @extends mw.mmv.ui.Element
* Represents the buttons in the interface - next, previous, close, and fullscreen.
* @constructor
*/
function Buttons( $container, $closeButton, $fullscreenButton ) {
var buttons = this;
mw.mmv.ui.Element.call( this, $container );
this.$close = $closeButton;
this.$fullscreen = $fullscreenButton;
this.$next = $( '<div>' )
.addClass( 'mw-mlb-next-image disabled' )
.html( '&nbsp;' );
this.$prev = $( '<div>' )
.addClass( 'mw-mlb-prev-image disabled' )
.html( '&nbsp;' );
this.$nav = this.$next
.add( this.$prev );
this.$buttons = this.$close
.add( this.$fullscreen )
.add( this.$next )
.add( this.$prev );
this.$buttons.appendTo( this.$container );
lightboxHooks.register( 'closeInterface', function () {
buttons.$nav.addClass( 'disabled' );
} );
$( document ).on( 'jq-fullscreen-change.lip', function ( e ) {
if ( e.fullscreen ) {
mw.mmv.logger.log( 'fullscreen-link-click' );
} else {
mw.mmv.logger.log( 'defullscreen-link-click' );
}
} );
this.$close.click( function () {
mw.mmv.logger.log( 'close-link-click' );
$container.trigger( $.Event( 'mmv-close' ) );
} );
this.$fullscreen.click( function () {
$container.trigger( $.Event( 'mmv-fullscreen' ) );
} );
this.$next.click( function () {
$container.trigger( $.Event( 'mmv-next' ) );
} );
this.$prev.click( function () {
$container.trigger( $.Event( 'mmv-prev' ) );
} );
}
oo.inheritClass( Buttons, mw.mmv.ui.Element );
/**
* Sets the top offset for the navigation buttons.
* @param {number} offset
*/
Buttons.prototype.setOffset = function ( offset ) {
this.$nav.css( {
top: offset
} );
};
/**
* Stops the fading animation of the buttons and cancel any opacity value
*/
Buttons.prototype.stopFade = function () {
this.$buttons
.stop( true )
.css( 'opacity', '' );
};
/**
* Toggles buttons being disabled or not
* @param {boolean} showPrevButton
* @param {boolean} showNextButton
*/
Buttons.prototype.toggle = function ( showPrevButton, showNextButton ) {
this.$next.toggleClass( 'disabled', !showPrevButton );
this.$prev.toggleClass( 'disabled', !showNextButton );
};
/**
* Fades out the active buttons
*/
Buttons.prototype.fadeOut = function () {
var buttons = this;
// We don't use animation chaining because delay() can't be stop()ed
this.buttonsFadeTimeout = setTimeout( function() {
buttons.$buttons.not( '.disabled' ).animate( { opacity: 0 }, 1000 );
}, 1500 );
};
/**
* Checks if any active buttons are currently hovered, given a position
* @param {number} x The horizontal coordinate of the position
* @param {number} y The vertical coordinate of the position
* @return bool
*/
Buttons.prototype.isAnyActiveButtonHovered = function ( x, y ) {
// We don't use mouseenter/mouseleave events because content is subject
// to change underneath the cursor, eg. when entering fullscreen or
// when going prev/next (the button can disappear when reaching ends)
var hovered = false;
this.$buttons.not( '.disabled' ).each( function( idx, e ) {
var $e = $( e ),
offset = $e.offset();
if ( y >= offset.top
&& y <= offset.top + $e.height()
&& x >= offset.left
&& x <= offset.left + $e.width() ) {
hovered = true;
}
} );
return hovered;
};
/**
* Reveals all active buttons and schedule a fade out if needed
*/
Buttons.prototype.revealAndFade = function ( mousePosition ) {
if ( this.buttonsFadeTimeout ) {
clearTimeout( this.buttonsFadeTimeout );
}
// Stop ongoing animations and make sure the buttons that need to be displayed are displayed
this.stopFade();
// mousePosition can be empty, for instance when we enter fullscreen and haven't
// recorded a real mousemove event yet
if ( !mousePosition
|| !this.isAnyActiveButtonHovered( mousePosition.x, mousePosition.y ) ) {
this.fadeOut();
}
};
mw.mmv.ui.Buttons = Buttons;
}( mediaWiki, jQuery, OO ) );

View file

@ -0,0 +1,73 @@
@navbutton-width: 18px;
@buttons-offset: 5px;
.mlb-close,
.mlb-fullscreen,
.mw-mlb-next-image,
.mw-mlb-prev-image {
position: fixed;
background-repeat: no-repeat;
opacity: 0.8;
border: none;
z-index: 1003;
&:hover {
opacity: 1;
}
}
.mlb-close,
.mlb-fullscreen {
right: @buttons-offset;
left: auto;
transition: opacity 0.25s;
background-position: center;
}
.mw-mlb-next-image,
.mw-mlb-prev-image {
top: -999px;
width: 80px;
height: 120px;
cursor: pointer;
transition: opacity 0.25s, margin 0.25s;
&.disabled {
display: none;
cursor: none;
}
}
.mlb-close {
top: @buttons-offset;
/* @embed */
background-image: url(img/mw-close.svg);
}
.mlb-fullscreen {
top: (@buttons-offset + 37px);
/* @embed */
background-image: url(img/mw-fullscreen-ltr.svg);
}
.mw-mlb-next-image {
/* @embed */
background-image: url(img/next-ltr.svg);
background-position: right;
right: @navbutton-width;
&:hover {
margin-right: -4px;
}
}
.mw-mlb-prev-image {
/* @embed */
background-image: url(img/prev-ltr.svg);
background-position: left;
left: @navbutton-width;
&:hover {
margin-left: -4px;
}
}

View file

@ -184,5 +184,14 @@
return false;
};
/**
* Closes the dialog forcefully
*/
FRP.closeDialog = function () {
if ( this.$dialog ) {
this.$dialog.dialog( 'close' );
}
};
mw.mmv.ui.FileReuse = FileReuse;
}( mediaWiki, jQuery, OO ) );

View file

@ -69,37 +69,37 @@
assert.ok( !lightbox.isFullscreen, 'Lightbox knows that it\'s not in fullscreen mode' );
assert.ok( lightbox.panel.$imageMetadata.is( ':visible' ), 'Image metadata is visible' );
lightbox.fadeOutButtons = function() {
lightbox.buttons.fadeOut = function() {
assert.ok( true, 'Opening fullscreen triggers a fadeout' );
};
// Pretend that the mouse cursor is on top of the button
buttonOffset = lightbox.$fullscreenButton.offset();
buttonOffset = lightbox.buttons.$fullscreen.offset();
lightbox.mousePosition = { x: buttonOffset.left, y: buttonOffset.top };
// Enter fullscreen
lightbox.$fullscreenButton.click();
lightbox.buttons.$fullscreen.click();
lightbox.fadeOutButtons = $.noop;
lightbox.buttons.fadeOut = $.noop;
assert.ok( lightbox.isFullscreen, 'Lightbox knows that it\'s in fullscreen mode' );
oldRevealButtonsAndFadeIfNeeded = lightbox.revealButtonsAndFadeIfNeeded;
oldRevealButtonsAndFadeIfNeeded = lightbox.buttons.revealAndFade;
lightbox.revealButtonsAndFadeIfNeeded = function() {
lightbox.buttons.revealAndFade = function( position ) {
assert.ok( true, 'Moving the cursor triggers a reveal + fade' );
oldRevealButtonsAndFadeIfNeeded.call( this );
oldRevealButtonsAndFadeIfNeeded.call( this, position );
};
// Pretend that the mouse cursor moved to the top-left corner
lightbox.mousemove( { pageX: 0, pageY: 0 } );
lightbox.revealButtonsAndFadeIfNeeded = $.noop;
lightbox.buttons.revealAndFadeIfNeeded = $.noop;
assert.ok( !lightbox.panel.$imageMetadata.is( ':visible' ), 'Image metadata is hidden' );
// Exit fullscreen
lightbox.$fullscreenButton.click();
// Exiting fullscreen
lightbox.buttons.$fullscreen.click();
assert.ok( lightbox.panel.$imageMetadata.is( ':visible' ), 'Image metadata is visible' );
assert.ok( !lightbox.isFullscreen, 'Lightbox knows that it\'s not in fullscreen mode' );
@ -117,26 +117,26 @@
// Attach lightbox to testing fixture to avoid interference with other tests.
lightbox.attach( '#qunit-fixture' );
$.each ( lightbox.$buttons, function ( idx, e ) {
$.each ( lightbox.buttons.$buttons, function ( idx, e ) {
var $e = $( e ),
offset = $e.show().offset(),
width = $e.width(),
height = $e.height(),
disabled = $e.hasClass( 'disabled' );
assert.strictEqual( lightbox.isAnyActiveButtonHovered( offset.left, offset.top ),
assert.strictEqual( lightbox.buttons.isAnyActiveButtonHovered( offset.left, offset.top ),
!disabled,
'Hover detection works for top-left corner of element' );
assert.strictEqual( lightbox.isAnyActiveButtonHovered( offset.left + width, offset.top ),
assert.strictEqual( lightbox.buttons.isAnyActiveButtonHovered( offset.left + width, offset.top ),
!disabled,
'Hover detection works for top-right corner of element' );
assert.strictEqual( lightbox.isAnyActiveButtonHovered( offset.left, offset.top + height ),
assert.strictEqual( lightbox.buttons.isAnyActiveButtonHovered( offset.left, offset.top + height ),
!disabled,
'Hover detection works for bottom-left corner of element' );
assert.strictEqual( lightbox.isAnyActiveButtonHovered( offset.left + width, offset.top + height ),
assert.strictEqual( lightbox.buttons.isAnyActiveButtonHovered( offset.left + width, offset.top + height ),
!disabled,
'Hover detection works for bottom-right corner of element' );
assert.strictEqual( lightbox.isAnyActiveButtonHovered(
assert.strictEqual( lightbox.buttons.isAnyActiveButtonHovered(
offset.left + ( width / 2 ), offset.top + ( height / 2 ) ),
!disabled,
'Hover detection works for center of element' );