jenkins-bot has submitted this change and it was merged.

Change subject: Add 'back' button throughout UW workflow
......................................................................


Add 'back' button throughout UW workflow

* Renamed moveFrom to moveNext, now that we also have
  a second direction to move to

* Added a 'previous' button, very similar to existing
  'next' buttons

* The list of uploaded files needs to be reset in the upload step
  after having completed an upload previously. This is currently
  done when loading the 'upload' step, but that's no longer
  desirable now that we're also able to move back. Instead, the
  uploads should be reset when the wizard actually ends.
  That event hook already existed, it just wasn't being called.

* Upload's moveTo needed some minor changes to support this: it
  shouldn't always hide buttons, it shouldn't assume an empty list
  of uploads, and it should show the correct buttons when continuing.

* showNext should be able to handle already-uploaded files
  (so the finishState should be reset)

* It's now possible to go back to the first step (even when it was
  skipped) and change your preference.

Bug: T122923
Change-Id: Ib8931cdc6d7b2f9ab33536641aa822376ebf6b35
---
M extension.json
M i18n/en.json
M i18n/qqq.json
M includes/specials/SpecialUploadWizard.php
M resources/controller/uw.controller.Deed.js
M resources/controller/uw.controller.Details.js
M resources/controller/uw.controller.Step.js
M resources/controller/uw.controller.Thanks.js
M resources/controller/uw.controller.Tutorial.js
M resources/controller/uw.controller.Upload.js
M resources/mw.UploadWizard.js
M resources/ui/steps/uw.ui.Deed.js
M resources/ui/steps/uw.ui.Details.js
M resources/ui/steps/uw.ui.Tutorial.js
M resources/ui/steps/uw.ui.Upload.js
M resources/ui/uw.ui.Step.js
M resources/uploadWizard.css
M tests/qunit/controller/uw.controller.Tutorial.test.js
18 files changed, 261 insertions(+), 134 deletions(-)

Approvals:
  Bartosz Dziewoński: Looks good to me, approved
  Siebrand: Looks good to me, but someone else must approve
  jenkins-bot: Verified



diff --git a/extension.json b/extension.json
index 6ee37fe..e14d2cd 100644
--- a/extension.json
+++ b/extension.json
@@ -426,6 +426,7 @@
                                "mwe-upwiz-next-file",
                                "mwe-upwiz-next-deeds",
                                "mwe-upwiz-next-details",
+                               "mwe-upwiz-previous",
                                "mwe-upwiz-home",
                                "mwe-upwiz-upload-another",
                                "mwe-prevent-close",
diff --git a/i18n/en.json b/i18n/en.json
index 961a3d7..1ba472f 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -180,6 +180,7 @@
        "mwe-upwiz-next-file": "Continue",
        "mwe-upwiz-next-deeds": "Next",
        "mwe-upwiz-next-details": "Next",
+       "mwe-upwiz-previous": "Back",
        "mwe-upwiz-home": "Go to wiki home page",
        "mwe-upwiz-upload-another": "Upload more files",
        "mwe-prevent-close": "Leaving this page may cause you to lose any 
changes you have made.",
diff --git a/i18n/qqq.json b/i18n/qqq.json
index bbf4e56..40ba432 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -204,6 +204,7 @@
        "mwe-upwiz-next-file": "{{Identical|Continue}}",
        "mwe-upwiz-next-deeds": "{{Identical|Next}}",
        "mwe-upwiz-next-details": "{{Identical|Next}}",
+       "mwe-upwiz-previous": "Button text for going back to the previous 
Upload Wizard stage.\n{{Identical|Back}}",
        "mwe-upwiz-home": "[[file:commons-uw-L52S.png|The End of the 
\"{{MediaWiki:mwe-upwiz-step-thanks/en}}\" page|thumb|right]]\n\nThis is a 
button label, used at the end in the \"{{MediaWiki:mwe-upwiz-step-thanks/en}}\" 
page of the [[:mw:Extension:UploadWizard|MediaWiki Upload Wizard]].",
        "mwe-upwiz-upload-another": "[[file:commons-uw-L52S.png|The End of the 
\"{{MediaWiki:mwe-upwiz-step-thanks/en}}\" page|thumb|right]]\n\nThis is a 
button label, used at the end in the \"{{MediaWiki:mwe-upwiz-step-thanks/en}}\" 
page of the [[:mw:Extension:UploadWizard|MediaWiki Upload Wizard]].",
        "mwe-prevent-close": "Default message for a confirmation dialog used to 
