r89918 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r89917‎ | r89918 | r89919 >
Date:05:28, 12 June 2011
Author:neilk
Status:resolved (Comments)
Tags:
Comment:
Use FileAPI for previews when possible.
This complicates the situation of when a thumbnail may be generated. Depending on config or layout, it could be any of:

1) Thumbnail wanted before ready
2) Thumbnail wanted after ready

multiplied by

1) FileAPI, locally scaled
2) Stashed Image API, remotely scaled, with exponential backoff retries
3) Image API, remotely scaled, with exponential backoff retries

And of course all the error conditions!

So, I have moved to a different model for obtaining thumbnails, where you just declare that a certain HTML element needs a thumbnail with
setThumbnail(), and the framework figures out what it's supposed to do, and the thumbnail should show up eventually (or finally
give you a broken image and an error condition).

The thumbnail framework is now using a pub/sub model with a twist; certain events can happen only once, and you can subscribe to them
in the past. This is a little bit like the 'ready' state of some DOM elements, so these are called by 'publishReady' and 'subscribeReady'.
Think of it as "publish that we are now ready to show this thumbnail" or "do this action when the thumbnail is ready (or right away if
it already is)".
Modified paths:
  • /trunk/extensions/UploadWizard/UploadWizardHooks.php (modified) (history)
  • /trunk/extensions/UploadWizard/resources/mw.UploadWizard.js (modified) (history)
  • /trunk/extensions/UploadWizard/resources/mw.UploadWizardUploadInterface.js (modified) (history)

Diff [purge]

Index: trunk/extensions/UploadWizard/UploadWizardHooks.php
@@ -46,6 +46,8 @@
4747 'resources/jquery/jquery.validate.wmCommonsBlacklist.js',
4848
4949 // common utilities
 50+ 'resources/mw.fileApi.js',
 51+ 'resources/mw.units.js',
5052 'resources/mw.Log.js',
5153 'resources/mw.Utilities.js',
5254 'resources/mw.UtilitiesTime.js',
@@ -333,7 +335,12 @@
334336 'mwe-upwiz-feedback-adding',
335337 'mwe-upwiz-feedback-error1',
336338 'mwe-upwiz-feedback-error2',
337 - 'mwe-upwiz-feedback-error3'
 339+ 'mwe-upwiz-feedback-error3',
 340+ 'size-terabytes',
 341+ 'size-gigabytes',
 342+ 'size-megabytes',
 343+ 'size-kilobytes',
 344+ 'size-bytes'
338345 ),
339346 'group' => 'ext.uploadWizard'
340347 ),
Index: trunk/extensions/UploadWizard/resources/mw.UploadWizardUploadInterface.js
@@ -15,7 +15,13 @@
1616 _this.isFilled = false;
1717
1818 _this.$fileInputCtrl = $j('<input size="1" class="mwe-upwiz-file-input" name="file" type="file"/>')
19 - .change( function() { _this.fileChanged(); } );
 19+ .change( function() {
 20+ _this.clearErrors();
 21+ _this.upload.extractLocalFileInfo(
 22+ this, // the file input
 23+ function() { _this.fileChanged(); }
 24+ );
 25+ } );
2026
2127 _this.$indicator = $j( '<div class="mwe-upwiz-file-indicator"></div>' );
2228
@@ -82,6 +88,14 @@
8389 $j( _this.div ).bind( 'transportProgressEvent', function(e) { _this.showTransportProgress(); } );
8490 // $j( _this.div ).bind( 'transportedEvent', function(e) { _this.showStashed(); } );
8591
 92+ // XXX feature envy
 93+ var $preview = $j( this.div ).find( '.mwe-upwiz-file-preview' );
 94+ _this.upload.setThumbnail(
 95+ $preview,
 96+ mw.UploadWizard.config[ 'thumbnailWidth' ],
 97+ mw.UploadWizard.config[ 'thumbnailMaxHeight' ]
 98+ );
 99+
