/*
* 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 .
*/
const UiElement = require( './mmv.ui.js' );
/**
* Represents the buttons which are displayed over the image - next, previous, close
* and fullscreen.
*/
class CanvasButtons extends UiElement {
/**
* @param {jQuery} $container The parent element we should put the buttons into.
* @param {jQuery} $closeButton The close button element from the parent class.
* @param {jQuery} $fullscreenButton The fullscreen button from the parent class.
* @fires MultimediaViewer#mmv-close
*/
constructor( $container, $closeButton, $fullscreenButton ) {
super( $container );
this.$close = $closeButton;
this.$fullscreen = $fullscreenButton;
this.$reuse = $( '' )
.attr( 'role', 'button' )
.addClass( 'mw-mmv-reuse-button' )
.text( '\u00A0' )
.prop( 'title', mw.msg( 'multimediaviewer-reuse-link' ) );
this.$options = $( '' )
.text( ' ' )
.prop( 'title', mw.msg( 'multimediaviewer-options-tooltip' ) )
.addClass( 'mw-mmv-options-button' );
this.$download = $( '' )
.attr( 'role', 'button' )
.addClass( 'mw-mmv-download-button' )
.text( '\u00A0' )
.prop( 'title', mw.msg( 'multimediaviewer-download-link' ) );
this.$next = $( '' )
.prop( 'title', mw.msg( 'multimediaviewer-next-image-alt-text' ) )
.addClass( 'mw-mmv-next-image disabled' )
.text( '\u00A0' );
this.$prev = $( '' )
.prop( 'title', mw.msg( 'multimediaviewer-prev-image-alt-text' ) )
.addClass( 'mw-mmv-prev-image disabled' )
.text( '\u00A0' );
this.$nav = this.$next
.add( this.$prev );
this.$buttons = this.$close
.add( this.$download )
.add( this.$reuse )
.add( this.$fullscreen )
.add( this.$options )
.add( this.$next )
.add( this.$prev );
this.$buttons.appendTo( this.$container );
$( document ).on( 'mmv-close', () => {
this.$nav.addClass( 'disabled' );
} );
this.$close.on( 'click', () => {
$container.trigger( $.Event( 'mmv-close' ) );
} );
this.$next.on( 'click', () => {
this.emit( 'next' );
} );
this.$prev.on( 'click', () => {
this.emit( 'prev' );
} );
}
/**
* Sets the top offset for the navigation buttons.
*
* @param {number} offset
*/
setOffset( offset ) {
this.$nav.css( {
top: offset
} );
}
/**
* Stops the fading animation of the buttons and cancel any opacity value
*/
stopFade() {
this.$buttons
.stop( true )
.removeClass( 'hidden' )
.css( 'opacity', '' );
this.$container.trigger( $.Event( 'mmv-fade-stopped' ) );
}
/**
* Toggles buttons being disabled or not
*
* @param {boolean} showPrevButton
* @param {boolean} showNextButton
*/
toggle( showPrevButton, showNextButton ) {
this.$next.toggleClass( 'disabled', !showPrevButton );
this.$prev.toggleClass( 'disabled', !showNextButton );
}
/**
* Fades out the active buttons
*/
fadeOut() {
// We don't use animation chaining because delay() can't be stop()ed
this.buttonsFadeTimeout = setTimeout( () => {
// FIXME: Use CSS transition
// eslint-disable-next-line no-jquery/no-animate
this.$buttons.not( '.disabled' ).animate( { opacity: 0 }, 1000, 'swing',
() => {
this.$buttons.addClass( 'hidden' );
this.$container.trigger( $.Event( 'mmv-faded-out' ) );
} );
}, 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 {boolean}
*/
isAnyActiveButtonHovered( 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)
let hovered = false;
this.$buttons.not( '.disabled' ).each( ( idx, e ) => {
const $e = $( e );
const offset = $e.offset();
if ( y >= offset.top &&
// using css( 'height' ) & css( 'width' ) instead of .height()
// and .width() since those don't include padding, and as a
// result can return a smaller size than is actually the button
y <= offset.top + parseInt( $e.css( 'height' ) ) &&
x >= offset.left &&
x <= offset.left + parseInt( $e.css( 'width' ) ) ) {
hovered = true;
}
} );
return hovered;
}
/**
* Reveals all active buttons and schedule a fade out if needed
*
* @param {Object} [mousePosition] Mouse position containing 'x' and 'y' properties
*/
revealAndFade( 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();
}
}
/**
* Registers listeners.
*
* @fires ReuseDialog#mmv-reuse-opened
* @fires ReuseDialog#mmv-reuse-closed
* @fires DownloadDialog#mmv-download-opened
* @fires DownloadDialog#mmv-download-closed
* @fires OptionsDialog#mmv-options-opened
* @fires OptionsDialog#mmv-options-closed
*/
attach() {
this.$reuse.on( 'click.mmv-canvasButtons', ( e ) => {
$( document ).trigger( 'mmv-reuse-open', e );
return false;
} );
this.handleEvent( 'mmv-reuse-opened', () => this.$reuse.addClass( 'open' ) );
this.handleEvent( 'mmv-reuse-closed', () => this.$reuse.removeClass( 'open' ) );
this.$download.on( 'click.mmv-canvasButtons', ( e ) => {
$( document ).trigger( 'mmv-download-open', e );
return false;
} );
this.handleEvent( 'mmv-download-opened', () => this.$download.addClass( 'open' ) );
this.handleEvent( 'mmv-download-closed', () => this.$download.removeClass( 'open' ) );
this.$options.on( 'click.mmv-canvasButtons', ( e ) => {
$( document ).trigger( 'mmv-options-open', e );
e.stopPropagation();
} );
this.handleEvent( 'mmv-options-opened', () => this.$options.addClass( 'open' ) );
this.handleEvent( 'mmv-options-closed', () => this.$options.removeClass( 'open' ) );
this.$download
.add( this.$reuse )
.add( this.$options )
.add( this.$close )
.add( this.$fullscreen );
}
/**
* Removes all UI things from the DOM, or hides them
*/
unattach() {
super.unattach();
this.$download
.add( this.$reuse )
.add( this.$options )
.add( this.$close )
.add( this.$fullscreen )
.off( 'click.mmv-canvasButtons' );
}
/**
* @param {Image} image
*/
set( image ) {
this.$reuse.prop( 'href', image.descriptionUrl );
this.$download.prop( 'href', image.url );
}
empty() {
this.$reuse
.removeClass( 'open' )
.prop( 'href', null );
this.$download
.prop( 'href', null );
}
}
module.exports = CanvasButtons;