From 3dbaa900a4b26e2ccf9fdb16e8212e46c8696507 Mon Sep 17 00:00:00 2001 From: Andrew Nicolaou Date: Mon, 9 Sep 2019 18:52:14 +0200 Subject: [PATCH] Splits CollectionList into smaller files --- .../modules/IDE/components/CollectionList.jsx | 398 ------------------ .../CollectionList/CollectionList.jsx | 188 +++++++++ .../CollectionList/CollectionListRow.jsx | 253 +++++++++++ .../IDE/components/CollectionList/index.js | 1 + 4 files changed, 442 insertions(+), 398 deletions(-) delete mode 100644 client/modules/IDE/components/CollectionList.jsx create mode 100644 client/modules/IDE/components/CollectionList/CollectionList.jsx create mode 100644 client/modules/IDE/components/CollectionList/CollectionListRow.jsx create mode 100644 client/modules/IDE/components/CollectionList/index.js diff --git a/client/modules/IDE/components/CollectionList.jsx b/client/modules/IDE/components/CollectionList.jsx deleted file mode 100644 index c967be63..00000000 --- a/client/modules/IDE/components/CollectionList.jsx +++ /dev/null @@ -1,398 +0,0 @@ -import format from 'date-fns/format'; -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 { Link } from 'react-router'; -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 * as IdeActions from '../actions/ide'; -import getSortedCollections from '../selectors/collections'; -import Loader from '../../App/components/loader'; - -const arrowUp = require('../../../images/sort-arrow-up.svg'); -const arrowDown = require('../../../images/sort-arrow-down.svg'); -const downFilledTriangle = require('../../../images/down-filled-triangle.svg'); - -class CollectionListRowBase extends React.Component { - static projectInCollection(project, collection) { - return collection.items.find(item => item.project.id === project.id) != null; - } - - constructor(props) { - super(props); - this.state = { - optionsOpen: false, - renameOpen: false, - renameValue: props.collection.name, - isFocused: false - }; - } - - onFocusComponent = () => { - this.setState({ isFocused: true }); - } - - onBlurComponent = () => { - this.setState({ isFocused: false }); - setTimeout(() => { - if (!this.state.isFocused) { - this.closeAll(); - } - }, 200); - } - - openOptions = () => { - this.setState({ - optionsOpen: true - }); - } - - closeOptions = () => { - this.setState({ - optionsOpen: false - }); - } - - toggleOptions = () => { - if (this.state.optionsOpen) { - this.closeOptions(); - } else { - this.openOptions(); - } - } - - openRename = () => { - this.setState({ - renameOpen: true - }); - } - - closeRename = () => { - this.setState({ - renameOpen: false - }); - } - - closeAll = () => { - this.setState({ - renameOpen: false, - optionsOpen: false - }); - } - - handleRenameChange = (e) => { - this.setState({ - renameValue: e.target.value - }); - } - - handleRenameEnter = (e) => { - if (e.key === 'Enter') { - // TODO pass this func - this.props.changeProjectName(this.props.collection.id, this.state.renameValue); - this.closeAll(); - } - } - - resetSketchName = () => { - this.setState({ - renameValue: this.props.collection.name - }); - } - - handleDropdownOpen = () => { - this.closeAll(); - this.openOptions(); - } - - handleRenameOpen = () => { - this.closeAll(); - this.openRename(); - } - - handleSketchDownload = () => { - this.props.exportProjectAsZip(this.props.collection.id); - } - - handleSketchDuplicate = () => { - this.closeAll(); - this.props.cloneProject(this.props.collection.id); - } - - handleSketchShare = () => { - this.closeAll(); - this.props.showShareModal(this.props.collection.id, this.props.collection.name, this.props.username); - } - - handleSketchDelete = () => { - this.closeAll(); - if (window.confirm(`Are you sure you want to delete "${this.props.collection.name}"?`)) { - this.props.deleteProject(this.props.collection.id); - } - } - - handleCollectionAdd = () => { - this.props.addToCollection(this.props.collection.id, this.props.project.id); - } - - handleCollectionRemove = () => { - this.props.removeFromCollection(this.props.collection.id, this.props.project.id); - } - - render() { - const { collection, username } = this.props; - const { renameOpen, optionsOpen, renameValue } = this.state; - const userIsOwner = this.props.user.username === this.props.username; - - const dropdown = ( - - - {optionsOpen && - - } - - ); - - return ( - - - - {renameOpen ? '' : collection.name} - - {renameOpen - && - e.stopPropagation()} - /> - } - - {format(new Date(collection.createdAt), 'MMM D, YYYY h:mm A')} - {format(new Date(collection.updatedAt), 'MMM D, YYYY h:mm A')} - {(collection.items || []).length} - {dropdown} - ); - } -} - -CollectionListRowBase.propTypes = { - addToCollection: PropTypes.func.isRequired, - removeFromCollection: PropTypes.func.isRequired, - collection: PropTypes.shape({ - id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired - }).isRequired, - username: PropTypes.string.isRequired, - user: PropTypes.shape({ - username: PropTypes.string, - authenticated: PropTypes.bool.isRequired - }).isRequired, - deleteProject: PropTypes.func.isRequired, - showShareModal: PropTypes.func.isRequired, - cloneProject: PropTypes.func.isRequired, - exportProjectAsZip: PropTypes.func.isRequired, - changeProjectName: PropTypes.func.isRequired -}; - -function mapDispatchToPropsSketchListRow(dispatch) { - return bindActionCreators(Object.assign({}, CollectionsActions, ProjectActions, IdeActions), dispatch); -} - -const CollectionListRow = connect(null, mapDispatchToPropsSketchListRow)(CollectionListRowBase); - -class CollectionList extends React.Component { - constructor(props) { - super(props); - this.props.getCollections(this.props.username); - this.props.resetSorting(); - this._renderFieldHeader = this._renderFieldHeader.bind(this); - } - - 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`; - } - - hasCollections() { - return !this.props.loading && this.props.collections.length > 0; - } - - _renderLoader() { - if (this.props.loading) return ; - return null; - } - - _renderEmptyTable() { - if (!this.props.loading && this.props.collections.length === 0) { - return (

No collections.

); - } - return null; - } - - _renderFieldHeader(fieldName, displayName) { - const { field, direction } = this.props.sorting; - const headerClass = classNames({ - 'sketches-table__header': true, - 'sketches-table__header--selected': field === fieldName - }); - return ( - - - - ); - } - - render() { - const username = this.props.username !== undefined ? this.props.username : this.props.user.username; - return ( -
- - {this.getTitle()} - - - New collection - - {this._renderLoader()} - {this._renderEmptyTable()} - {this.hasCollections() && - - - - {this._renderFieldHeader('name', 'Name')} - {this._renderFieldHeader('createdAt', 'Date Created')} - {this._renderFieldHeader('updatedAt', 'Date Updated')} - {this._renderFieldHeader('numItems', '# sketches')} - - - - - {this.props.collections.map(collection => - ())} - -
} -
- ); - } -} - -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, - getCollections: 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, - toggleDirectionForField: PropTypes.func.isRequired, - resetSorting: PropTypes.func.isRequired, - sorting: PropTypes.shape({ - field: PropTypes.string.isRequired, - direction: PropTypes.string.isRequired - }).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) { - return { - user: state.user, - collections: getSortedCollections(state), - sorting: state.sorting, - loading: state.loading, - project: state.project - }; -} - -function mapDispatchToProps(dispatch) { - return bindActionCreators( - Object.assign({}, CollectionsActions, ProjectsActions, ToastActions, SortingActions), - dispatch - ); -} - -export default connect(mapStateToProps, mapDispatchToProps)(CollectionList); diff --git a/client/modules/IDE/components/CollectionList/CollectionList.jsx b/client/modules/IDE/components/CollectionList/CollectionList.jsx new file mode 100644 index 00000000..cbf0613f --- /dev/null +++ b/client/modules/IDE/components/CollectionList/CollectionList.jsx @@ -0,0 +1,188 @@ +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 CollectionListRow from './CollectionListRow'; + +const arrowUp = require('../../../../images/sort-arrow-up.svg'); +const arrowDown = require('../../../../images/sort-arrow-down.svg'); + +class CollectionList extends React.Component { + constructor(props) { + super(props); + + if (props.projectId) { + props.getProject(props.projectId); + } + + this.props.getCollections(this.props.username); + this.props.resetSorting(); + this._renderFieldHeader = this._renderFieldHeader.bind(this); + } + + 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`; + } + + hasCollections() { + return !this.props.loading && this.props.collections.length > 0; + } + + _renderLoader() { + if (this.props.loading) return ; + return null; + } + + _renderEmptyTable() { + if (!this.props.loading && this.props.collections.length === 0) { + return (

No collections.

); + } + return null; + } + + _renderFieldHeader(fieldName, displayName) { + const { field, direction } = this.props.sorting; + const headerClass = classNames({ + 'sketches-table__header': true, + 'sketches-table__header--selected': field === fieldName + }); + return ( + + + + ); + } + + render() { + const username = this.props.username !== undefined ? this.props.username : this.props.user.username; + + return ( +
+ + {this.getTitle()} + + + {this._renderLoader()} + {this._renderEmptyTable()} + {this.hasCollections() && + + + + {this._renderFieldHeader('name', 'Name')} + {this._renderFieldHeader('createdAt', 'Date Created')} + {this._renderFieldHeader('updatedAt', 'Date Updated')} + {this._renderFieldHeader('numItems', '# sketches')} + + + + + {this.props.collections.map(collection => + ())} + +
} +
+ ); + } +} + +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, + getCollections: 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, + toggleDirectionForField: PropTypes.func.isRequired, + resetSorting: PropTypes.func.isRequired, + sorting: PropTypes.shape({ + field: PropTypes.string.isRequired, + direction: PropTypes.string.isRequired + }).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/CollectionList/CollectionListRow.jsx b/client/modules/IDE/components/CollectionList/CollectionListRow.jsx new file mode 100644 index 00000000..f720f628 --- /dev/null +++ b/client/modules/IDE/components/CollectionList/CollectionListRow.jsx @@ -0,0 +1,253 @@ +import format from 'date-fns/format'; +import PropTypes from 'prop-types'; +import React from 'react'; +import InlineSVG from 'react-inlinesvg'; +import { connect } from 'react-redux'; +import { Link } from 'react-router'; +import { bindActionCreators } from 'redux'; +import * as ProjectActions from '../../actions/project'; +import * as CollectionsActions from '../../actions/collections'; +import * as IdeActions from '../../actions/ide'; +import * as ToastActions from '../../actions/toast'; + +const downFilledTriangle = require('../../../../images/down-filled-triangle.svg'); + +class CollectionListRowBase extends React.Component { + static projectInCollection(project, collection) { + console.log('project in collection', project, collection); + return collection.items.find(item => item.project.id === project.id) != null; + } + + constructor(props) { + super(props); + this.state = { + optionsOpen: false, + renameOpen: false, + renameValue: props.collection.name, + isFocused: false + }; + } + + onFocusComponent = () => { + this.setState({ isFocused: true }); + } + + onBlurComponent = () => { + this.setState({ isFocused: false }); + setTimeout(() => { + if (!this.state.isFocused) { + this.closeAll(); + } + }, 200); + } + + openOptions = () => { + this.setState({ + optionsOpen: true + }); + } + + closeOptions = () => { + this.setState({ + optionsOpen: false + }); + } + + toggleOptions = () => { + if (this.state.optionsOpen) { + this.closeOptions(); + } else { + this.openOptions(); + } + } + + openRename = () => { + this.setState({ + renameOpen: true + }); + } + + closeRename = () => { + this.setState({ + renameOpen: false + }); + } + + closeAll = () => { + this.setState({ + renameOpen: false, + optionsOpen: false + }); + } + + handleRenameChange = (e) => { + this.setState({ + renameValue: e.target.value + }); + } + + handleRenameEnter = (e) => { + if (e.key === 'Enter') { + // TODO pass this func + this.props.changeProjectName(this.props.collection.id, this.state.renameValue); + this.closeAll(); + } + } + + resetSketchName = () => { + this.setState({ + renameValue: this.props.collection.name + }); + } + + handleDropdownOpen = () => { + this.closeAll(); + this.openOptions(); + } + + handleRenameOpen = () => { + this.closeAll(); + this.openRename(); + } + + handleSketchDownload = () => { + this.props.exportProjectAsZip(this.props.collection.id); + } + + handleSketchDuplicate = () => { + this.closeAll(); + this.props.cloneProject(this.props.collection.id); + } + + handleSketchShare = () => { + this.closeAll(); + this.props.showShareModal(this.props.collection.id, this.props.collection.name, this.props.username); + } + + handleSketchDelete = () => { + this.closeAll(); + if (window.confirm(`Are you sure you want to delete "${this.props.collection.name}"?`)) { + this.props.deleteProject(this.props.collection.id); + } + } + + handleCollectionAdd = () => { + this.props.addToCollection(this.props.collection.id, this.props.project.id); + } + + handleCollectionRemove = () => { + this.props.removeFromCollection(this.props.collection.id, this.props.project.id); + } + + renderActions = () => { + const { optionsOpen } = this.state; + const userIsOwner = this.props.user.username === this.props.username; + + if (this.props.addMode === true && this.props.project) { + return CollectionListRowBase.projectInCollection(this.props.project, this.props.collection) ? : ; + } + + return ( + + + {optionsOpen && +
    + {userIsOwner && +
  • + +
  • } +
+ } +
+ ); + } + + renderCollectionName = () => { + const { addMode, collection, username } = this.props; + const { renameOpen, renameValue } = this.state; + + if (addMode) { + return collection.name; + } + + return ( + + + {renameOpen ? '' : collection.name} + + {renameOpen + && + e.stopPropagation()} + /> + } + + ) + } + + render() { + const { collection } = this.props; + + return ( + + + {this.renderCollectionName()} + + {format(new Date(collection.createdAt), 'MMM D, YYYY h:mm A')} + {format(new Date(collection.updatedAt), 'MMM D, YYYY h:mm A')} + {(collection.items || []).length} + + {this.renderActions()} + + + ); + } +} + +CollectionListRowBase.propTypes = { + addToCollection: PropTypes.func.isRequired, + removeFromCollection: PropTypes.func.isRequired, + collection: PropTypes.shape({ + id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired + }).isRequired, + username: PropTypes.string.isRequired, + user: PropTypes.shape({ + username: PropTypes.string, + authenticated: PropTypes.bool.isRequired + }).isRequired, + deleteProject: PropTypes.func.isRequired, + showShareModal: PropTypes.func.isRequired, + cloneProject: PropTypes.func.isRequired, + exportProjectAsZip: PropTypes.func.isRequired, + changeProjectName: PropTypes.func.isRequired +}; + +function mapDispatchToPropsSketchListRow(dispatch) { + return bindActionCreators(Object.assign({}, CollectionsActions, ProjectActions, IdeActions, ToastActions), dispatch); +} + +export default connect(null, mapDispatchToPropsSketchListRow)(CollectionListRowBase); diff --git a/client/modules/IDE/components/CollectionList/index.js b/client/modules/IDE/components/CollectionList/index.js new file mode 100644 index 00000000..51d43417 --- /dev/null +++ b/client/modules/IDE/components/CollectionList/index.js @@ -0,0 +1 @@ +export { default } from './CollectionList';