2018-02-07 19:06:07 +01:00
|
|
|
import PropTypes from 'prop-types';
|
|
|
|
import React from 'react';
|
2017-07-11 17:37:43 +02:00
|
|
|
import { connect } from 'react-redux';
|
|
|
|
import { bindActionCreators } from 'redux';
|
|
|
|
import { Link } from 'react-router';
|
2018-02-23 17:31:41 +01:00
|
|
|
import { Helmet } from 'react-helmet';
|
2017-07-11 17:37:43 +02:00
|
|
|
import prettyBytes from 'pretty-bytes';
|
|
|
|
|
2019-07-24 18:55:58 +02:00
|
|
|
import Loader from '../../App/components/loader';
|
2017-07-11 17:37:43 +02:00
|
|
|
import * as AssetActions from '../actions/assets';
|
2020-04-30 00:34:37 +02:00
|
|
|
import DownFilledTriangleIcon from '../../../images/down-filled-triangle.svg';
|
2019-09-11 00:42:23 +02:00
|
|
|
|
|
|
|
class AssetListRowBase extends React.Component {
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
this.state = {
|
|
|
|
isFocused: false,
|
|
|
|
optionsOpen: false
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
onFocusComponent = () => {
|
|
|
|
this.setState({ isFocused: true });
|
|
|
|
}
|
|
|
|
|
|
|
|
onBlurComponent = () => {
|
|
|
|
this.setState({ isFocused: false });
|
|
|
|
setTimeout(() => {
|
|
|
|
if (!this.state.isFocused) {
|
2019-09-25 23:54:46 +02:00
|
|
|
this.closeOptions();
|
2019-09-11 00:42:23 +02:00
|
|
|
}
|
|
|
|
}, 200);
|
|
|
|
}
|
|
|
|
|
|
|
|
openOptions = () => {
|
|
|
|
this.setState({
|
|
|
|
optionsOpen: true
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
closeOptions = () => {
|
|
|
|
this.setState({
|
|
|
|
optionsOpen: false
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
toggleOptions = () => {
|
|
|
|
if (this.state.optionsOpen) {
|
|
|
|
this.closeOptions();
|
|
|
|
} else {
|
|
|
|
this.openOptions();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
handleDropdownOpen = () => {
|
|
|
|
this.closeOptions();
|
|
|
|
this.openOptions();
|
|
|
|
}
|
|
|
|
|
|
|
|
handleAssetDelete = () => {
|
|
|
|
const { key, name } = this.props.asset;
|
|
|
|
this.closeOptions();
|
|
|
|
if (window.confirm(`Are you sure you want to delete "${name}"?`)) {
|
|
|
|
this.props.deleteAssetRequest(key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
const { asset, username } = this.props;
|
|
|
|
const { optionsOpen } = this.state;
|
|
|
|
return (
|
|
|
|
<tr className="asset-table__row" key={asset.key}>
|
|
|
|
<th scope="row">
|
|
|
|
<Link to={asset.url} target="_blank">
|
|
|
|
{asset.name}
|
|
|
|
</Link>
|
|
|
|
</th>
|
|
|
|
<td>{prettyBytes(asset.size)}</td>
|
|
|
|
<td>
|
|
|
|
{ asset.sketchId && <Link to={`/${username}/sketches/${asset.sketchId}`}>{asset.sketchName}</Link> }
|
|
|
|
</td>
|
|
|
|
<td className="asset-table__dropdown-column">
|
|
|
|
<button
|
|
|
|
className="asset-table__dropdown-button"
|
|
|
|
onClick={this.toggleOptions}
|
|
|
|
onBlur={this.onBlurComponent}
|
|
|
|
onFocus={this.onFocusComponent}
|
2020-05-06 01:03:58 +02:00
|
|
|
aria-label="Toggle Open/Close Asset Options"
|
2019-09-11 00:42:23 +02:00
|
|
|
>
|
2020-05-06 01:03:58 +02:00
|
|
|
<DownFilledTriangleIcon focusable="false" aria-hidden="true" />
|
2019-09-11 00:42:23 +02:00
|
|
|
</button>
|
|
|
|
{optionsOpen &&
|
|
|
|
<ul
|
|
|
|
className="asset-table__action-dialogue"
|
|
|
|
>
|
|
|
|
<li>
|
|
|
|
<button
|
|
|
|
className="asset-table__action-option"
|
|
|
|
onClick={this.handleAssetDelete}
|
|
|
|
onBlur={this.onBlurComponent}
|
|
|
|
onFocus={this.onFocusComponent}
|
|
|
|
>
|
|
|
|
Delete
|
|
|
|
</button>
|
|
|
|
</li>
|
|
|
|
<li>
|
|
|
|
<Link
|
|
|
|
to={asset.url}
|
|
|
|
target="_blank"
|
|
|
|
onBlur={this.onBlurComponent}
|
|
|
|
onFocus={this.onFocusComponent}
|
2019-09-25 23:54:46 +02:00
|
|
|
className="asset-table__action-option"
|
2019-09-11 00:42:23 +02:00
|
|
|
>
|
|
|
|
Open in New Tab
|
|
|
|
</Link>
|
|
|
|
</li>
|
|
|
|
</ul>}
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
AssetListRowBase.propTypes = {
|
|
|
|
asset: PropTypes.shape({
|
|
|
|
key: PropTypes.string.isRequired,
|
|
|
|
url: PropTypes.string.isRequired,
|
|
|
|
sketchId: PropTypes.string,
|
|
|
|
sketchName: PropTypes.string,
|
2020-03-03 22:33:52 +01:00
|
|
|
name: PropTypes.string.isRequired,
|
|
|
|
size: PropTypes.number.isRequired
|
2019-09-11 00:42:23 +02:00
|
|
|
}).isRequired,
|
|
|
|
deleteAssetRequest: PropTypes.func.isRequired,
|
|
|
|
username: PropTypes.string.isRequired
|
|
|
|
};
|
|
|
|
|
|
|
|
function mapStateToPropsAssetListRow(state) {
|
|
|
|
return {
|
|
|
|
username: state.user.username
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function mapDispatchToPropsAssetListRow(dispatch) {
|
|
|
|
return bindActionCreators(AssetActions, dispatch);
|
|
|
|
}
|
|
|
|
|
|
|
|
const AssetListRow = connect(mapStateToPropsAssetListRow, mapDispatchToPropsAssetListRow)(AssetListRowBase);
|
2017-07-11 17:37:43 +02:00
|
|
|
|
|
|
|
class AssetList extends React.Component {
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
2018-05-30 06:37:10 +02:00
|
|
|
this.props.getAssets();
|
2017-07-11 17:37:43 +02:00
|
|
|
}
|
|
|
|
|
2018-02-23 17:31:41 +01:00
|
|
|
getAssetsTitle() {
|
2019-09-11 00:42:23 +02:00
|
|
|
return 'p5.js Web Editor | My assets';
|
2018-02-23 17:31:41 +01:00
|
|
|
}
|
|
|
|
|
2019-07-24 18:55:58 +02:00
|
|
|
hasAssets() {
|
|
|
|
return !this.props.loading && this.props.assetList.length > 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
renderLoader() {
|
|
|
|
if (this.props.loading) return <Loader />;
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
renderEmptyTable() {
|
|
|
|
if (!this.props.loading && this.props.assetList.length === 0) {
|
|
|
|
return (<p className="asset-table__empty">No uploaded assets.</p>);
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2017-07-11 17:37:43 +02:00
|
|
|
render() {
|
2020-03-03 22:33:52 +01:00
|
|
|
const { assetList } = this.props;
|
2017-07-11 17:37:43 +02:00
|
|
|
return (
|
|
|
|
<div className="asset-table-container">
|
2018-02-23 17:31:41 +01:00
|
|
|
<Helmet>
|
|
|
|
<title>{this.getAssetsTitle()}</title>
|
|
|
|
</Helmet>
|
2019-07-24 18:55:58 +02:00
|
|
|
{this.renderLoader()}
|
|
|
|
{this.renderEmptyTable()}
|
|
|
|
{this.hasAssets() &&
|
2017-07-11 17:37:43 +02:00
|
|
|
<table className="asset-table">
|
|
|
|
<thead>
|
|
|
|
<tr>
|
2020-03-03 22:33:52 +01:00
|
|
|
<th>Name</th>
|
2019-09-11 00:42:23 +02:00
|
|
|
<th>Size</th>
|
|
|
|
<th>Sketch</th>
|
|
|
|
<th scope="col"></th>
|
2017-07-11 17:37:43 +02:00
|
|
|
</tr>
|
|
|
|
</thead>
|
|
|
|
<tbody>
|
2019-09-11 00:42:23 +02:00
|
|
|
{assetList.map(asset => <AssetListRow asset={asset} key={asset.key} />)}
|
2017-07-11 17:37:43 +02:00
|
|
|
</tbody>
|
|
|
|
</table>}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
AssetList.propTypes = {
|
2018-02-23 17:31:41 +01:00
|
|
|
user: PropTypes.shape({
|
|
|
|
username: PropTypes.string
|
|
|
|
}).isRequired,
|
2019-07-24 18:55:58 +02:00
|
|
|
assetList: PropTypes.arrayOf(PropTypes.shape({
|
2017-07-11 17:37:43 +02:00
|
|
|
key: PropTypes.string.isRequired,
|
|
|
|
name: PropTypes.string.isRequired,
|
|
|
|
url: PropTypes.string.isRequired,
|
2020-02-05 00:40:54 +01:00
|
|
|
sketchName: PropTypes.string,
|
|
|
|
sketchId: PropTypes.string
|
2017-07-11 17:37:43 +02:00
|
|
|
})).isRequired,
|
|
|
|
getAssets: PropTypes.func.isRequired,
|
2019-07-24 18:55:58 +02:00
|
|
|
loading: PropTypes.bool.isRequired
|
2017-07-11 17:37:43 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
function mapStateToProps(state) {
|
|
|
|
return {
|
|
|
|
user: state.user,
|
2019-07-24 18:55:58 +02:00
|
|
|
assetList: state.assets.list,
|
|
|
|
loading: state.loading
|
2017-07-11 17:37:43 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function mapDispatchToProps(dispatch) {
|
|
|
|
return bindActionCreators(Object.assign({}, AssetActions), dispatch);
|
|
|
|
}
|
|
|
|
|
|
|
|
export default connect(mapStateToProps, mapDispatchToProps)(AssetList);
|