diff --git a/client/constants.js b/client/constants.js index 7d1a04dc..477409fc 100644 --- a/client/constants.js +++ b/client/constants.js @@ -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'; diff --git a/client/modules/IDE/actions/assets.js b/client/modules/IDE/actions/assets.js index e2b49cf6..483e6d4e 100644 --- a/client/modules/IDE/actions/assets.js +++ b/client/modules/IDE/actions/assets.js @@ -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 + }); + }); }; } diff --git a/client/modules/IDE/components/AssetList.jsx b/client/modules/IDE/components/AssetList.jsx index 8c5d0826..491dc2b5 100644 --- a/client/modules/IDE/components/AssetList.jsx +++ b/client/modules/IDE/components/AssetList.jsx @@ -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 ( + + + + {asset.name} + + + {prettyBytes(asset.size)} + + { asset.sketchId && {asset.sketchName} } + + + + {optionsOpen && + } + + + ); + } +} + +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,10 +151,7 @@ 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`; + return 'p5.js Web Editor | My assets'; } hasAssets() { @@ -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 (
+ {/* Eventually, this copy should be Total / 250 MB Used */} + {this.hasAssets() && totalSize && +

{`${prettyBytes(totalSize)} Total`}

+ } {this.getAssetsTitle()} @@ -52,24 +187,14 @@ class AssetList extends React.Component { - - - + + + + - {assetList.map(asset => - ( - - - - - - ))} + {assetList.map(asset => )}
NameSizeSketchNameSizeSketch
- - {asset.name} - - {prettyBytes(asset.size)}{asset.sketchName}
}
@@ -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 }; } diff --git a/client/modules/IDE/components/Sidebar.jsx b/client/modules/IDE/components/Sidebar.jsx index 739c7867..f6a5bdd1 100644 --- a/client/modules/IDE/components/Sidebar.jsx +++ b/client/modules/IDE/components/Sidebar.jsx @@ -110,7 +110,7 @@ class Sidebar extends React.Component { onBlur={this.onBlurComponent} onFocus={this.onFocusComponent} > - Add file + Create file
  • @@ -123,7 +123,7 @@ class Sidebar extends React.Component { onBlur={this.onBlurComponent} onFocus={this.onFocusComponent} > - Add file + Upload file
  • diff --git a/client/modules/IDE/components/UploadFileModal.jsx b/client/modules/IDE/components/UploadFileModal.jsx index 35e2fd9e..3e335fc0 100644 --- a/client/modules/IDE/components/UploadFileModal.jsx +++ b/client/modules/IDE/components/UploadFileModal.jsx @@ -2,33 +2,54 @@ 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 (
    { this.modal = element; }}> - { this.props.reachedTotalSizeLimit && -

    - { - `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 ` - } - assets - {'.'} -

    - } - { !this.props.reachedTotalSizeLimit && -
    - +
    +
    +

    Upload File

    +
    - } + { this.props.reachedTotalSizeLimit && +

    + { + `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 ` + } + assets + {'.'} +

    + } + { !this.props.reachedTotalSizeLimit && +
    + +
    + } +
    ); } diff --git a/client/modules/IDE/pages/IDEView.jsx b/client/modules/IDE/pages/IDEView.jsx index 8b589e51..f7179a14 100644 --- a/client/modules/IDE/pages/IDEView.jsx +++ b/client/modules/IDE/pages/IDEView.jsx @@ -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} /> { 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; } diff --git a/client/modules/IDE/selectors/users.js b/client/modules/IDE/selectors/users.js index d223e4d9..d597c894 100644 --- a/client/modules/IDE/selectors/users.js +++ b/client/modules/IDE/selectors/users.js @@ -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; } ); diff --git a/client/styles/components/_asset-list.scss b/client/styles/components/_asset-list.scss index 6d4e30b9..7cee736e 100644 --- a/client/styles/components/_asset-list.scss +++ b/client/styles/components/_asset-list.scss @@ -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); +}