summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/MultimediaViewer/resources/mmv/ui/mmv.ui.canvas.js
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/extensions/MultimediaViewer/resources/mmv/ui/mmv.ui.canvas.js')
-rw-r--r--www/wiki/extensions/MultimediaViewer/resources/mmv/ui/mmv.ui.canvas.js467
1 files changed, 467 insertions, 0 deletions
diff --git a/www/wiki/extensions/MultimediaViewer/resources/mmv/ui/mmv.ui.canvas.js b/www/wiki/extensions/MultimediaViewer/resources/mmv/ui/mmv.ui.canvas.js
new file mode 100644
index 00000000..61d33624
--- /dev/null
+++ b/www/wiki/extensions/MultimediaViewer/resources/mmv/ui/mmv.ui.canvas.js
@@ -0,0 +1,467 @@
+/*
+ * This file is part of the MediaWiki extension MediaViewer.
+ *
+ * MediaViewer 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.
+ *
+ * MediaViewer 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 MediaViewer. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+( function ( mw, $, oo ) {
+ var C;
+
+ /**
+ * UI component that contains the multimedia element to be displayed.
+ * This first version assumes an image but it can be extended to other
+ * media types (video, sound, presentation, etc.).
+ *
+ * @class mw.mmv.ui.Canvas
+ * @extends mw.mmv.ui.Element
+ * @constructor
+ * @param {jQuery} $container Canvas' container
+ * @param {jQuery} $imageWrapper
+ * @param {jQuery} $mainWrapper
+ */
+ function Canvas( $container, $imageWrapper, $mainWrapper ) {
+ mw.mmv.ui.Element.call( this, $container );
+
+ /**
+ * @property {boolean}
+ * @private
+ */
+ this.dialogOpen = false;
+
+ /**
+ * @property {mw.mmv.ThumbnailWidthCalculator}
+ * @private
+ */
+ this.thumbnailWidthCalculator = new mw.mmv.ThumbnailWidthCalculator();
+
+ /**
+ * Contains image.
+ * @property {jQuery}
+ */
+ this.$imageDiv = $( '<div>' )
+ .addClass( 'mw-mmv-image' );
+
+ this.$imageDiv.appendTo( this.$container );
+
+ /**
+ * Container of canvas and controls, needed for canvas size calculations.
+ * @property {jQuery}
+ * @private
+ */
+ this.$imageWrapper = $imageWrapper;
+
+ /**
+ * Main container of image and metadata, needed to propagate events.
+ * @property {jQuery}
+ * @private
+ */
+ this.$mainWrapper = $mainWrapper;
+
+ /**
+ * Raw metadata of current image, needed for canvas size calculations.
+ * @property {mw.mmv.LightboxImage}
+ * @private
+ */
+ this.imageRawMetadata = null;
+ }
+ oo.inheritClass( Canvas, mw.mmv.ui.Element );
+ C = Canvas.prototype;
+
+ /**
+ * Maximum blownup factor tolerated
+ * @property MAX_BLOWUP_FACTOR
+ * @static
+ */
+ Canvas.MAX_BLOWUP_FACTOR = 11;
+
+ /**
+ * Blowup factor threshold at which blurring kicks in
+ * @property BLUR_BLOWUP_FACTOR_THRESHOLD
+ * @static
+ */
+ Canvas.BLUR_BLOWUP_FACTOR_THRESHOLD = 2;
+
+ /**
+ * Clears everything.
+ */
+ C.empty = function () {
+ this.$imageDiv.addClass( 'empty' ).removeClass( 'error' );
+
+ this.$imageDiv.empty();
+ };
+
+ /**
+ * Sets image on the canvas; does not resize it to fit. This is used to make the placeholder
+ * image available; it will be resized and displayed by #maybeDisplayPlaceholder().
+ * FIXME maybeDisplayPlaceholder() receives the placeholder so it is very unclear why this
+ * is necessary at all (apart from setting the LightboxImage, which is used in size calculations).
+ *
+ * @param {mw.mmv.LightboxImage} imageRawMetadata
+ * @param {jQuery} $imageElement
+ */
+ C.set = function ( imageRawMetadata, $imageElement ) {
+ this.$imageDiv.removeClass( 'empty' );
+
+ this.imageRawMetadata = imageRawMetadata;
+ this.$image = $imageElement;
+ this.setUpImageClick();
+
+ this.$imageDiv.html( this.$image );
+ };
+
+ /**
+ * Resizes image to the given dimensions and displays it on the canvas.
+ * This is used to display the actual image; it assumes set function was already called before.
+ *
+ * @param {mw.mmv.model.Thumbnail} thumbnail thumbnail information
+ * @param {HTMLImageElement} imageElement
+ * @param {mw.mmv.model.ThumbnailWidth} imageWidths
+ */
+ C.setImageAndMaxDimensions = function ( thumbnail, imageElement, imageWidths ) {
+ var $image = $( imageElement );
+
+ // we downscale larger images but do not scale up smaller ones, that would look ugly
+ if ( thumbnail.width > imageWidths.cssWidth ) {
+ imageElement.width = imageWidths.cssWidth;
+ imageElement.height = imageWidths.cssHeight;
+ }
+
+ if ( !this.$image.is( imageElement ) ) { // http://bugs.jquery.com/ticket/4087
+ this.$image.replaceWith( $image );
+ this.$image = $image;
+
+ // Since the image element got replaced, we need to rescue the dialog-open class.
+ this.$image.toggleClass( 'mw-mmv-dialog-is-open', this.dialogOpen );
+
+ this.setUpImageClick();
+ }
+ };
+
+ /**
+ * Handles a "dialog open/close" event from dialogs on the page.
+ *
+ * @param {jQuery.Event} e
+ */
+ C.handleDialogEvent = function ( e ) {
+ switch ( e.type ) {
+ case 'mmv-download-opened':
+ this.downloadOpen = true;
+ break;
+ case 'mmv-download-closed':
+ this.downloadOpen = false;
+ break;
+ case 'mmv-reuse-opened':
+ this.reuseOpen = true;
+ break;
+ case 'mmv-reuse-closed':
+ this.reuseOpen = false;
+ break;
+ case 'mmv-options-opened':
+ this.optionsOpen = true;
+ break;
+ case 'mmv-options-closed':
+ this.optionsOpen = false;
+ break;
+ }
+
+ this.dialogOpen = this.reuseOpen || this.downloadOpen || this.optionsOpen;
+ this.$image.toggleClass( 'mw-mmv-dialog-is-open', this.dialogOpen );
+ };
+
+ /**
+ * Registers click listener on the image.
+ */
+ C.setUpImageClick = function () {
+ var canvas = this;
+
+ this.handleEvent( 'mmv-reuse-opened', $.proxy( this.handleDialogEvent, this ) );
+ this.handleEvent( 'mmv-reuse-closed', $.proxy( this.handleDialogEvent, this ) );
+ this.handleEvent( 'mmv-download-opened', $.proxy( this.handleDialogEvent, this ) );
+ this.handleEvent( 'mmv-download-closed', $.proxy( this.handleDialogEvent, this ) );
+ this.handleEvent( 'mmv-options-opened', $.proxy( this.handleDialogEvent, this ) );
+ this.handleEvent( 'mmv-options-closed', $.proxy( this.handleDialogEvent, this ) );
+
+ this.$image.on( 'click.mmv-canvas', function ( e ) {
+ // ignore clicks if the metadata panel or one of the dialogs is open - assume the intent is to
+ // close it in this case; that will be handled elsewhere
+ if (
+ !canvas.dialogOpen &&
+ // FIXME a UI component should not know about its parents
+ canvas.$container.closest( '.metadata-panel-is-open' ).length === 0
+ ) {
+ e.stopPropagation(); // don't let $imageWrapper handle this
+ mw.mmv.actionLogger.log( 'view-original-file' ).always( function () {
+ $( document ).trigger( 'mmv-viewfile' );
+ } );
+ }
+ } );
+
+ // open the download panel on right clicking the image
+ this.$image.on( 'mousedown.mmv-canvas', function ( e ) {
+ if ( e.which === 3 ) {
+ mw.mmv.actionLogger.log( 'right-click-image' );
+ if ( !canvas.downloadOpen ) {
+ $( document ).trigger( 'mmv-download-open', e );
+ e.stopPropagation();
+ }
+ }
+ } );
+ };
+
+ /**
+ * Registers listeners.
+ */
+ C.attach = function () {
+ var canvas = this;
+
+ $( window ).on( 'resize.mmv-canvas', $.debounce( 100, function () {
+ canvas.$mainWrapper.trigger( $.Event( 'mmv-resize-end' ) );
+ } ) );
+
+ this.$imageWrapper.on( 'click.mmv-canvas', function () {
+ if ( canvas.$container.closest( '.metadata-panel-is-open' ).length > 0 ) {
+ canvas.$mainWrapper.trigger( 'mmv-panel-close-area-click' );
+ }
+ } );
+ };
+
+ /**
+ * Clears listeners.
+ */
+ C.unattach = function () {
+ this.clearEvents();
+
+ $( window ).off( 'resize.mmv-canvas' );
+
+ this.$imageWrapper.off( 'click.mmv-canvas' );
+ };
+
+ /**
+ * Sets page thumbnail for display if blowupFactor <= MAX_BLOWUP_FACTOR. Otherwise thumb is not set.
+ * The image gets also blured to avoid pixelation if blowupFactor > BLUR_BLOWUP_FACTOR_THRESHOLD.
+ * We set SVG files to the maximum screen size available.
+ * Assumes set function called before.
+ *
+ * @param {{width: number, height: number}} size
+ * @param {jQuery} $imagePlaceholder Image placeholder to be displayed while the real image loads.
+ * @param {mw.mmv.model.ThumbnailWidth} imageWidths
+ * @return {boolean} Whether the image was blured or not
+ */
+ C.maybeDisplayPlaceholder = function ( size, $imagePlaceholder, imageWidths ) {
+ var targetWidth,
+ targetHeight,
+ blowupFactor,
+ blurredThumbnailShown = false;
+
+ // Assume natural thumbnail size¸
+ targetWidth = size.width;
+ targetHeight = size.height;
+
+ // If the image is bigger than the screen we need to resize it
+ if ( size.width > imageWidths.cssWidth ) { // This assumes imageInfo.width in CSS units
+ targetWidth = imageWidths.cssWidth;
+ targetHeight = imageWidths.cssHeight;
+ }
+
+ blowupFactor = targetWidth / $imagePlaceholder.width();
+
+ // If the placeholder is too blown up, it's not worth showing it
+ if ( blowupFactor > Canvas.MAX_BLOWUP_FACTOR ) {
+ return blurredThumbnailShown;
+ }
+
+ $imagePlaceholder.width( targetWidth );
+ $imagePlaceholder.height( targetHeight );
+
+ // Only blur the placeholder if it's blown up significantly
+ if ( blowupFactor > Canvas.BLUR_BLOWUP_FACTOR_THRESHOLD ) {
+ this.blur( $imagePlaceholder );
+ blurredThumbnailShown = true;
+ }
+
+ this.set( this.imageRawMetadata, $imagePlaceholder.show() );
+
+ return blurredThumbnailShown;
+ };
+
+ /**
+ * Blur image
+ *
+ * @param {jQuery} $image Image to be blurred.
+ */
+ C.blur = function ( $image ) {
+ // We have to apply the SVG filter here, it doesn't work when defined in the .less file
+ // We can't use an external SVG file because filters can't be accessed cross-domain
+ // We can't embed the SVG file because accessing the filter inside of it doesn't work
+ $image.addClass( 'blurred' ).css( 'filter', 'url("#gaussian-blur")' );
+ };
+
+ /**
+ * Animates the image into focus
+ */
+ C.unblurWithAnimation = function () {
+ var self = this,
+ animationLength = 300;
+
+ // The blurred class has an opacity < 1. This animated the image to become fully opaque
+ this.$image
+ .addClass( 'blurred' )
+ .animate( { opacity: 1.0 }, animationLength );
+
+ // During the same amount of time (animationLength) we animate a blur value from 3.0 to 0.0
+ // We pass that value to an inline CSS Gaussian blur effect
+ $( { blur: 3.0 } ).animate( { blur: 0.0 }, {
+ duration: animationLength,
+ step: function ( step ) {
+ self.$image.css( { '-webkit-filter': 'blur(' + step + 'px)',
+ filter: 'blur(' + step + 'px)' } );
+ },
+ complete: function () {
+ // When the animation is complete, the blur value is 0, clean things up
+ self.unblur();
+ }
+ } );
+ };
+
+ C.unblur = function () {
+ // We apply empty CSS values to remove the inline styles applied by jQuery
+ // so that they don't get in the way of styles defined in CSS
+ this.$image.css( { '-webkit-filter': '', opacity: '', filter: '' } )
+ .removeClass( 'blurred' );
+ };
+
+ /**
+ * Displays a message and error icon when loading the image fails.
+ *
+ * @param {string} error error message
+ */
+ C.showError = function ( error ) {
+ var errorDetails, description, errorUri, retryLink, reportLink,
+ canvasDimensions = this.getDimensions(),
+ thumbnailDimensions = this.getCurrentImageWidths(),
+ htmlUtils = new mw.mmv.HtmlUtils();
+
+ errorDetails = [
+ 'error: ' + error,
+ 'URL: ' + location.href,
+ 'user agent: ' + navigator.userAgent,
+ 'screen size: ' + screen.width + 'x' + screen.height,
+ 'canvas size: ' + canvasDimensions.width + 'x' + canvasDimensions.height,
+ 'image size: ' + this.imageRawMetadata.originalWidth + 'x' + this.imageRawMetadata.originalHeight,
+ 'thumbnail size: CSS: ' + thumbnailDimensions.cssWidth + 'x' + thumbnailDimensions.cssHeight +
+ ', screen width: ' + thumbnailDimensions.screen + ', real width: ' + thumbnailDimensions.real
+ ];
+ // ** is bolding in Phabricator
+ description = '**' + mw.message( 'multimediaviewer-errorreport-privacywarning' ).text() + '**\n\n\n' +
+ 'Error details:\n\n' + errorDetails.join( '\n' );
+ errorUri = mw.msg( 'multimediaviewer-report-issue-url', encodeURIComponent( description ) );
+
+ retryLink = $( '<a>' ).addClass( 'mw-mmv-retry-link' ).text(
+ mw.msg( 'multimediaviewer-thumbnail-error-retry' ) );
+ reportLink = $( '<a>' ).attr( 'href', errorUri ).text(
+ mw.msg( 'multimediaviewer-thumbnail-error-report' ) );
+
+ this.$imageDiv.empty()
+ .addClass( 'error' )
+ .append(
+ $( '<div>' ).addClass( 'error-box' ).append(
+ $( '<div>' ).addClass( 'mw-mmv-error-text' ).text(
+ mw.msg( 'multimediaviewer-thumbnail-error' )
+ )
+ ).append(
+ $( '<div>' ).addClass( 'mw-mmv-error-description' ).append(
+ mw.msg( 'multimediaviewer-thumbnail-error-description',
+ htmlUtils.jqueryToHtml( retryLink ),
+ error,
+ htmlUtils.jqueryToHtml( reportLink )
+ )
+ )
+ )
+ );
+ this.$imageDiv.find( '.mw-mmv-retry-link' ).click( function () {
+ location.reload();
+ } );
+ };
+
+ /**
+ * Returns width and height of the canvas area (i.e. the space available for the image).
+ *
+ * @param {boolean} forFullscreen if true, return size in fullscreen mode; otherwise, return current size
+ * (which might still be fullscreen mode).
+ * @return {Object} Width and height in CSS pixels
+ */
+ C.getDimensions = function ( forFullscreen ) {
+ var $window = $( window ),
+ $aboveFold = $( '.mw-mmv-above-fold' ),
+ isFullscreened = !!$aboveFold.closest( '.jq-fullscreened' ).length,
+ // Don't rely on this.$imageWrapper's sizing because it's fragile.
+ // Depending on what the wrapper contains, its size can be 0 on some browsers.
+ // Therefore, we calculate the available space manually
+ availableWidth = $window.width(),
+ availableHeight = $window.height() - ( isFullscreened ? 0 : $aboveFold.outerHeight() );
+
+ if ( forFullscreen ) {
+ return {
+ width: screen.width,
+ height: screen.height
+ };
+ } else {
+ return {
+ width: availableWidth,
+ height: availableHeight
+ };
+ }
+ };
+
+ /**
+ * Gets the widths for a given lightbox image.
+ *
+ * @param {mw.mmv.LightboxImage} image
+ * @return {mw.mmv.model.ThumbnailWidth}
+ */
+ C.getLightboxImageWidths = function ( image ) {
+ var thumb = image.thumbnail,
+ canvasDimensions = this.getDimensions();
+
+ return this.thumbnailWidthCalculator.calculateWidths(
+ canvasDimensions.width, canvasDimensions.height, thumb.width, thumb.height );
+ };
+
+ /**
+ * Gets the fullscreen widths for a given lightbox image.
+ * Intended for use before the viewer is in fullscreen mode
+ * (in fullscreen mode getLightboxImageWidths() works fine).
+ *
+ * @param {mw.mmv.LightboxImage} image
+ * @return {mw.mmv.model.ThumbnailWidth}
+ */
+ C.getLightboxImageWidthsForFullscreen = function ( image ) {
+ var thumb = image.thumbnail,
+ canvasDimensions = this.getDimensions( true );
+
+ return this.thumbnailWidthCalculator.calculateWidths(
+ canvasDimensions.width, canvasDimensions.height, thumb.width, thumb.height );
+ };
+
+ /**
+ * Gets the widths for the current lightbox image.
+ *
+ * @return {mw.mmv.model.ThumbnailWidth}
+ */
+ C.getCurrentImageWidths = function () {
+ return this.getLightboxImageWidths( this.imageRawMetadata );
+ };
+
+ mw.mmv.ui.Canvas = Canvas;
+}( mediaWiki, jQuery, OO ) );