prevent the user from closing the window accidentally.",
diff --git a/includes/specials/SpecialUploadWizard.php 
b/includes/specials/SpecialUploadWizard.php
index a92bd25..6948cd1 100644
--- a/includes/specials/SpecialUploadWizard.php
+++ b/includes/specials/SpecialUploadWizard.php
@@ -344,14 +344,9 @@
                                );
                }
 
-               $tutorialHtml = '';
-               // only load the tutorial HTML if we aren't skipping the first 
step
-               if ( !$this->getUser()->getBoolOption( 'upwiz_skiptutorial' ) &&
-                       $config['tutorial'] !== null && $config['tutorial'] !== 
[] &&
-                       $config['tutorial']['skip'] !== true
-               ) {
-                       $tutorialHtml = UploadWizardTutorial::getHtml( 
$this->campaign );
-               }
+               // always load the html: even if the tutorial is skipped, users 
can
+               // still move back to view it
+               $tutorialHtml = UploadWizardTutorial::getHtml( $this->campaign 
);
 
                // TODO move this into UploadWizard.js or some other javascript 
resource so the upload wizard
                // can be dynamically included ( for example the add media 
wizard )
diff --git a/resources/controller/uw.controller.Deed.js 
b/resources/controller/uw.controller.Deed.js
index 877f8e9..5e48e57 100644
--- a/resources/controller/uw.controller.Deed.js
+++ b/resources/controller/uw.controller.Deed.js
@@ -37,14 +37,14 @@
 
        OO.inheritClass( uw.controller.Deed, uw.controller.Step );
 
-       uw.controller.Deed.prototype.moveFrom = function () {
+       uw.controller.Deed.prototype.moveNext = function () {
                var
                        deedController = this,
                        valid = true,
                        fields;
 
                if ( !this.deedChooser ) {
-                       uw.controller.Step.prototype.moveFrom.call( this );
+                       uw.controller.Step.prototype.moveNext.call( this );
                        return;
                }
 
@@ -69,7 +69,7 @@
                                        }
                                }
 
-                               uw.controller.Step.prototype.moveFrom.call( 
deedController );
+                               uw.controller.Step.prototype.moveNext.call( 
deedController );
                        } );
                }
        };
@@ -94,7 +94,7 @@
                // If all of the uploads are from URLs, then we know the 
licenses
                // already, we don't need this step.
                if ( !showDeed ) {
-                       this.moveFrom();
+                       this.moveNext();
                        return;
                }
 
diff --git a/resources/controller/uw.controller.Details.js 
b/resources/controller/uw.controller.Details.js
index a8595e4..bb9a94a 100644
--- a/resources/controller/uw.controller.Details.js
+++ b/resources/controller/uw.controller.Details.js
@@ -54,15 +54,12 @@
         */
        uw.controller.Details.prototype.moveTo = function ( uploads ) {
                var details = this,
-                       successes = 0;
+                       failures, successes;
 
                uw.controller.Step.prototype.moveTo.call( this, uploads );
 
-               $.each( uploads, function ( i, upload ) {
-                       if ( upload && upload.state !== 'aborted' && 
upload.state !== 'error' ) {
-                               successes++;
-                       }
-               } );
+               failures = this.getUploadStatesCount( [ 'aborted', 'error' ] ) 
+ this.countEmpties();
+               successes = uploads.length - failures;
 
                $.each( uploads, function ( i, upload ) {
                        if ( upload === undefined ) {
@@ -82,22 +79,36 @@
                }
                if ( successes > 0 ) {
                        $.each( uploads, function ( i, upload ) {
-                               upload.on( 'remove-upload', function () {
-                                       details.removeCopyMetadataFeature();
-
-                                       // Make sure we still have more 
multiple uploads adding the
-                                       // copy feature again
-                                       successes--;
-                                       if ( successes > 1 && 
details.config.copyMetadataFeature ) {
-                                               details.addCopyMetadataFeature( 
uploads );
-                                       } else if ( successes < 1 ) {
-                                               // If we have no more uploads, 
go to the "Upload" step. (This will go to "Thanks" step,
-                                               // which will skip itself in 
moveTo() because there are no uploads left.)
-                                               details.moveFrom();
-                                       }
-                               } );
+                               upload.on( 'remove-upload', 
details.removeUpload.bind( details ) );
                        } );
                }
+       };
+
+       uw.controller.Details.prototype.moveNext = function () {
+               var controller = this;
+
+               $.each( this.uploads, function ( i, upload ) {
+                       upload.off( 'remove-upload', 
controller.removeUpload.bind( controller ) );
+               } );
+
+               uw.controller.Step.prototype.moveNext.call( this );
+       };
+
+       uw.controller.Details.prototype.movePrevious = function () {
+               var controller = this;
+
+               $.each( this.uploads, function ( i, upload ) {
+                       // reset step name: if upload was attempted and failed, 
step name
+                       // would be an error, and upload would be removed when 
moving back
+                       upload.state = controller.stepName;
+
+                       // get rid of remove handler, this handler only makes 
sense in this
+                       // exact step - having it bound while in other steps 
could cause
+                       // unexpected issues
+                       upload.off( 'remove-upload', 
controller.removeUpload.bind( controller ) );
+               } );
+
+               uw.controller.Step.prototype.movePrevious.call( this );
        };
 
        uw.controller.Details.prototype.addCopyMetadataFeature = function ( 
uploads ) {
@@ -291,7 +302,7 @@
        uw.controller.Details.prototype.canTransition = function ( upload ) {
                return (
                        uw.controller.Step.prototype.canTransition.call( this, 
upload ) &&
-                       upload.state === 'details'
+                       upload.state === this.stepName
                );
        };
 
@@ -345,7 +356,7 @@
 
                        // Clear error state
                        if ( upload.state === 'error' ) {
-                               upload.state = 'details';
+                               upload.state = details.stepName;
                        }
 
                        // Set details view to have correct title
@@ -354,6 +365,7 @@
 
                // Disable edit interface
                this.ui.disableEdits();
+               this.ui.previousButton.$element.hide();
                // No way to restore this later... We don't handle 
partially-successful uploads very well
                this.removeCopyMetadataFeature();
 
@@ -361,7 +373,7 @@
                        details.showErrors();
 
                        if ( details.showNext() ) {
-                               details.moveFrom();
+                               details.moveNext();
                        }
                } );
        };
