UNPKG

scratch-gui

Version:

GraphicaL User Interface for creating and running Scratch 3.0 projects

203 lines (194 loc) 7.87 kB
import classNames from 'classnames'; import bindAll from 'lodash.bindall'; 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 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 styles from './library.css'; const ALL_TAG_TITLE = 'All'; const tagListPrefix = [{title: ALL_TAG_TITLE}]; const messages = defineMessages({ filterPlaceholder: { id: 'gui.library.filterPlaceholder', defaultMessage: 'Search', description: 'Placeholder text for library search field' } }); class LibraryComponent extends React.Component { constructor (props) { super(props); bindAll(this, [ 'handleBlur', 'handleFilterChange', 'handleFilterClear', 'handleFocus', 'handleMouseEnter', 'handleMouseLeave', 'handleSelect', 'handleTagClick' ]); this.state = { selectedItem: null, filterQuery: '', selectedTag: ALL_TAG_TITLE.toLowerCase() }; } handleBlur (id) { this.handleMouseLeave(id); } handleFocus (id) { this.handleMouseEnter(id); } handleSelect (id) { this.props.onRequestClose(); this.props.onItemSelected(this.getFilteredData()[id]); } handleTagClick (tag) { this.setState({ filterQuery: '', selectedTag: tag.toLowerCase() }); } handleMouseEnter (id) { if (this.props.onItemMouseEnter) this.props.onItemMouseEnter(this.getFilteredData()[id]); } handleMouseLeave (id) { if (this.props.onItemMouseLeave) this.props.onItemMouseLeave(this.getFilteredData()[id]); } handleFilterChange (event) { this.setState({ filterQuery: event.target.value, selectedTag: ALL_TAG_TITLE.toLowerCase() }); } handleFilterClear () { this.setState({filterQuery: ''}); } getFilteredData () { if (this.state.selectedTag === 'all') { if (!this.state.filterQuery) return this.props.data; return this.props.data.filter(dataItem => ( (dataItem.tags || []) // Second argument to map sets `this` .map(String.prototype.toLowerCase.call, String.prototype.toLowerCase) .concat(dataItem.name.toLowerCase()) .join('\n') // unlikely to partially match newlines .indexOf(this.state.filterQuery.toLowerCase()) !== -1 )); } return this.props.data.filter(dataItem => ( dataItem.tags && dataItem.tags .map(String.prototype.toLowerCase.call, String.prototype.toLowerCase) .indexOf(this.state.selectedTag) !== -1 )); } render () { return ( <Modal fullScreen contentLabel={this.props.title} id={this.props.id} onRequestClose={this.props.onRequestClose} > {(this.props.filterable || this.props.tags) && ( <div className={styles.filterBar}> {this.props.filterable && ( <Filter className={classNames( styles.filterBarItem, styles.filter )} filterQuery={this.state.filterQuery} inputClassName={styles.filterInput} placeholderText={this.props.intl.formatMessage(messages.filterPlaceholder)} onChange={this.handleFilterChange} onClear={this.handleFilterClear} /> )} {this.props.filterable && this.props.tags && ( <Divider className={classNames(styles.filterBarItem, styles.divider)} /> )} {this.props.tags && <div className={styles.tagWrapper}> {tagListPrefix.concat(this.props.tags).map((tagProps, id) => ( <TagButton active={this.state.selectedTag === tagProps.title.toLowerCase()} className={classNames( styles.filterBarItem, styles.tagButton, tagProps.className )} key={`tag-button-${id}`} onClick={this.handleTagClick} {...tagProps} /> ))} </div> } </div> )} <div className={classNames(styles.libraryScrollGrid, { [styles.withFilterBar]: this.props.filterable || this.props.tags })} > {this.getFilteredData().map((dataItem, index) => { const scratchURL = dataItem.md5 ? `https://cdn.assets.scratch.mit.edu/internalapi/asset/${dataItem.md5}/get/` : dataItem.rawURL; return ( <LibraryItem description={dataItem.description} disabled={dataItem.disabled} featured={dataItem.featured} iconURL={scratchURL} id={index} key={`item_${index}`} name={dataItem.name} onBlur={this.handleBlur} onFocus={this.handleFocus} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} onSelect={this.handleSelect} /> ); })} </div> </Modal> ); } } LibraryComponent.propTypes = { data: PropTypes.arrayOf( /* eslint-disable react/no-unused-prop-types, lines-around-comment */ // An item in the library PropTypes.shape({ // @todo remove md5/rawURL prop from library, refactor to use storage md5: PropTypes.string, name: PropTypes.oneOfType([ PropTypes.string, PropTypes.node ]).isRequired, rawURL: PropTypes.string }) /* eslint-enable react/no-unused-prop-types, lines-around-comment */ ), filterable: PropTypes.bool, id: PropTypes.string.isRequired, intl: intlShape.isRequired, onItemMouseEnter: PropTypes.func, onItemMouseLeave: PropTypes.func, onItemSelected: PropTypes.func, onRequestClose: PropTypes.func, tags: PropTypes.arrayOf(PropTypes.shape(TagButton.propTypes)), title: PropTypes.string.isRequired }; LibraryComponent.defaultProps = { filterable: true }; export default injectIntl(LibraryComponent);