Fix merge conflicts for cherry picking 0bac332a9eb360978e686d6be8bff92f0fa1740c
This commit is contained in:
parent
b8fb51d283
commit
7c1aa2e589
9 changed files with 246 additions and 48 deletions
|
@ -129,6 +129,7 @@ export const CLEAR_PERSISTED_STATE = 'CLEAR_PERSISTED_STATE';
|
|||
export const HIDE_RUNTIME_ERROR_WARNING = 'HIDE_RUNTIME_ERROR_WARNING';
|
||||
export const SHOW_RUNTIME_ERROR_WARNING = 'SHOW_RUNTIME_ERROR_WARNING';
|
||||
export const SET_ASSETS = 'SET_ASSETS';
|
||||
export const DELETE_ASSET = 'DELETE_ASSET';
|
||||
|
||||
export const TOGGLE_DIRECTION = 'TOGGLE_DIRECTION';
|
||||
export const SET_SORTING = 'SET_SORTING';
|
||||
|
|
|
@ -30,8 +30,23 @@ export function getAssets() {
|
|||
};
|
||||
}
|
||||
|
||||
export function deleteAsset(assetKey, userId) {
|
||||
export function deleteAsset(assetKey) {
|
||||
return {
|
||||
type: 'PLACEHOLDER'
|
||||
type: ActionTypes.DELETE_ASSET,
|
||||
key: assetKey
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteAssetRequest(assetKey) {
|
||||
return (dispatch) => {
|
||||
axios.delete(`${ROOT_URL}/S3/${assetKey}`, { withCredentials: true })
|
||||
.then((response) => {
|
||||
dispatch(deleteAsset(assetKey));
|
||||
})
|
||||
.catch(() => {
|
||||
dispatch({
|
||||
type: ActionTypes.ERROR
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,9 +5,144 @@ import { bindActionCreators } from 'redux';
|
|||
import { Link } from 'react-router';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import prettyBytes from 'pretty-bytes';
|
||||
import InlineSVG from 'react-inlinesvg';
|
||||
|
||||
import Loader from '../../App/components/loader';
|
||||
import * as AssetActions from '../actions/assets';
|
||||
import downFilledTriangle from '../../../images/down-filled-triangle.svg';
|
||||
|
||||
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) {
|
||||
this.closeAll();
|
||||
}
|
||||
}, 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}
|
||||
>
|
||||
<InlineSVG src={downFilledTriangle} alt="Menu" />
|
||||
</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}
|
||||
>
|
||||
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,
|
||||
name: PropTypes.string.isRequired
|
||||
}).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);
|
||||
|
||||
class AssetList extends React.Component {
|
||||
constructor(props) {
|
||||
|
@ -16,11 +151,8 @@ class AssetList extends React.Component {
|
|||
}
|
||||
|
||||
getAssetsTitle() {
|
||||
if (!this.props.username || this.props.username === this.props.user.username) {
|
||||
return 'p5.js Web Editor | My assets';
|
||||
}
|
||||
return `p5.js Web Editor | ${this.props.username}'s assets`;
|
||||
}
|
||||
|
||||
hasAssets() {
|
||||
return !this.props.loading && this.props.assetList.length > 0;
|
||||
|
@ -39,10 +171,13 @@ class AssetList extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const username = this.props.username !== undefined ? this.props.username : this.props.user.username;
|
||||
const { assetList } = this.props;
|
||||
const { assetList, totalSize } = this.props;
|
||||
return (
|
||||
<div className="asset-table-container">
|
||||
{/* Eventually, this copy should be Total / 250 MB Used */}
|
||||
{this.hasAssets() && totalSize &&
|
||||
<p className="asset-table__total">{`${prettyBytes(totalSize)} Total`}</p>
|
||||
}
|
||||
<Helmet>
|
||||
<title>{this.getAssetsTitle()}</title>
|
||||
</Helmet>
|
||||
|
@ -52,24 +187,14 @@ class AssetList extends React.Component {
|
|||
<table className="asset-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Size</th>
|
||||
<th scope="col">Sketch</th>
|
||||
<th>Name</th>
|
||||
<th>Size</th>
|
||||
<th>Sketch</th>
|
||||
<th scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{assetList.map(asset =>
|
||||
(
|
||||
<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><Link to={`/${username}/sketches/${asset.sketchId}`}>{asset.sketchName}</Link></td>
|
||||
</tr>
|
||||
))}
|
||||
{assetList.map(asset => <AssetListRow asset={asset} key={asset.key} />)}
|
||||
</tbody>
|
||||
</table>}
|
||||
</div>
|
||||
|
@ -81,7 +206,6 @@ AssetList.propTypes = {
|
|||
user: PropTypes.shape({
|
||||
username: PropTypes.string
|
||||
}).isRequired,
|
||||
username: PropTypes.string.isRequired,
|
||||
assetList: PropTypes.arrayOf(PropTypes.shape({
|
||||
key: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
|
@ -89,15 +213,20 @@ AssetList.propTypes = {
|
|||
sketchName: PropTypes.string,
|
||||
sketchId: PropTypes.string
|
||||
})).isRequired,
|
||||
totalSize: PropTypes.number,
|
||||
getAssets: PropTypes.func.isRequired,
|
||||
loading: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
AssetList.defaultProps = {
|
||||
totalSize: undefined
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
user: state.user,
|
||||
assetList: state.assets.list,
|
||||
totalSize: state.assets.totalSize,
|
||||
totalSize: state.user.totalSize,
|
||||
loading: state.loading
|
||||
};
|
||||
}
|
||||
|
|
|
@ -110,7 +110,7 @@ class Sidebar extends React.Component {
|
|||
onBlur={this.onBlurComponent}
|
||||
onFocus={this.onFocusComponent}
|
||||
>
|
||||
Add file
|
||||
Create file
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
|
@ -123,7 +123,7 @@ class Sidebar extends React.Component {
|
|||
onBlur={this.onBlurComponent}
|
||||
onFocus={this.onFocusComponent}
|
||||
>
|
||||
Add file
|
||||
Upload file
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
@ -2,17 +2,37 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { Link } from 'react-router';
|
||||
import InlineSVG from 'react-inlinesvg';
|
||||
import FileUploader from './FileUploader';
|
||||
import { getreachedTotalSizeLimit } from '../selectors/users';
|
||||
|
||||
import exitUrl from '../../../images/exit.svg';
|
||||
|
||||
class UploadFileModal extends React.Component {
|
||||
propTypes = {
|
||||
reachedTotalSizeLimit: PropTypes.bool.isRequired
|
||||
reachedTotalSizeLimit: PropTypes.bool.isRequired,
|
||||
closeModal: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.focusOnModal();
|
||||
}
|
||||
|
||||
focusOnModal = () => {
|
||||
this.modal.focus();
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
return (
|
||||
<section className="modal" ref={(element) => { this.modal = element; }}>
|
||||
<div className="modal-content">
|
||||
<div className="modal__header">
|
||||
<h2 className="modal__title">Upload File</h2>
|
||||
<button className="modal__exit-button" onClick={this.props.closeModal}>
|
||||
<InlineSVG src={exitUrl} alt="Close New File Modal" />
|
||||
</button>
|
||||
</div>
|
||||
{ this.props.reachedTotalSizeLimit &&
|
||||
<p>
|
||||
{
|
||||
|
@ -29,6 +49,7 @@ class UploadFileModal extends React.Component {
|
|||
<FileUploader />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -239,6 +239,8 @@ class IDEView extends React.Component {
|
|||
newFolder={this.props.newFolder}
|
||||
user={this.props.user}
|
||||
owner={this.props.project.owner}
|
||||
openUploadFileModal={this.props.openUploadFileModal}
|
||||
closeUploadFileModal={this.props.closeUploadFileModal}
|
||||
/>
|
||||
<SplitPane
|
||||
split="vertical"
|
||||
|
@ -578,6 +580,7 @@ IDEView.propTypes = {
|
|||
showRuntimeErrorWarning: PropTypes.func.isRequired,
|
||||
hideRuntimeErrorWarning: PropTypes.func.isRequired,
|
||||
startSketch: PropTypes.func.isRequired,
|
||||
openUploadFileModal: PropTypes.func.isRequired,
|
||||
closeUploadFileModal: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
|
|
@ -2,14 +2,15 @@ import * as ActionTypes from '../../../constants';
|
|||
|
||||
// 1,000,000 bytes in a MB. can't upload if totalSize is bigger than this.
|
||||
const initialState = {
|
||||
list: [],
|
||||
totalSize: 0
|
||||
list: []
|
||||
};
|
||||
|
||||
const assets = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.SET_ASSETS:
|
||||
return { list: action.assets, totalSize: action.totalSize };
|
||||
return { list: action.assets };
|
||||
case ActionTypes.DELETE_ASSET:
|
||||
return { list: state.list.filter(asset => asset.key !== action.key) };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -18,7 +18,8 @@ export const getCanUploadMedia = createSelector(
|
|||
export const getreachedTotalSizeLimit = createSelector(
|
||||
getTotalSize,
|
||||
(totalSize) => {
|
||||
if (totalSize > 250000000) return true;
|
||||
// if (totalSize > 250000000) return true;
|
||||
if (totalSize > 1000) return true;
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
|
||||
max-height: 100%;
|
||||
border-spacing: 0;
|
||||
& .sketch-list__dropdown-column {
|
||||
position: relative;
|
||||
& .asset-table__dropdown-column {
|
||||
width: #{60 / $base-font-size}rem;
|
||||
position: relative;
|
||||
}
|
||||
|
@ -66,3 +67,29 @@
|
|||
font-size: #{16 / $base-font-size}rem;
|
||||
padding: #{42 / $base-font-size}rem 0;
|
||||
}
|
||||
|
||||
.asset-table__total {
|
||||
padding: 0 #{20 / $base-font-size}rem;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
@include themify() {
|
||||
background-color: getThemifyVariable('background-color');
|
||||
}
|
||||
}
|
||||
|
||||
.asset-table__dropdown-button {
|
||||
width:#{25 / $base-font-size}rem;
|
||||
height:#{25 / $base-font-size}rem;
|
||||
|
||||
@include themify() {
|
||||
& polygon {
|
||||
fill: getThemifyVariable('dropdown-color');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.asset-table__action-dialogue {
|
||||
@extend %dropdown-open-right;
|
||||
top: 63%;
|
||||
right: calc(100% - 26px);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue