Matthias Mullie has uploaded a new change for review. (
https://gerrit.wikimedia.org/r/404614 )
Change subject: Display placeholder text until 3D thumb is loaded
......................................................................
Display placeholder text until 3D thumb is loaded
Bug: T183310
Change-Id: I69af9fe914690cee7c7437da0b32c480be9c8a64
---
M extension.json
M i18n/en.json
M i18n/qqq.json
M modules/ext.3d.js
M modules/mmv.3d.head.js
5 files changed, 154 insertions(+), 14 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/3D
refs/changes/14/404614/1
diff --git a/extension.json b/extension.json
index adea519..0982c8f 100644
--- a/extension.json
+++ b/extension.json
@@ -30,7 +30,11 @@
"ext.3d.less"
],
"messages": [
- "3d-badge-text"
+ "3d-badge-text",
+ "3d-thumb-placeholder"
+ ],
+ "dependencies": [
+ "jquery.spinner"
]
},
"mmv.3d": {
diff --git a/i18n/en.json b/i18n/en.json
index ae681da..50cf79e 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -6,5 +6,6 @@
},
"3d": "3d",
"3d-desc": "Provides support for 3d file formats",
- "3d-badge-text": "3D"
+ "3d-badge-text": "3D",
+ "3d-thumb-placeholder": "Loading thumbnail..."
}
diff --git a/i18n/qqq.json b/i18n/qqq.json
index c6937a2..d72bbcc 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -8,5 +8,6 @@
},
"3d": "{{name}}",
"3d-desc":
"{{desc|name=3d|url=https://www.mediawiki.org/wiki/Extension:3d}}\n\n[[wikipedia:3D
computer graphics|Read more about 3D]]",
- "3d-badge-text": "{{optional}}\nText for the badge shown in the top
left corner of images to identify 3D files"
+ "3d-badge-text": "{{optional}}\nText for the badge shown in the top
left corner of images to identify 3D files",
+ "3d-thumb-placeholder": "Placeholder text to display while the
thumbnail is still loading"
}
diff --git a/modules/ext.3d.js b/modules/ext.3d.js
index bfb53c3..7a6315c 100644
--- a/modules/ext.3d.js
+++ b/modules/ext.3d.js
@@ -19,26 +19,158 @@
'use strict';
mw.threed = {
- wrap: function ( $element ) {
- if ( !$element.parent().hasClass( 'mw-3d-wrapper' ) ) {
- $element.wrap( $( '<span>' ).addClass(
'mw-3d-wrapper' ) );
- }
+ /**
+ * @type {object}
+ */
+ thumbnailPromises: {},
- return $element.parent();
+ /**
+ * @param {jQuery} $elements
+ */
+ wrap: function ( $elements ) {
+ $elements.each( function ( i, element ) {
+ if ( !$( element ).parent().hasClass(
'mw-3d-wrapper' ) ) {
+ $( element ).wrap( $( '<span>'
).addClass( 'mw-3d-wrapper' ) );
+ }
+ } );
+
+ return $elements.parent();
},
/**
- * @param {jQuery} $element
+ * @param {jQuery} $elements
*/
- attachBadge: function ( $element ) {
- var $wrap = this.wrap( $element ),
+ attachBadge: function ( $elements ) {
+ var $wrap = this.wrap( $elements ),
$badge = $( '<span>' )
.addClass( 'mw-3d-badge' )
.text( mw.message( '3d-badge-text'
).text() );
- $wrap.append( $badge );
+ $elements.each( function ( i, element ) {
+ this.thumbnailLoadComplete( element ).then(
function () { $wrap.append( $badge ) } );
+ }.bind( this ) );
+ },
+
+ /**
+ * @param {jQuery} $elements
+ */
+ addThumbnailPlaceholder: function ( $elements ) {
+ var $spinner = $.createSpinner( { size: 'small', type:
'inline' } ),
+ $placeholder = $( '<p>' )
+ .addClass( 'mw-3d-thumb-placeholder' )
+ .text( ' ' + mw.message(
'3d-thumb-placeholder' ).text() + ' ' )
+ .prepend( $spinner );
+
+ // hide the image and put a placeholder there instead
+ $elements.hide().after( $placeholder );
+
+ $elements.each( function( i, element ) {
+ this.thumbnailLoadComplete( element )
+ .then( function ( element ) {
+ // image confirmed to have
loaded: show it & remove placeholder
+ $( element ).siblings(
'.mw-3d-thumb-placeholder' ).remove();
+ $( element ).show();
+ } );
+ }.bind( this ) );
+ },
+
+ /**
+ * Figure out if the thumbnail has completed loading.
+ *
+ * This is different from imageLoadComplete in that that one
will just check
+ * for an HTMLImageElement to have completed loading, whereas
this method
+ * will continuously attempt to load the image (even after it
has failed to
+ * load the first time around) until it succeeds, at which
point it will
+ * replace the original HTMLImageElement with the one where
loading succeeded.
+ *
+ * @param {HTMLImageElement} img
+ * @return {$.Promise} Promise that resolves with the thumbnail
HTMLImageElement
+ */
+ thumbnailLoadComplete: function ( img ) {
+ // safeguard to prevent the thumbnail node cloning
below from being executed
+ // more than once...
+ if ( img.src in this.thumbnailPromises ) {
+ return this.thumbnailPromises[ img.src ];
+ }
+
+ // I could nest this.imageLoadComplete's returned
promises, but if it takes
+ // forever to load the image, we'd keep filling up the
call stack to the
+ // point that it could crash. Instead, I'll create a
new deferred object
+ // that'll resolve once we've found the thumbnail
loading to be complete.
+ var deferred = $.Deferred(),
+ reload = function () {
+ // cloning the img element will trigger
the browser to load the
+ // image again, after which we can
simply redo this procedure,
+ // until the image has completed loading
+ var $clone = $( img ).clone();
+ this.imageLoadComplete( $clone.get( 0 )
).then(
+ function () {
+ $( img ).replaceWith(
$clone );
+ deferred.resolve(
$clone.get( 0 ) );
+ },
+ function() {
+ // wait 5 seconds
before attempting to load the image again
+ setTimeout( reload,
5000 );
+ }
+ );
+ }.bind( this );
+
+ this.thumbnailPromises[ img.src ] = deferred.promise();
+
+ reload();
+
+ return this.thumbnailPromises[ img.src ];
+ },
+
+ /**
+ * Figure out image status by returning a promise that resolves
when the
+ * images had loaded successfully, or rejects when it errored.
+ *
+ * We can't figure out the http status code of <img> elements
to test
+ * if it succeeded to load. Instead, let's first check if the
image
+ * has completed loading at all, then check the naturalHeight of
+ * the image - if it's non-zero, we know that the thumb exists;
+ * otherwise, it's obviously not there & we'll want to display a
+ * placeholder & periodically retry loading it.
+ * naturalHeight is safe to use on all supported browsers:
+ *
+ * @see
https://www.mediawiki.org/wiki/Compatibility#Browser_support_matrix
+ * @see https://caniuse.com/#feat=img-naturalwidth-naturalheight
+ *
+ * @param {HTMLImageElement} img
+ * @return {$.Promise} resolves with HTMLImageElement when/once
image has
+ * loaded successfully; rejects if it failed
+ * @private
+ */
+ imageLoadComplete: function ( img ) {
+ var deferred = $.Deferred(),
+ func,
+ interval;
+
+ func = function() {
+ if ( !img.complete ) {
+ return;
+ }
+
+ clearInterval( interval );
+
+ if ( img.naturalHeight === 0 ) {
+ deferred.reject();
+ } else {
+ deferred.resolve( img );
+ }
+ };
+
+ // schedule interval, but also test it right away; it
may have loaded
+ // already...
+ interval = setInterval( func, 100 );
+ func();
+
+ return deferred.promise();
}
};
- mw.threed.attachBadge( $( 'img[src$=".stl.png"]' ) );
+ var $thumbs = $( 'img[src$=".stl.png"]' );
+ mw.threed.attachBadge( $thumbs );
+ mw.threed.addThumbnailPlaceholder( $thumbs );
}( mediaWiki, jQuery ) );
diff --git a/modules/mmv.3d.head.js b/modules/mmv.3d.head.js
index 8fb6823..85dbfb8 100644
--- a/modules/mmv.3d.head.js
+++ b/modules/mmv.3d.head.js
@@ -44,7 +44,9 @@
view.on( 'click', this.open.bind( this, $image, $link )
);
download.on( 'click', this.download.bind( this, $link )
);
- $wrap.append( $buttonWrap );
+ $image.each( function ( i, element ) {
+ mw.threed.thumbnailLoadComplete( element
).then( function () { $wrap.append( $buttonWrap ) } );
+ }.bind( this ) );
// clicking file should open it in MMV instead of
prompting download
$link.on( 'click', function ( e ) {
--
To view, visit https://gerrit.wikimedia.org/r/404614
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I69af9fe914690cee7c7437da0b32c480be9c8a64
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/3D
Gerrit-Branch: master
Gerrit-Owner: Matthias Mullie <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits