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 }) => (
+ {items.map(item => (- onSelect(item)
+ }
+ />))}
+
+);
+
+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';