diff --git a/client/images/check_encircled.svg b/client/images/check_encircled.svg new file mode 100644 index 00000000..63ddbf51 --- /dev/null +++ b/client/images/check_encircled.svg @@ -0,0 +1,11 @@ + + + + + + + diff --git a/client/images/close.svg b/client/images/close.svg new file mode 100644 index 00000000..b6516ed9 --- /dev/null +++ b/client/images/close.svg @@ -0,0 +1,12 @@ + + + + + + + diff --git a/client/modules/IDE/components/AddToCollectionList.jsx b/client/modules/IDE/components/AddToCollectionList.jsx new file mode 100644 index 00000000..12af01d3 --- /dev/null +++ b/client/modules/IDE/components/AddToCollectionList.jsx @@ -0,0 +1,169 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import { Helmet } from 'react-helmet'; +import InlineSVG from 'react-inlinesvg'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import classNames from 'classnames'; + +import * as ProjectActions from '../actions/project'; +import * as ProjectsActions from '../actions/projects'; +import * as CollectionsActions from '../actions/collections'; +import * as ToastActions from '../actions/toast'; +import * as SortingActions from '../actions/sorting'; +import getSortedCollections from '../selectors/collections'; +import Loader from '../../App/components/loader'; +import QuickAddList from './QuickAddList/QuickAddList'; + +const projectInCollection = (project, collection) => collection.items.find(item => item.project.id === project.id) != null; + +class CollectionList extends React.Component { + constructor(props) { + super(props); + + if (props.projectId) { + props.getProject(props.projectId); + } + + this.props.getCollections(this.props.username); + + this.state = { + hasLoadedData: false, + }; + } + + componentDidUpdate(prevProps) { + if (prevProps.loading === true && this.props.loading === false) { + // eslint-disable-next-line react/no-did-update-set-state + this.setState({ + hasLoadedData: true, + }); + } + } + + getTitle() { + if (this.props.username === this.props.user.username) { + return 'p5.js Web Editor | My collections'; + } + return `p5.js Web Editor | ${this.props.username}'s collections`; + } + + handleCollectionAdd = (collection) => { + this.props.addToCollection(collection.id, this.props.project.id); + } + + handleCollectionRemove = (collection) => { + this.props.removeFromCollection(collection.id, this.props.project.id); + } + + handleAddRemove = (collection) => { + if (projectInCollection(this.props.project, collection)) { + this.handleCollectionRemove(collection); + } else { + this.handleCollectionAdd(collection); + } + } + + render() { + const username = this.props.username !== undefined ? this.props.username : this.props.user.username; + const { collections, project } = this.props; + const hasCollections = collections.length > 0; + const collectionWithSketchStatus = collections.map(collection => ({ + ...collection, + url: `/${collection.owner.username}/collections/${collection.id}`, + isAdded: projectInCollection(project, collection), + })); + + let content = null; + + if (this.props.loading && !this.state.hasLoadedData) { + content = ; + } else if (hasCollections) { + content = ; + } else { + content = 'No collections'; + } + + return ( +
+ + {this.getTitle()} + + + {content} +
+ ); + } +} + +const ProjectShape = PropTypes.shape({ + id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + createdAt: PropTypes.string.isRequired, + updatedAt: PropTypes.string.isRequired, + user: PropTypes.shape({ + username: PropTypes.string.isRequired + }).isRequired, +}); + +const ItemsShape = PropTypes.shape({ + createdAt: PropTypes.string.isRequired, + updatedAt: PropTypes.string.isRequired, + project: ProjectShape +}); + +CollectionList.propTypes = { + user: PropTypes.shape({ + username: PropTypes.string, + authenticated: PropTypes.bool.isRequired + }).isRequired, + projectId: PropTypes.string.isRequired, + getCollections: PropTypes.func.isRequired, + getProject: PropTypes.func.isRequired, + addToCollection: PropTypes.func.isRequired, + removeFromCollection: PropTypes.func.isRequired, + collections: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + description: PropTypes.string, + createdAt: PropTypes.string.isRequired, + updatedAt: PropTypes.string.isRequired, + items: PropTypes.arrayOf(ItemsShape), + })).isRequired, + username: PropTypes.string, + loading: PropTypes.bool.isRequired, + project: PropTypes.shape({ + id: PropTypes.string, + owner: PropTypes.shape({ + id: PropTypes.string + }) + }) +}; + +CollectionList.defaultProps = { + project: { + id: undefined, + owner: undefined + }, + username: undefined +}; + +function mapStateToProps(state, ownProps) { + return { + user: state.user, + collections: getSortedCollections(state), + sorting: state.sorting, + loading: state.loading, + project: state.project, + projectId: ownProps && ownProps.params ? ownProps.prams.project_id : null, + }; +} + +function mapDispatchToProps(dispatch) { + return bindActionCreators( + Object.assign({}, CollectionsActions, ProjectsActions, ProjectActions, ToastActions, SortingActions), + dispatch + ); +} + +export default connect(mapStateToProps, mapDispatchToProps)(CollectionList); diff --git a/client/modules/IDE/components/QuickAddList/Icons.jsx b/client/modules/IDE/components/QuickAddList/Icons.jsx new file mode 100644 index 00000000..41e0c52f --- /dev/null +++ b/client/modules/IDE/components/QuickAddList/Icons.jsx @@ -0,0 +1,27 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import InlineSVG from 'react-inlinesvg'; + +const check = require('../../../../images/check_encircled.svg'); +const close = require('../../../../images/close.svg'); + +const Icons = ({ isAdded }) => { + const classes = [ + 'quick-add__icon', + isAdded ? 'quick-add__icon--in-collection' : 'quick-add__icon--not-in-collection' + ].join(' '); + + return ( +
+ + + +
+ ); +}; + +Icons.propTypes = { + isAdded: PropTypes.bool.isRequired, +}; + +export default Icons; diff --git a/client/modules/IDE/components/QuickAddList/QuickAddList.jsx b/client/modules/IDE/components/QuickAddList/QuickAddList.jsx new file mode 100644 index 00000000..d1e88ac8 --- /dev/null +++ b/client/modules/IDE/components/QuickAddList/QuickAddList.jsx @@ -0,0 +1,46 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Link } from 'react-router'; + +import Icons from './Icons'; + +const Item = ({ + isAdded, onSelect, name, url +}) => ( +
  • + + View +
  • +); + +const ItemType = PropTypes.shape({ + name: PropTypes.string.isRequired, + url: PropTypes.string.isRequired, + isAdded: PropTypes.bool.isRequired, +}); + +Item.propTypes = { + ...ItemType, + onSelect: PropTypes.func.isRequired, +}; + +const QuickAddList = ({ items, onSelect }) => ( + +); + +QuickAddList.propTypes = { + items: PropTypes.arrayOf(ItemType).isRequired, + onSelect: PropTypes.func.isRequired, +}; + +export default QuickAddList; diff --git a/client/modules/IDE/components/QuickAddList/index.js b/client/modules/IDE/components/QuickAddList/index.js new file mode 100644 index 00000000..4503fca5 --- /dev/null +++ b/client/modules/IDE/components/QuickAddList/index.js @@ -0,0 +1 @@ +export { default } from './QuickAddList.jsx'; diff --git a/client/modules/IDE/pages/IDEView.jsx b/client/modules/IDE/pages/IDEView.jsx index 99e37ba2..e7447eff 100644 --- a/client/modules/IDE/pages/IDEView.jsx +++ b/client/modules/IDE/pages/IDEView.jsx @@ -30,7 +30,7 @@ import * as ConsoleActions from '../actions/console'; import { getHTMLFile } from '../reducers/files'; import Overlay from '../../App/components/Overlay'; import About from '../components/About'; -import CollectionList from '../components/CollectionList'; +import CollectionList from '../components/AddToCollectionList'; import Feedback from '../components/Feedback'; class IDEView extends React.Component { @@ -392,7 +392,6 @@ class IDEView extends React.Component { previousPath={this.props.ide.previousPath} > * { + display: none; +} + +.quick-add__in-icon { + display: inline-block; + + & svg { + opacity: 0.3; + } +} + +.quick-add__icon--in-collection .quick-add__in-icon svg { + opacity: 1; +} + +.quick-add__add-icon { + transform: rotate(45deg); +} + +.quick-add__item-toggle:hover { + .quick-add__in-icon { + display: none; + } + + .quick-add__icon--in-collection { + .quick-add__remove-icon { + display: inline-block; + } + + .quick-add__add-icon { + display: none; + } + } + + .quick-add__icon--not-in-collection { + .quick-add__add-icon { + display: inline-block; + } + + .quick-add__remove-icon { + display: none; + } + } +} diff --git a/client/styles/components/_sketch-list.scss b/client/styles/components/_sketch-list.scss index b734485b..a78ec308 100644 --- a/client/styles/components/_sketch-list.scss +++ b/client/styles/components/_sketch-list.scss @@ -118,70 +118,6 @@ width: #{35 / $base-font-size}rem; } -.sketch-list__icon { - display: inline-block; - margin-right:#{15 / $base-font-size}rem; - width:#{35 / $base-font-size}rem; - height:#{35 / $base-font-size}rem; - @include icon(); - @include themify() { - & path { - fill: getThemifyVariable('dropdown-color'); - } - - & svg { - width:#{35 / $base-font-size}rem; - height:#{35 / $base-font-size}rem; - } - } -} - -.sketch-list__icon > * { - display: none; -} - -.sketch-list__in-icon { - display: inline-block; - - & svg { - opacity: 0.3; - } -} - -.sketch-list__icon--in-collection .sketch-list__in-icon svg { - opacity: 1; -} - -.sketch-list__add-icon { - transform: rotate(45deg); -} - -.sketches-table__row:hover { - .sketch-list__in-icon { - display: none; - } - - .sketch-list__icon--in-collection { - .sketch-list__remove-icon { - display: inline-block; - } - - .sketch-list__add-icon { - display: none; - } - } - - .sketch-list__icon--not-in-collection { - .sketch-list__add-icon { - display: inline-block; - } - - .sketch-list__remove-icon { - display: none; - } - } -} - .sketch-list__action-dialogue { @extend %dropdown-open-right; top: 63%; diff --git a/client/styles/main.scss b/client/styles/main.scss index d0b9833b..468e2b9b 100644 --- a/client/styles/main.scss +++ b/client/styles/main.scss @@ -52,6 +52,7 @@ @import 'components/collection'; @import 'components/collection-create'; @import 'components/collection-popover'; +@import 'components/quick-add'; @import 'layout/dashboard'; @import 'layout/ide';