@@ -371,8 +383,30 @@
         * See UI class for more.
         */
        uw.controller.Details.prototype.showErrors = function () {
+               this.ui.previousButton.$element.show();
+
                this.ui.showWarnings(); // Scroll to the warning first so that 
any errors will have precedence
                this.ui.showErrors();
        };
 
+       /**
+        * Handler for when an upload is removed.
+        */
+       uw.controller.Details.prototype.removeUpload = function () {
+               var failures = this.getUploadStatesCount( [ 'aborted', 'error' 
] ) + this.countEmpties(),
+                       successes = this.uploads.length - failures;
+
+               this.removeCopyMetadataFeature();
+
+               // Make sure we still have more multiple uploads adding the
+               // copy feature again
+               if ( successes > 1 && this.config.copyMetadataFeature ) {
+                       this.addCopyMetadataFeature( this.uploads );
+               } else if ( successes < 1 ) {
+                       // If we have no more uploads, go to the "Upload" step. 
(This will go to "Thanks" step,
+                       // which will skip itself in moveTo() because there are 
no uploads left.)
+                       this.moveNext();
+               }
+       };
+
 }( mediaWiki, mediaWiki.uploadWizard, jQuery, OO ) );
diff --git a/resources/controller/uw.controller.Step.js 
b/resources/controller/uw.controller.Step.js
index ce84ee5..9eda8e4 100644
--- a/resources/controller/uw.controller.Step.js
+++ b/resources/controller/uw.controller.Step.js
@@ -42,8 +42,14 @@
 
                this.ui = ui;
 
+               this.uploads = [];
+
                this.ui.on( 'next-step', function () {
-                       step.moveFrom();
+                       step.moveNext();
+               } );
+
+               this.ui.on( 'previous-step', function () {
+                       step.movePrevious();
                } );
 
                /**
@@ -51,6 +57,12 @@
                 * The next step in the process.
                 */
                this.nextStep = null;
+
+               /**
+                * @property {uw.controller.Step} previousStep
+                * The previous step in the process.
+                */
+               this.previousStep = null;
        };
 
        OO.mixinClass( uw.controller.Step, OO.EventEmitter );
@@ -73,6 +85,16 @@
        };
 
        /**
+        * Set the previous step in the process.
+        *
+        * @param {uw.controller.Step} step
+        */
+       uw.controller.Step.prototype.setPreviousStep = function ( step ) {
+               this.previousStep = step;
+               this.ui.enablePreviousButton();
+       };
+
+       /**
         * Move to this step.
         *
         * @param {mw.UploadWizardUpload[]} uploads List of uploads being 
carried forward.
@@ -82,8 +104,6 @@
 
                this.movedFrom = false;
 
-               // Through some very convoluted route, this reached code in 
mw.UploadWizard that can
-               // remove items from the `uploads` array here.
                this.emit( 'load' );
 
                this.uploads = uploads || [];
@@ -102,10 +122,10 @@
        };
 
        /**
-        * Move out of this step.
+        * Move to the next step.
         */