86100 };
87101
88102
@@ -143,7 +157,6 @@
144158 * Set the status line for this upload with an internationalized message string.
145159 * @param String msgKey: key for the message
146160 * @param Array args: array of values, in case any need to be fed to the image.
147 - * @param Boolean error: if true, show an error
148161 */
149162 setStatus: function( msgKey, args ) {
150163 if ( !mw.isDefined( args ) ) {
@@ -155,6 +168,14 @@
156169 },
157170
158171 /**
 172+ * Set status line directly with a string
 173+ * @param {String}
 174+ */
 175+ setStatusString: function( s ) {
 176+ $j( this.div ).find( '.mwe-upwiz-file-status' ).html( s ).show();
 177+ },
 178+
 179+ /**
159180 * Clear the status line for this upload (hide it, in case there are paddings and such which offset other things.)
160181 */
161182 clearStatus: function() {
@@ -209,10 +230,10 @@
210231 */
211232 fileChanged: function() {
212233 var _this = this;
213 - _this.clearErrors();
214 - _this.upload.extractLocalFileInfo( _this.$fileInputCtrl.val() );
215234 var extension = _this.upload.title.getExtension();
216235 var hasExtension = ! mw.isEmpty( extension );
 236+
 237+ /* this should not be in the interface */
217238 var isGoodExtension = false;
218239 if ( hasExtension ) {
219240 isGoodExtension = $j.inArray( extension.toLowerCase(), mw.UploadWizard.config[ 'fileExtensions' ] ) !== -1;
@@ -220,19 +241,20 @@
221242 if ( hasExtension && isGoodExtension ) {
222243 _this.updateFilename();
223244 } else {
 245+ var $errorMessage;
224246 // Check if firefogg should be recommended to be installed ( user selects an extension that can be converted)
225247 if( mw.UploadWizard.config['enableFirefogg']
226248 &&
227249 $j.inArray( extension.toLowerCase(), mw.UploadWizard.config['transcodeExtensionList'] ) !== -1
228250 ){
229 - var $errorMessage = $j( '<p>' ).msg('mwe-upwiz-upload-error-bad-extension-video-firefogg',
 251+ $errorMessage = $j( '<p>' ).msg('mwe-upwiz-upload-error-bad-extension-video-firefogg',
230252 mw.Firefogg.getFirefoggInstallUrl(),
231253 'http://commons.wikimedia.org/wiki/Help:Converting_video'
232254 );
233255
234256 } else {
235257 var errorKey = hasExtension ? 'mwe-upwiz-upload-error-bad-filename-extension' : 'mwe-upwiz-upload-error-bad-filename-no-extension';
236 - var $errorMessage = $j( '<p>' ).msg( errorKey, extension );
 258+ $errorMessage = $j( '<p>' ).msg( errorKey, extension );
237259 }
238260 $( '<div></div>' )
239261 .append(
@@ -250,7 +272,11 @@
251273 modal: true
252274 });
253275 }
 276+
254277 this.clearStatus();
 278+ if ( this.upload.file ) {
 279+ this.setStatusString( mw.units.bytes( this.upload.file.size ) );
 280+ }
255281 },
256282
257283 /**
@@ -326,6 +352,8 @@
327353 // But for UploadWizard, at this stage, it's the reverse. We want to stop same-content dead, but for now we ignore same-filename
328354 $j( _this.filenameCtrl ).val( ( new Date() ).getTime().toString() +_this.upload.title.getMain() );
329355
 356+
 357+ // deal with styling the file inputs and making it react to mouse
330358 if ( ! _this.isFilled ) {
331359 var $div = $j( _this.div );
332360 _this.isFilled = true;
Index: trunk/extensions/UploadWizard/resources/mw.UploadWizard.js
@@ -8,6 +8,10 @@
99 ( function( $j ) {
1010
1111 mw.UploadWizardUpload = function( api, filesDiv ) {
 12+
 13+ this.index = mw.UploadWizardUpload.prototype.count;
 14+ mw.UploadWizardUpload.prototype.count++;
 15+
1216 this.api = api;
1317 this.state = 'new';
1418 this.thumbnails = {};
@@ -20,8 +24,8 @@
2125 this.sessionKey = undefined;
2226
2327 // this should be moved to the interface, if we even keep this
24 - this.transportWeight = 1; // default
25 - this.detailsWeight = 1; // default
 28+ this.transportWeight = 1; // default all same
 29+ this.detailsWeight = 1; // default all same
2630
2731 // details
2832 this.ui = new mw.UploadWizardUploadInterface( this, filesDiv );
@@ -32,8 +36,6 @@
3337 this.handler = this.getUploadHandler();
3438
3539
36 - this.index = mw.UploadWizardUpload.prototype.count;
37 - mw.UploadWizardUpload.prototype.count++;
3840 };
3941
4042 mw.UploadWizardUpload.prototype = {
@@ -231,32 +233,18 @@
232234 * @param {Mixed} result -- result of AJAX call
233235 */
234236 setSuccess: function( result ) {
235 - var _this = this; // was a triumph
 237+ var _this = this;
236238 _this.state = 'transported';
237239 _this.transportProgress = 1;
238240
239 - // I'm making a note here
240241 _this.ui.setStatus( 'mwe-upwiz-getting-metadata' );
241242 if ( result.upload ) {
242243 _this.extractUploadInfo( result.upload );
243 - _this.getThumbnail(
244 - function( image ) {
245 - // n.b. if server returns a URL, which is a 404, we do NOT get broken image
246 - _this.ui.setPreview( image ); // make the thumbnail the preview image
247 - },
248 - mw.UploadWizard.config[ 'thumbnailWidth' ],
249 - mw.UploadWizard.config[ 'thumbnailMaxHeight' ]
250 - );
251 - // create the large thumbnail that the other thumbnails link to
252 - _this.getThumbnail(
253 - function( image ) {},
254 - mw.UploadWizard.config[ 'largeThumbnailWidth' ],
255 - mw.UploadWizard.config[ 'largeThumbnailMaxHeight' ]
256 - );
257244 _this.deedPreview.setup();
258245 _this.details.populate();
259246 _this.state = 'stashed';
260247 _this.ui.showStashed();
 248+ $.publishReady( 'thumbnails.' + _this.index, 'api' );
261249 } else {
262250 _this.setError( 'noimageinfo' );
263251 }
@@ -265,18 +253,46 @@
266254
267255 /**
268256 * Called when the file is entered into the file input
269 - * Get as much data as possible -- maybe exif, even thumbnail maybe
 257+ * Get as much data as possible with this browser, including thumbnail.
 258+ * TODO exif
 259+ * @param {HTMLFileInput} file input field
 260+ * @param {Function} callback when ready
270261 */
271 - extractLocalFileInfo: function( filename ) {
272 - if ( false ) { // FileAPI, one day
273 - this.transportWeight = getFileSize();
274 - }
275 - // XXX sanitize filename
 262+ extractLocalFileInfo: function( fileInput, callback ) {
 263+ var _this = this;
 264+ if ( mw.fileApi.isAvailable() ) {
 265+ if ( fileInput.files && fileInput.files.length ) {
 266+ // TODO multiple files in an input
 267+ this.file = fileInput.files[0];
 268+ }
 269+ // TODO check max upload size, alert user if too big
 270+ this.transportWeight = this.file.size;
 271+
 272+ if ( mw.fileApi.isPreviewableFile( this.file ) ) {
 273+ // TODO all that other complicated file rotation / binary stuff from mediawiki.special.upload.js
 274+ var image = document.createElement( 'img' );
 275+ image.onload = function() {
 276+ $.publishReady( 'thumbnails.' + _this.index, image );
 277+ };
 278+ var reader = new FileReader();
 279+ reader.onload = function( progressEvent ) {
 280+ _this.thumbnails['*'] = reader.result;
 281+ image.src = reader.result;
 282+ };
 283+ reader.readAsDataURL( _this.file );
 284+ }
 285+
 286+ }
 287+ // TODO sanitize filename
 288+ var filename = fileInput.value;
276289 try {
277290 this.title = new mw.Title( mw.UploadWizardUtil.getBasename( filename ).replace( /:/g, '_' ), 'file' );
278291 } catch ( e ) {
279292 this.setError( 'mwe-upwiz-unparseable-filename', filename );
280293 }
 294+ if ( this.title ) {
 295+ callback();
 296+ }
281297 },
282298
283299 /**
@@ -285,16 +301,17 @@
286302 * @param result The JSON object from a successful API upload result.
287303 */
288304 extractUploadInfo: function( resultUpload ) {
 305+
289306 if ( resultUpload.sessionkey ) {
290307 this.sessionKey = resultUpload.sessionkey;
291308 }
 309+
292310 if ( resultUpload.imageinfo ) {
293311 this.extractImageInfo( resultUpload.imageinfo );
294312 } else if ( resultUpload.stashimageinfo ) {
295313 this.extractImageInfo( resultUpload.stashimageinfo );
296314 }
297315
298 -
299316 },
300317
301318 /**
@@ -335,6 +352,10 @@
336353 }
337354 */
338355 }
 356+
 357+
 358+
 359+
339360 },
340361
341362 /**
@@ -386,7 +407,69 @@
387408
388409 this.api.get( params, { ok: ok, err: err } );
389410 },
 411+
 412+
390413 /**
 414+ * Get information about published images
 415+ * (There is some overlap with getStashedImageInfo, but it's different at every stage so it's clearer to have separate functions)
 416+ * See API documentation for prop=imageinfo for what 'props' can contain
 417+ * @param {Function} callback -- called with null if failure, with imageinfo data structure if success
 418+ * @param {Array} properties to extract
 419+ * @param {Number} optional, width of thumbnail. Will force 'url' to be added to props
 420+ * @param {Number} optional, height of thumbnail. Will force 'url' to be added to props
 421+ */
 422+ getImageInfo: function( callback, props, width, height ) {
 423+ var _this = this;
 424+ if (!mw.isDefined( props ) ) {
 425+ props = [];
 426+ }
 427+ var requestedTitle = _this.title.getPrefixedText();
 428+ var params = {
 429+ 'prop': 'imageinfo',
 430+ 'titles': requestedTitle,
 431+ 'iiprop': props.join( '|' )
 432+ };
 433+
 434+ if ( mw.isDefined( width ) || mw.isDefined( height ) ) {
 435+ if ( ! $j.inArray( 'url', props ) ) {
 436+ props.push( 'url' );
 437+ }
 438+ if ( mw.isDefined( width ) ) {
 439+ params['iiurlwidth'] = width;
 440+ }
 441+ if ( mw.isDefined( height ) ) {
 442+ params['iiurlheight'] = height;
 443+ }
 444+ }
 445+
 446+ var ok = function( data ) {
 447+ if ( data && data.query && data.query.pages ) {
 448+ var found = false;
 449+ $j.each( data.query.pages, function( pageId, page ) {
 450+ if ( page.title && page.title === requestedTitle && page.imageinfo ) {
 451+ found = true;
 452+ callback( page.imageinfo );
 453+ return false;
 454+ }
 455+ } );
 456+ if ( found ) {
 457+ return;
 458+ }
 459+ }
 460+ mw.log("mw.UploadWizardUpload::getImageInfo> No data matching " + requestedTitle + " ? ");
 461+ callback( null );
 462+ };
 463+
 464+ var err = function( code, result ) {
 465+ mw.log( 'mw.UploadWizardUpload::getImageInfo> error: ' + code, 'debug' );
 466+ callback( null );
 467+ };
 468+
 469+ this.api.get( params, { ok: ok, err: err } );
 470+ },
 471+
 472+
 473+ /**
391474 * Get the upload handler per browser capabilities
392475 */
393476 getUploadHandler: function(){
@@ -405,173 +488,167 @@
406489 }
407490 return this.uploadHandler;
408491 },
 492+
409493 /**
410 - * Fetch a thumbnail for a stashed upload of the desired width.
411 - * It is assumed you don't call this until it's been transported.
 494+ * Explicitly fetch a thumbnail for a stashed upload of the desired width.
 495+ * Publishes to any event listeners that might have wanted it.
412496 *
413 - * @param callback - callback to execute once thumbnail has been obtained -- must accept Image object for success, null for error
414497 * @param width - desired width of thumbnail (height will scale to match)
415498 * @param height - (optional) maximum height of thumbnail
416499 */
417 - getThumbnail: function( callback, width, height ) {
 500+ getAndPublishApiThumbnail: function( key, width, height ) {
418501 var _this = this;
 502+
419503 if ( mw.isEmpty( height ) ) {
420504 height = -1;
421505 }
422 - // this key is overspecified for this thumbnail ( we don't need to reiterate the index ) but
423 - // we can use this as key as an event now, that might fire much later
424 - var key = 'thumbnail.' + _this.index + '.width' + width + ',height' + height;
425 - if ( mw.isDefined( _this.thumbnails[key] ) ) {
426 - callback( _this.thumbnails[key] );
427 - return;
428 - }
429506
430 - // subscribe to the event that this thumbnail is ready -- will give null or an Image to the callback
431 - $j.subscribe( key, callback );
432 -
433 - // if someone else already started a thumbnail fetch & publish, then don't bother, just wait for the event.
434 - if ( ! mw.isDefined( _this.thumbnailPublishers[ key ] ) ) {
435 - // The thumbnail publisher accepts the result of a stashImageInfo, and then tries to get the thumbnail, and eventually
436 - // will trigger the event we just subscribed to.
437 - _this.thumbnailPublishers[ key ] = _this.getThumbnailPublisher( key );
438 - _this.getStashImageInfo( _this.thumbnailPublishers[ key ], [ 'url' ], width, height );
439 - }
440 - },
441 -
442 - /**
443 - * Returns a callback that can be used with a stashImageInfo call to fetch images, and then fire off an event to
444 - * let everyone else know the image is loaded.
445 - *
446 - * Will retry the thumbnail URL several times, as thumbnails are known to be slow in production.
447 - * @param {String} name of event to publish when thumbnails received (or final failure)
448 - */
449 - getThumbnailPublisher: function( key ) {
450 - var _this = this;
451 - return function( thumbnails ) {
 507+ if ( !mw.isDefined( _this.thumbnailPublishers[key] ) ) {
 508+ var thumbnailPublisher = function( thumbnails ) {
452509 if ( thumbnails === null ) {
453 - // the api call failed somehow, no thumbnail data.
454 - $j.publish( key, null );
 510+ // the api call failed somehow, no thumbnail data.
 511+ $j.publishReady( key, null );
455512 } else {
456 - // ok, the api callback has returned us information on where the thumbnail(s) ARE, but that doesn't mean
457 - // they are actually there yet. Keep trying to set the source ( which should trigger "error" or "load" event )
458 - // on the image. If it loads publish the event with the image. If it errors out too many times, give up and publish
459 - // the event with a null.
460 - $j.each( thumbnails, function( i, thumb ) {
 513+ // ok, the api callback has returned us information on where the thumbnail(s) ARE, but that doesn't mean
 514+ // they are actually there yet. Keep trying to set the source ( which should trigger "error" or "load" event )
 515+ // on the image. If it loads publish the event with the image. If it errors out too many times, give up and publish
 516+ // the event with a null.
 517+ $j.each( thumbnails, function( i, thumb ) {
461518 if ( thumb.thumberror || ( ! ( thumb.thumburl && thumb.thumbwidth && thumb.thumbheight ) ) ) {
462519 mw.log( "mw.UploadWizardUpload::getThumbnail> thumbnail error or missing information" );
463 - $j.publish( key, null );
 520+ $j.publishReady( key, null );
464521 return;
465522 }
466523
467 - // try to load this image with exponential backoff
468 - // if the delay goes past 8 seconds, it gives up and publishes the event with null
469 - var timeoutMs = 100;
470 -
 524+ // try to load this image with exponential backoff
 525+ // if the delay goes past 8 seconds, it gives up and publishes the event with null
 526+ var timeoutMs = 100;
471527 var image = document.createElement( 'img' );
472528 image.width = thumb.thumbwidth;
473529 image.height = thumb.thumbheight;
474 - $j( image )
475 - .load( function() {
476 - // cache this thumbnail
477 - _this.thumbnails[key] = image;
478 - // publish the image to anyone who wanted it
479 - $j.publish( key, image );
480 - } )
481 - .error( function() {
482 - // retry with exponential backoff
483 - if ( timeoutMs < 8000 ) {
484 - setTimeout( function() {
485 - timeoutMs = timeoutMs * 2 + Math.round( Math.random() * ( timeoutMs / 10 ) );
486 - setSrc();
487 - }, timeoutMs );
488 - } else {
489 - $j.publish( key, null );
490 - }
491 - } );
 530+ $j( image )
 531+ .load( function() {
 532+ // cache this thumbnail
 533+ _this.thumbnails[key] = image;
 534+ // publish the image to anyone who wanted it
 535+ $j.publishReady( key, image );
 536+ } )
 537+ .error( function() {
 538+ // retry with exponential backoff
 539+ if ( timeoutMs < 8000 ) {
 540+ setTimeout( function() {
 541+ timeoutMs = timeoutMs * 2 + Math.round( Math.random() * ( timeoutMs / 10 ) );
 542+ setSrc();
 543+ }, timeoutMs );
 544+ } else {
 545+ $j.publishReady( key, null );
 546+ }
 547+ } );
492548
493 - // executing this should cause a .load() or .error() event on the image
494 - function setSrc() {
495 - image.src = thumb.thumburl;
496 - }
 549+ // executing this should cause a .load() or .error() event on the image
 550+ function setSrc() {
 551+ image.src = thumb.thumburl;
 552+ }
497553
498 - // and, go!
499 - setSrc();
500 - } );
 554+ // and, go!
 555+ setSrc();
 556+ } );
 557+ }
 558+ };
 559+
 560+ _this.thumbnailPublishers[key] = thumbnailPublisher;
 561+ if ( _this.state !== 'complete' ) {
 562+ _this.getStashImageInfo( thumbnailPublisher, [ 'url' ], width, height );
 563+ } else {
 564+ _this.getImageInfo( thumbnailPublisher, [ 'url' ], width, height );
 565+ }
 566+
501567 }
502 - };
503568 },
504569
505570 /**
506 - * Look up thumbnail info and set it in HTML, with loading spinner
 571+ * Given a jQuery selector, subscribe to the "ready" event that fills the thumbnail
 572+ * This will trigger if the thumbnail is added in the future or if it already has been
507573 *
508574 * @param selector
509 - * @param width
510 - * @param height (optional)
 575+ * @param width Width constraint
 576+ * @param height Height constraint (optional)
511577 */
512578 setThumbnail: function( selector, width, height ) {
513579 var _this = this;
514580 if ( typeof width === 'undefined' || width === null || width <= 0 ) {
515 - width = mw.UploadWizard.config[ 'thumbnailWidth' ];
 581+ width = mw.UploadWizard.config['thumbnailWidth'];
516582 }
517 - width = parseInt( width, 10 );
518 - height = null;
519 - if ( !mw.isEmpty( height ) ) {
520 - height = parseInt( height, 10 );
521 - }
 583+ var constraints = {
 584+ width: parseInt( width, 10 ),
 585+ height: ( mw.isDefined( height ) ? parseInt( height, 10 ) : null )
 586+ };
522587
523 - var callback = function( image ) {
 588+ /**
 589+ * This callback will add an image to the selector, using in-browser scaling if necessary
 590+ * @param {HTMLImageElement}
 591+ */
 592+ var placed = false;
 593+ var placeImageCallback = function( image ) {
524594 if ( image === null ) {
525595 $j( selector ).addClass( 'mwe-upwiz-file-preview-broken' );
526596 _this.ui.setStatus( 'mwe-upwiz-thumbnail-failed' );
527 - } else {
528 - var $thumbnailLink = $j( '<a class="mwe-upwiz-thumbnail-link"></a>' );
529 - if ( _this.state != 'complete' ) { // don't use lightbox for thank you page thumbnail
530 - $thumbnailLink
 597+ return;
 598+ }
 599+
 600+ // if this debugger isn't here, it's okay??
 601+ // figure out what scaling is needed, if any
 602+ var scaling = 1;
 603+ $j.each( [ 'width', 'height' ], function( i, dim ) {
 604+ if ( constraints[dim] && image[dim] > constraints[dim] ) {
 605+ var s = constraints[dim] / image[dim];
 606+ if ( s < scaling ) {
 607+ scaling = s;
 608+ }
 609+ }
 610+ } );
 611+
 612+ // add the image to the DOM, finally
 613+ $j( selector ).html(
 614+ $j( '<a class="mwe-upwiz-thumbnail-link"></a>' ).append(
 615+ $j( '<img/>' )
531616 .attr( {
532 - 'href': '#',
533 - 'target' : '_new'
 617+ width: parseInt( image.width * scaling, 10 ),
 618+ height: parseInt( image.height * scaling, 10 ),
 619+ src: image.src
534620 } )
535 - // set up lightbox behavior for thumbnail
536 - .click( function() {
537 - // get large preview image
538 - _this.getThumbnail(
539 - // open large preview in modal dialog box
540 - function( image ) {
541 - var dialogWidth = ( image.width > 200 ) ? image.width : 200;
542 - $( '<div class="mwe-upwiz-lightbox"></div>' )
543 - .append( image )
544 - .dialog( {
545 - 'width': dialogWidth,
546 - 'autoOpen': true,
547 - 'title': gM( 'mwe-upwiz-image-preview' ),
548 - 'modal': true,
549 - 'resizable': false
550 - } );
551 - },
552 - mw.UploadWizard.config[ 'largeThumbnailWidth' ],
553 - mw.UploadWizard.config[ 'largeThumbnailMaxHeight' ]
554 - );
555 - return false;
556 - } ); // close thumbnail click function
557 - } // close if
558 -
559 - $j( selector ).html(
560 - // insert the thumbnail into the anchor
561 - $thumbnailLink.append(
562 - $j( '<img/>' )
563 - .attr( {
564 - 'width': image.width,
565 - 'height': image.height,
566 - 'src': image.src
567 - } )
568 - ) // close append
569 - ); // close html
570 - } // close image !== null else condition
 621+ )
 622+ );
 623+ placed = true;
571624 };
572625
573 - _this.getThumbnail( callback, width, height );
 626+ // Listen for even which says some kind of thumbnail is available.
 627+ // The argument is an either an ImageHtmlElement ( if we could get the thumbnail locally ) or the string 'api' indicating you
 628+ // now need to get the scaled thumbnail via the API
 629+ $.subscribeReady(
 630+ 'thumbnails.' + _this.index,
 631+ function ( x ) {
 632+ if ( !placed ) {
 633+ if ( x === 'api' ) {
 634+ // get the thumbnail via API. This also works with an async pub/sub model; if this thumbnail was already
 635+ // fetched for some reason, we'll get it immediately
 636+ var key = 'apiThumbnail.' + _this.index + ',width=' + width + ',height=' + height;
 637+ $.subscribeReady( key, placeImageCallback );
 638+ _this.getAndPublishApiThumbnail( key, width, height );
 639+ } else if ( x instanceof HTMLImageElement ) {
 640+ placeImageCallback( x );
 641+ } else {
 642+ // something else went wrong, place broken image
 643+ mw.log( 'unexpected argument to thumbnails event: ' + x );
 644+ placeImageCallback( null );
 645+ }
 646+ }
 647+ }
 648+ );
 649+
574650 },
575651
 652+
576653 /**
577654 * Given a filename like "Foo.jpg", get the URL to that filename, assuming the browser is on the same wiki.
578655 * Candidate for a utility function...

Follow-up revisions

RevisionCommit summaryAuthorDate
r90123add canvas scaling and rotation. mostly fixes bug 29383 (a few issues remaini...neilk17:35, 15 June 2011
r90511use orientation in canvas preview. do not blank metadata again when uploadedneilk05:03, 21 June 2011

Comments

#Comment by Brion VIBBER (talk | contribs)   18:04, 13 June 2011

If this isn't doing the rotation checks for fileApi previews but we are on the post-uploaded thumbnails, do we have a regression in thumbnail previewing of EXIF-rotated photos?

#Comment by Brion VIBBER (talk | contribs)   18:57, 13 June 2011

Ok, confirmed that the current state on UW with FileAPI preview doesn't properly rotate, while the current Special:Upload FileAPI preview does.

Created a clean sample file & attached on bug 6672 (the original exif-orientation upload handling bug).

Let's also double-check that it works as expected without FileAPI, when going through the stashed upload API.

#Comment by NeilK (talk | contribs)   20:41, 13 June 2011

Although we discussed this in person, I changed my mind -- I don't think it should be a FIXME. It's more like a missing feature with UploadWizard.

Originally, I deliberately excluded the file-rotation code because I found it hard to follow, and I still think it should be cleaned up a bit. And throwing in new dependencies on Canvas and a different form of file reading may not be a good idea just before our deploy this afternoon.

So, okay to mark this OK? I have created a new bug instead. (bug 29383)

#Comment by Brion VIBBER (talk | contribs)   20:47, 13 June 2011

IMO showing an incorrectly rotated preview thumbnail is a regression; if I disable the fileApi support, I see a correctly-oriented thumbnail after upload, which carries through the remaining steps of the process.

If I leave it on, I see an incorrectly-oriented thumbnail all the way through the process, until I arrive on the actual File page afterwards and discover it's been rotated back into place.

#Comment by NeilK (talk | contribs)   15:02, 22 August 2011

This is long fixed (and deployed even) since around r90511-r91656, so marking it resolved

#Comment by NeilK (talk | contribs)   15:03, 22 August 2011

er, I mean "new".

Status & tagging log