From ddae7eb5ef82f4a3cddc7be587b3e69a81cac3ad Mon Sep 17 00:00:00 2001 From: Alf Eaton <eaton.alf@gmail.com> Date: Fri, 30 Jun 2017 16:02:21 +0100 Subject: [PATCH] Continue separating views and containers --- app/components/Project.js | 101 +++----------------- app/components/RemoveProject.js | 16 ++++ app/components/RolesSummary.js | 8 +- app/components/RolesSummaryItem.js | 76 ++++----------- app/containers/ProjectContainer.js | 99 +++++++++++++++++++ app/containers/RolesSummaryItemContainer.js | 59 ++++++++++++ app/routes.jsx | 4 +- stories/index.js | 26 ++++- 8 files changed, 230 insertions(+), 159 deletions(-) create mode 100644 app/components/RemoveProject.js create mode 100644 app/containers/ProjectContainer.js create mode 100644 app/containers/RolesSummaryItemContainer.js diff --git a/app/components/Project.js b/app/components/Project.js index 97aabd326e..69766dc996 100644 --- a/app/components/Project.js +++ b/app/components/Project.js @@ -1,100 +1,21 @@ import React from 'react' import PropTypes from 'prop-types' -import { connect } from 'react-redux' -import { browserHistory, Link } from 'react-router' -import { Button } from 'react-bootstrap' -import { deleteCollection, getCollection } from 'pubsweet-client/src/actions/collections' -import { getFragments } from 'pubsweet-client/src/actions/fragments' import './Project.css' -import RolesSummary from './RolesSummary' +import { Link } from 'react-router' -class Project extends React.Component { - componentDidMount () { - const { params } = this.props +const Project = ({ children, project }) => ( + <div> + <div className="project-title"> + <Link to={`/projects/${project.id}`}>{project.title}</Link> + </div> - this.fetch(params.project) - } - - componentWillReceiveProps (nextProps) { - const { params } = nextProps - - if (params.project !== this.props.params.project) { - this.fetch(params.project) - } - } - - fetch (id) { - const { getCollection, getFragments } = this.props - - getCollection({ id }) - getFragments({ id }, { - fields: ['version', 'submitted'] - }) - } - - remove = () => { - const { project, deleteCollection } = this.props - - if (!window.confirm('Delete this submission?')) { - return - } - - deleteCollection(project).then(() => { - browserHistory.push('/') - }) - } - - render () { - const { project, children } = this.props - - if (!project) return null - - // TODO: how to ensure that user info is loaded for each role? - - return ( - <div className="content-text main" style={{paddingBottom: 90}}> - <div className="container"> - <Button bsSize="small" bsStyle="link" onClick={this.remove} style={{ color: '#eee', background: 'none', position: 'fixed', top: 50, right: 50 }}><span className="fa fa-remove"/></Button> - - <div className="project-title"> - <Link to={`/projects/${project.id}`}>{project.title}</Link> - </div> - - <div style={{ display: 'flex' }}> - <div style={{ flex: 1 }}> - {children} - </div> - - <div className="content-metadata" style={{ width: 200 }}> - {project.roles && ( - <RolesSummary project={project}/> - )} - </div> - </div> - </div> - </div> - ) - } -} + {children} + </div> +) Project.propTypes = { children: PropTypes.node, - params: PropTypes.object.isRequired, - project: PropTypes.object, - getFragments: PropTypes.func.isRequired, - deleteCollection: PropTypes.func.isRequired, - getCollection: PropTypes.func.isRequired + project: PropTypes.object.isRequired } -export default connect( - (state, ownProps) => ({ - project: state.collections.find(collection => { - return collection.id === ownProps.params.project - }) - }), - { - getFragments, - deleteCollection, - getCollection - } -)(Project) +export default Project diff --git a/app/components/RemoveProject.js b/app/components/RemoveProject.js new file mode 100644 index 0000000000..9062f2b6d5 --- /dev/null +++ b/app/components/RemoveProject.js @@ -0,0 +1,16 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { Button } from 'react-bootstrap' + +const RemoveProject = ({ onClick }) => ( + <Button bsSize="small" bsStyle="link" onClick={onClick} + style={{ color: '#eee', background: 'none', position: 'fixed', top: 50, right: 50 }}> + <span className="fa fa-remove"/> + </Button> +) + +RemoveProject.propTypes = { + onClick: PropTypes.func.isRequired +} + +export default RemoveProject diff --git a/app/components/RolesSummary.js b/app/components/RolesSummary.js index 5ad7f15d4e..8f8732c883 100644 --- a/app/components/RolesSummary.js +++ b/app/components/RolesSummary.js @@ -1,8 +1,6 @@ import React from 'react' import PropTypes from 'prop-types' -import { Link } from 'react-router' -import { connect } from 'react-redux' -import RolesSummaryItem from './RolesSummaryItem' +import RolesSummaryItemContainer from '../containers/RolesSummaryItemContainer' const roleTypes = ['owner', 'editor', 'reviewer'] @@ -19,7 +17,7 @@ const RolesSummary = ({ project }) => ( const role = roles[id] return ( - <RolesSummaryItem key={id} roleId={id} roleType={roleType} project={project} role={role}/> + <RolesSummaryItemContainer key={id} roleId={id} roleType={roleType} project={project} role={role}/> ) }) })} @@ -32,4 +30,4 @@ RolesSummary.propTypes = { project: PropTypes.object.isRequired } -export default connect()(RolesSummary) +export default RolesSummary diff --git a/app/components/RolesSummaryItem.js b/app/components/RolesSummaryItem.js index d7aefb5eb0..8989be9b3d 100644 --- a/app/components/RolesSummaryItem.js +++ b/app/components/RolesSummaryItem.js @@ -1,68 +1,24 @@ import React from 'react' import PropTypes from 'prop-types' -import { connect } from 'react-redux' -import { getUser } from 'pubsweet-client/src/actions/users' import { Link } from 'react-router' -const ucfirst = (text) => { - return text.substr(0, 1).toUpperCase() + text.substr(1) -} - -class RolesSummaryItem extends React.Component { - componentDidMount () { - const userId = this.props.role.user.id - - if (userId) { - this.fetch(userId) - } - } - - componentWillReceiveProps (nextProps) { - const userId = nextProps.role.user.id - - if (userId && userId !== this.props.role.user.id) { - this.fetch(userId) - } - } - - fetch (id) { - this.props.getUser({ id }) - } - - render () { - const { project, roleId, roleType, user } = this.props - - if (!user) return null - - return ( - <div style={{ display: 'table-row' }}> - <div style={{ display: 'table-cell', padding: '2px 5px 2px 15px', color: '#4990E2' }}> - {ucfirst(roleType)} - </div> - <div style={{ display: 'table-cell', padding: '2px 5px' }}> - <Link to={`/projects/${project.id}/roles/${roleType}/${roleId}`}> - {user.name || user.username} - </Link> - </div> - </div> - ) - } -} +const RolesSummaryItem = ({ label, url, user }) => ( + <div style={{ display: 'table-row' }}> + <div style={{ display: 'table-cell', padding: '2px 5px 2px 15px', color: '#4990E2' }}> + {label} + </div> + <div style={{ display: 'table-cell', padding: '2px 5px' }}> + <Link to={url}> + {user.name || user.username} + </Link> + </div> + </div> +) RolesSummaryItem.propTypes = { - getUser: PropTypes.func.isRequired, - roleId: PropTypes.string.isRequired, - roleType: PropTypes.string.isRequired, - project: PropTypes.object.isRequired, - role: PropTypes.object.isRequired, - user: PropTypes.object + label: PropTypes.string.isRequired, + url: PropTypes.string.isRequired, + user: PropTypes.object.isRequired } -export default connect( - (state, ownProps) => ({ - user: ownProps.role.user.id ? state.users.users.find(user => user.id === ownProps.role.user.id) : ownProps.role.user - }), - { - getUser - } -)(RolesSummaryItem) +export default RolesSummaryItem diff --git a/app/containers/ProjectContainer.js b/app/containers/ProjectContainer.js new file mode 100644 index 0000000000..a9d83142b3 --- /dev/null +++ b/app/containers/ProjectContainer.js @@ -0,0 +1,99 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { connect } from 'react-redux' +import { push } from 'react-router-redux' +import { deleteCollection, getCollection } from 'pubsweet-client/src/actions/collections' +import { getFragments } from 'pubsweet-client/src/actions/fragments' +import Project from '../components/Project' +import RemoveProject from '../components/RemoveProject' +import RolesSummary from '../components/RolesSummary' + +class ProjectContainer extends React.Component { + componentDidMount () { + const { params } = this.props + + this.fetch(params.project) + } + + componentWillReceiveProps (nextProps) { + const { params } = nextProps + + if (params.project !== this.props.params.project) { + this.fetch(params.project) + } + } + + fetch (id) { + const { getCollection, getFragments } = this.props + + getCollection({ id }) + + getFragments({ id }, { + fields: ['version', 'submitted'] + }) + } + + remove = () => { + const { project, deleteCollection, push } = this.props + + if (!window.confirm('Delete this submission?')) { + return + } + + deleteCollection(project).then(() => { + push('/') + }) + } + + render () { + const { project, children } = this.props + + if (!project) return null + + return ( + <div className="content-text main" style={{ paddingBottom: 90 }}> + <div className="container"> + <RemoveProject onClick={this.remove}/> + + <div style={{ display: 'flex' }}> + <div style={{ flex: 1 }}> + <Project project={project}> + {children} + </Project> + </div> + + <div className="content-metadata" style={{ width: 200 }}> + {project.roles && ( + <RolesSummary project={project}/> + )} + </div> + </div> + </div> + </div> + ) + } +} + +ProjectContainer.propTypes = { + children: PropTypes.node, + params: PropTypes.object.isRequired, + project: PropTypes.object, + push: PropTypes.func.isRequired, + getFragments: PropTypes.func.isRequired, + deleteCollection: PropTypes.func.isRequired, + getCollection: PropTypes.func.isRequired +} + +export default connect( + (state, ownProps) => ({ + project: state.collections.find(collection => { + return collection.id === ownProps.params.project + }) + }), + { + getFragments, + deleteCollection, + getCollection, + push + } +)(ProjectContainer) diff --git a/app/containers/RolesSummaryItemContainer.js b/app/containers/RolesSummaryItemContainer.js new file mode 100644 index 0000000000..41aafc87e1 --- /dev/null +++ b/app/containers/RolesSummaryItemContainer.js @@ -0,0 +1,59 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { connect } from 'react-redux' +import { getUser } from 'pubsweet-client/src/actions/users' +import RolesSummaryItem from '../components/RolesSummaryItem' + +const ucfirst = (text) => { + return text.substr(0, 1).toUpperCase() + text.substr(1) +} + +class RolesSummaryItemContainer extends React.Component { + componentDidMount () { + const userId = this.props.role.user.id + + if (userId) { + this.fetch(userId) + } + } + + componentWillReceiveProps (nextProps) { + const userId = nextProps.role.user.id + + if (userId && userId !== this.props.role.user.id) { + this.fetch(userId) + } + } + + fetch (id) { + this.props.getUser({ id }) + } + + render () { + const { project, roleId, roleType, user } = this.props + + if (!user) return null + + return ( + <RolesSummaryItem label={ucfirst(roleType)} user={user} link={`/projects/${project.id}/roles/${roleType}/${roleId}`}/> + ) + } +} + +RolesSummaryItemContainer.propTypes = { + getUser: PropTypes.func.isRequired, + roleId: PropTypes.string.isRequired, + roleType: PropTypes.string.isRequired, + project: PropTypes.object.isRequired, + role: PropTypes.object.isRequired, + user: PropTypes.object +} + +export default connect( + (state, ownProps) => ({ + user: ownProps.role.user.id ? state.users.users.find(user => user.id === ownProps.role.user.id) : ownProps.role.user + }), + { + getUser + } +)(RolesSummaryItemContainer) diff --git a/app/routes.jsx b/app/routes.jsx index 6eb3814a76..103359df56 100644 --- a/app/routes.jsx +++ b/app/routes.jsx @@ -15,9 +15,9 @@ export default ( <Route path="/" component={App}> <Route component={AuthenticatedContainer}> - <Route path="projects" component={chunk(import('./components/ProjectList'))}/> + <Route path="projects" component={chunk(import('./containers/ProjectListContainer'))}/> - <Route path="projects/:project" component={chunk(import('./components/Project'))}> + <Route path="projects/:project" component={chunk(import('./containers/ProjectContainer'))}> <IndexRoute component={chunk(import('./components/Snapshots'))}/> <Route path="declarations" component={chunk(import('./components/Declarations'))}/> diff --git a/stories/index.js b/stories/index.js index 34d9f5a759..feefa38021 100644 --- a/stories/index.js +++ b/stories/index.js @@ -14,19 +14,41 @@ import projects from './data/projects' import Upload from '../app/components/Upload' import ProjectList from '../app/components/ProjectList' +import Project from '../app/components/Project' +import RemoveProject from '../app/components/RemoveProject' +import RolesSummaryItem from '../app/components/RolesSummaryItem' // storiesOf('Welcome', module) // .add('to Storybook', () => <Welcome showApp={linkTo('Button')} />) +const project = projects[0] + storiesOf('Upload', module) .add('dropzone', () => ( - <Upload onDrop={action('dropped')} ink={{ isFetching: false }}/> + <Upload onDrop={action('drop')} ink={{ isFetching: false }}/> )) .add('converting', () => ( - <Upload onDrop={action('dropped')} ink={{ isFetching: true }}/> + <Upload onDrop={action('drop')} ink={{ isFetching: true }}/> )) storiesOf('ProjectList', module) .add('items', () => ( <ProjectList projects={projects}/> )) + +storiesOf('Project', module) + .add('title', () => ( + <Project project={project}/> + )) + +storiesOf('RemoveProject', module) + .add('button', () => ( + <RemoveProject onClick={action('remove')}/> + )) + +storiesOf('RolesSummaryItem', module) + .add('item', () => ( + <RolesSummaryItem label="Owner" url="#" user={{ + username: 'foo' + }}/> + )) -- GitLab