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