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 getSortedSketches from '../selectors/projects';
|
||||||
import Loader from '../../App/components/loader';
|
import Loader from '../../App/components/loader';
|
||||||
import AddRemoveButton from '../../../components/AddRemoveButton';
|
import AddRemoveButton from '../../../components/AddRemoveButton';
|
||||||
|
import CollectionPopover from './CollectionPopover';
|
||||||
|
|
||||||
const arrowUp = require('../../../images/sort-arrow-up.svg');
|
const arrowUp = require('../../../images/sort-arrow-up.svg');
|
||||||
const arrowDown = require('../../../images/sort-arrow-down.svg');
|
const arrowDown = require('../../../images/sort-arrow-down.svg');
|
||||||
|
@ -29,7 +30,8 @@ class SketchListRowBase extends React.Component {
|
||||||
optionsOpen: false,
|
optionsOpen: false,
|
||||||
renameOpen: false,
|
renameOpen: false,
|
||||||
renameValue: props.sketch.name,
|
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);
|
this.props.exportProjectAsZip(this.props.sketch.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleShowCollectionPopover = () => {
|
||||||
|
this.setState({
|
||||||
|
showPopover: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCloseCollectionPopover = () => {
|
||||||
|
this.setState({
|
||||||
|
showPopover: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
handleSketchDuplicate = () => {
|
handleSketchDuplicate = () => {
|
||||||
this.closeAll();
|
this.closeAll();
|
||||||
this.props.cloneProject(this.props.sketch.id);
|
this.props.cloneProject(this.props.sketch.id);
|
||||||
|
@ -186,6 +200,17 @@ class SketchListRowBase extends React.Component {
|
||||||
Duplicate
|
Duplicate
|
||||||
</button>
|
</button>
|
||||||
</li>}
|
</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>
|
{ /* <li>
|
||||||
<button
|
<button
|
||||||
className="sketch-list__action-option"
|
className="sketch-list__action-option"
|
||||||
|
@ -208,6 +233,7 @@ class SketchListRowBase extends React.Component {
|
||||||
</button>
|
</button>
|
||||||
</li>}
|
</li>}
|
||||||
</ul>}
|
</ul>}
|
||||||
|
{this.state.showPopover && <CollectionPopover onClose={this.handleCloseCollectionPopover} project={this.props.sketch} />}
|
||||||
</td>
|
</td>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -231,15 +257,16 @@ class SketchListRowBase extends React.Component {
|
||||||
url = `/${username}/sketches/${slugify(sketch.name, '_')}`;
|
url = `/${username}/sketches/${slugify(sketch.name, '_')}`;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<tr
|
<React.Fragment>
|
||||||
className="sketches-table__row"
|
<tr
|
||||||
key={sketch.id}
|
className="sketches-table__row"
|
||||||
>
|
key={sketch.id}
|
||||||
<th scope="row">
|
>
|
||||||
<Link to={url}>
|
<th scope="row">
|
||||||
{renameOpen ? '' : sketch.name}
|
<Link to={url}>
|
||||||
</Link>
|
{renameOpen ? '' : sketch.name}
|
||||||
{renameOpen
|
</Link>
|
||||||
|
{renameOpen
|
||||||
&&
|
&&
|
||||||
<input
|
<input
|
||||||
value={renameValue}
|
value={renameValue}
|
||||||
|
@ -248,12 +275,13 @@ class SketchListRowBase extends React.Component {
|
||||||
onBlur={this.resetSketchName}
|
onBlur={this.resetSketchName}
|
||||||
onClick={e => e.stopPropagation()}
|
onClick={e => e.stopPropagation()}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</th>
|
</th>
|
||||||
<td>{format(new Date(sketch.createdAt), 'MMM D, YYYY h:mm A')}</td>
|
<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>
|
<td>{format(new Date(sketch.updatedAt), 'MMM D, YYYY h:mm A')}</td>
|
||||||
{this.renderActions()}
|
{this.renderActions()}
|
||||||
</tr>);
|
</tr>
|
||||||
|
</React.Fragment>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,6 +318,18 @@ class SketchList extends React.Component {
|
||||||
this.props.getProjects(this.props.username);
|
this.props.getProjects(this.props.username);
|
||||||
this.props.resetSorting();
|
this.props.resetSorting();
|
||||||
this._renderFieldHeader = this._renderFieldHeader.bind(this);
|
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() {
|
getSketchesTitle() {
|
||||||
|
@ -300,16 +340,20 @@ class SketchList extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
hasSketches() {
|
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() {
|
_renderLoader() {
|
||||||
if (this.props.loading) return <Loader />;
|
if (this.isLoading()) return <Loader />;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderEmptyTable() {
|
_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 (<p className="sketches-table__empty">No sketches.</p>);
|
||||||
}
|
}
|
||||||
return null;
|
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/editable-input';
|
||||||
@import 'components/collection';
|
@import 'components/collection';
|
||||||
@import 'components/collection-create';
|
@import 'components/collection-create';
|
||||||
|
@import 'components/collection-popover';
|
||||||
|
|
||||||
@import 'layout/dashboard';
|
@import 'layout/dashboard';
|
||||||
@import 'layout/ide';
|
@import 'layout/ide';
|
||||||
|
|
Loading…
Reference in a new issue