/*
* This file is part of the MediaWiki extension UploadWizard.
*
* UploadWizard 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.
*
* UploadWizard 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 UploadWizard. If not, see .
*/
( function ( mw, uw, OO, $ ) {
/**
* Represents a step in the wizard.
*
* @class
* @mixins OO.EventEmitter
* @abstract
* @param {uw.ui.Step} ui The UI object that controls this step.
* @param {mw.Api} api
* @param {Object} config UploadWizard config object.
*/
uw.controller.Step = function UWControllerStep( ui, api, config ) {
var step = this;
OO.EventEmitter.call( this );
/**
* @property {Object} config
*/
this.config = config;
/**
* @property {mw.Api} api
*/
this.api = api;
this.ui = ui;
this.uploads = [];
/**
* Upload object event handlers to be bound on load & unbound on unload.
* This is an object literal where the keys are callback names, and
* values all callback. These callbacks will be called with the
* controller as content (`this`), and the upload as first argument.
* This'll effectively be:
* `upload.on( , .bind( this, upload ) );`
*
* @property {Object}
*
*/
this.uploadHandlers = {
'remove-upload': this.removeUpload
};
this.ui.on( 'next-step', function () {
step.moveNext();
} );
this.ui.on( 'previous-step', function () {
step.movePrevious();
} );
/**
* @property {uw.controller.Step} nextStep
* 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 );
/**
* Set the next step in the process.
*
* @param {uw.controller.Step} step
*/
uw.controller.Step.prototype.setNextStep = function ( step ) {
this.nextStep = step;
this.ui.enableNextButton();
};
/**
* 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();
};
/**
* Initialize this step.
*
* @param {mw.UploadWizardUpload[]} uploads List of uploads being carried forward.
*/
uw.controller.Step.prototype.load = function ( uploads ) {
var step = this;
this.emit( 'load' );
this.uploads = uploads || [];
// prevent the window from being closed as long as we have data
this.allowCloseWindow = mw.confirmCloseWindow( {
message: mw.message( 'mwe-upwiz-prevent-close' ).text(),
test: step.hasData.bind( this )
} );
$.each( this.uploads, function ( i, upload ) {
upload.state = step.stepName;
step.bindUploadHandlers( upload );
} );
this.ui.load( uploads );
uw.eventFlowLogger.logStep( this.stepName );
};
/**
* Cleanup this step.
*/
uw.controller.Step.prototype.unload = function () {
var step = this;
$.each( this.uploads, function ( i, upload ) {
step.unbindUploadHandlers( upload );
} );
this.allowCloseWindow.release();
this.ui.unload();
this.emit( 'unload' );
};
/**
* Move to the next step.
*/
uw.controller.Step.prototype.moveNext = function () {
this.unload();
if ( this.nextStep ) {
this.nextStep.load( this.uploads );
}
};
/**
* Move to the previous step.
*/
uw.controller.Step.prototype.movePrevious = function () {
this.unload();
if ( this.previousStep ) {
this.previousStep.load( this.uploads );
}
};
/**
* Attaches controller-specific upload event handlers.
*
* @param {mw.UploadWizardUpload} upload
*/
uw.controller.Step.prototype.bindUploadHandlers = function ( upload ) {
var controller = this;
$.each( this.uploadHandlers, function ( event, callback ) {
upload.on( event, callback, [ upload ], controller );
} );
};
/**
* Removes controller-specific upload event handlers.
*
* @param {mw.UploadWizardUpload} upload
*/
uw.controller.Step.prototype.unbindUploadHandlers = function ( upload ) {
var controller = this;
$.each( this.uploadHandlers, function ( event, callback ) {
upload.off( event, callback, controller );
} );
};
/**
* Check if upload is able to be put through this step's changes.
*
* @return {boolean}
*/
uw.controller.Step.prototype.canTransition = function () {
return true;
};
/**
* Figure out what to do and what options to show after the uploads have stopped.
* Uploading has stopped for one of the following reasons:
* 1) The user removed all uploads before they completed, in which case we are at upload.length === 0. We should start over and allow them to add new ones
* 2) All succeeded - show link to next step
* 3) Some failed, some succeeded - offer them the chance to retry the failed ones or go on to the next step
* 4) All failed -- have to retry, no other option
* In principle there could be other configurations, like having the uploads not all in error or stashed state, but
* we trust that this hasn't happened.
*
* For uploads that have succeeded, now is the best time to add the relevant previews and details to the DOM
* in the right order.
*
* @return {boolean} Whether all of the uploads are in a successful state.
*/
uw.controller.Step.prototype.showNext = function () {
var okCount = this.getUploadStatesCount( this.finishState ),
$buttons;
// abort if all uploads have been removed
if ( this.uploads.length === 0 ) {
return false;
}
this.updateProgressBarCount( okCount );
$buttons = this.ui.$div.find( '.mwe-upwiz-buttons' );
$buttons.show();
$buttons.find( '.mwe-upwiz-file-next-all-ok' ).hide();
$buttons.find( '.mwe-upwiz-file-next-some-failed' ).hide();
$buttons.find( '.mwe-upwiz-file-next-all-failed' ).hide();
if ( okCount === this.uploads.length ) {
$buttons.find( '.mwe-upwiz-file-next-all-ok' ).show();
return true;
}
if ( this.getUploadStatesCount( [ 'error', 'recoverable-error' ] ) === this.uploads.length ) {
$buttons.find( '.mwe-upwiz-file-next-all-failed' ).show();
} else if ( this.getUploadStatesCount( 'transporting' ) === 0 ) {
$buttons.find( '.mwe-upwiz-file-next-some-failed' ).show();
}
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 ( states.indexOf( upload.state ) > -1 ) {
count++;
}
} );
return count;
};
/**
* Function used by some steps to update progress bar for the whole
* batch of uploads.
*/
uw.controller.Step.prototype.updateProgressBarCount = function () {};
/**
* Check if this step has data, to test if the window can be close (i.e. if
* content is going to be lost)
*
* @return {boolean}
*/
uw.controller.Step.prototype.hasData = function () {
return this.uploads.length !== 0;
};
/**
* Add an upload.
*
* @param {mw.UploadWizardUpload} upload
*/
uw.controller.Step.prototype.addUpload = function ( upload ) {
this.uploads.push( upload );
};
/**
* Remove an upload.
*
* @param {mw.UploadWizardUpload} upload
*/
uw.controller.Step.prototype.removeUpload = function ( upload ) {
// remove the upload from the uploads array
var index = this.uploads.indexOf( upload );
if ( index !== -1 ) {
this.uploads.splice( index, 1 );
}
// let the upload object cleanup itself!
upload.remove();
};
/**
* Remove multiple uploads.
*
* @param {mw.UploadWizardUpload[]} uploads
*/
uw.controller.Step.prototype.removeUploads = function ( uploads ) {
var i,
// clone the array of uploads, just to be sure it's not a reference
// to this.uploads, which will be modified (and we can't have that
// while we're looping it)
copy = uploads.slice();
for ( i = 0; i < copy.length; i++ ) {
this.removeUpload( copy[ i ] );
}
};
/**
* Clear out uploads that are in error mode, perhaps before proceeding to the next step
*/
uw.controller.Step.prototype.removeErrorUploads = function () {
// We must not remove items from an array while iterating over it with $.each (it causes the
// next item to be skipped). Find and queue them first, then remove them.
var toRemove = [];
$.each( this.uploads, function ( i, upload ) {
if ( upload.state === 'error' || upload.state === 'recoverable-error' ) {
toRemove.push( upload );
}
} );
this.removeUploads( toRemove );
};
}( mediaWiki, mediaWiki.uploadWizard, OO, jQuery ) );