-       uw.controller.Step.prototype.moveFrom = function () {
-               this.ui.moveFrom( this.uploads );
+       uw.controller.Step.prototype.moveNext = function () {
+               this.ui.moveNext( this.uploads );
 
                this.movedFrom = true;
 
@@ -115,11 +135,16 @@
        };
 
        /**
-        * Skip this step.
+        * Move to the previous step.
         */
-       uw.controller.Step.prototype.skip = function () {
-               uw.eventFlowLogger.logSkippedStep( this.stepName );
-               this.moveFrom();
+       uw.controller.Step.prototype.movePrevious = function () {
+               this.ui.movePrevious( this.uploads );
+
+               this.movedFrom = true;
+
+               if ( this.previousStep ) {
+                       this.previousStep.moveTo( this.uploads );
+               }
        };
 
        /**
@@ -184,30 +209,13 @@
         * @return {boolean} Whether all of the uploads are in a successful 
state.
         */
        uw.controller.Step.prototype.showNext = function () {
-               var errorCount = 0,
-                       okCount = 0,
-                       stillGoing = 0,
-                       allOk = false,
-                       desiredState = this.finishState,
+               var okCount = this.getUploadStatesCount( this.finishState ),
                        $buttons;
 
                // abort if all uploads have been removed
                if ( this.uploads.length === 0 ) {
                        return false;
                }
-
-               $.each( this.uploads, function ( i, upload ) {
-                       if ( upload === undefined ) {
-                               return;
-                       }
-                       if ( upload.state === 'error' ) {
-                               errorCount++;
-                       } else if ( upload.state === desiredState ) {
-                               okCount++;
-                       } else if ( upload.state === 'transporting' ) {
-                               stillGoing += 1;
-                       }
-               } );
 
                this.updateProgressBarCount( okCount );
 
@@ -219,17 +227,40 @@
                $buttons.find( '.mwe-upwiz-file-next-all-failed' ).hide();
 
                if ( okCount === ( this.uploads.length - this.countEmpties() ) 
) {
-                       allOk = true;
                        $buttons.find( '.mwe-upwiz-file-next-all-ok' ).show();
-               } else if ( errorCount === ( this.uploads.length - 
this.countEmpties() ) ) {
+                       return true;
+               }
+
+               if ( this.getUploadStatesCount( 'error' ) === ( 
this.uploads.length - this.countEmpties() ) ) {
                        $buttons.find( '.mwe-upwiz-file-next-all-failed' 
).show();
-               } else if ( stillGoing !== 0 ) {
-                       return false;
-               } else {
+               } else if ( this.getUploadStatesCount( 'transporting' ) === 0 ) 
{
                        $buttons.find( '.mwe-upwiz-file-next-some-failed' 
).show();
                }
 
-               return allOk;
+               return false;
+       };
+
+       /**
+        * @param {string|string[]} states List of upload states we want the 
count for
+        * @return {number}
+        */
+       uw.controller.Step.prototype.getUploadStatesCount = function ( states ) 
{
+               var count = 0;
+
+               // normalize to array of states, even though input can be 1 
string
+               states = Array.isArray( states ) ? states : [ states ];
+
+               $.each( this.uploads, function ( i, upload ) {
+                       if ( upload === undefined ) {
+                               return;
+                       }
+
+                       if ( states.indexOf( upload.state ) > -1 ) {
+                               count++;
+                       }
+               } );
+
+               return count;
        };
 
        /**
diff --git a/resources/controller/uw.controller.Thanks.js 
b/resources/controller/uw.controller.Thanks.js
index 5d48705..20cb75f 100644
--- a/resources/controller/uw.controller.Thanks.js
+++ b/resources/controller/uw.controller.Thanks.js
@@ -45,17 +45,23 @@
 
                if ( uploads.length === 0 ) {
                        // We got here after the user removed all uploads; just 
restart from "Upload" step
-                       this.skip();
+                       this.moveNext();
                        return;
                }
 
-               uw.controller.Step.prototype.moveTo.call( this );
+               uw.controller.Step.prototype.moveTo.call( this, uploads );
 
                $.each( uploads, function ( i, upload ) {
                        thanks.ui.addUpload( upload );
                } );
 
-               this.uploads = undefined;
+               this.uploads = [];
+       };
+
+       uw.controller.Thanks.prototype.moveNext = function () {
+               uw.controller.Step.prototype.moveNext.call( this );
+
+               this.emit( 'reset-wizard' );
        };
 
        uw.controller.Thanks.prototype.isComplete = function () {
diff --git a/resources/controller/uw.controller.Tutorial.js 
b/resources/controller/uw.controller.Tutorial.js
index 25ae3e6..2965694 100644
--- a/resources/controller/uw.controller.Tutorial.js
+++ b/resources/controller/uw.controller.Tutorial.js
@@ -27,6 +27,9 @@
         */
        uw.controller.Tutorial = function UWControllerTutorial( api, config ) {
                var controller = this;
+
+               this.skipPreference = Boolean( mw.user.options.get( 
'upwiz_skiptutorial' ) );
+
                this.shouldSkipTutorial = false;
 
                uw.controller.Step.call(
@@ -49,51 +52,43 @@
                );
 
                this.stepName = 'tutorial';
+
+               this.ui.setSelected( this.skipPreference || ( config && 
config.tutorial && config.tutorial.skip ) );
        };
 
        OO.inheritClass( uw.controller.Tutorial, uw.controller.Step );
 
        /**
         * Set the skip tutorial user preference via the options API
+        *
+        * @param {boolean} skip
         */
-       uw.controller.Tutorial.prototype.setSkipPreference = function () {
-               var api = this.api,
+       uw.controller.Tutorial.prototype.setSkipPreference = function ( skip ) {
+               var controller = this,
                        allowCloseWindow = mw.confirmCloseWindow( {
                                message: function () { return mw.message( 
'mwe-upwiz-prevent-close-wait' ).text(); }
                        } );
 
-               api.postWithToken( 'options', {
+               this.api.postWithToken( 'options', {
                        action: 'options',
-                       change: 'upwiz_skiptutorial=1'
+                       change: skip ? 'upwiz_skiptutorial=1' : 
'upwiz_skiptutorial'
                } ).done( function () {
                        allowCloseWindow.release();
+                       controller.skipPreference = 
controller.shouldSkipTutorial;
                } ).fail( function ( code, err ) {
                        mw.notify( err.textStatus );
                } );
        };
 
-       uw.controller.Tutorial.prototype.moveTo = function () {
-               var tconf = mw.config.get( 'UploadWizardConfig' ).tutorial;
-
-               if (
-                       mw.user.options.get( 'upwiz_skiptutorial' ) ||
-                       ( tconf && tconf.skip )
-               ) {
-                       this.skip();
-               } else {
-                       uw.controller.Step.prototype.moveTo.call( this );
-               }
-       };
-
-       uw.controller.Tutorial.prototype.moveFrom = function () {
+       uw.controller.Tutorial.prototype.moveNext = function () {
                uw.eventFlowLogger.logTutorialAction( 'continue' );
 
                // if the skip checkbox is checked, set the skip user preference
-               if ( this.shouldSkipTutorial ) {
-                       this.setSkipPreference();
+               if ( this.shouldSkipTutorial !== this.skipPreference ) {
+                       this.setSkipPreference( this.shouldSkipTutorial );
                }
 
-               uw.controller.Step.prototype.moveFrom.call( this );
+               uw.controller.Step.prototype.moveNext.call( this );
        };
 
        uw.controller.Tutorial.prototype.isComplete = function () {
diff --git a/resources/controller/uw.controller.Upload.js 
b/resources/controller/uw.controller.Upload.js
index 06fd57d..85ed124 100644
--- a/resources/controller/uw.controller.Upload.js
+++ b/resources/controller/uw.controller.Upload.js
@@ -56,12 +56,11 @@
                var fewerThanMax, haveUploads,
                        max = this.config.maxUploads;
 
-               this.ui.hideEndButtons();
-
                haveUploads = 
uw.controller.Step.prototype.updateFileCounts.call( this, uploads );
 
                fewerThanMax = this.uploads.length < max;
 
+               this.updateProgressBarCount( this.uploads.length );
                this.ui.updateFileCounts( haveUploads, fewerThanMax );
 
                if ( !haveUploads ) {
@@ -83,10 +82,30 @@
                this.ui.empty();
        };
 
-       uw.controller.Upload.prototype.moveTo = function () {
-               this.updateFileCounts( [] );
-               uw.controller.Step.prototype.moveTo.call( this );
-               this.progressBar = undefined;
+       uw.controller.Upload.prototype.moveTo = function ( uploads ) {
+               var controller = this;
+
+               uw.controller.Step.prototype.moveTo.call( this, uploads );
+               this.startProgressBar();
+
+               if ( uploads.length > 0 ) {
+                       /*
+                        * If we have uploads, we're coming back from a later 
step.
+                        * In order to be able to reliably use showNext() (used 
to determine
+                        * which 'next' buttons to show), we should reset the 
upload's state
+                        * to the desired finish state for this step.
+                        * Having already completed this step, it's safe to 
continue with
+                        * these current files.
+                        * If other files are to be added, the showNext() 
callback will deal
+                        * with new uploads, and still understand the existing 
files that
+                        * we've just reset the state for.
+                        */
+                       $.each( uploads, function ( i, upload ) {
+                               upload.state = controller.finishState;
+                       } );
+
+                       this.showNext();
+               }
        };
 
        /**
diff --git a/resources/mw.UploadWizard.js b/resources/mw.UploadWizard.js
index b433d87..6db73ba 100644
--- a/resources/mw.UploadWizard.js
+++ b/resources/mw.UploadWizard.js
@@ -112,18 +112,19 @@
                        } );
 
                        // "select" the first step - highlight, make it 
visible, hide all others
-                       this.steps.firstStep.moveTo();
+                       this.steps.firstStep.moveTo( [] );
                },
 
                /**
                 * Initialise the steps in the wizard
                 */
                initialiseSteps: function () {
-                       var wizard = this;
+                       var wizard = this,
+                               skipTutorial = this.config.tutorial.skip ||
+                                       mw.user.options.get( 
'upwiz_skiptutorial' ) ||
+                                       ( this.config.tutorial && 
this.config.tutorial.skip );
 
-                       if ( !this.config.tutorial.skip ) {
-                               this.steps.tutorial = new 
uw.controller.Tutorial( this.api, this.config );
-                       }
+                       this.steps.tutorial = new uw.controller.Tutorial( 
this.api, this.config );
                        this.steps.file = new uw.controller.Upload( this.api, 
this.config )
                                .on( 'flickr-ui-init', function () {
                                        wizard.flickrInterfaceInit();
@@ -131,8 +132,6 @@
                                } )
 
                                .on( 'load', function () {
-                                       wizard.reset();
-
                                        // Check for iOS 5 Safari's lack of 
file uploads (T34328#364508).
                                        // While this looks extremely unlikely 
to be right, it actually is. Blame Apple.
                                        if ( $( '<input type="file">' ).prop( 
'disabled' ) ) {
@@ -155,7 +154,7 @@
 
                                .on( 'finalize-details-after-removal', function 
() {
                                        wizard.removeErrorUploads();
-                                       wizard.steps.details.moveFrom();
+                                       wizard.steps.details.moveNext();
                                } );
 
                        this.steps.thanks = new uw.controller.Thanks( this.api, 
this.config )
@@ -163,11 +162,10 @@
                                        wizard.reset();
                                } );
 
-                       if ( !this.config.tutorial.skip ) {
-                               this.steps.tutorial.setNextStep( 
this.steps.file );
-                               this.steps.firstStep = this.steps.tutorial;
-                       } else {
+                       if ( skipTutorial ) {
                                this.steps.firstStep = this.steps.file;
+                       } else {
+                               this.steps.firstStep = this.steps.tutorial;
                        }
 
                        $.each( this.steps, function ( name, step ) {
@@ -177,9 +175,18 @@
                                        } );
                        } );
 
+                       this.steps.tutorial.setNextStep( this.steps.file );
+
+                       this.steps.file.setPreviousStep( this.steps.tutorial );
                        this.steps.file.setNextStep( this.steps.deeds );
+
+                       this.steps.deeds.setPreviousStep( this.steps.file );
                        this.steps.deeds.setNextStep( this.steps.details );
+
+                       this.steps.details.setPreviousStep( this.steps.deeds );
                        this.steps.details.setNextStep( this.steps.thanks );
+
+                       // thanks doesn't need a "previous" step, there's no 
undoing uploads!
                        this.steps.thanks.setNextStep( this.steps.file );
 
                        $( '#mwe-upwiz-steps' ).arrowSteps();
diff --git a/resources/ui/steps/uw.ui.Deed.js b/resources/ui/steps/uw.ui.Deed.js
index 808d64e..2dbf418 100644
--- a/resources/ui/steps/uw.ui.Deed.js
+++ b/resources/ui/steps/uw.ui.Deed.js
@@ -29,12 +29,15 @@
                        'deeds'
                );
 
+               this.addPreviousButton();
                this.addNextButton();
        };
 
        OO.inheritClass( uw.ui.Deed, uw.ui.Step );
 
        uw.ui.Deed.prototype.moveTo = function ( uploads ) {
+               var ui = this;
+
                uw.ui.Step.prototype.moveTo.call( this, uploads );
 
                this.$div.prepend(
@@ -48,12 +51,6 @@
                                .attr( 'id', 'mwe-upwiz-deeds-custom' )
                                .addClass( 'ui-helper-clearfix' )
                );
-       };
-
-       uw.ui.Deed.prototype.addNextButton = function () {
-               var ui = this;
-
-               uw.ui.Step.prototype.addNextButton.call( this );
 
                this.nextButtonPromise.done( function () {
                        // hide "next" button, controller will only show it 
once license has
diff --git a/resources/ui/steps/uw.ui.Details.js 
b/resources/ui/steps/uw.ui.Details.js
index 2e8602b..dfaad0f 100644
--- a/resources/ui/steps/uw.ui.Details.js
+++ b/resources/ui/steps/uw.ui.Details.js
@@ -62,6 +62,8 @@
                        flags: [ 'progressive', 'primary' ]
                } ).on( 'click', startDetails );
 
+               this.$buttons.append( this.$errorCount, this.$warningCount );
+               this.addPreviousButton();
                this.addNextButton();
        };
 
@@ -86,8 +88,6 @@
                var ui = this;
 
                this.nextButtonPromise.done( function () {
-                       ui.$buttons.append( ui.$errorCount, ui.$warningCount );
-
                        ui.$buttons.append(
                                $( '<div>' )
                                        .addClass( 'mwe-upwiz-file-next-all-ok 
mwe-upwiz-file-endchoice' )
diff --git a/resources/ui/steps/uw.ui.Tutorial.js 
b/resources/ui/steps/uw.ui.Tutorial.js
index 5e02cd8..016de82 100644
--- a/resources/ui/steps/uw.ui.Tutorial.js
+++ b/resources/ui/steps/uw.ui.Tutorial.js
@@ -88,10 +88,15 @@
                        ui.emit( 'helpdesk-click' );
                } );
 
+               this.addPreviousButton();
                this.addNextButton();
        };
 
        OO.inheritClass( uw.ui.Tutorial, uw.ui.Step );
+
+       uw.ui.Tutorial.prototype.setSelected = function ( selected ) {
+               this.skipCheckbox.setSelected( selected );
+       };
 
        uw.ui.Tutorial.prototype.moveTo = function ( uploads ) {
                uw.ui.Step.prototype.moveTo.call( this, uploads );
@@ -104,6 +109,8 @@
                                        this.tutorialHtml.show()
                                )
                );
+
+               this.skipCheckbox.popup.updateDimensions();
        };
 
        uw.ui.Tutorial.prototype.addNextButton = function () {
diff --git a/resources/ui/steps/uw.ui.Upload.js 
b/resources/ui/steps/uw.ui.Upload.js
index ae6489c..707286e 100644
--- a/resources/ui/steps/uw.ui.Upload.js
+++ b/resources/ui/steps/uw.ui.Upload.js
@@ -131,6 +131,7 @@
                        .attr( 'id', 'mwe-upwiz-progress' )
                        .addClass( 'ui-helper-clearfix' );
 
+               this.addPreviousButton();
                this.addNextButton();
        };
 
@@ -194,7 +195,7 @@
                        this.$fileListings.filter( ':odd' ).addClass( 'odd' );
                        this.$fileListings.filter( ':even' ).removeClass( 'odd' 
);
                } else {
-                       this.$buttons.hide();
+                       this.hideEndButtons();
 
                        if ( this.isFlickrImportEnabled() ) {
                                this.$uploadCenterDivide.show();
@@ -241,8 +242,9 @@
        uw.ui.Upload.prototype.empty = function () {
                this.$addFileContainer
                        .add( this.$buttons )
-                       .add( this.$progress )
-                       .hide();
+                       .add( this.$progress );
+
+               this.hideEndButtons();
 
                this.$addFileContainer
                        .add( this.$uploadCenterDivide )
@@ -274,10 +276,12 @@
                this.$flickrSelect.unbind();
        };
 
-       uw.ui.Upload.prototype.moveTo = function () {
-               uw.ui.Step.prototype.moveTo.call( this );
+       uw.ui.Upload.prototype.moveTo = function ( uploads ) {
+               uw.ui.Step.prototype.moveTo.call( this, uploads );
 
-               this.$fileList.removeClass( 'mwe-upwiz-filled-filelist' );
+               if ( uploads.length === 0 ) {
+                       this.$fileList.removeClass( 'mwe-upwiz-filled-filelist' 
);
+               }
 
                this.$div.prepend(
                        $( '<div>' )
diff --git a/resources/ui/uw.ui.Step.js b/resources/ui/uw.ui.Step.js
index a631987..cab8c2f 100644
--- a/resources/ui/uw.ui.Step.js
+++ b/resources/ui/uw.ui.Step.js
@@ -46,9 +46,10 @@
 
                $( '#mwe-upwiz-steps' ).append( this.$arrow );
 
-               // this will make sure that button will only be added if it's 
been
+               // this will make sure that buttons will only be added if 
they've been
                // set in the controller, otherwise there's nowhere to go...
                this.nextButtonPromise = $.Deferred();
+               this.previousButtonPromise = $.Deferred();
        };
 
        OO.mixinClass( uw.ui.Step, OO.EventEmitter );
@@ -72,9 +73,16 @@
        };
 
        /**
-        * Move out of the step.
+        * Move to the next step.
         */
-       uw.ui.Step.prototype.moveFrom = function () {
+       uw.ui.Step.prototype.moveNext = function () {
+               this.$div.children().detach();
+       };
+
+       /**
+        * Move to the previous step.
+        */
+       uw.ui.Step.prototype.movePrevious = function () {
                this.$div.children().detach();
        };
 
@@ -85,6 +93,10 @@
 
        uw.ui.Step.prototype.enableNextButton = function () {
                this.nextButtonPromise.resolve();
+       };
+
+       uw.ui.Step.prototype.enablePreviousButton = function () {
+               this.previousButtonPromise.resolve();
        };
 
        /**
@@ -106,4 +118,21 @@
                } );
        };
 
+       /**
+        * Add a 'previous' button to the step's button container
+        */
+       uw.ui.Step.prototype.addPreviousButton = function () {
+               var ui = this;
+
+               this.previousButton = new OO.ui.ButtonWidget( {
+                       classes: [ 'mwe-upwiz-button-previous' ],
+                       label: mw.message( 'mwe-upwiz-previous' ).text()
+               } ).on( 'click', function () {
+                       ui.emit( 'previous-step' );
+               } );
+
+               this.previousButtonPromise.done( function () {
+                       ui.$buttons.append( ui.previousButton.$element );
+               } );
+       };
 }( mediaWiki, jQuery, mediaWiki.uploadWizard, OO ) );
diff --git a/resources/uploadWizard.css b/resources/uploadWizard.css
index b8bef2c..ecb8f48 100644
--- a/resources/uploadWizard.css
+++ b/resources/uploadWizard.css
@@ -243,10 +243,6 @@
        margin-top: 1em;
 }
 
-#mwe-upwiz-stepdiv-file .mwe-upwiz-buttons {
-       display: none;
-}
-
 #mwe-upwiz-stepdiv-file .mwe-upwiz-buttons .mwe-upwiz-file-endchoice {
        display: none;
 }
@@ -432,6 +428,10 @@
        text-align: right;
 }
 
+.mwe-upwiz-button-previous {
+       float: left;
+}
+
 a.mwe-upwiz-tooltip-link {
        cursor: pointer;
 }
diff --git a/tests/qunit/controller/uw.controller.Tutorial.test.js 
b/tests/qunit/controller/uw.controller.Tutorial.test.js
index e24d36b..561c9fd 100644
--- a/tests/qunit/controller/uw.controller.Tutorial.test.js
+++ b/tests/qunit/controller/uw.controller.Tutorial.test.js
@@ -36,7 +36,7 @@
                this.sandbox.stub( mw, 'confirmCloseWindow' ).returns( acwStub 
);
                this.sandbox.stub( api, 'postWithToken' ).returns( 
pwtd.promise() );
 
-               step.setSkipPreference();
+               step.setSkipPreference( true );
 
                assert.ok( mw.confirmCloseWindow.called );
                assert.ok( api.postWithToken.calledWithExactly( 'options', {
@@ -55,7 +55,7 @@
 
                this.sandbox.stub( api, 'postWithToken' ).returns( 
pwtd.promise() );
 
-               step.setSkipPreference();
+               step.setSkipPreference( true );
                assert.ok( !acwStub.release.called );
 
                pwtd.reject( 'http', { textStatus: 'Foo bar' } );

-- 
To view, visit https://gerrit.wikimedia.org/r/314024
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Ib8931cdc6d7b2f9ab33536641aa822376ebf6b35
Gerrit-PatchSet: 15
Gerrit-Project: mediawiki/extensions/UploadWizard
Gerrit-Branch: master
Gerrit-Owner: Matthias Mullie <[email protected]>
Gerrit-Reviewer: Bartosz Dziewoński <[email protected]>
Gerrit-Reviewer: MarkTraceur <[email protected]>
Gerrit-Reviewer: Matthias Mullie <[email protected]>
Gerrit-Reviewer: Siebrand <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to