Popover component to add sketches to collection from SketchList
This commit is contained in:
parent
edfddcc75f
commit
af8d25f142
6 changed files with 285 additions and 19 deletions
|
@ -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 = () => (
|
||||
<div>
|
||||
<p>No collections</p>
|
||||
{/* <p>
|
||||
<Link
|
||||
to="/andrewn/collections/create"
|
||||
className="searchbar__clear-button"
|
||||
onClick={() => {}}
|
||||
>Create
|
||||
</Link>
|
||||
</p> */}
|
||||
</div>);
|
||||
|
||||
|
||||
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 (
|
||||
<div className="collection-popover">
|
||||
<div className="collection-popover__header">
|
||||
<h4>Add to collection</h4>
|
||||
<button className="collection-popover__exit-button" onClick={onClose}>
|
||||
<InlineSVG src={exitUrl} alt="Close Add to Collection" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="collection-popover__filter">
|
||||
<Searchbar searchLabel="Search collections..." searchTerm={searchTerm} setSearchTerm={setSearchTerm} resetSearchTerm={setSearchTerm} />
|
||||
</div>
|
||||
|
||||
<div className="collection-popover__items">
|
||||
<ul>
|
||||
{
|
||||
collections.map(collection => <Item key={collection.id} collection={collection} onSelect={() => handleAddToCollection(collection.id)} />)
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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);
|
26
client/modules/IDE/components/CollectionPopover/Item.jsx
Normal file
26
client/modules/IDE/components/CollectionPopover/Item.jsx
Normal file
|
@ -0,0 +1,26 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
const CollectionItem = ({ collection, onSelect }) => (
|
||||
<li className="collection-popover__item">
|
||||
<div className="collection-popover__item__info">
|
||||
<button onClick={onSelect}>
|
||||
{collection.name}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="collection-popover__item__view">
|
||||
<Link className="collection-popover__item__view-button" to={`/${collection.owner.username}/collections/${collection.id}`}>View</Link>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
|
||||
CollectionItem.propTypes = {
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
collection: PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export default CollectionItem;
|
1
client/modules/IDE/components/CollectionPopover/index.js
Normal file
1
client/modules/IDE/components/CollectionPopover/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
export { default } from './CollectionPopover.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
|
||||
</button>
|
||||
</li>}
|
||||
{this.props.user.authenticated &&
|
||||
<li>
|
||||
<button
|
||||
className="sketch-list__action-option"
|
||||
onClick={this.handleShowCollectionPopover}
|
||||
onBlur={this.onBlurComponent}
|
||||
onFocus={this.onFocusComponent}
|
||||
>
|
||||
Add to collection
|
||||
</button>
|
||||
</li>}
|
||||
{ /* <li>
|
||||
<button
|
||||
className="sketch-list__action-option"
|
||||
|
@ -208,6 +233,7 @@ class SketchListRowBase extends React.Component {
|
|||
</button>
|
||||
</li>}
|
||||
</ul>}
|
||||
{this.state.showPopover && <CollectionPopover onClose={this.handleCloseCollectionPopover} project={this.props.sketch} />}
|
||||
</td>
|
||||
);
|
||||
}
|
||||
|
@ -231,15 +257,16 @@ class SketchListRowBase extends React.Component {
|
|||
url = `/${username}/sketches/${slugify(sketch.name, '_')}`;
|
||||
}
|
||||
return (
|
||||
<tr
|
||||
className="sketches-table__row"
|
||||
key={sketch.id}
|
||||
>
|
||||
<th scope="row">
|
||||
<Link to={url}>
|
||||
{renameOpen ? '' : sketch.name}
|
||||
</Link>
|
||||
{renameOpen
|
||||
<React.Fragment>
|
||||
<tr
|
||||
className="sketches-table__row"
|
||||
key={sketch.id}
|
||||
>
|
||||
<th scope="row">
|
||||
<Link to={url}>
|
||||
{renameOpen ? '' : sketch.name}
|
||||
</Link>
|
||||
{renameOpen
|
||||
&&
|
||||
<input
|
||||
value={renameValue}
|
||||
|
@ -248,12 +275,13 @@ class SketchListRowBase extends React.Component {
|
|||
onBlur={this.resetSketchName}
|
||||
onClick={e => e.stopPropagation()}
|
||||
/>
|
||||
}
|
||||
</th>
|
||||
<td>{format(new Date(sketch.createdAt), 'MMM D, YYYY h:mm A')}</td>
|
||||
<td>{format(new Date(sketch.updatedAt), 'MMM D, YYYY h:mm A')}</td>
|
||||
{this.renderActions()}
|
||||
</tr>);
|
||||
}
|
||||
</th>
|
||||
<td>{format(new Date(sketch.createdAt), 'MMM D, YYYY h:mm A')}</td>
|
||||
<td>{format(new Date(sketch.updatedAt), 'MMM D, YYYY h:mm A')}</td>
|
||||
{this.renderActions()}
|
||||
</tr>
|
||||
</React.Fragment>);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 <Loader />;
|
||||
if (this.isLoading()) return <Loader />;
|
||||
return null;
|
||||
}
|
||||
|
||||
_renderEmptyTable() {
|
||||
if (!this.props.loading && this.props.sketches.length === 0) {
|
||||
if (!this.isLoading() && this.props.sketches.length === 0) {
|
||||
return (<p className="sketches-table__empty">No sketches.</p>);
|
||||
}
|
||||
return null;
|
||||
|
|
89
client/styles/components/_collection-popover.scss
Normal file
89
client/styles/components/_collection-popover.scss
Normal file
|
@ -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;
|
||||
}
|
|
@ -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';
|
||||
|
|
Loading…
Reference in a new issue