Fix sprite lib animation: move load into LibraryItem
The `LibraryItem` component has been split into a container, responsible for handling interaction with the storage module, and a component, responsible for presentation. The component now always takes an `iconURI` and the logic to load an icon from storage has been moved from `Library` into `LibraryItem`. TODO: move animation logic from `SpriteLibrary` into `LibraryItem`. This should simplify the code quite a bit and might also improve performance.
This commit is contained in:
1 parent
86b6e12453
commit
db9236bef2
3 files changed
+105
-71
No files matched your search
@@ -10,16 +10,9 @@ import classNames from 'classnames';
|
||||
import bluetoothIconURL from './bluetooth.svg';
|
||||
import internetConnectionIconURL from './internet-connection.svg';
|
||||
|
||||
class LibraryItem extends React.PureComponent {
|
||||
class LibraryItemComponent extends React.PureComponent {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
iconURL: null
|
||||
};
|
||||
Promise.resolve(props.iconURL)
|
||||
.then(url => this.setState({
|
||||
iconURL: url
|
||||
}));
|
||||
bindAll(this, [
|
||||
'handleBlur',
|
||||
'handleClick',
|
||||
@@ -79,7 +72,7 @@ class LibraryItem extends React.PureComponent {
|
||||
) : null}
|
||||
<img
|
||||
className={styles.featuredImage}
|
||||
src={this.state.iconURL}
|
||||
src={this.props.iconURI}
|
||||
/>
|
||||
</div>
|
||||
{this.props.insetIconURL ? (
|
||||
@@ -165,7 +158,7 @@ class LibraryItem extends React.PureComponent {
|
||||
<Box className={styles.libraryItemImageContainer}>
|
||||
<img
|
||||
className={styles.libraryItemImage}
|
||||
src={this.state.iconURL}
|
||||
src={this.props.iconURI}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -175,7 +168,7 @@ class LibraryItem extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
LibraryItem.propTypes = {
|
||||
LibraryItemComponent.propTypes = {
|
||||
bluetoothRequired: PropTypes.bool,
|
||||
collaborator: PropTypes.string,
|
||||
description: PropTypes.oneOfType([
|
||||
@@ -186,10 +179,7 @@ LibraryItem.propTypes = {
|
||||
extensionId: PropTypes.string,
|
||||
featured: PropTypes.bool,
|
||||
hidden: PropTypes.bool,
|
||||
iconURL: PropTypes.oneOfType([
|
||||
PropTypes.string, // URL
|
||||
PropTypes.instanceOf(Promise) // Promise for URL
|
||||
]),
|
||||
iconURI: PropTypes.string,
|
||||
id: PropTypes.number.isRequired,
|
||||
insetIconURL: PropTypes.string,
|
||||
internetConnectionRequired: PropTypes.bool,
|
||||
@@ -204,8 +194,8 @@ LibraryItem.propTypes = {
|
||||
onSelect: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
LibraryItem.defaultProps = {
|
||||
LibraryItemComponent.defaultProps = {
|
||||
disabled: false
|
||||
};
|
||||
|
||||
export default LibraryItem;
|
||||
export default LibraryItemComponent;
|
||||
@@ -4,13 +4,12 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {defineMessages, injectIntl, intlShape} from 'react-intl';
|
||||
|
||||
import LibraryItem from '../library-item/library-item.jsx';
|
||||
import LibraryItem from '../../containers/library-item.jsx';
|
||||
import Modal from '../../containers/modal.jsx';
|
||||
import Divider from '../divider/divider.jsx';
|
||||
import Filter from '../filter/filter.jsx';
|
||||
import TagButton from '../../containers/tag-button.jsx';
|
||||
import analytics from '../../lib/analytics';
|
||||
import storage from '../../lib/storage';
|
||||
|
||||
import styles from './library.css';
|
||||
|
||||
@@ -30,23 +29,6 @@ const messages = defineMessages({
|
||||
const ALL_TAG = {tag: 'all', intlLabel: messages.allTag};
|
||||
const tagListPrefix = [ALL_TAG];
|
||||
|
||||
/**
|
||||
* @param {string} extension - the extension to look up.
|
||||
* @returns {AssetType} - the AssetType corresponding to the extension, if any.
|
||||
*/
|
||||
const getAssetTypeForExtension = function (extension) {
|
||||
const compareOptions = {
|
||||
sensitivity: 'accent',
|
||||
usage: 'search'
|
||||
};
|
||||
for (const assetTypeId of Object.keys(storage.AssetType)) {
|
||||
const assetType = storage.AssetType[assetTypeId];
|
||||
if (extension.localeCompare(assetType.runtimeFormat, compareOptions) === 0) {
|
||||
return assetType;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class LibraryComponent extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
@@ -190,41 +172,29 @@ class LibraryComponent extends React.Component {
|
||||
})}
|
||||
ref={this.setFilteredDataRef}
|
||||
>
|
||||
{this.getFilteredData().map((dataItem, index) => {
|
||||
let iconURL;
|
||||
if (dataItem.md5) {
|
||||
// TODO: adjust libraries to be more storage-friendly; don't use split() here.
|
||||
const [md5, ext] = dataItem.md5.split('.');
|
||||
const assetType = getAssetTypeForExtension(ext);
|
||||
iconURL = storage.load(assetType, md5)
|
||||
.then(asset => asset.encodeDataURI());
|
||||
} else {
|
||||
iconURL = dataItem.rawURL;
|
||||
}
|
||||
|
||||
return (
|
||||
<LibraryItem
|
||||
bluetoothRequired={dataItem.bluetoothRequired}
|
||||
collaborator={dataItem.collaborator}
|
||||
description={dataItem.description}
|
||||
disabled={dataItem.disabled}
|
||||
extensionId={dataItem.extensionId}
|
||||
featured={dataItem.featured}
|
||||
hidden={dataItem.hidden}
|
||||
iconURL={iconURL}
|
||||
id={index}
|
||||
insetIconURL={dataItem.insetIconURL}
|
||||
internetConnectionRequired={dataItem.internetConnectionRequired}
|
||||
key={`item_${index}`}
|
||||
name={dataItem.name}
|
||||
onBlur={this.handleBlur}
|
||||
onFocus={this.handleFocus}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
onSelect={this.handleSelect}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{this.getFilteredData().map((dataItem, index) => (
|
||||
<LibraryItem
|
||||
bluetoothRequired={dataItem.bluetoothRequired}
|
||||
collaborator={dataItem.collaborator}
|
||||
description={dataItem.description}
|
||||
disabled={dataItem.disabled}
|
||||
extensionId={dataItem.extensionId}
|
||||
featured={dataItem.featured}
|
||||
hidden={dataItem.hidden}
|
||||
iconMD5={dataItem.md5} // either this or iconURL must be defined
|
||||
iconURL={dataItem.rawURL} // either this or iconMD5 must be defined
|
||||
id={index}
|
||||
insetIconURL={dataItem.insetIconURL}
|
||||
internetConnectionRequired={dataItem.internetConnectionRequired}
|
||||
key={`item_${index}`}
|
||||
name={dataItem.name}
|
||||
onBlur={this.handleBlur}
|
||||
onFocus={this.handleFocus}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
onSelect={this.handleSelect}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import React from 'react';
|
||||
|
||||
import LibraryItemComponent from '../components/library-item/library-item.jsx';
|
||||
import storage from '../lib/storage';
|
||||
|
||||
/**
|
||||
* @param {string} extension - the extension to look up.
|
||||
* @returns {AssetType} - the AssetType corresponding to the extension, if any.
|
||||
*/
|
||||
const getAssetTypeForExtension = function (extension) {
|
||||
const compareOptions = {
|
||||
sensitivity: 'accent',
|
||||
usage: 'search'
|
||||
};
|
||||
for (const assetTypeId of Object.keys(storage.AssetType)) {
|
||||
const assetType = storage.AssetType[assetTypeId];
|
||||
if (extension.localeCompare(assetType.runtimeFormat, compareOptions) === 0) {
|
||||
return assetType;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class LibraryItem extends React.PureComponent {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
this.state = Object.assign(this.state || {}, {
|
||||
iconURI: props.iconURL // may be undefined if we're using iconMD5 instead
|
||||
});
|
||||
}
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (nextProps.iconURL) {
|
||||
this.setState({iconURI: nextProps.iconURL});
|
||||
} else if ((!this.state.iconURI) || nextProps.iconMD5 !== this.props.iconMD5) {
|
||||
// TODO: adjust libraries to be more storage-friendly; don't use split() here.
|
||||
const [md5, ext] = nextProps.iconMD5.split('.');
|
||||
const assetType = getAssetTypeForExtension(ext);
|
||||
storage.load(assetType, md5)
|
||||
.then(asset => {
|
||||
this.setState({iconURI: asset.encodeDataURI()});
|
||||
});
|
||||
}
|
||||
}
|
||||
render () {
|
||||
const {iconMD5: _iconMD5, iconURL: _iconURL, ...childProps} = this.props;
|
||||
return (<LibraryItemComponent
|
||||
iconURI={this.state.iconURI}
|
||||
{...childProps}
|
||||
/>);
|
||||
}
|
||||
}
|
||||
|
||||
LibraryItem.requireUrlOrMD5 = function (props) {
|
||||
if (typeof props.iconMD5 === 'string' && typeof props.iconURL === 'undefined') {
|
||||
return;
|
||||
}
|
||||
if (typeof props.iconMD5 === 'undefined' && typeof props.iconURL === 'string') {
|
||||
return;
|
||||
}
|
||||
return new Error('Exactly one of iconURL or iconMD5 must be set and the value must be a string.');
|
||||
};
|
||||
|
||||
LibraryItem.propTypes = Object.assign({},
|
||||
(() => {
|
||||
// copy all props EXCEPT iconURI from LibraryItemComponent
|
||||
const {iconURI: _iconURI, ...otherProps} = LibraryItemComponent.propTypes;
|
||||
return otherProps;
|
||||
})(),
|
||||
{
|
||||
iconMD5: LibraryItem.requireUrlOrMD5,
|
||||
iconURL: LibraryItem.requireUrlOrMD5
|
||||
}
|
||||
);
|
||||
|
||||
export default LibraryItem;
|
||||
Reference in new issue
Block a user