mw.Upload: Refactor error handling for the umpteenth time
[lhc/web/wiklou.git] / resources / src / mediawiki / mediawiki.Upload.js
1 ( function ( mw, $ ) {
2 var UP;
3
4 /**
5 * @class mw.Upload
6 *
7 * Used to represent an upload in progress on the frontend.
8 * Most of the functionality is implemented in mw.Api.plugin.upload,
9 * but this model class will tie it together as well as let you perform
10 * actions in a logical way.
11 *
12 * A simple example:
13 *
14 * var file = new OO.ui.SelectFileWidget(),
15 * button = new OO.ui.ButtonWidget( { label: 'Save' } ),
16 * upload = new mw.Upload;
17 *
18 * button.on( 'click', function () {
19 * upload.setFile( file.getValue() );
20 * upload.setFilename( file.getValue().name );
21 * upload.upload();
22 * } );
23 *
24 * $( 'body' ).append( file.$element, button.$element );
25 *
26 * You can also choose to {@link #uploadToStash stash the upload} and
27 * {@link #finishStashUpload finalize} it later:
28 *
29 * var file, // Some file object
30 * upload = new mw.Upload,
31 * stashPromise = $.Deferred();
32 *
33 * upload.setFile( file );
34 * upload.uploadToStash().then( function () {
35 * stashPromise.resolve();
36 * } );
37 *
38 * stashPromise.then( function () {
39 * upload.setFilename( 'foo' );
40 * upload.setText( 'bar' );
41 * upload.finishStashUpload().then( function () {
42 * console.log( 'Done!' );
43 * } );
44 * } );
45 *
46 * @constructor
47 * @param {Object|mw.Api} [apiconfig] A mw.Api object (or subclass), or configuration
48 * to pass to the constructor of mw.Api.
49 */
50 function Upload( apiconfig ) {
51 this.api = ( apiconfig instanceof mw.Api ) ? apiconfig : new mw.Api( apiconfig );
52
53 this.watchlist = false;
54 this.text = '';
55 this.comment = '';
56 this.filename = null;
57 this.file = null;
58 this.setState( Upload.State.NEW );
59
60 this.imageinfo = undefined;
61 }
62
63 UP = Upload.prototype;
64
65 /**
66 * Set the text of the file page, to be created on file upload.
67 *
68 * @param {string} text
69 */
70 UP.setText = function ( text ) {
71 this.text = text;
72 };
73
74 /**
75 * Set the filename, to be finalized on upload.
76 *
77 * @param {string} filename
78 */
79 UP.setFilename = function ( filename ) {
80 this.filename = filename;
81 };
82
83 /**
84 * Sets the filename based on the filename as it was on the upload.
85 */
86 UP.setFilenameFromFile = function () {
87 var file = this.getFile();
88 if ( file.nodeType && file.nodeType === Node.ELEMENT_NODE ) {
89 // File input element, use getBasename to cut out the path
90 this.setFilename( this.getBasename( file.value ) );
91 } else if ( file.name && file.lastModified ) {
92 // HTML5 FileAPI File object, but use getBasename to be safe
93 this.setFilename( this.getBasename( file.name ) );
94 }
95 };
96
97 /**
98 * Set the file to be uploaded.
99 *
100 * @param {HTMLInputElement|File} file
101 */
102 UP.setFile = function ( file ) {
103 this.file = file;
104 };
105
106 /**
107 * Set whether the file should be watchlisted after upload.
108 *
109 * @param {boolean} watchlist
110 */
111 UP.setWatchlist = function ( watchlist ) {
112 this.watchlist = watchlist;
113 };
114
115 /**
116 * Set the edit comment for the upload.
117 *
118 * @param {string} comment
119 */
120 UP.setComment = function ( comment ) {
121 this.comment = comment;
122 };
123
124 /**
125 * Get the text of the file page, to be created on file upload.
126 *
127 * @return {string}
128 */
129 UP.getText = function () {
130 return this.text;
131 };
132
133 /**
134 * Get the filename, to be finalized on upload.
135 *
136 * @return {string}
137 */
138 UP.getFilename = function () {
139 return this.filename;
140 };
141
142 /**
143 * Get the file being uploaded.
144 *
145 * @return {HTMLInputElement|File}
146 */
147 UP.getFile = function () {
148 return this.file;
149 };
150
151 /**
152 * Get the boolean for whether the file will be watchlisted after upload.
153 *
154 * @return {boolean}
155 */
156 UP.getWatchlist = function () {
157 return this.watchlist;
158 };
159
160 /**
161 * Get the current value of the edit comment for the upload.
162 *
163 * @return {string}
164 */
165 UP.getComment = function () {
166 return this.comment;
167 };
168
169 /**
170 * Gets the base filename from a path name.
171 *
172 * @param {string} path
173 * @return {string}
174 */
175 UP.getBasename = function ( path ) {
176 if ( path === undefined || path === null ) {
177 return '';
178 }
179
180 // Find the index of the last path separator in the
181 // path, and add 1. Then, take the entire string after that.
182 return path.slice(
183 Math.max(
184 path.lastIndexOf( '/' ),
185 path.lastIndexOf( '\\' )
186 ) + 1
187 );
188 };
189
190 /**
191 * Sets the state and state details (if any) of the upload.
192 *
193 * @param {mw.Upload.State} state
194 * @param {Object} stateDetails
195 */
196 UP.setState = function ( state, stateDetails ) {
197 this.state = state;
198 this.stateDetails = stateDetails;
199 };
200
201 /**
202 * Gets the state of the upload.
203 *
204 * @return {mw.Upload.State}
205 */
206 UP.getState = function () {
207 return this.state;
208 };
209
210 /**
211 * Gets details of the current state.
212 *
213 * @return {string}
214 */
215 UP.getStateDetails = function () {
216 return this.stateDetails;
217 };
218
219 /**
220 * Get the imageinfo object for the finished upload.
221 * Only available once the upload is finished! Don't try to get it
222 * beforehand.
223 *
224 * @return {Object|undefined}
225 */
226 UP.getImageInfo = function () {
227 return this.imageinfo;
228 };
229
230 /**
231 * Upload the file directly.
232 *
233 * @return {jQuery.Promise}
234 */
235 UP.upload = function () {
236 var upload = this;
237
238 if ( !this.getFile() ) {
239 return $.Deferred().reject( 'No file to upload. Call setFile to add one.' );
240 }
241
242 if ( !this.getFilename() ) {
243 return $.Deferred().reject( 'No filename set. Call setFilename to add one.' );
244 }
245
246 this.setState( Upload.State.UPLOADING );
247
248 return this.api.upload( this.getFile(), {
249 watchlist: ( this.getWatchlist() ) ? 1 : undefined,
250 comment: this.getComment(),
251 filename: this.getFilename(),
252 text: this.getText()
253 } ).then( function ( result ) {
254 upload.setState( Upload.State.UPLOADED );
255 upload.imageinfo = result.upload.imageinfo;
256 return result;
257 }, function ( errorCode, result ) {
258 if ( result && result.upload && result.upload.warnings ) {
259 upload.setState( Upload.State.WARNING, result );
260 } else {
261 upload.setState( Upload.State.ERROR, result );
262 }
263 return $.Deferred().reject( errorCode, result );
264 } );
265 };
266
267 /**
268 * Upload the file to the stash to be completed later.
269 *
270 * @return {jQuery.Promise}
271 */
272 UP.uploadToStash = function () {
273 var upload = this;
274
275 if ( !this.getFile() ) {
276 return $.Deferred().reject( 'No file to upload. Call setFile to add one.' );
277 }
278
279 if ( !this.getFilename() ) {
280 this.setFilenameFromFile();
281 }
282
283 this.setState( Upload.State.UPLOADING );
284
285 this.stashPromise = this.api.uploadToStash( this.getFile(), {
286 filename: this.getFilename()
287 } ).then( function ( finishStash ) {
288 upload.setState( Upload.State.STASHED );
289 return finishStash;
290 }, function ( errorCode, result ) {
291 if ( result && result.upload && result.upload.warnings ) {
292 upload.setState( Upload.State.WARNING, result );
293 } else {
294 upload.setState( Upload.State.ERROR, result );
295 }
296 return $.Deferred().reject( errorCode, result );
297 } );
298
299 return this.stashPromise;
300 };
301
302 /**
303 * Finish a stash upload.
304 *
305 * @return {jQuery.Promise}
306 */
307 UP.finishStashUpload = function () {
308 var upload = this;
309
310 if ( !this.stashPromise ) {
311 return $.Deferred().reject( 'This upload has not been stashed, please upload it to the stash first.' );
312 }
313
314 return this.stashPromise.then( function ( finishStash ) {
315 upload.setState( Upload.State.UPLOADING );
316
317 return finishStash( {
318 watchlist: ( upload.getWatchlist() ) ? 1 : undefined,
319 comment: upload.getComment(),
320 filename: upload.getFilename(),
321 text: upload.getText()
322 } ).then( function ( result ) {
323 upload.setState( Upload.State.UPLOADED );
324 upload.imageinfo = result.upload.imageinfo;
325 return result;
326 }, function ( errorCode, result ) {
327 if ( result && result.upload && result.upload.warnings ) {
328 upload.setState( Upload.State.WARNING, result );
329 } else {
330 upload.setState( Upload.State.ERROR, result );
331 }
332 return $.Deferred().reject( errorCode, result );
333 } );
334 } );
335 };
336
337 /**
338 * @enum mw.Upload.State
339 * State of uploads represented in simple terms.
340 */
341 Upload.State = {
342 /** Upload not yet started */
343 NEW: 0,
344
345 /** Upload finished, but there was a warning */
346 WARNING: 1,
347
348 /** Upload finished, but there was an error */
349 ERROR: 2,
350
351 /** Upload in progress */
352 UPLOADING: 3,
353
354 /** Upload finished, but not published, call #finishStashUpload */
355 STASHED: 4,
356
357 /** Upload finished and published */
358 UPLOADED: 5
359 };
360
361 mw.Upload = Upload;
362 }( mediaWiki, jQuery ) );