From 22bdbd404154b4bee4257e766bbc82422f6bdd61 Mon Sep 17 00:00:00 2001 From: Gilles Dubuc Date: Tue, 8 Apr 2014 11:54:30 +0200 Subject: [PATCH] Show tooltip when all sorts of conditions are met Change-Id: I987d5d517c3db2409e138b85b90115260d9116bd Mingle: https://wikimedia.mingle.thoughtworks.com/projects/multimedia/cards/261 --- resources/mmv/mmv.js | 27 ++++++ resources/mmv/ui/mmv.ui.metadataPanel.js | 2 +- resources/mmv/ui/mmv.ui.stripeButtons.js | 91 ++++++++++++++++++- .../qunit/mmv/ui/mmv.ui.stripeButtons.test.js | 59 +++++++++++- 4 files changed, 175 insertions(+), 4 deletions(-) diff --git a/resources/mmv/mmv.js b/resources/mmv/mmv.js index 71724a28d..be456a922 100755 --- a/resources/mmv/mmv.js +++ b/resources/mmv/mmv.js @@ -632,6 +632,33 @@ } }; + /** + * @event mmv.close + * Fired when the viewer should be closed. This is used by components (e.g. the close button) + * to notify the main app that it should close. + */ + /** + * @event mmv-close + * Fired when the viewer is closed. This is used by the main app to notify other components + * (notably the bootstrap). + */ + /** + * @event mmv-next + * Fired when the user requests the next image. + */ + /** + * @event mmv-prev + * Fired when the user requests the previous image. + */ + /** + * @event mmv-resize + * Fired when the screen size changes. + */ + /** + * @event mmv-request-thumbnail + * Used by components to request a thumbnail URL for the current thumbnail, with a given size. + * @param {number} size + */ /** * Registers all event handlers */ diff --git a/resources/mmv/ui/mmv.ui.metadataPanel.js b/resources/mmv/ui/mmv.ui.metadataPanel.js index 2c1714d7e..2967fcb04 100644 --- a/resources/mmv/ui/mmv.ui.metadataPanel.js +++ b/resources/mmv/ui/mmv.ui.metadataPanel.js @@ -215,7 +215,7 @@ }; MPP.initializeButtons = function () { - this.buttons = new mw.mmv.ui.StripeButtons( this.$titleDiv ); + this.buttons = new mw.mmv.ui.StripeButtons( this.$titleDiv, window.localStorage ); }; /** diff --git a/resources/mmv/ui/mmv.ui.stripeButtons.js b/resources/mmv/ui/mmv.ui.stripeButtons.js index dc82bfc45..756aa0f65 100644 --- a/resources/mmv/ui/mmv.ui.stripeButtons.js +++ b/resources/mmv/ui/mmv.ui.stripeButtons.js @@ -25,10 +25,14 @@ * metadata panel). * @constructor * @param {jQuery} $container + * @param {Object} localStorage the localStorage object, for dependency injection */ - function StripeButtons( $container ) { + function StripeButtons( $container, localStorage ) { mw.mmv.ui.Element.call( this, $container ); + /** @property {Object} localStorage the window.localStorage object */ + this.localStorage = localStorage; + this.$buttonContainer = $( '
' ) .addClass( 'mw-mmv-stripe-button-container' ) .appendTo( $container ); @@ -106,10 +110,83 @@ href: this.getFeedbackSurveyUrl() } ).click( function ( e ) { buttons.openSurveyInNewWindow(); + buttons.maxOutTooltipDisplayCount(); e.preventDefault(); } ); }; + SBP.feedbackSettings = { + /** Show the tooltip this many seconds to get the user's attention, even when it is not hovered. */ + tooltipDisplayDuration: 5, + /** Wait for this long after the viewer is opened, before showing the tooltip. */ + tooltipDelay: 5, + /** Only show the tooltip this many times */ + tooltipMaxDisplayCount: 3 + }; + + /** + * Returns the number of times the tooltip was shown so far. This number is set to 999 if the + * user clicked on the link already, or we cannot count how many times the tooltip was shown + * already. + * @return {number} + */ + SBP.getTooltipDisplayCount = function () { + if ( !this.localStorage ) { + return 999; + } + if ( this.tooltipDisplayCount === undefined ) { + this.tooltipDisplayCount = this.localStorage.getItem( 'mmv.tooltipDisplayCount' ); + if ( this.tooltipDisplayCount === null ) { + this.tooltipDisplayCount = 0; + this.localStorage.setItem( 'mmv.tooltipDisplayCount', 0 ); + } + } + return this.tooltipDisplayCount; + }; + + /** + * Increases tooltip display count. + */ + SBP.increaseTooltipDisplayCount = function () { + this.getTooltipDisplayCount(); + if ( this.tooltipDisplayCount !== undefined ) { + this.tooltipDisplayCount++; + this.localStorage.setItem( 'mmv.tooltipDisplayCount', this.tooltipDisplayCount ); + } + }; + + /** + * Sets tooltip display count so large that the tooltip will never be shown again. + * We use this for users who already opened the form. + */ + SBP.maxOutTooltipDisplayCount = function () { + this.getTooltipDisplayCount(); + if ( this.tooltipDisplayCount !== undefined ) { + this.tooltipDisplayCount = 999; + this.localStorage.setItem( 'mmv.tooltipDisplayCount', this.tooltipDisplayCount ); + } + }; + + /** + * Show the tooltip to the user if it was not shown often enough yet. + */ + SBP.maybeDisplayTooltip = function () { + if ( + this.readyToShowFeedbackTooltip && + this.getTooltipDisplayCount() < this.feedbackSettings.tooltipMaxDisplayCount + ) { + this.buttons.$feedback.tipsy( 'show' ); + this.setTimer( 'feedbackTooltip.hide', function () { + this.buttons.$feedback.tipsy( 'hide' ); + }, this.feedbackSettings.tooltipDisplayDuration * 1000 ); + this.increaseTooltipDisplayCount(); + this.readyToShowFeedbackTooltip = false; + } else { + // if the tooltip is visible already, make sure it is not hidden too quickly + this.resetTimer( 'feedbackTooltip.hide' ); + } + }; + /** * Checks if it is suitable to show a survey to the current user. */ @@ -193,6 +270,8 @@ if ( !mw.user.isAnon() ) { this.setDescriptionPageButton( imageInfo, repoInfo ); } + + this.maybeDisplayTooltip(); }; /** @@ -221,7 +300,7 @@ this.setInlineStyle( 'stripe-button-description-page', '.mw-mmv-stripe-button-dynamic:before {' + 'background-image: url("' + repoInfo.favIcon + '");' + - '}' + '}' ); } }; @@ -263,6 +342,12 @@ this.handleEvent( 'mmv-reuse-closed', function () { buttons.$reuse.removeClass( 'open' ); } ); + + this.readyToShowFeedbackTooltip = false; + this.setTimer( 'feedbackTooltip.show', function () { + this.readyToShowFeedbackTooltip = true; + this.maybeDisplayTooltip(); + }, this.feedbackSettings.tooltipDelay * 1000 ); }; /** @@ -271,6 +356,8 @@ SBP.unattach = function () { this.constructor.super.prototype.unattach.call( this ); this.buttons.$reuse.off( 'click.mmv-stripeButtons' ); + + this.clearTimer( 'feedbackTooltip.show' ); }; mw.mmv.ui.StripeButtons = StripeButtons; diff --git a/tests/qunit/mmv/ui/mmv.ui.stripeButtons.test.js b/tests/qunit/mmv/ui/mmv.ui.stripeButtons.test.js index ed1280bc9..11ca67b79 100644 --- a/tests/qunit/mmv/ui/mmv.ui.stripeButtons.test.js +++ b/tests/qunit/mmv/ui/mmv.ui.stripeButtons.test.js @@ -23,6 +23,7 @@ // pretend surveys are enabled for this site oldShowSurvey = mw.config.get( 'wgMultimediaViewer' ).showSurvey; mw.config.get( 'wgMultimediaViewer' ).showSurvey = true; + this.clock = this.sandbox.useFakeTimers(); }, teardown: function () { mw.config.get( 'wgMultimediaViewer' ).showSurvey = oldShowSurvey; @@ -31,7 +32,10 @@ function createStripeButtons() { var fixture = $( '#qunit-fixture' ); - return new mw.mmv.ui.StripeButtons( fixture ); + return new mw.mmv.ui.StripeButtons( fixture, { + getItem: function () { return 999; }, + setItem: $.noop + } ); } QUnit.test( 'Sanity test, object creation and UI construction', 4, function ( assert ) { @@ -110,4 +114,57 @@ $( document ).off( 'mmv-reuse-open.test' ); } ); + + QUnit.test( 'Feedback tooltip', 8, function ( assert ) { + var buttons = createStripeButtons(), + displayCount, + hasTooltip = function () { return !!$( '.tipsy' ).length; }; + + this.sandbox.stub( buttons.localStorage, 'getItem', function () { return displayCount; } ); + this.sandbox.stub( buttons.localStorage, 'setItem', function ( _, val ) { displayCount = val; } ); + + displayCount = 0; + buttons.attach(); + + assert.ok( !hasTooltip(), 'No tooltip initially' ); + + this.clock.tick( 1000 ); + assert.ok( !hasTooltip(), 'No tooltip after 1s' ); + + this.clock.tick( 5000 ); + assert.ok( hasTooltip(), 'Tooltip visible after 6s' ); + assert.strictEqual( displayCount, 1, 'displayCount was increased' ); + + this.clock.tick( 5000 ); + assert.ok( !hasTooltip(), 'Tooltip hidden again after 11s' ); + + buttons.unattach(); + delete buttons.tooltipDisplayCount; + + displayCount = 3; + buttons.attach(); + + this.clock.tick( 6000 ); + assert.ok( !hasTooltip(), 'No tooltip after 6s when display count limit reached' ); + + buttons.unattach(); + delete buttons.tooltipDisplayCount; + + displayCount = 0; + buttons.openSurveyInNewWindow = this.sandbox.stub(); + buttons.attach(); + buttons.buttons.$feedback.triggerHandler( 'click' ); + + this.clock.tick( 6000 ); + assert.ok( !hasTooltip(), 'No tooltip if button was clicked' ); + + buttons.unattach(); + delete buttons.tooltipDisplayCount; + + displayCount = 0; + buttons.attach(); + buttons.unattach(); + this.clock.tick( 6000 ); + assert.ok( !hasTooltip(), 'No tooltip when unattached' ); + } ); }( mediaWiki, jQuery ) );