diff --git a/package.json b/package.json index fd356bbff..1a396271b 100644 --- a/package.json +++ b/package.json @@ -99,6 +99,7 @@ "react-test-renderer": "16.2.0", "react-tooltip": "3.8.0", "react-virtualized": "9.20.1", + "react-visibility-sensor": "5.0.2", "redux": "3.7.2", "redux-mock-store": "^1.2.3", "redux-throttle": "0.1.1", diff --git a/src/components/scratch-image/scratch-image.jsx b/src/components/scratch-image/scratch-image.jsx index 4cf0c5d95..bd3a6c8eb 100644 --- a/src/components/scratch-image/scratch-image.jsx +++ b/src/components/scratch-image/scratch-image.jsx @@ -1,12 +1,57 @@ import PropTypes from 'prop-types'; import React from 'react'; +import VisibilitySensor from 'react-visibility-sensor'; import storage from '../../lib/storage'; class ScratchImage extends React.PureComponent { + static get pendingImages () { + return this._pendingImages || (this._pendingImages = new Set()); + } + static get maxParallelism () { + return this._maxParallelism > 0 ? this._maxParallelism : (this._maxParallelism = 6); + } + + static loadPendingImages () { + if (this._currentJobs >= this.maxParallelism) { + // already busy + return; + } + + // Find the first visible image. If there aren't any, find the first non-visible image. + let nextImage; + for (const image of this.pendingImages) { + if (image.isVisible) { + nextImage = image; + break; + } else { + nextImage = nextImage || image; + } + } + + // If we found an image to load: + // 1) Remove it from the queue + // 2) Load the image + // 3) Pump the queue again + if (nextImage) { + this.pendingImages.delete(nextImage); + const imageSource = nextImage.props.imageSource; + ++this._currentJobs; + storage + .load(imageSource.assetType, imageSource.assetId) + .then(asset => { + const dataURI = asset.encodeDataURI(); + nextImage.setState({ + imageURI: dataURI + }); + --this._currentJobs; + this.loadPendingImages(); + }); + } + } + constructor (props) { super(props); - this.state = {}; Object.assign(this.state, this._loadImageSource(props.imageSource)); } @@ -24,18 +69,14 @@ class ScratchImage extends React.PureComponent { */ _loadImageSource (imageSource) { if (imageSource.uri) { + ScratchImage.pendingImages.delete(this); return { imageURI: imageSource.uri, lastRequestedAsset: null }; } if (this.state.lastRequestedAsset !== imageSource.assetId) { - storage.load(imageSource.assetType, imageSource.assetId) - .then(asset => { - this.setState({ - imageURI: asset.encodeDataURI() - }); - }); + ScratchImage.pendingImages.add(this); return { lastRequestedAsset: imageSource.assetId }; @@ -49,10 +90,29 @@ class ScratchImage extends React.PureComponent { imageSource: _imageSource, ...imgProps } = this.props; - return (); + return ( + + { + ({isVisible}) => { + this.isVisible = isVisible; + if (isVisible) { + ScratchImage.loadPendingImages(); + } + return ( + + ); + } + } + + ); } }