build: Replace jscs+jshint with eslint
[lhc/web/wiklou.git] / resources / src / mediawiki / mediawiki.ForeignStructuredUpload.BookletLayout.js
index 97d81fb..2712ddb 100644 (file)
@@ -1,4 +1,4 @@
-/*global moment */
+/* global moment, Uint8Array */
 ( function ( $, mw ) {
 
        /**
                var booklet = this;
                return mw.ForeignStructuredUpload.BookletLayout.parent.prototype.initialize.call( this ).then(
                        function () {
-                               // Point the CategorySelector to the right wiki
-                               return booklet.upload.getApi().then(
-                                       function ( api ) {
+                               return $.when(
+                                       // Point the CategorySelector to the right wiki
+                                       booklet.upload.getApi().then( function ( api ) {
                                                // If this is a ForeignApi, it will have a apiUrl, otherwise we don't need to do anything
                                                if ( api.apiUrl ) {
                                                        // Can't reuse the same object, CategorySelector calls #abort on its mw.Api instance
                                                        booklet.categoriesWidget.api = new mw.ForeignApi( api.apiUrl );
                                                }
                                                return $.Deferred().resolve();
-                                       },
-                                       function () {
-                                               return $.Deferred().resolve();
-                                       }
+                                       } ),
+                                       // Set up booklet fields and license messages to match configuration
+                                       booklet.upload.loadConfig().then( function ( config ) {
+                                               var
+                                                       msgPromise,
+                                                       isLocal = booklet.upload.target === 'local',
+                                                       fields = config.fields,
+                                                       msgs = config.licensemessages[ isLocal ? 'local' : 'foreign' ];
+
+                                               // Hide disabled fields
+                                               booklet.descriptionField.toggle( !!fields.description );
+                                               booklet.categoriesField.toggle( !!fields.categories );
+                                               booklet.dateField.toggle( !!fields.date );
+                                               // Update form validity
+                                               booklet.onInfoFormChange();
+
+                                               // Load license messages from the remote wiki if we don't have these messages locally
+                                               // (this means that we only load messages from the foreign wiki for custom config)
+                                               if ( mw.message( 'upload-form-label-own-work-message-' + msgs ).exists() ) {
+                                                       msgPromise = $.Deferred().resolve();
+                                               } else {
+                                                       msgPromise = booklet.upload.apiPromise.then( function ( api ) {
+                                                               return api.loadMessages( [
+                                                                       'upload-form-label-own-work-message-' + msgs,
+                                                                       'upload-form-label-not-own-work-message-' + msgs,
+                                                                       'upload-form-label-not-own-work-local-' + msgs
+                                                               ] );
+                                                       } );
+                                               }
+
+                                               // Update license messages
+                                               return msgPromise.then( function () {
+                                                       var $labels;
+                                                       booklet.$ownWorkMessage.msg( 'upload-form-label-own-work-message-' + msgs );
+                                                       booklet.$notOwnWorkMessage.msg( 'upload-form-label-not-own-work-message-' + msgs );
+                                                       booklet.$notOwnWorkLocal.msg( 'upload-form-label-not-own-work-local-' + msgs );
+
+                                                       $labels = $( [
+                                                               booklet.$ownWorkMessage[ 0 ],
+                                                               booklet.$notOwnWorkMessage[ 0 ],
+                                                               booklet.$notOwnWorkLocal[ 0 ]
+                                                       ] );
+
+                                                       // Improve the behavior of links inside these labels, which may point to important
+                                                       // things like licensing requirements or terms of use
+                                                       $labels.find( 'a' )
+                                                               .attr( 'target', '_blank' )
+                                                               .on( 'click', function ( e ) {
+                                                                       // OO.ui.FieldLayout#onLabelClick is trying to prevent default on all clicks,
+                                                                       // which causes the links to not be openable. Don't let it do that.
+                                                                       e.stopPropagation();
+                                                               } );
+                                               } );
+                                       } )
                                );
-                       },
-                       function () {
-                               return $.Deferred().resolve();
                        }
+               ).then(
+                       null,
+                       // Always resolve, never reject
+                       function () { return $.Deferred().resolve(); }
                );
        };
 
         * @inheritdoc
         */
        mw.ForeignStructuredUpload.BookletLayout.prototype.renderUploadForm = function () {
-               var
-                       query = /[?&]uploadbucket=(\d)/.exec( location.search ),
-                       isTestEnabled = !!mw.config.get( 'wgForeignUploadTestEnabled' ),
-                       defaultBucket = mw.config.get( 'wgForeignUploadTestDefault' ) || 1,
-                       userId = mw.config.get( 'wgUserId' );
-
-               if ( query && query[ 1 ] ) {
-                       // Testing and debugging
-                       this.shouldRecordBucket = false;
-                       this.bucket = Number( query[ 1 ] );
-               } else if ( !userId || !isTestEnabled ) {
-                       // a) Anonymous user. This can actually happen, because our software sucks.
-                       // b) Test is not enabled on this wiki.
-                       // In either case, display the old interface and don't record bucket on uploads.
-                       this.shouldRecordBucket = false;
-                       this.bucket = defaultBucket;
-               } else {
-                       // Regular logged in user on a wiki where the test is running
-                       this.shouldRecordBucket = true;
-                       this.bucket = ( userId % 4 ) + 1; // 1, 2, 3, 4
-               }
-
-               return this[ 'renderUploadForm' + this.bucket ]();
-       };
-
-       /**
-        * Test option 1, the original one. See T120867.
-        */
-       mw.ForeignStructuredUpload.BookletLayout.prototype.renderUploadForm1 = function () {
-               var fieldset, $ownWorkMessage, $notOwnWorkMessage,
-                       onUploadFormChange,
-                       ownWorkMessage, notOwnWorkMessage, notOwnWorkLocal,
-                       validTargets = mw.config.get( 'wgForeignUploadTargets' ),
-                       target = this.target || validTargets[ 0 ] || 'local',
+               var fieldset,
                        layout = this;
 
-               // Temporary override to make my life easier during A/B test
-               target = 'shared';
-
-               // foreign-structured-upload-form-label-own-work-message-local
-               // foreign-structured-upload-form-label-own-work-message-shared
-               ownWorkMessage = mw.message( 'foreign-structured-upload-form-label-own-work-message-' + target );
-               // foreign-structured-upload-form-label-not-own-work-message-local
-               // foreign-structured-upload-form-label-not-own-work-message-shared
-               notOwnWorkMessage = mw.message( 'foreign-structured-upload-form-label-not-own-work-message-' + target );
-               // foreign-structured-upload-form-label-not-own-work-local-local
-               // foreign-structured-upload-form-label-not-own-work-local-shared
-               notOwnWorkLocal = mw.message( 'foreign-structured-upload-form-label-not-own-work-local-' + target );
-
-               if ( !ownWorkMessage.exists() ) {
-                       ownWorkMessage = mw.message( 'foreign-structured-upload-form-label-own-work-message-default' );
-               }
-               if ( !notOwnWorkMessage.exists() ) {
-                       notOwnWorkMessage = mw.message( 'foreign-structured-upload-form-label-not-own-work-message-default' );
-               }
-               if ( !notOwnWorkLocal.exists() ) {
-                       notOwnWorkLocal = mw.message( 'foreign-structured-upload-form-label-not-own-work-local-default' );
-               }
-
-               $ownWorkMessage = $( '<p>' ).append( ownWorkMessage.parseDom() )
+               // These elements are filled with text in #initialize
+               // TODO Refactor this to be in one place
+               this.$ownWorkMessage = $( '<p>' )
                        .addClass( 'mw-foreignStructuredUpload-bookletLayout-license' );
-               $notOwnWorkMessage = $( '<div>' ).append(
-                       $( '<p>' ).append( notOwnWorkMessage.parseDom() ),
-                       $( '<p>' ).append( notOwnWorkLocal.parseDom() )
-               );
-               $ownWorkMessage.add( $notOwnWorkMessage ).find( 'a' )
-                       .attr( 'target', '_blank' )
-                       .on( 'click', function ( e ) {
-                               // Some stupid code is trying to prevent default on all clicks, which causes the links to
-                               // not be openable, don't let it
-                               e.stopPropagation();
-                       } );
+               this.$notOwnWorkMessage = $( '<p>' );
+               this.$notOwnWorkLocal = $( '<p>' );
 
-               this.selectFileWidget = new OO.ui.SelectFileWidget();
+               this.selectFileWidget = new OO.ui.SelectFileWidget( {
+                       showDropTarget: true
+               } );
                this.messageLabel = new OO.ui.LabelWidget( {
-                       label: $notOwnWorkMessage
+                       label: $( '<div>' ).append(
+                               this.$notOwnWorkMessage,
+                               this.$notOwnWorkLocal
+                       )
                } );
                this.ownWorkCheckbox = new OO.ui.CheckboxInputWidget().on( 'change', function ( on ) {
                        layout.messageLabel.toggle( !on );
                fieldset = new OO.ui.FieldsetLayout();
                fieldset.addItems( [
                        new OO.ui.FieldLayout( this.selectFileWidget, {
-                               align: 'top',
-                               label: mw.msg( 'upload-form-label-select-file' )
+                               align: 'top'
                        } ),
                        new OO.ui.FieldLayout( this.ownWorkCheckbox, {
                                align: 'inline',
                                label: $( '<div>' ).append(
-                                       $( '<p>' ).text( mw.msg( 'foreign-structured-upload-form-label-own-work' ) ),
-                                       $ownWorkMessage
+                                       $( '<p>' ).text( mw.msg( 'upload-form-label-own-work' ) ),
+                                       this.$ownWorkMessage
                                )
                        } ),
                        new OO.ui.FieldLayout( this.messageLabel, {
                ] );
                this.uploadForm = new OO.ui.FormLayout( { items: [ fieldset ] } );
 
-               onUploadFormChange = function () {
-                       var file = this.selectFileWidget.getValue(),
-                               ownWork = this.ownWorkCheckbox.isSelected(),
-                               valid = !!file && ownWork;
-                       this.emit( 'uploadValid', valid );
-               };
-
                // Validation
-               this.selectFileWidget.on( 'change', onUploadFormChange.bind( this ) );
-               this.ownWorkCheckbox.on( 'change', onUploadFormChange.bind( this ) );
+               this.selectFileWidget.on( 'change', this.onUploadFormChange.bind( this ) );
+               this.ownWorkCheckbox.on( 'change', this.onUploadFormChange.bind( this ) );
 
                this.selectFileWidget.on( 'change', function () {
                        var file = layout.getFile();
                        layout.getDateFromExif( file ).done( function ( date ) {
                                layout.dateWidget.setValue( date );
                        } );
-               } );
-
-               return this.uploadForm;
-       };
-
-       /**
-        * Test option 2, idea A from T121021. See T120867.
-        */
-       mw.ForeignStructuredUpload.BookletLayout.prototype.renderUploadForm2 = function () {
-               var fieldset, checkboxes, fields, onUploadFormChange;
-
-               this.selectFileWidget = new OO.ui.SelectFileWidget();
-               this.licenseCheckboxes = checkboxes = [
-                       new OO.ui.CheckboxInputWidget(),
-                       new OO.ui.CheckboxInputWidget(),
-                       new OO.ui.CheckboxInputWidget(),
-                       new OO.ui.CheckboxInputWidget()
-               ];
-
-               fields = [
-                       new OO.ui.FieldLayout( this.selectFileWidget, {
-                               align: 'top',
-                               label: mw.msg( 'upload-form-label-select-file' )
-                       } ),
-                       new OO.ui.FieldLayout( new OO.ui.LabelWidget( {
-                               label: mw.message( 'foreign-structured-upload-form-2-label-intro' ).parseDom()
-                       } ), {
-                               align: 'top'
-                       } ),
-                       new OO.ui.FieldLayout( checkboxes[ 0 ], {
-                               align: 'inline',
-                               classes: [
-                                       'mw-foreignStructuredUpload-bookletLayout-withicon',
-                                       'mw-foreignStructuredUpload-bookletLayout-ownwork'
-                               ],
-                               label: mw.message( 'foreign-structured-upload-form-2-label-ownwork' ).parseDom()
-                       } ),
-                       new OO.ui.FieldLayout( checkboxes[ 1 ], {
-                               align: 'inline',
-                               classes: [
-                                       'mw-foreignStructuredUpload-bookletLayout-withicon',
-                                       'mw-foreignStructuredUpload-bookletLayout-noderiv'
-                               ],
-                               label: mw.message( 'foreign-structured-upload-form-2-label-noderiv' ).parseDom()
-                       } ),
-                       new OO.ui.FieldLayout( checkboxes[ 2 ], {
-                               align: 'inline',
-                               classes: [
-                                       'mw-foreignStructuredUpload-bookletLayout-withicon',
-                                       'mw-foreignStructuredUpload-bookletLayout-useful'
-                               ],
-                               label: mw.message( 'foreign-structured-upload-form-2-label-useful' ).parseDom()
-                       } ),
-                       new OO.ui.FieldLayout( checkboxes[ 3 ], {
-                               align: 'inline',
-                               classes: [
-                                       'mw-foreignStructuredUpload-bookletLayout-withicon',
-                                       'mw-foreignStructuredUpload-bookletLayout-ccbysa'
-                               ],
-                               label: mw.message( 'foreign-structured-upload-form-2-label-ccbysa' ).parseDom()
-                       } ),
-                       new OO.ui.FieldLayout( new OO.ui.LabelWidget( {
-                               label: $()
-                                       .add( $( '<p>' ).msg( 'foreign-structured-upload-form-2-label-alternative' ) )
-                                       .add( $( '<p>' ).msg( 'foreign-structured-upload-form-2-label-termsofuse' )
-                                               .addClass( 'mw-foreignStructuredUpload-bookletLayout-license' ) )
-                       } ), {
-                               align: 'top'
-                       } )
-               ];
-
-               fieldset = new OO.ui.FieldsetLayout( { items: fields } );
-               this.uploadForm = new OO.ui.FormLayout( { items: [ fieldset ] } );
-
-               this.uploadForm.$element.find( 'a' )
-                       .attr( 'target', '_blank' )
-                       .on( 'click', function ( e ) {
-                               // Some stupid code is trying to prevent default on all clicks, which causes the links to
-                               // not be openable, don't let it
-                               e.stopPropagation();
-                       } );
-
-               onUploadFormChange = function () {
-                       var file = this.selectFileWidget.getValue(),
-                               checks = checkboxes.every( function ( checkbox ) {
-                                       return checkbox.isSelected();
-                               } ),
-                               valid = !!file && checks;
-                       this.emit( 'uploadValid', valid );
-               };
-
-               // Validation
-               this.selectFileWidget.on( 'change', onUploadFormChange.bind( this ) );
-               checkboxes[ 0 ].on( 'change', onUploadFormChange.bind( this ) );
-               checkboxes[ 1 ].on( 'change', onUploadFormChange.bind( this ) );
-               checkboxes[ 2 ].on( 'change', onUploadFormChange.bind( this ) );
-               checkboxes[ 3 ].on( 'change', onUploadFormChange.bind( this ) );
-
-               return this.uploadForm;
-       };
 
-       /**
-        * Test option 3, idea D from T121021. See T120867.
-        */
-       mw.ForeignStructuredUpload.BookletLayout.prototype.renderUploadForm3 = function () {
-               var ownWorkCheckbox, fieldset, yesMsg, noMsg, selects, selectFields,
-                       alternativeField, fields, onUploadFormChange;
-
-               this.selectFileWidget = new OO.ui.SelectFileWidget();
-               this.ownWorkCheckbox = ownWorkCheckbox = new OO.ui.CheckboxInputWidget();
-
-               yesMsg = mw.message( 'foreign-structured-upload-form-3-label-yes' ).text();
-               noMsg = mw.message( 'foreign-structured-upload-form-3-label-no' ).text();
-               selects = [
-                       new OO.ui.RadioSelectWidget( {
-                               items: [
-                                       new OO.ui.RadioOptionWidget( { data: false, label: yesMsg } ),
-                                       new OO.ui.RadioOptionWidget( { data: true, label: noMsg } )
-                               ]
-                       } ),
-                       new OO.ui.RadioSelectWidget( {
-                               items: [
-                                       new OO.ui.RadioOptionWidget( { data: true, label: yesMsg } ),
-                                       new OO.ui.RadioOptionWidget( { data: false, label: noMsg } )
-                               ]
-                       } ),
-                       new OO.ui.RadioSelectWidget( {
-                               items: [
-                                       new OO.ui.RadioOptionWidget( { data: false, label: yesMsg } ),
-                                       new OO.ui.RadioOptionWidget( { data: true, label: noMsg } )
-                               ]
-                       } )
-               ];
-
-               this.licenseSelectFields = selectFields = [
-                       new OO.ui.FieldLayout( selects[ 0 ], {
-                               align: 'top',
-                               classes: [ 'mw-foreignStructuredUpload-bookletLayout-question' ],
-                               label: mw.message( 'foreign-structured-upload-form-3-label-question-website' ).parseDom()
-                       } ),
-                       new OO.ui.FieldLayout( selects[ 1 ], {
-                               align: 'top',
-                               classes: [ 'mw-foreignStructuredUpload-bookletLayout-question' ],
-                               label: mw.message( 'foreign-structured-upload-form-3-label-question-ownwork' ).parseDom()
-                       } ).toggle( false ),
-                       new OO.ui.FieldLayout( selects[ 2 ], {
-                               align: 'top',
-                               classes: [ 'mw-foreignStructuredUpload-bookletLayout-question' ],
-                               label: mw.message( 'foreign-structured-upload-form-3-label-question-noderiv' ).parseDom()
-                       } ).toggle( false )
-               ];
-
-               alternativeField = new OO.ui.FieldLayout( new OO.ui.LabelWidget( {
-                       label: mw.message( 'foreign-structured-upload-form-3-label-alternative' ).parseDom()
-               } ), {
-                       align: 'top'
-               } ).toggle( false );
-
-               // Choosing the right answer to each question shows the next question.
-               // Switching to wrong answer hides all subsequent questions.
-               selects.forEach( function ( select, i ) {
-                       select.on( 'choose', function ( selectedOption ) {
-                               var isRightAnswer = !!selectedOption.getData();
-                               alternativeField.toggle( !isRightAnswer );
-                               if ( i + 1 === selectFields.length ) {
-                                       // Last question
-                                       return;
-                               }
-                               if ( isRightAnswer ) {
-                                       selectFields[ i + 1 ].toggle( true );
-                               } else {
-                                       selectFields.slice( i + 1 ).forEach( function ( field ) {
-                                               field.fieldWidget.selectItem( null );
-                                               field.toggle( false );
-                                       } );
-                               }
-                       } );
+                       layout.updateFilePreview();
                } );
 
-               fields = [
-                       new OO.ui.FieldLayout( this.selectFileWidget, {
-                               align: 'top',
-                               label: mw.msg( 'upload-form-label-select-file' )
-                       } ),
-                       selectFields[ 0 ],
-                       selectFields[ 1 ],
-                       selectFields[ 2 ],
-                       alternativeField,
-                       new OO.ui.FieldLayout( ownWorkCheckbox, {
-                               classes: [ 'mw-foreignStructuredUpload-bookletLayout-checkbox' ],
-                               align: 'inline',
-                               label: mw.message( 'foreign-structured-upload-form-label-own-work-message-shared' ).parseDom()
-                       } )
-               ];
-
-               // Must be done late, after it's been associated with the FieldLayout
-               ownWorkCheckbox.setDisabled( true );
-
-               fieldset = new OO.ui.FieldsetLayout( { items: fields } );
-               this.uploadForm = new OO.ui.FormLayout( { items: [ fieldset ] } );
-
-               this.uploadForm.$element.find( 'a' )
-                       .attr( 'target', '_blank' )
-                       .on( 'click', function ( e ) {
-                               // Some stupid code is trying to prevent default on all clicks, which causes the links to
-                               // not be openable, don't let it
-                               e.stopPropagation();
-                       } );
-
-               onUploadFormChange = function () {
-                       var file = this.selectFileWidget.getValue(),
-                               checkbox = ownWorkCheckbox.isSelected(),
-                               rightAnswers = selects.every( function ( select ) {
-                                       return select.getSelectedItem() && !!select.getSelectedItem().getData();
-                               } ),
-                               valid = !!file && checkbox && rightAnswers;
-                       ownWorkCheckbox.setDisabled( !rightAnswers );
-                       if ( !rightAnswers ) {
-                               ownWorkCheckbox.setSelected( false );
-                       }
-                       this.emit( 'uploadValid', valid );
-               };
-
-               // Validation
-               this.selectFileWidget.on( 'change', onUploadFormChange.bind( this ) );
-               this.ownWorkCheckbox.on( 'change', onUploadFormChange.bind( this ) );
-               selects[ 0 ].on( 'choose', onUploadFormChange.bind( this ) );
-               selects[ 1 ].on( 'choose', onUploadFormChange.bind( this ) );
-               selects[ 2 ].on( 'choose', onUploadFormChange.bind( this ) );
-
-               return this.uploadForm;
-       };
-
-       /**
-        * Test option 4, idea E from T121021. See T120867.
-        */
-       mw.ForeignStructuredUpload.BookletLayout.prototype.renderUploadForm4 = function () {
-               var fieldset, $guide;
-               this.renderUploadForm1();
-               fieldset = this.uploadForm.getItems()[ 0 ];
-
-               $guide = mw.template.get( 'mediawiki.ForeignStructuredUpload.BookletLayout', 'guide.html' ).render();
-               $guide.find( '.mw-foreignStructuredUpload-bookletLayout-guide-text-wrapper-good span' )
-                       .msg( 'foreign-structured-upload-form-4-label-good' );
-               $guide.find( '.mw-foreignStructuredUpload-bookletLayout-guide-text-wrapper-bad span' )
-                       .msg( 'foreign-structured-upload-form-4-label-bad' );
-
-               // Note the index, we insert after the SelectFileWidget field
-               fieldset.addItems( [
-                       new OO.ui.FieldLayout( new OO.ui.Widget( {
-                               $content: $guide
-                       } ), {
-                               align: 'top'
-                       } )
-               ], 1 );
-
-               // Hook for custom styles
-               fieldset.getItems()[ 2 ].$element.addClass( 'mw-foreignStructuredUpload-bookletLayout-guide-checkbox' );
-
-               // Streamline: remove mention of local Special:Upload
-               fieldset.getItems()[ 3 ].$element.find( 'p' ).last().remove();
-
                return this.uploadForm;
        };
 
        /**
         * @inheritdoc
         */
-       mw.ForeignStructuredUpload.BookletLayout.prototype.onUploadFormChange = function () {};
+       mw.ForeignStructuredUpload.BookletLayout.prototype.onUploadFormChange = function () {
+               var file = this.selectFileWidget.getValue(),
+                       ownWork = this.ownWorkCheckbox.isSelected(),
+                       valid = !!file && ownWork;
+               this.emit( 'uploadValid', valid );
+       };
 
        /**
         * @inheritdoc
        mw.ForeignStructuredUpload.BookletLayout.prototype.renderInfoForm = function () {
                var fieldset;
 
+               this.filePreview = new OO.ui.Widget( {
+                       classes: [ 'mw-upload-bookletLayout-filePreview' ]
+               } );
+               this.progressBarWidget = new OO.ui.ProgressBarWidget( {
+                       progress: 0
+               } );
+               this.filePreview.$element.append( this.progressBarWidget.$element );
+
                this.filenameWidget = new OO.ui.TextInputWidget( {
                        required: true,
                        validate: /.+/
                        mustBeBefore: moment().add( 1, 'day' ).locale( 'en' ).format( 'YYYY-MM-DD' ) // Tomorrow
                } );
 
+               this.filenameField = new OO.ui.FieldLayout( this.filenameWidget, {
+                       label: mw.msg( 'upload-form-label-infoform-name' ),
+                       align: 'top',
+                       classes: [ 'mw-foreignStructuredUploa-bookletLayout-small-notice' ],
+                       notices: [ mw.msg( 'upload-form-label-infoform-name-tooltip' ) ]
+               } );
+               this.descriptionField = new OO.ui.FieldLayout( this.descriptionWidget, {
+                       label: mw.msg( 'upload-form-label-infoform-description' ),
+                       align: 'top',
+                       classes: [ 'mw-foreignStructuredUploa-bookletLayout-small-notice' ],
+                       notices: [ mw.msg( 'upload-form-label-infoform-description-tooltip' ) ]
+               } );
+               this.categoriesField = new OO.ui.FieldLayout( this.categoriesWidget, {
+                       label: mw.msg( 'upload-form-label-infoform-categories' ),
+                       align: 'top'
+               } );
+               this.dateField = new OO.ui.FieldLayout( this.dateWidget, {
+                       label: mw.msg( 'upload-form-label-infoform-date' ),
+                       align: 'top'
+               } );
+
                fieldset = new OO.ui.FieldsetLayout( {
                        label: mw.msg( 'upload-form-label-infoform-title' )
                } );
                fieldset.addItems( [
-                       new OO.ui.FieldLayout( this.filenameWidget, {
-                               label: mw.msg( 'upload-form-label-infoform-name' ),
-                               align: 'top',
-                               help: mw.msg( 'upload-form-label-infoform-name-tooltip' )
-                       } ),
-                       new OO.ui.FieldLayout( this.descriptionWidget, {
-                               label: mw.msg( 'upload-form-label-infoform-description' ),
-                               align: 'top',
-                               help: mw.msg( 'upload-form-label-infoform-description-tooltip' )
-                       } ),
-                       new OO.ui.FieldLayout( this.categoriesWidget, {
-                               label: mw.msg( 'foreign-structured-upload-form-label-infoform-categories' ),
-                               align: 'top'
-                       } ),
-                       new OO.ui.FieldLayout( this.dateWidget, {
-                               label: mw.msg( 'foreign-structured-upload-form-label-infoform-date' ),
-                               align: 'top'
-                       } )
+                       this.filenameField,
+                       this.descriptionField,
+                       this.categoriesField,
+                       this.dateField
                ] );
-               this.infoForm = new OO.ui.FormLayout( { items: [ fieldset ] } );
+               this.infoForm = new OO.ui.FormLayout( {
+                       classes: [ 'mw-upload-bookletLayout-infoForm' ],
+                       items: [ this.filePreview, fieldset ]
+               } );
 
                // Validation
                this.filenameWidget.on( 'change', this.onInfoFormChange.bind( this ) );
                this.descriptionWidget.on( 'change', this.onInfoFormChange.bind( this ) );
                this.dateWidget.on( 'change', this.onInfoFormChange.bind( this ) );
 
+               this.on( 'fileUploadProgress', function ( progress ) {
+                       this.progressBarWidget.setProgress( progress * 100 );
+               }.bind( this ) );
+
                return this.infoForm;
        };
 
         * @inheritdoc
         */
        mw.ForeignStructuredUpload.BookletLayout.prototype.onInfoFormChange = function () {
-               var layout = this;
-               $.when(
-                       this.filenameWidget.getValidity(),
-                       this.descriptionWidget.getValidity(),
-                       this.dateWidget.getValidity()
-               ).done( function () {
+               var layout = this,
+                       validityPromises = [];
+
+               validityPromises.push( this.filenameWidget.getValidity() );
+               if ( this.descriptionField.isVisible() ) {
+                       validityPromises.push( this.descriptionWidget.getValidity() );
+               }
+               if ( this.dateField.isVisible() ) {
+                       validityPromises.push( this.dateWidget.getValidity() );
+               }
+
+               $.when.apply( $, validityPromises ).done( function () {
                        layout.emit( 'infoValid', true );
                } ).fail( function () {
                        layout.emit( 'infoValid', false );
                } );
        };
 
+       /**
+        * @param {mw.Title} filename
+        * @return {jQuery.Promise} Resolves (on success) or rejects with OO.ui.Error
+        */
+       mw.ForeignStructuredUpload.BookletLayout.prototype.validateFilename = function ( filename ) {
+               return ( new mw.Api() ).get( {
+                       action: 'query',
+                       prop: 'info',
+                       titles: filename.getPrefixedDb(),
+                       formatversion: 2
+               } ).then(
+                       function ( result ) {
+                               // if the file already exists, reject right away, before
+                               // ever firing finishStashUpload()
+                               if ( !result.query.pages[ 0 ].missing ) {
+                                       return $.Deferred().reject( new OO.ui.Error(
+                                               $( '<p>' ).msg( 'fileexists', filename.getPrefixedDb() ),
+                                               { recoverable: false }
+                                       ) );
+                               }
+                       },
+                       function () {
+                               // API call failed - this could be a connection hiccup...
+                               // Let's just ignore this validation step and turn this
+                               // failure into a successful resolve ;)
+                               return $.Deferred().resolve();
+                       }
+               );
+       };
+
+       /**
+        * @inheritdoc
+        */
+       mw.ForeignStructuredUpload.BookletLayout.prototype.saveFile = function () {
+               var title = mw.Title.newFromText(
+                               this.getFilename(),
+                               mw.config.get( 'wgNamespaceIds' ).file
+                       );
+
+               return this.uploadPromise
+                       .then( this.validateFilename.bind( this, title ) )
+                       .then( mw.ForeignStructuredUpload.BookletLayout.parent.prototype.saveFile.bind( this ) );
+       };
+
        /* Getters */
 
        /**
                if ( file && file.type === 'image/jpeg' ) {
                        fileReader = new FileReader();
                        fileReader.onload = function () {
+                               // TODO: fileStr is never used.
                                var fileStr, arr, i, metadata;
 
                                if ( typeof fileReader.result === 'string' ) {
                                }
 
                                try {
-                                       metadata = mw.libs.jpegmeta( this.result, file.name );
+                                       metadata = mw.libs.jpegmeta( fileStr, file.name );
                                } catch ( e ) {
                                        metadata = null;
                                }
        mw.ForeignStructuredUpload.BookletLayout.prototype.clear = function () {
                mw.ForeignStructuredUpload.BookletLayout.parent.prototype.clear.call( this );
 
-               if ( this.ownWorkCheckbox ) {
-                       this.ownWorkCheckbox.setSelected( false );
-               }
-               if ( this.licenseCheckboxes ) {
-                       this.licenseCheckboxes.forEach( function ( checkbox ) {
-                               checkbox.setSelected( false );
-                       } );
-               }
-               if ( this.licenseSelectFields ) {
-                       this.licenseSelectFields.forEach( function ( field, i ) {
-                               field.fieldWidget.selectItem( null );
-                               if ( i !== 0 ) {
-                                       field.toggle( false );
-                               }
-                       } );
-               }
-
+               this.ownWorkCheckbox.setSelected( false );
                this.categoriesWidget.setItemsFromData( [] );
                this.dateWidget.setValue( '' ).setValidityFlag( true );
        };