Fix merge conflicts for cherry picking 0bac332a9eb360978e686d6be8bff92f0fa1740c

This commit is contained in:
Cassie Tarakajian 2019-09-10 18:42:23 -04:00
parent b8fb51d283
commit 7c1aa2e589
9 changed files with 246 additions and 48 deletions

View File

@ -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';

View File

@ -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
});
});
};
}

View File

@ -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
};
}

View File

@ -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>

View File

@ -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>
);
}

View File

@ -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
};

View File

@ -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;
}

View File

@ -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;
}
);

View File

@ -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);
}