Introducing mw.libs, moving JpegMeta into it.
[mediawiki.git] / resources / mediawiki.special / mediawiki.special.upload.js
blob51f6bd8b7100096a017beacdd83e199e49a48838
1 /*
2  * JavaScript for Special:Upload
3  * Note that additional code still lives in skins/common/upload.js
4  */
6 /**
7  * Add a preview to the upload form
8  */
9 jQuery( function( $ ) {
10         /**
11          * Is the FileAPI available with sufficient functionality?
12          */
13         function hasFileAPI(){
14                 return typeof window.FileReader !== 'undefined';
15         }
17         /**
18          * Check if this is a recognizable image type...
19          * Also excludes files over 10M to avoid going insane on memory usage.
20          *
21          * @todo is there a way we can ask the browser what's supported in <img>s?
22          *
23          * @param {File} file
24          * @return boolean
25          */
26         function fileIsPreviewable( file ) {
27                 var     known = ['image/png', 'image/gif', 'image/jpeg', 'image/svg+xml'],
28                         tooHuge = 10 * 1024 * 1024;
29                 return ( $.inArray( file.type, known ) !== -1 ) && file.size > 0 && file.size < tooHuge;
30         }
32         /**
33          * Show a thumbnail preview of PNG, JPEG, GIF, and SVG files prior to upload
34          * in browsers supporting HTML5 FileAPI.
35          *
36          * As of this writing, known good:
37          * - Firefox 3.6+
38          * - Chrome 7.something
39          *
40          * @todo check file size limits and warn of likely failures
41          *
42          * @param {File} file
43          */
44         function showPreview( file ) {
45                 var     previewSize = 180,
46                         thumb = $( '<div id="mw-upload-thumbnail" class="thumb tright">' +
47                                                 '<div class="thumbinner">' +
48                                                         '<canvas width="' + previewSize + '" height="' + previewSize + '" ></canvas>' +
49                                                         '<div class="thumbcaption"><div class="filename"></div><div class="fileinfo"></div></div>' +
50                                                 '</div>' +
51                                         '</div>' );
52                 thumb.find( '.filename' ).text( file.name ).end()
53                         .find( '.fileinfo' ).text( prettySize( file.size ) ).end();
54                 
55                 var     ctx = thumb.find( 'canvas' )[0].getContext( '2d' ),
56                         spinner = new Image();
57                 spinner.onload = function() { 
58                         ctx.drawImage( spinner, (previewSize - spinner.width) / 2, 
59                                         (previewSize - spinner.height) / 2 ); 
60                 };
61                 spinner.src = mw.config.get( 'wgScriptPath' ) + '/skins/common/images/spinner.gif';
62                 $( '#mw-htmlform-source' ).parent().prepend( thumb );
64                 var meta;
65                 fetchPreview( file, function( dataURL ) {
66                         var     img = new Image(),
67                                 rotation = 0;
68                         
69                         if ( meta && meta.tiff && meta.tiff.Orientation ) {
70                                 rotation = (360 - function () {
71                                         // See includes/media/Bitmap.php
72                                         switch ( meta.tiff.Orientation.value ) {
73                                                 case 8:
74                                                         return 90;
75                                                 case 3:
76                                                         return 180;
77                                                 case 6:
78                                                         return 270;
79                                                 default:
80                                                         return 0;
81                                         }
82                                 }() ) % 360;
83                         }
84                         
85                         img.onload = function() {
86                                 var width, height, x, y, dx, dy;
87                                 // Fit the image within the previewSizexpreviewSize box
88                                 if ( img.width > img.height ) {
89                                         width = previewSize;
90                                         height = img.height / img.width * previewSize;
91                                 } else {
92                                         height = previewSize;
93                                         width = img.width / img.height * previewSize;
94                                 }
95                                 // Determine the offset required to center the image
96                                 dx = (180 - width) / 2;
97                                 dy = (180 - height) / 2;
98                                 switch ( rotation ) {
99                                         // If a rotation is applied, the direction of the axis
100                                         // changes as well. You can derive the values below by 
101                                         // drawing on paper an axis system, rotate it and see
102                                         // where the positive axis direction is
103                                         case 0:
104                                                 x = dx;
105                                                 y = dy;
106                                                 break;
107                                         case 90:
108                                                 
109                                                 x = dx;
110                                                 y = dy - previewSize;
111                                                 break;
112                                         case 180:
113                                                 x = dx - previewSize;
114                                                 y = dy - previewSize;
115                                                 break;
116                                         case 270:
117                                                 x = dx - previewSize;
118                                                 y = dy;
119                                                 break;
120                                 }
121                                 
122                                 ctx.clearRect( 0, 0, 180, 180 );
123                                 ctx.rotate( rotation / 180 * Math.PI );
124                                 ctx.drawImage( img, x, y, width, height );
125                                 
126                                 // Image size
127                                 var info = mw.msg( 'widthheight', img.width, img.height ) +
128                                         ', ' + prettySize( file.size );
129                                 $( '#mw-upload-thumbnail .fileinfo' ).text( info );
130                         };
131                         img.src = dataURL;
132                 }, mw.config.get( 'wgFileCanRotate' ) ? function ( data ) {
133                         try {
134                                 meta = mw.libs.jpegmeta( data, file.fileName );
135                                 meta._binary_data = null;
136                         } catch ( e ) {
137                                 meta = null;
138                         }
139                 } : null );
140         }
142         /**
143          * Start loading a file into memory; when complete, pass it as a
144          * data URL to the callback function. If the callbackBinary is set it will
145          * first be read as binary and afterwards as data URL. Useful if you want
146          * to do preprocessing on the binary data first.
147          *
148          * @param {File} file
149          * @param {function} callback
150          * @param {function} callbackBinary
151          */
152         function fetchPreview( file, callback, callbackBinary ) {
153                 var reader = new FileReader();
154                 reader.onload = function() {
155                         if ( callbackBinary ) {
156                                 callbackBinary( reader.result );
157                                 reader.onload = function() {
158                                         callback( reader.result );
159                                 };
160                                 reader.readAsDataURL( file );
161                         } else {
162                                 callback( reader.result );
163                         }
164                 };
165                 if ( callbackBinary ) {
166                         reader.readAsBinaryString( file );
167                 } else {
168                         reader.readAsDataURL( file );
169                 }
170         }
172         /**
173          * Format a file size attractively.
174          * @todo match numeric formatting
175          *
176          * @param {number} s
177          * @return string
178          */
179         function prettySize( s ) {
180                 var sizes = ['size-bytes', 'size-kilobytes', 'size-megabytes', 'size-gigabytes'];
181                 while ( s >= 1024 && sizes.length > 1 ) {
182                         s /= 1024;
183                         sizes = sizes.slice( 1 );
184                 }
185                 return mw.msg( sizes[0], Math.round( s ) );
186         }
188         /**
189          * Clear the file upload preview area.
190          */
191         function clearPreview() {
192                 $( '#mw-upload-thumbnail' ).remove();
193         }
194         
195         /**
196          * Check if the file does not exceed the maximum size
197          */
198         function checkMaxUploadSize( file ) {
199                 function getMaxUploadSize( type ) {
200                         var sizes = mw.config.get( 'wgMaxUploadSize' );
201                         if ( sizes[type] !== undefined ) {
202                                 return sizes[type];
203                         }
204                         return sizes['*'];
205                 }
206                 $( '.mw-upload-source-error' ).remove();
207                 
208                 var maxSize = getMaxUploadSize( 'file' ); 
209                 if ( file.size > maxSize ) {
210                         var error = $( '<p class="error mw-upload-source-error" id="wpSourceTypeFile-error">' + 
211                                         mw.message( 'largefileserver', file.size, maxSize ).escaped() + '</p>' );
212                         $( '#wpUploadFile' ).after( error );
213                         return false;
214                 }
215                 return true;
216         }
217         
218         
219         /**
220          * Initialization
221          */
222         if ( hasFileAPI() ) {
223                 // Update thumbnail when the file selection control is updated.
224                 $( '#wpUploadFile' ).change( function() {
225                         clearPreview();
226                         if ( this.files && this.files.length ) {
227                                 // Note: would need to be updated to handle multiple files.
228                                 var file = this.files[0];
229                                 
230                                 if ( !checkMaxUploadSize( file ) ) {
231                                         return;
232                                 }
233                                 
234                                 if ( fileIsPreviewable( file ) ) {
235                                         showPreview( file );
236                                 }
237                         }
238                 } );
239         }
240 } );
243  * Disable all upload source fields except the selected one
244  */
245 jQuery( function ( $ ) {
246         var rows = $( '.mw-htmlform-field-UploadSourceField' );
247         for ( var i = rows.length; i; i-- ) {
248                 var row = rows[i - 1];
249                 $( 'input[name="wpSourceType"]', row ).change( function () {
250                         var currentRow = row; // Store current row in our own scope
251                         return function () {
252                                 $( '.mw-upload-source-error' ).remove();
253                                 if ( this.checked ) {
254                                         // Disable all inputs
255                                         $( 'input[name!="wpSourceType"]', rows ).attr( 'disabled', true );
256                                         // Re-enable the current one
257                                         $( 'input', currentRow ).attr( 'disabled', false );
258                                 }
259                         };
260                 }() );
261         }
262 } );