From af8d25f142e6740ba80c5d3a2fefa893ea1f686e Mon Sep 17 00:00:00 2001 From: Andrew Nicolaou Date: Sun, 20 Oct 2019 21:59:16 +0200 Subject: [PATCH] Popover component to add sketches to collection from SketchList --- .../CollectionPopover/CollectionPopover.jsx | 105 ++++++++++++++++++ .../IDE/components/CollectionPopover/Item.jsx | 26 +++++ .../IDE/components/CollectionPopover/index.js | 1 + client/modules/IDE/components/SketchList.jsx | 82 ++++++++++---- .../components/_collection-popover.scss | 89 +++++++++++++++ client/styles/main.scss | 1 + 6 files changed, 285 insertions(+), 19 deletions(-) create mode 100644 client/modules/IDE/components/CollectionPopover/CollectionPopover.jsx create mode 100644 client/modules/IDE/components/CollectionPopover/Item.jsx create mode 100644 client/modules/IDE/components/CollectionPopover/index.js create mode 100644 client/styles/components/_collection-popover.scss diff --git a/client/modules/IDE/components/CollectionPopover/CollectionPopover.jsx b/client/modules/IDE/components/CollectionPopover/CollectionPopover.jsx new file mode 100644 index 00000000..96471d8f --- /dev/null +++ b/client/modules/IDE/components/CollectionPopover/CollectionPopover.jsx @@ -0,0 +1,105 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import InlineSVG from 'react-inlinesvg'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; + +import * as CollectionsActions from '../../actions/collections'; +import getSortedCollections from '../../selectors/collections'; + +// import { Link } from 'react-router'; + +import exitUrl from '../../../../images/exit.svg'; + +import { Searchbar } from '../Searchbar'; +import Item from './Item'; + +// const reducer = () => { +// switch () +// case 'noItems': +// return 'NoCollections'; +// case +// } + +const NoCollections = () => ( +
+

No collections

