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 HIDE_RUNTIME_ERROR_WARNING = 'HIDE_RUNTIME_ERROR_WARNING';
|
||||||
export const SHOW_RUNTIME_ERROR_WARNING = 'SHOW_RUNTIME_ERROR_WARNING';
|
export const SHOW_RUNTIME_ERROR_WARNING = 'SHOW_RUNTIME_ERROR_WARNING';
|
||||||
export const SET_ASSETS = 'SET_ASSETS';
|
export const SET_ASSETS = 'SET_ASSETS';
|
||||||
|
export const DELETE_ASSET = 'DELETE_ASSET';
|
||||||
|
|
||||||
export const TOGGLE_DIRECTION = 'TOGGLE_DIRECTION';
|
export const TOGGLE_DIRECTION = 'TOGGLE_DIRECTION';
|
||||||
export const SET_SORTING = 'SET_SORTING';
|
export const SET_SORTING = 'SET_SORTING';
|
||||||
|
|
|
@ -30,8 +30,23 @@ export function getAssets() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteAsset(assetKey, userId) {
|
export function deleteAsset(assetKey) {
|
||||||
return {
|
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 { Link } from 'react-router';
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
import prettyBytes from 'pretty-bytes';
|
import prettyBytes from 'pretty-bytes';
|
||||||
|
import InlineSVG from 'react-inlinesvg';
|
||||||
|
|
||||||
import Loader from '../../App/components/loader';
|
import Loader from '../../App/components/loader';
|
||||||
import * as AssetActions from '../actions/assets';
|
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 {
|
class AssetList extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -16,10 +151,7 @@ class AssetList extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
getAssetsTitle() {
|
getAssetsTitle() {
|
||||||
if (!this.props.username || this.props.username === this.props.user.username) {
|
return 'p5.js Web Editor | My assets';
|
||||||
return 'p5.js Web Editor | My assets';
|
|
||||||
}
|
|
||||||
return `p5.js Web Editor | ${this.props.username}'s assets`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hasAssets() {
|
hasAssets() {
|
||||||
|
@ -39,10 +171,13 @@ class AssetList extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const username = this.props.username !== undefined ? this.props.username : this.props.user.username;
|
const { assetList, totalSize } = this.props;
|
||||||
const { assetList } = this.props;
|
|
||||||
return (
|
return (
|
||||||
<div className="asset-table-container">
|
<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>
|
<Helmet>
|
||||||
<title>{this.getAssetsTitle()}</title>
|
<title>{this.getAssetsTitle()}</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
@ -52,24 +187,14 @@ class AssetList extends React.Component {
|
||||||
<table className="asset-table">
|
<table className="asset-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">Name</th>
|
<th>Name</th>
|
||||||
<th scope="col">Size</th>
|
<th>Size</th>
|
||||||
<th scope="col">Sketch</th>
|
<th>Sketch</th>
|
||||||
|
<th scope="col"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{assetList.map(asset =>
|
{assetList.map(asset => <AssetListRow asset={asset} key={asset.key} />)}
|
||||||
(
|
|
||||||
<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>
|
|
||||||
))}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>}
|
</table>}
|
||||||
</div>
|
</div>
|
||||||
|
@ -81,7 +206,6 @@ AssetList.propTypes = {
|
||||||
user: PropTypes.shape({
|
user: PropTypes.shape({
|
||||||
username: PropTypes.string
|
username: PropTypes.string
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
username: PropTypes.string.isRequired,
|
|
||||||
assetList: PropTypes.arrayOf(PropTypes.shape({
|
assetList: PropTypes.arrayOf(PropTypes.shape({
|
||||||
key: PropTypes.string.isRequired,
|
key: PropTypes.string.isRequired,
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
|
@ -89,15 +213,20 @@ AssetList.propTypes = {
|
||||||
sketchName: PropTypes.string,
|
sketchName: PropTypes.string,
|
||||||
sketchId: PropTypes.string
|
sketchId: PropTypes.string
|
||||||
})).isRequired,
|
})).isRequired,
|
||||||
|
totalSize: PropTypes.number,
|
||||||
getAssets: PropTypes.func.isRequired,
|
getAssets: PropTypes.func.isRequired,
|
||||||
loading: PropTypes.bool.isRequired
|
loading: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
AssetList.defaultProps = {
|
||||||
|
totalSize: undefined
|
||||||
|
};
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
return {
|
return {
|
||||||
user: state.user,
|
user: state.user,
|
||||||
assetList: state.assets.list,
|
assetList: state.assets.list,
|
||||||
totalSize: state.assets.totalSize,
|
totalSize: state.user.totalSize,
|
||||||
loading: state.loading
|
loading: state.loading
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,7 +110,7 @@ class Sidebar extends React.Component {
|
||||||
onBlur={this.onBlurComponent}
|
onBlur={this.onBlurComponent}
|
||||||
onFocus={this.onFocusComponent}
|
onFocus={this.onFocusComponent}
|
||||||
>
|
>
|
||||||
Add file
|
Create file
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
@ -123,7 +123,7 @@ class Sidebar extends React.Component {
|
||||||
onBlur={this.onBlurComponent}
|
onBlur={this.onBlurComponent}
|
||||||
onFocus={this.onFocusComponent}
|
onFocus={this.onFocusComponent}
|
||||||
>
|
>
|
||||||
Add file
|
Upload file
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -2,33 +2,54 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
|
import InlineSVG from 'react-inlinesvg';
|
||||||
import FileUploader from './FileUploader';
|
import FileUploader from './FileUploader';
|
||||||
import { getreachedTotalSizeLimit } from '../selectors/users';
|
import { getreachedTotalSizeLimit } from '../selectors/users';
|
||||||
|
|
||||||
|
import exitUrl from '../../../images/exit.svg';
|
||||||
|
|
||||||
class UploadFileModal extends React.Component {
|
class UploadFileModal extends React.Component {
|
||||||
propTypes = {
|
propTypes = {
|
||||||
reachedTotalSizeLimit: PropTypes.bool.isRequired
|
reachedTotalSizeLimit: PropTypes.bool.isRequired,
|
||||||
|
closeModal: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.focusOnModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
focusOnModal = () => {
|
||||||
|
this.modal.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<section className="modal" ref={(element) => { this.modal = element; }}>
|
<section className="modal" ref={(element) => { this.modal = element; }}>
|
||||||
{ this.props.reachedTotalSizeLimit &&
|
<div className="modal-content">
|
||||||
<p>
|
<div className="modal__header">
|
||||||
{
|
<h2 className="modal__title">Upload File</h2>
|
||||||
`You have reached the size limit for the number of files you can upload to your account.
|
<button className="modal__exit-button" onClick={this.props.closeModal}>
|
||||||
If you would like to upload more, please remove the ones you aren't using anymore by
|
<InlineSVG src={exitUrl} alt="Close New File Modal" />
|
||||||
looking through your `
|
</button>
|
||||||
}
|
|
||||||
<Link to="/assets">assets</Link>
|
|
||||||
{'.'}
|
|
||||||
</p>
|
|
||||||
}
|
|
||||||
{ !this.props.reachedTotalSizeLimit &&
|
|
||||||
<div>
|
|
||||||
<FileUploader />
|
|
||||||
</div>
|
</div>
|
||||||
}
|
{ this.props.reachedTotalSizeLimit &&
|
||||||
|
<p>
|
||||||
|
{
|
||||||
|
`You have reached the size limit for the number of files you can upload to your account.
|
||||||
|
If you would like to upload more, please remove the ones you aren't using anymore by
|
||||||
|
looking through your `
|
||||||
|
}
|
||||||
|
<Link to="/assets">assets</Link>
|
||||||
|
{'.'}
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
{ !this.props.reachedTotalSizeLimit &&
|
||||||
|
<div>
|
||||||
|
<FileUploader />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -239,6 +239,8 @@ class IDEView extends React.Component {
|
||||||
newFolder={this.props.newFolder}
|
newFolder={this.props.newFolder}
|
||||||
user={this.props.user}
|
user={this.props.user}
|
||||||
owner={this.props.project.owner}
|
owner={this.props.project.owner}
|
||||||
|
openUploadFileModal={this.props.openUploadFileModal}
|
||||||
|
closeUploadFileModal={this.props.closeUploadFileModal}
|
||||||
/>
|
/>
|
||||||
<SplitPane
|
<SplitPane
|
||||||
split="vertical"
|
split="vertical"
|
||||||
|
@ -578,6 +580,7 @@ IDEView.propTypes = {
|
||||||
showRuntimeErrorWarning: PropTypes.func.isRequired,
|
showRuntimeErrorWarning: PropTypes.func.isRequired,
|
||||||
hideRuntimeErrorWarning: PropTypes.func.isRequired,
|
hideRuntimeErrorWarning: PropTypes.func.isRequired,
|
||||||
startSketch: PropTypes.func.isRequired,
|
startSketch: PropTypes.func.isRequired,
|
||||||
|
openUploadFileModal: PropTypes.func.isRequired,
|
||||||
closeUploadFileModal: 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.
|
// 1,000,000 bytes in a MB. can't upload if totalSize is bigger than this.
|
||||||
const initialState = {
|
const initialState = {
|
||||||
list: [],
|
list: []
|
||||||
totalSize: 0
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const assets = (state = initialState, action) => {
|
const assets = (state = initialState, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case ActionTypes.SET_ASSETS:
|
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:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,8 @@ export const getCanUploadMedia = createSelector(
|
||||||
export const getreachedTotalSizeLimit = createSelector(
|
export const getreachedTotalSizeLimit = createSelector(
|
||||||
getTotalSize,
|
getTotalSize,
|
||||||
(totalSize) => {
|
(totalSize) => {
|
||||||
if (totalSize > 250000000) return true;
|
// if (totalSize > 250000000) return true;
|
||||||
|
if (totalSize > 1000) return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,7 +9,8 @@
|
||||||
|
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
& .sketch-list__dropdown-column {
|
position: relative;
|
||||||
|
& .asset-table__dropdown-column {
|
||||||
width: #{60 / $base-font-size}rem;
|
width: #{60 / $base-font-size}rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
@ -66,3 +67,29 @@
|
||||||
font-size: #{16 / $base-font-size}rem;
|
font-size: #{16 / $base-font-size}rem;
|
||||||
padding: #{42 / $base-font-size}rem 0;
|
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