diff --git a/packages/scratch-gui/src/components/library-item/library-item.jsx b/packages/scratch-gui/src/components/library-item/library-item.jsx
index 0d2490e997a..8992bc0365c 100644
--- a/packages/scratch-gui/src/components/library-item/library-item.jsx
+++ b/packages/scratch-gui/src/components/library-item/library-item.jsx
@@ -32,24 +32,10 @@ class LibraryItemComponent extends React.PureComponent {
]);
}
renderImage (className, imageSource) {
- // Scratch Android and Scratch Desktop assume the user is offline and has
- // local access to the image assets. In those cases we use the `ScratchImage`
- // component which loads the local assets by using a queue. In Scratch Web
- // we don't have the assets locally and want to directly download them from
- // the assets service.
- // TODO: Abstract this logic in the `ScratchImage` component itself.
- const url = imageSource.uri ?? imageSource.assetServiceUri;
-
- if (this.props.platform === PLATFORM.ANDROID ||
- this.props.platform === PLATFORM.DESKTOP) {
- return ();
- }
- return (
);
}
render () {
diff --git a/packages/scratch-gui/src/components/library/library.jsx b/packages/scratch-gui/src/components/library/library.jsx
index 9ac67b6d38d..2ce982757bb 100644
--- a/packages/scratch-gui/src/components/library/library.jsx
+++ b/packages/scratch-gui/src/components/library/library.jsx
@@ -106,8 +106,7 @@ const getItemIcons = function (item) {
if (item.assetId && item.dataFormat) {
return {
assetId: item.assetId,
- assetType: getAssetTypeForFileExtension(item.dataFormat),
- assetServiceUri: `https://cdn.assets.scratch.mit.edu/internalapi/asset/${item.assetId}.${item.dataFormat}/get/`
+ assetType: getAssetTypeForFileExtension(item.dataFormat)
};
}
@@ -116,8 +115,7 @@ const getItemIcons = function (item) {
const [assetId, fileExtension] = md5ext.split('.');
return {
assetId: assetId,
- assetType: getAssetTypeForFileExtension(fileExtension),
- assetServiceUri: `https://cdn.assets.scratch.mit.edu/internalapi/asset/${md5ext}/get/`
+ assetType: getAssetTypeForFileExtension(fileExtension)
};
}
};
diff --git a/packages/scratch-gui/src/components/scratch-image/scratch-image.jsx b/packages/scratch-gui/src/components/scratch-image/scratch-image.jsx
index 831b1f8caab..bc4bbf77151 100644
--- a/packages/scratch-gui/src/components/scratch-image/scratch-image.jsx
+++ b/packages/scratch-gui/src/components/scratch-image/scratch-image.jsx
@@ -3,12 +3,15 @@ import React from 'react';
import VisibilitySensor from 'react-visibility-sensor';
import {legacyConfig} from '../../legacy-config';
+import {PLATFORM} from '../../lib/platform.js';
+import bindAll from 'lodash.bindall';
class ScratchImage extends React.PureComponent {
static init () {
this._maxParallelism = 6;
this._currentJobs = 0;
this._pendingImages = new Set();
+ this._assetCache = new Map();
}
static loadPendingImages () {
@@ -17,13 +20,15 @@ class ScratchImage extends React.PureComponent {
return;
}
- // Find the first visible image. If there aren't any, find the first non-visible image.
+ // Find the first visible image. Fall back to the first non-visible image only
+ // when parallelism is capped (desktop/Android), so that off-screen assets are
+ // eventually pre-loaded as slots free up.
let nextImage;
for (const image of this._pendingImages) {
if (image.isVisible) {
nextImage = image;
break;
- } else {
+ } else if (this._maxParallelism !== Infinity) {
// TODO: Why was this commented out on native branch?
nextImage = nextImage || image;
}
@@ -35,14 +40,15 @@ class ScratchImage extends React.PureComponent {
// 3) Pump the queue again
if (nextImage) {
this._pendingImages.delete(nextImage);
- const imageSource = nextImage.props.imageSource;
+ const assetId = nextImage._pendingAssetId;
+ const assetType = nextImage._pendingAssetType;
++this._currentJobs;
legacyConfig.storage.scratchStorage
- .load(imageSource.assetType, imageSource.assetId)
+ .load(assetType, assetId)
.then(asset => {
+ const dataURI = asset.encodeDataURI();
+ ScratchImage._assetCache.set(assetId, dataURI);
if (!nextImage.wasUnmounted) {
- const dataURI = asset.encodeDataURI();
-
nextImage.setState({
imageURI: dataURI
});
@@ -55,12 +61,27 @@ class ScratchImage extends React.PureComponent {
constructor (props) {
super(props);
+ bindAll(this, [
+ 'handleVisibilityChange'
+ ]);
this.state = {};
+ if (props.platform === PLATFORM.WEB) {
+ ScratchImage._maxParallelism = Infinity;
+ }
Object.assign(this.state, this._loadImageSource(props.imageSource));
}
componentWillReceiveProps (nextProps) {
+ if (this.props.platform !== nextProps.platform) {
+ ScratchImage._maxParallelism = nextProps.platform === PLATFORM.WEB ? Infinity : 6;
+ }
const newState = this._loadImageSource(nextProps.imageSource);
this.setState(newState);
+ // If a new asset was queued and this component is already visible, pump the
+ // queue immediately so the new frame loads without waiting for a scroll event
+ // (e.g. icon rotation on hover).
+ if (newState.lastRequestedAsset && this.isVisible) {
+ ScratchImage.loadPendingImages();
+ }
}
componentWillUnmount () {
this.wasUnmounted = true;
@@ -82,7 +103,21 @@ class ScratchImage extends React.PureComponent {
lastRequestedAsset: null
};
}
+ const cached = ScratchImage._assetCache.get(imageSource.assetId);
+ if (cached) {
+ ScratchImage._pendingImages.delete(this);
+ return {
+ imageURI: cached,
+ lastRequestedAsset: null
+ };
+ }
if (this.state.lastRequestedAsset !== imageSource.assetId) {
+ // Capture assetId/assetType now so loadPendingImages uses the
+ // correct values. Reading props.imageSource at pop time would
+ // give the previous frame because componentWillReceiveProps
+ // fires before React updates this.props.
+ this._pendingAssetId = imageSource.assetId;
+ this._pendingAssetType = imageSource.assetType;
ScratchImage._pendingImages.add(this);
return {
lastRequestedAsset: imageSource.assetId
@@ -92,11 +127,18 @@ class ScratchImage extends React.PureComponent {
// Nothing to do - don't change any state.
return {};
}
+ handleVisibilityChange (isVisible) {
+ this.isVisible = isVisible;
+ if (isVisible) {
+ ScratchImage.loadPendingImages();
+ }
+ }
render () {
const {
// TODO: Does this cause issues for desktop?
src: _src, // eslint-disable-line react/prop-types
imageSource: _imageSource,
+ platform: _platform,
...imgProps
} = this.props;
@@ -104,23 +146,16 @@ class ScratchImage extends React.PureComponent {
- {
- ({isVisible}) => {
- this.isVisible = isVisible;
- ScratchImage.loadPendingImages();
- return (
-
- );
- }
- }
+
);
}
@@ -133,8 +168,7 @@ ScratchImage.ImageSourcePropType = PropTypes.oneOfType([
Object.values(
legacyConfig.storage.scratchStorage.AssetType
)
- ).isRequired,
- assetServiceUri: PropTypes.string.isRequired
+ ).isRequired
}),
PropTypes.shape({
uri: PropTypes.string.isRequired
@@ -142,7 +176,8 @@ ScratchImage.ImageSourcePropType = PropTypes.oneOfType([
]);
ScratchImage.propTypes = {
- imageSource: ScratchImage.ImageSourcePropType.isRequired
+ imageSource: ScratchImage.ImageSourcePropType.isRequired,
+ platform: PropTypes.oneOf(Object.values(PLATFORM))
};
ScratchImage.init();