+ {/*

+ {}} + >Create + +

*/} +
); + + +const CollectionPopover = ({ + onClose, project, collections, addToCollection, getCollections, user +}) => { + const [searchTerm, setSearchTerm] = React.useState(''); + + React.useEffect(() => { + getCollections(user.username); + }, [user]); + + const handleAddToCollection = (collectionId) => { + addToCollection(collectionId, project.id); + }; + + return ( +
+
+

Add to collection

+ +
+ +
+ +
+ +
+
    + { + collections.map(collection => handleAddToCollection(collection.id)} />) + } +
+
+
+ ); +}; + +CollectionPopover.propTypes = { + onClose: PropTypes.func.isRequired, + getCollections: PropTypes.func.isRequired, + addToCollection: PropTypes.func.isRequired, + user: PropTypes.shape({ + username: PropTypes.string.isRequired, + }).isRequired, + collections: PropTypes.arrayOf(PropTypes.shape({ + name: PropTypes.string, + })).isRequired, + project: PropTypes.shape({ + id: PropTypes.string, + }).isRequired, +}; + +function mapStateToProps(state, ownProps) { + return { + user: state.user, + collections: getSortedCollections(state), + loading: state.loading, + }; +} + +function mapDispatchToProps(dispatch) { + return bindActionCreators( + Object.assign({}, CollectionsActions), + dispatch + ); +} + +export default connect(mapStateToProps, mapDispatchToProps)(CollectionPopover); diff --git a/client/modules/IDE/components/CollectionPopover/Item.jsx b/client/modules/IDE/components/CollectionPopover/Item.jsx new file mode 100644 index 00000000..1d3d4efa --- /dev/null +++ b/client/modules/IDE/components/CollectionPopover/Item.jsx @@ -0,0 +1,26 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import { Link } from 'react-router'; + +const CollectionItem = ({ collection, onSelect }) => ( +
  • +
    + +
    + +
    + View +
    +
  • +); + +CollectionItem.propTypes = { + onSelect: PropTypes.func.isRequired, + collection: PropTypes.shape({ + name: PropTypes.string, + }).isRequired, +}; + +export default CollectionItem; diff --git a/client/modules/IDE/components/CollectionPopover/index.js b/client/modules/IDE/components/CollectionPopover/index.js new file mode 100644 index 00000000..2457586e --- /dev/null +++ b/client/modules/IDE/components/CollectionPopover/index.js @@ -0,0 +1 @@ +export { default } from './CollectionPopover.jsx'; diff --git a/client/modules/IDE/components/SketchList.jsx b/client/modules/IDE/components/SketchList.jsx index 268d5d89..3cfa421e 100644 --- a/client/modules/IDE/components/SketchList.jsx +++ b/client/modules/IDE/components/SketchList.jsx @@ -17,6 +17,7 @@ import * as IdeActions from '../actions/ide'; import getSortedSketches from '../selectors/projects'; import Loader from '../../App/components/loader'; import AddRemoveButton from '../../../components/AddRemoveButton'; +import CollectionPopover from './CollectionPopover'; const arrowUp = require('../../../images/sort-arrow-up.svg'); const arrowDown = require('../../../images/sort-arrow-down.svg'); @@ -29,7 +30,8 @@ class SketchListRowBase extends React.Component { optionsOpen: false, renameOpen: false, renameValue: props.sketch.name, - isFocused: false + isFocused: false, + showPopover: false, }; } @@ -119,6 +121,18 @@ class SketchListRowBase extends React.Component { this.props.exportProjectAsZip(this.props.sketch.id); } + handleShowCollectionPopover = () => { + this.setState({ + showPopover: true + }); + } + + handleCloseCollectionPopover = () => { + this.setState({ + showPopover: false + }); + } + handleSketchDuplicate = () => { this.closeAll(); this.props.cloneProject(this.props.sketch.id); @@ -186,6 +200,17 @@ class SketchListRowBase extends React.Component { Duplicate } + {this.props.user.authenticated && +
  • + +
  • } { /*
  • } } + {this.state.showPopover && } ); } @@ -231,15 +257,16 @@ class SketchListRowBase extends React.Component { url = `/${username}/sketches/${slugify(sketch.name, '_')}`; } return ( - - - - {renameOpen ? '' : sketch.name} - - {renameOpen + + + + + {renameOpen ? '' : sketch.name} + + {renameOpen && e.stopPropagation()} /> - } - - {format(new Date(sketch.createdAt), 'MMM D, YYYY h:mm A')} - {format(new Date(sketch.updatedAt), 'MMM D, YYYY h:mm A')} - {this.renderActions()} - ); + } + + {format(new Date(sketch.createdAt), 'MMM D, YYYY h:mm A')} + {format(new Date(sketch.updatedAt), 'MMM D, YYYY h:mm A')} + {this.renderActions()} + + ); } } @@ -290,6 +318,18 @@ class SketchList extends React.Component { this.props.getProjects(this.props.username); this.props.resetSorting(); this._renderFieldHeader = this._renderFieldHeader.bind(this); + + this.state = { + isInitialDataLoad: true, + }; + } + + componentWillReceiveProps(nextProps) { + if (this.props.sketches !== nextProps.sketches && Array.isArray(nextProps.sketches)) { + this.setState({ + isInitialDataLoad: false, + }); + } } getSketchesTitle() { @@ -300,16 +340,20 @@ class SketchList extends React.Component { } hasSketches() { - return !this.props.loading && this.props.sketches.length > 0; + return !this.isLoading() && this.props.sketches.length > 0; + } + + isLoading() { + return this.props.loading && this.state.isInitialDataLoad; } _renderLoader() { - if (this.props.loading) return ; + if (this.isLoading()) return ; return null; } _renderEmptyTable() { - if (!this.props.loading && this.props.sketches.length === 0) { + if (!this.isLoading() && this.props.sketches.length === 0) { return (

    No sketches.

    ); } return null; diff --git a/client/styles/components/_collection-popover.scss b/client/styles/components/_collection-popover.scss new file mode 100644 index 00000000..3c65dda5 --- /dev/null +++ b/client/styles/components/_collection-popover.scss @@ -0,0 +1,89 @@ +.collection-popover { + position: absolute; + height: auto; + width: #{400 / $base-font-size}rem; + top: 63%; + right: calc(100% - 26px); + + z-index: 9999; + + display: flex; + flex-direction: column; + + border-radius: #{6 / $base-font-size}rem; + + @include themify() { + background-color: map-get($theme-map, 'modal-background-color'); + border: 1px solid map-get($theme-map, 'modal-border-color'); + box-shadow: 0 0 18px 0 getThemifyVariable('shadow-color'); + color: getThemifyVariable('dropdown-color'); + } + + text-align: left; +} + +.collection-popover__header { + display: flex; + margin-left: #{17 / $base-font-size}rem; + margin-right: #{17 / $base-font-size}rem; +} + +.collection-popover__filter { + display: flex; + margin-bottom: #{8 / $base-font-size}rem; +} + +.collection-popover__exit-button { + @include icon(); + margin-left: auto; +} + +.collection-popover__items { + height: #{70 * 4 / $base-font-size}rem; + overflow: auto; +} + + +.collection-popover__item { + display: flex; + align-items: center; + justify-content: space-between; + + height: #{60 / $base-font-size}rem; + margin: 5px; + padding: 5px; +} + +.collection-popover__item:nth-child(odd) { + @include themify() { + background: getThemifyVariable('table-row-stripe-color'); + } +} + +.collection-popover__item__info { + flex: 1; + width: 100%; + height: 100%; + + display: flex; + justify-content: stretch; + align-items: stretch; +} + +.collection-popover__item__info button, +.collection-popover__item__info button:hover { + flex: 1; + width: 100%; + height: 100%; + + text-align: left; + color: black; +} + +.collection-popover__item__view { +} + + +.collection-popover__item__view-button { + @extend %button; +} diff --git a/client/styles/main.scss b/client/styles/main.scss index afcc2d14..d0b9833b 100644 --- a/client/styles/main.scss +++ b/client/styles/main.scss @@ -51,6 +51,7 @@ @import 'components/editable-input'; @import 'components/collection'; @import 'components/collection-create'; +@import 'components/collection-popover'; @import 'layout/dashboard'; @import 'layout/ide';