From 002de342ae544925c5413cd9e3e0239367f1b832 Mon Sep 17 00:00:00 2001 From: Jim O'Donnell Date: Wed, 31 May 2017 15:02:42 +0100 Subject: [PATCH 01/10] Extract ProjectContents from editor --- src/containers/ProjectContentsContainer.jsx | 9 ++++++--- src/index.jsx | 7 +++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/containers/ProjectContentsContainer.jsx b/src/containers/ProjectContentsContainer.jsx index 5db3bf50..aefee203 100644 --- a/src/containers/ProjectContentsContainer.jsx +++ b/src/containers/ProjectContentsContainer.jsx @@ -3,13 +3,16 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import * as contentsActions from '../ducks/contents'; -import ProjectContents from '../components/ProjectContents'; import { BaseModal, ModalBody, ModalFooter } from 'pui-react-modals'; import { DefaultButton } from 'pui-react-buttons'; const propTypes = { actions: PropTypes.object.isRequired, - contents: PropTypes.object.isRequired, + children: PropTypes.node, + contents: PropTypes.object, + params: PropTypes.shape({ + project_id: PropTypes.string + }) }; class ProjectContentsContainer extends Component { @@ -55,7 +58,7 @@ class ProjectContentsContainer extends Component {
- + {React.cloneElement(this.props.children, { contents })}
); diff --git a/src/index.jsx b/src/index.jsx index c7a47806..0ed70578 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -8,7 +8,8 @@ import App from './components/App'; import About from './components/About'; import config from './config'; import configureStore from './store'; -import ProjectContents from './containers/ProjectContentsContainer'; +import ProjectContentsContainer from './containers/ProjectContentsContainer'; +import ProjectContents from './components/ProjectContents'; import ProjectList from './containers/ProjectListContainer'; // Todo: let's find a better way to include Styles, @@ -24,7 +25,9 @@ oauth.init(config.panoptesAppId) - + + + From f17ca331fce7ee36cf617389acd856a376cbc62b Mon Sep 17 00:00:00 2001 From: Jim O'Donnell Date: Wed, 31 May 2017 16:04:40 +0100 Subject: [PATCH 02/10] Link up workflow contents Adds workflow contents, by ID. Lists all workflow strings. --- src/components/ProjectList.jsx | 15 +++++++++- src/components/WorkflowContents.jsx | 33 +++++++++++++++++++++ src/containers/ProjectContentsContainer.jsx | 4 ++- src/ducks/contents.js | 9 ++++-- src/ducks/projects.js | 3 +- src/index.jsx | 2 ++ 6 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 src/components/WorkflowContents.jsx diff --git a/src/components/ProjectList.jsx b/src/components/ProjectList.jsx index e1ff54e5..6394a6c8 100644 --- a/src/components/ProjectList.jsx +++ b/src/components/ProjectList.jsx @@ -11,6 +11,19 @@ function ProjectListItem(props) { return(
  • {props.project.display_name} +

    Workflows

    +
      + {props.project.links.workflows.map(workflow => { + return ( +
    • + + {workflow} + +
    • + ); + })} +
    +
  • ); } @@ -22,7 +35,7 @@ class ProjectList extends Component {

    Projects

      - {projects.data.map(project => )} + {projects.data.map(project => )}
    ); diff --git a/src/components/WorkflowContents.jsx b/src/components/WorkflowContents.jsx new file mode 100644 index 00000000..b89d6695 --- /dev/null +++ b/src/components/WorkflowContents.jsx @@ -0,0 +1,33 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import fixIt, { options } from 'react-fix-it'; + +const propTypes = { + contents: PropTypes.object.isRequired, +}; + +options.log = (test) => { + console.warn(test); +}; + +class WorkflowContents extends Component { + + render() { + const { contents } = this.props; + const workflow_contents = contents.data.length ? contents.data[0] : {strings: []}; + console.log(workflow_contents) + const strings = []; + for (const key in workflow_contents.strings) { + strings.push(

    {key} {workflow_contents.strings[key]}

    ); + } + return ( +
    +

    Workflow Contents

    + {strings} +
    + ); + } +} + +WorkflowContents.propTypes = propTypes; +export default fixIt(WorkflowContents); diff --git a/src/containers/ProjectContentsContainer.jsx b/src/containers/ProjectContentsContainer.jsx index aefee203..82a3691f 100644 --- a/src/containers/ProjectContentsContainer.jsx +++ b/src/containers/ProjectContentsContainer.jsx @@ -27,7 +27,9 @@ class ProjectContentsContainer extends Component { componentDidMount() { const { actions } = this.props; - return actions.fetchProjectContents(this.props.params.project_id); + const type = this.props.routes[2].path; + const id = type ? this.props.params.resource_id : this.props.params.project_id; + return actions.fetchProjectContents(id, type); } handleClick(event) { diff --git a/src/ducks/contents.js b/src/ducks/contents.js index a6696e07..f36c032e 100644 --- a/src/ducks/contents.js +++ b/src/ducks/contents.js @@ -26,13 +26,16 @@ const projectContentsReducer = (state = initialState, action) => { }; // Action Creators -const fetchProjectContents = (project_id) => { +const fetchProjectContents = (id, type) => { + type = type ? type.split('/')[0] : 'project'; return (dispatch) => { dispatch({ type: FETCH_PROJECT_CONTENTS, }); - const query = { project_id }; - apiClient.type('project_contents').get(query) + const key = `${type}_id`; + const query = {}; + query[key] = id; + apiClient.type(`${type}_contents`).get(query) .then((projectContents) => { dispatch({ type: FETCH_PROJECT_CONTENTS_SUCCESS, diff --git a/src/ducks/projects.js b/src/ducks/projects.js index 1c6f6215..249d15c7 100644 --- a/src/ducks/projects.js +++ b/src/ducks/projects.js @@ -32,7 +32,8 @@ const fetchProjects = () => { type: FETCH_PROJECTS, }); const query = { - current_user_roles: ['owner', 'translator'] + current_user_roles: ['owner', 'translator'], + include: ['workflows'] } apiClient.type('projects').get(query) .then((projects) => { diff --git a/src/index.jsx b/src/index.jsx index 0ed70578..f8b00697 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -11,6 +11,7 @@ import configureStore from './store'; import ProjectContentsContainer from './containers/ProjectContentsContainer'; import ProjectContents from './components/ProjectContents'; import ProjectList from './containers/ProjectListContainer'; +import WorkflowContents from './components/WorkflowContents'; // Todo: let's find a better way to include Styles, // currently Styles looks like an unused var to eslint @@ -27,6 +28,7 @@ oauth.init(config.panoptesAppId) + From 365665dbe909fe55b56fe72621a80b41dc99d694 Mon Sep 17 00:00:00 2001 From: Jim O'Donnell Date: Tue, 6 Jun 2017 16:05:23 +0100 Subject: [PATCH 03/10] Add actions for generic Panoptes resources --- src/containers/ProjectContentsContainer.jsx | 10 +-- src/ducks/contents.js | 9 +-- src/ducks/reducer.js | 4 +- src/ducks/resource.js | 69 +++++++++++++++++++++ 4 files changed, 80 insertions(+), 12 deletions(-) create mode 100644 src/ducks/resource.js diff --git a/src/containers/ProjectContentsContainer.jsx b/src/containers/ProjectContentsContainer.jsx index 82a3691f..93e22f78 100644 --- a/src/containers/ProjectContentsContainer.jsx +++ b/src/containers/ProjectContentsContainer.jsx @@ -2,7 +2,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; -import * as contentsActions from '../ducks/contents'; +import * as contentsActions from '../ducks/resource'; import { BaseModal, ModalBody, ModalFooter } from 'pui-react-modals'; import { DefaultButton } from 'pui-react-buttons'; @@ -29,7 +29,7 @@ class ProjectContentsContainer extends Component { const { actions } = this.props; const type = this.props.routes[2].path; const id = type ? this.props.params.resource_id : this.props.params.project_id; - return actions.fetchProjectContents(id, type); + return actions.fetchResource(id, type); } handleClick(event) { @@ -40,7 +40,7 @@ class ProjectContentsContainer extends Component { } render() { - const { contents } = this.props; + const { resource } = this.props; return (
    - {React.cloneElement(this.props.children, { contents })} + {React.cloneElement(this.props.children, { contents: resource })}
    ); @@ -68,7 +68,7 @@ class ProjectContentsContainer extends Component { } const mapStateToProps = (state) => ({ - contents: state.contents, + resource: state.resource, }); const mapDispatchToProps = (dispatch) => ({ actions: bindActionCreators(contentsActions, dispatch), diff --git a/src/ducks/contents.js b/src/ducks/contents.js index f36c032e..a6696e07 100644 --- a/src/ducks/contents.js +++ b/src/ducks/contents.js @@ -26,16 +26,13 @@ const projectContentsReducer = (state = initialState, action) => { }; // Action Creators -const fetchProjectContents = (id, type) => { - type = type ? type.split('/')[0] : 'project'; +const fetchProjectContents = (project_id) => { return (dispatch) => { dispatch({ type: FETCH_PROJECT_CONTENTS, }); - const key = `${type}_id`; - const query = {}; - query[key] = id; - apiClient.type(`${type}_contents`).get(query) + const query = { project_id }; + apiClient.type('project_contents').get(query) .then((projectContents) => { dispatch({ type: FETCH_PROJECT_CONTENTS_SUCCESS, diff --git a/src/ducks/reducer.js b/src/ducks/reducer.js index 47f6349e..1ba5aebb 100644 --- a/src/ducks/reducer.js +++ b/src/ducks/reducer.js @@ -2,9 +2,11 @@ import { combineReducers } from 'redux'; import contents from './contents'; import login from './login'; import projects from './projects'; +import resource from './resource'; export default combineReducers({ contents, login, - projects + projects, + resource }); diff --git a/src/ducks/resource.js b/src/ducks/resource.js new file mode 100644 index 00000000..542989bd --- /dev/null +++ b/src/ducks/resource.js @@ -0,0 +1,69 @@ +import apiClient from 'panoptes-client/lib/api-client'; + +// Action Types +export const FETCH_RESOURCE = 'FETCH_RESOURCE'; +export const FETCH_RESOURCE_SUCCESS = 'FETCH_RESOURCE_SUCCESS'; +export const FETCH_RESOURCE_ERROR = 'FETCH_RESOURCE_ERROR'; + +// Reducer +const initialState = { + data: [], + error: false, + loading: false, +}; + +const resourceReducer = (state = initialState, action) => { + switch (action.type) { + case FETCH_RESOURCE: + return Object.assign({}, initialState, { loading: true }); + case FETCH_RESOURCE_SUCCESS: + return Object.assign({}, state, { data: action.payload, loading: false }); + case FETCH_RESOURCE_ERROR: + return Object.assign({}, state, { error: action.payload, loading: false }); + default: + return state; + } +}; + +// Action Creators +const fetchResource = (id, type) => { + type = type ? type.split('/')[0] : 'project'; + return (dispatch) => { + dispatch({ + type: FETCH_RESOURCE, + }); + const key = `${type}_id`; + const query = {}; + query[key] = id; + apiClient.type(`${type}_contents`).get(query) + .then((resource) => { + dispatch({ + type: FETCH_RESOURCE_SUCCESS, + payload: resource, + }); + }); + }; +}; + +const createNewTranslation = (type) => + (dispatch, getState) => { + const { contents } = getState(); + const translation = apiClient.type(type).create({ + title: contents.title, + description: contents.description, + introduction: contents.introduction, + language: 'nz', + 'links.project': contents.links.project, + }); + translation.save() + .then(res => console.info('Saved! ', res)) + .catch(error => console.error(error)); + }; + +// Exports +export default resourceReducer; + +export { + createNewTranslation, + fetchResource, +}; From ed87a3ebc5e231de7a53afed57dce4190e85debe Mon Sep 17 00:00:00 2001 From: Jim O'Donnell Date: Tue, 13 Jun 2017 12:39:06 +0100 Subject: [PATCH 04/10] Remove workflows from project list --- src/components/ProjectList.jsx | 13 ------------- src/ducks/projects.js | 5 ++--- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/components/ProjectList.jsx b/src/components/ProjectList.jsx index 6394a6c8..3afacbd8 100644 --- a/src/components/ProjectList.jsx +++ b/src/components/ProjectList.jsx @@ -11,19 +11,6 @@ function ProjectListItem(props) { return(
  • {props.project.display_name} -

    Workflows

    -
      - {props.project.links.workflows.map(workflow => { - return ( -
    • - - {workflow} - -
    • - ); - })} -
    -
  • ); } diff --git a/src/ducks/projects.js b/src/ducks/projects.js index 249d15c7..3a23e892 100644 --- a/src/ducks/projects.js +++ b/src/ducks/projects.js @@ -32,9 +32,8 @@ const fetchProjects = () => { type: FETCH_PROJECTS, }); const query = { - current_user_roles: ['owner', 'translator'], - include: ['workflows'] - } + current_user_roles: ['owner', 'translator'] + }; apiClient.type('projects').get(query) .then((projects) => { dispatch({ From 4b15a0908e01c2ab4d2ebd5b1e02c8b0b88f4543 Mon Sep 17 00:00:00 2001 From: Jim O'Donnell Date: Tue, 13 Jun 2017 14:57:18 +0100 Subject: [PATCH 05/10] Add a rough project dashboard --- src/components/ProjectDashboard.jsx | 42 +++++++++++++++ src/containers/ProjectDashboardContainer.jsx | 45 ++++++++++++++++ src/ducks/project.js | 57 ++++++++++++++++++++ src/ducks/reducer.js | 2 + src/index.jsx | 10 ++-- 5 files changed, 153 insertions(+), 3 deletions(-) create mode 100644 src/components/ProjectDashboard.jsx create mode 100644 src/containers/ProjectDashboardContainer.jsx create mode 100644 src/ducks/project.js diff --git a/src/components/ProjectDashboard.jsx b/src/components/ProjectDashboard.jsx new file mode 100644 index 00000000..dd615bc0 --- /dev/null +++ b/src/components/ProjectDashboard.jsx @@ -0,0 +1,42 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import fixIt from 'react-fix-it'; +import { Link } from 'react-router'; +import ProjectContentsContainer from '../containers/ProjectContentsContainer'; +import ProjectContents from './ProjectContents'; + +const propTypes = { + project: PropTypes.object.isRequired, +}; + +function ProjectDashboard(props) { + const { project } = props; + return ( +
    + + + +
      + {project.links.workflows.map((id) => { + return ( +
    • + {id} +
    • + ); + })} +
    +
    + ); +} + +ProjectDashboard.propTypes = propTypes; + +ProjectDashboard.defaultProps = { + project: { + links: { + workflows: [] + } + } +}; + +export default fixIt(ProjectDashboard); diff --git a/src/containers/ProjectDashboardContainer.jsx b/src/containers/ProjectDashboardContainer.jsx new file mode 100644 index 00000000..4b2b4ce0 --- /dev/null +++ b/src/containers/ProjectDashboardContainer.jsx @@ -0,0 +1,45 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import * as projectActions from '../ducks/project'; + +const propTypes = { + actions: PropTypes.object.isRequired, + children: PropTypes.node, + contents: PropTypes.object, + params: PropTypes.shape({ + project_id: PropTypes.string + }) +}; + +class ProjectDashboardContainer extends Component { + + componentDidMount() { + const { actions } = this.props; + actions.fetchProject(this.props.params.project_id); + } + + render() { + const project = this.props.project.data; + return ( +
    +

    Project Dashboard

    + {React.cloneElement(this.props.children, { project })} +
    + ); + } +} + +const mapStateToProps = (state) => ({ + project: state.project +}); +const mapDispatchToProps = (dispatch) => ({ + actions: bindActionCreators(projectActions, dispatch), +}); + +ProjectDashboardContainer.propTypes = propTypes; +export default connect( + mapStateToProps, + mapDispatchToProps, +)(ProjectDashboardContainer); diff --git a/src/ducks/project.js b/src/ducks/project.js new file mode 100644 index 00000000..eb3d9f02 --- /dev/null +++ b/src/ducks/project.js @@ -0,0 +1,57 @@ +import apiClient from 'panoptes-client/lib/api-client'; + +// Action Types +export const FETCH_PROJECT = 'FETCH_PROJECT'; +export const FETCH_PROJECT_SUCCESS = 'FETCH_PROJECT_SUCCESS'; +export const FETCH_PROJECT_ERROR = 'FETCH_PROJECT_ERROR'; + +// Reducer +const initialState = { + data: { + links: { + workflows: [] + } + }, + error: false, + loading: false, +}; + +const projectReducer = (state = initialState, action) => { + switch (action.type) { + case FETCH_PROJECT: + return Object.assign({}, initialState, { loading: true }); + case FETCH_PROJECT_SUCCESS: + return Object.assign({}, state, { data: action.payload, loading: false }); + case FETCH_PROJECT_ERROR: + return Object.assign({}, state, { error: action.payload, loading: false }); + default: + return state; + } +}; + +// Action Creators +const fetchProject = (id) => { + return (dispatch) => { + dispatch({ + type: FETCH_PROJECT, + }); + const query = { + id, + current_user_roles: ['owner', 'translator'] + }; + apiClient.type('projects').get(query) + .then(([project]) => { + dispatch({ + type: FETCH_PROJECT_SUCCESS, + payload: project, + }); + }); + }; +}; + +// Exports +export default projectReducer; + +export { + fetchProject +}; diff --git a/src/ducks/reducer.js b/src/ducks/reducer.js index 1ba5aebb..2ee57a40 100644 --- a/src/ducks/reducer.js +++ b/src/ducks/reducer.js @@ -1,12 +1,14 @@ import { combineReducers } from 'redux'; import contents from './contents'; import login from './login'; +import project from './project'; import projects from './projects'; import resource from './resource'; export default combineReducers({ contents, login, + project, projects, resource }); diff --git a/src/index.jsx b/src/index.jsx index f8b00697..3e7c0101 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -10,6 +10,8 @@ import config from './config'; import configureStore from './store'; import ProjectContentsContainer from './containers/ProjectContentsContainer'; import ProjectContents from './components/ProjectContents'; +import ProjectDashboardContainer from './containers/ProjectDashboardContainer'; +import ProjectDashboard from './components/ProjectDashboard'; import ProjectList from './containers/ProjectListContainer'; import WorkflowContents from './components/WorkflowContents'; @@ -26,9 +28,11 @@ oauth.init(config.panoptesAppId) - - - + + + + + From 7b5768774053341b58d05cd8af6da6371f2f0370 Mon Sep 17 00:00:00 2001 From: Jim O'Donnell Date: Tue, 13 Jun 2017 15:28:18 +0100 Subject: [PATCH 06/10] Load workflows with the project --- src/components/ProjectDashboard.jsx | 11 +++++----- src/ducks/project.js | 34 +++++++++++++++++++++++++---- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/components/ProjectDashboard.jsx b/src/components/ProjectDashboard.jsx index dd615bc0..dfc81ef2 100644 --- a/src/components/ProjectDashboard.jsx +++ b/src/components/ProjectDashboard.jsx @@ -16,11 +16,12 @@ function ProjectDashboard(props) { +

    Workflows

      - {project.links.workflows.map((id) => { + {project.workflows.map((workflow) => { return ( -
    • - {id} +
    • + {workflow.display_name}
    • ); })} @@ -33,9 +34,7 @@ ProjectDashboard.propTypes = propTypes; ProjectDashboard.defaultProps = { project: { - links: { - workflows: [] - } + workflows: [] } }; diff --git a/src/ducks/project.js b/src/ducks/project.js index eb3d9f02..1e1c7fc9 100644 --- a/src/ducks/project.js +++ b/src/ducks/project.js @@ -4,13 +4,14 @@ import apiClient from 'panoptes-client/lib/api-client'; export const FETCH_PROJECT = 'FETCH_PROJECT'; export const FETCH_PROJECT_SUCCESS = 'FETCH_PROJECT_SUCCESS'; export const FETCH_PROJECT_ERROR = 'FETCH_PROJECT_ERROR'; +export const FETCH_WORKFLOWS = 'FETCH_WORKFLOWS'; +export const FETCH_WORKFLOWS_SUCCESS = 'FETCH_WORKFLOWS_SUCCESS'; +export const FETCH_WORKFLOWS_ERROR = 'FETCH_WORKFLOWS_ERROR'; // Reducer const initialState = { data: { - links: { - workflows: [] - } + workflows: [] }, error: false, loading: false, @@ -24,6 +25,13 @@ const projectReducer = (state = initialState, action) => { return Object.assign({}, state, { data: action.payload, loading: false }); case FETCH_PROJECT_ERROR: return Object.assign({}, state, { error: action.payload, loading: false }); + case FETCH_WORKFLOWS: + return Object.assign({}, state, { loading: true }); + case FETCH_WORKFLOWS_SUCCESS: + return Object.assign({}, state, { + data: Object.assign(state.data, { workflows: action.payload }), + loading: false + }); default: return state; } @@ -37,10 +45,13 @@ const fetchProject = (id) => { }); const query = { id, - current_user_roles: ['owner', 'translator'] + current_user_roles: ['owner', 'translator'], + include: ['workflows'] }; apiClient.type('projects').get(query) .then(([project]) => { + project.workflows = []; + dispatch(fetchWorkflows(project)); dispatch({ type: FETCH_PROJECT_SUCCESS, payload: project, @@ -49,6 +60,21 @@ const fetchProject = (id) => { }; }; +function fetchWorkflows(project) { + return (dispatch) => { + dispatch({ + type: FETCH_WORKFLOWS, + }); + apiClient.type('workflows').get(project.links.workflows) + .then((workflows) => { + dispatch({ + type: FETCH_WORKFLOWS_SUCCESS, + payload: workflows, + }); + }); + }; +} + // Exports export default projectReducer; From dfb081dc4288722f790034abec13309d3ffee722 Mon Sep 17 00:00:00 2001 From: Jim O'Donnell Date: Wed, 14 Jun 2017 14:36:40 +0100 Subject: [PATCH 07/10] Add tutorials as generic resources. Generalise resource loading and display. Add a bit of a hack to handle ProjectContents and WorkflowContents. --- src/components/ProjectDashboard.jsx | 25 +++++++++---- src/components/Resource.jsx | 30 ++++++++++++++++ src/components/Tutorial.jsx | 29 +++++++++++++++ src/containers/ProjectContentsContainer.jsx | 2 +- src/ducks/project.js | 27 ++++++++++++++ src/ducks/resource.js | 40 ++++++++++++++++++--- src/index.jsx | 7 ++-- 7 files changed, 144 insertions(+), 16 deletions(-) create mode 100644 src/components/Resource.jsx create mode 100644 src/components/Tutorial.jsx diff --git a/src/components/ProjectDashboard.jsx b/src/components/ProjectDashboard.jsx index dfc81ef2..aa8e4825 100644 --- a/src/components/ProjectDashboard.jsx +++ b/src/components/ProjectDashboard.jsx @@ -18,13 +18,23 @@ function ProjectDashboard(props) {

      Workflows

        - {project.workflows.map((workflow) => { - return ( -
      • - {workflow.display_name} -
      • - ); - })} + {project.workflows.map((workflow) => { + return ( +
      • + {workflow.display_name} +
      • + ); + })} +
      +

      Tutorials

      +
        + {project.tutorials.map((tutorial) => { + return ( +
      • + {tutorial.id}: {tutorial.display_name} +
      • + ); + })}
      ); @@ -34,6 +44,7 @@ ProjectDashboard.propTypes = propTypes; ProjectDashboard.defaultProps = { project: { + tutorials: [], workflows: [] } }; diff --git a/src/components/Resource.jsx b/src/components/Resource.jsx new file mode 100644 index 00000000..a1551932 --- /dev/null +++ b/src/components/Resource.jsx @@ -0,0 +1,30 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import fixIt, { options } from 'react-fix-it'; +import Tutorial from './Tutorial'; +import WorkflowContents from './WorkflowContents'; + +const resources = { + tutorials: Tutorial, + workflows: WorkflowContents +}; + +const propTypes = { + contents: PropTypes.object.isRequired, + params: PropTypes.shape({ + resource_type: PropTypes.string + }) +}; + +options.log = (test) => { + console.warn(test); +}; + +function Resource(props) { + const { contents } = props; + const ResourceViewer = resources[props.params.resource_type]; + return (); +} + +Resource.propTypes = propTypes; +export default fixIt(Resource); diff --git a/src/components/Tutorial.jsx b/src/components/Tutorial.jsx new file mode 100644 index 00000000..e56f4a32 --- /dev/null +++ b/src/components/Tutorial.jsx @@ -0,0 +1,29 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import fixIt, { options } from 'react-fix-it'; + +const propTypes = { + contents: PropTypes.object.isRequired, +}; + +options.log = (test) => { + console.warn(test); +}; + +function Tutorial(props) { + const { contents } = props; + const tutorial = contents.data.length ? contents.data[0] : { steps: [] }; + const steps = []; + tutorial.steps && tutorial.steps.map((step, key) => { + steps.push(

      {key} {step.content}

      ); + }); + return ( +
      +

      Tutorial

      + {steps} +
      + ); +} + +Tutorial.propTypes = propTypes; +export default fixIt(Tutorial); diff --git a/src/containers/ProjectContentsContainer.jsx b/src/containers/ProjectContentsContainer.jsx index 93e22f78..256915f9 100644 --- a/src/containers/ProjectContentsContainer.jsx +++ b/src/containers/ProjectContentsContainer.jsx @@ -27,7 +27,7 @@ class ProjectContentsContainer extends Component { componentDidMount() { const { actions } = this.props; - const type = this.props.routes[2].path; + const type = this.props.params.resource_type; const id = type ? this.props.params.resource_id : this.props.params.project_id; return actions.fetchResource(id, type); } diff --git a/src/ducks/project.js b/src/ducks/project.js index 1e1c7fc9..0e44faf8 100644 --- a/src/ducks/project.js +++ b/src/ducks/project.js @@ -7,10 +7,14 @@ export const FETCH_PROJECT_ERROR = 'FETCH_PROJECT_ERROR'; export const FETCH_WORKFLOWS = 'FETCH_WORKFLOWS'; export const FETCH_WORKFLOWS_SUCCESS = 'FETCH_WORKFLOWS_SUCCESS'; export const FETCH_WORKFLOWS_ERROR = 'FETCH_WORKFLOWS_ERROR'; +export const FETCH_TUTORIALS = 'FETCH_TUTORIALS'; +export const FETCH_TUTORIALS_SUCCESS = 'FETCH_TUTORIALS_SUCCESS'; +export const FETCH_TUTORIALS_ERROR = 'FETCH_TUTORIALS_ERROR'; // Reducer const initialState = { data: { + tutorials: [], workflows: [] }, error: false, @@ -32,6 +36,13 @@ const projectReducer = (state = initialState, action) => { data: Object.assign(state.data, { workflows: action.payload }), loading: false }); + case FETCH_TUTORIALS: + return Object.assign({}, state, { loading: true }); + case FETCH_TUTORIALS_SUCCESS: + return Object.assign({}, state, { + data: Object.assign(state.data, { tutorials: action.payload }), + loading: false + }); default: return state; } @@ -52,6 +63,7 @@ const fetchProject = (id) => { .then(([project]) => { project.workflows = []; dispatch(fetchWorkflows(project)); + dispatch(fetchTutorials(project)) dispatch({ type: FETCH_PROJECT_SUCCESS, payload: project, @@ -75,6 +87,21 @@ function fetchWorkflows(project) { }; } +function fetchTutorials(project) { + return (dispatch) => { + dispatch({ + type: FETCH_TUTORIALS, + }); + apiClient.type('tutorials').get({project_id: project.id}) + .then((tutorials) => { + dispatch({ + type: FETCH_TUTORIALS_SUCCESS, + payload: tutorials, + }); + }); + }; +} + // Exports export default projectReducer; diff --git a/src/ducks/resource.js b/src/ducks/resource.js index 542989bd..b826e2a6 100644 --- a/src/ducks/resource.js +++ b/src/ducks/resource.js @@ -27,15 +27,47 @@ const resourceReducer = (state = initialState, action) => { // Action Creators const fetchResource = (id, type) => { - type = type ? type.split('/')[0] : 'project'; + type = type ? type : 'projects'; + return (dispatch) => { + switch (type) { + case 'projects': + case 'workflows': + dispatch(fetchResourceContents(id, type)); + break; + default: + dispatch({ + type: FETCH_RESOURCE, + }); + apiClient.type(type).get({ id }) + .then((resource) => { + dispatch({ + type: FETCH_RESOURCE_SUCCESS, + payload: resource, + }); + }); + } + }; +}; + +function fetchResourceContents(id, type) { return (dispatch) => { dispatch({ type: FETCH_RESOURCE, }); - const key = `${type}_id`; + let key = ''; + switch (type) { + case 'projects': + key = 'project_id'; + type = 'project_contents'; + break; + case 'workflows': + key = 'workflow_id'; + type = 'workflow_contents'; + break; + } const query = {}; query[key] = id; - apiClient.type(`${type}_contents`).get(query) + apiClient.type(type).get(query) .then((resource) => { dispatch({ type: FETCH_RESOURCE_SUCCESS, @@ -43,7 +75,7 @@ const fetchResource = (id, type) => { }); }); }; -}; +} const createNewTranslation = (type) => (dispatch, getState) => { diff --git a/src/index.jsx b/src/index.jsx index 3e7c0101..2f108778 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -9,11 +9,10 @@ import About from './components/About'; import config from './config'; import configureStore from './store'; import ProjectContentsContainer from './containers/ProjectContentsContainer'; -import ProjectContents from './components/ProjectContents'; import ProjectDashboardContainer from './containers/ProjectDashboardContainer'; import ProjectDashboard from './components/ProjectDashboard'; import ProjectList from './containers/ProjectListContainer'; -import WorkflowContents from './components/WorkflowContents'; +import Resource from './components/Resource'; // Todo: let's find a better way to include Styles, // currently Styles looks like an unused var to eslint @@ -30,8 +29,8 @@ oauth.init(config.panoptesAppId) - - + + From 2303339ada4cdf9b44725a5a959a8eef6c36b583 Mon Sep 17 00:00:00 2001 From: Jim O'Donnell Date: Mon, 19 Jun 2017 17:33:09 +0100 Subject: [PATCH 08/10] Add an action to create new translations --- src/components/ProjectContents.jsx | 2 +- src/components/Tutorial.jsx | 2 +- src/components/WorkflowContents.jsx | 2 +- src/ducks/resource.js | 34 +++++++++++++++++------------ 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/components/ProjectContents.jsx b/src/components/ProjectContents.jsx index a93179da..ead8d0b6 100644 --- a/src/components/ProjectContents.jsx +++ b/src/components/ProjectContents.jsx @@ -14,7 +14,7 @@ class ProjectContents extends Component { render() { const { contents } = this.props; - const project_contents = contents.data.length ? contents.data[0] : {}; + const project_contents = contents.original.length ? contents.original[0] : {}; return (

      Project Contents

      diff --git a/src/components/Tutorial.jsx b/src/components/Tutorial.jsx index e56f4a32..7d835680 100644 --- a/src/components/Tutorial.jsx +++ b/src/components/Tutorial.jsx @@ -12,7 +12,7 @@ options.log = (test) => { function Tutorial(props) { const { contents } = props; - const tutorial = contents.data.length ? contents.data[0] : { steps: [] }; + const tutorial = contents.original.length ? contents.original[0] : { steps: [] }; const steps = []; tutorial.steps && tutorial.steps.map((step, key) => { steps.push(

      {key} {step.content}

      ); diff --git a/src/components/WorkflowContents.jsx b/src/components/WorkflowContents.jsx index b89d6695..9027a0ce 100644 --- a/src/components/WorkflowContents.jsx +++ b/src/components/WorkflowContents.jsx @@ -14,7 +14,7 @@ class WorkflowContents extends Component { render() { const { contents } = this.props; - const workflow_contents = contents.data.length ? contents.data[0] : {strings: []}; + const workflow_contents = contents.original.length ? contents.original[0] : {strings: []}; console.log(workflow_contents) const strings = []; for (const key in workflow_contents.strings) { diff --git a/src/ducks/resource.js b/src/ducks/resource.js index b826e2a6..78e5c444 100644 --- a/src/ducks/resource.js +++ b/src/ducks/resource.js @@ -4,10 +4,12 @@ import apiClient from 'panoptes-client/lib/api-client'; export const FETCH_RESOURCE = 'FETCH_RESOURCE'; export const FETCH_RESOURCE_SUCCESS = 'FETCH_RESOURCE_SUCCESS'; export const FETCH_RESOURCE_ERROR = 'FETCH_RESOURCE_ERROR'; +export const CREATE_TRANSLATION_SUCCESS = 'CREATE_TRANSLATION_SUCCESS'; // Reducer const initialState = { - data: [], + original: [], + translation: null, error: false, loading: false, }; @@ -17,9 +19,11 @@ const resourceReducer = (state = initialState, action) => { case FETCH_RESOURCE: return Object.assign({}, initialState, { loading: true }); case FETCH_RESOURCE_SUCCESS: - return Object.assign({}, state, { data: action.payload, loading: false }); + return Object.assign({}, state, { original: action.payload, loading: false }); case FETCH_RESOURCE_ERROR: return Object.assign({}, state, { error: action.payload, loading: false }); + case CREATE_TRANSLATION_SUCCESS: + return Object.assign({}, state, { translation: action.payload, loading: false }); default: return state; } @@ -77,18 +81,20 @@ function fetchResourceContents(id, type) { }; } -const createNewTranslation = (type) => +const createTranslation = (type, lang) => (dispatch, getState) => { - const { contents } = getState(); - const translation = apiClient.type(type).create({ - title: contents.title, - description: contents.description, - introduction: contents.introduction, - language: 'nz', - 'links.project': contents.links.project, - }); - translation.save() - .then(res => console.info('Saved! ', res)) + const { original } = getState(); + const newResource = Object.assign({}, original); + newResource.lang = lang; + apiClient.type(type) + .create(newResource) + .save() + .then((translation) => { + dispatch({ + type: CREATE_TRANSLATION_SUCCESS, + payload: translation, + }); + }) .catch(error => console.error(error)); }; @@ -96,6 +102,6 @@ const createNewTranslation = (type) => export default resourceReducer; export { - createNewTranslation, + createTranslation, fetchResource, }; From d04baab4e89e028f9fd94de2b97ccbd8099337d8 Mon Sep 17 00:00:00 2001 From: Jim O'Donnell Date: Wed, 21 Jun 2017 14:16:05 +0100 Subject: [PATCH 09/10] Set appropriate defaults for workflows and tutorials Default workflows and tutorials to empty arrays. Store them in project state, not the project resource. --- src/components/ProjectDashboard.jsx | 6 ++++-- src/containers/ProjectDashboardContainer.jsx | 17 +++++++++++++-- src/ducks/project.js | 22 +++++++------------- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/components/ProjectDashboard.jsx b/src/components/ProjectDashboard.jsx index aa8e4825..38c2ad92 100644 --- a/src/components/ProjectDashboard.jsx +++ b/src/components/ProjectDashboard.jsx @@ -7,6 +7,8 @@ import ProjectContents from './ProjectContents'; const propTypes = { project: PropTypes.object.isRequired, + tutorials: PropTypes.array.isRequired, + workflows: PropTypes.array.isRequired, }; function ProjectDashboard(props) { @@ -18,7 +20,7 @@ function ProjectDashboard(props) {

      Workflows

        - {project.workflows.map((workflow) => { + {props.workflows.map((workflow) => { return (
      • {workflow.display_name} @@ -28,7 +30,7 @@ function ProjectDashboard(props) {

      Tutorials

        - {project.tutorials.map((tutorial) => { + {props.tutorials.map((tutorial) => { return (
      • {tutorial.id}: {tutorial.display_name} diff --git a/src/containers/ProjectDashboardContainer.jsx b/src/containers/ProjectDashboardContainer.jsx index 4b2b4ce0..cb2b9879 100644 --- a/src/containers/ProjectDashboardContainer.jsx +++ b/src/containers/ProjectDashboardContainer.jsx @@ -7,7 +7,11 @@ import * as projectActions from '../ducks/project'; const propTypes = { actions: PropTypes.object.isRequired, children: PropTypes.node, - contents: PropTypes.object, + project: PropTypes.shape({ + data: PropTypes.object, + tutorials: PropTypes.array, + workflows: PropTypes.array + }), params: PropTypes.shape({ project_id: PropTypes.string }) @@ -22,10 +26,11 @@ class ProjectDashboardContainer extends Component { render() { const project = this.props.project.data; + const { workflows, tutorials } = this.props.project; return (

        Project Dashboard

        - {React.cloneElement(this.props.children, { project })} + {React.cloneElement(this.props.children, { project, workflows, tutorials })}
        ); } @@ -39,6 +44,14 @@ const mapDispatchToProps = (dispatch) => ({ }); ProjectDashboardContainer.propTypes = propTypes; +ProjectDashboardContainer.defaultProps = { + children: null, + project: { + data: null, + tutorials: [], + workflows: [] + } +}; export default connect( mapStateToProps, mapDispatchToProps, diff --git a/src/ducks/project.js b/src/ducks/project.js index 0e44faf8..92a0f15a 100644 --- a/src/ducks/project.js +++ b/src/ducks/project.js @@ -13,10 +13,9 @@ export const FETCH_TUTORIALS_ERROR = 'FETCH_TUTORIALS_ERROR'; // Reducer const initialState = { - data: { - tutorials: [], - workflows: [] - }, + data: {}, + tutorials: [], + workflows: [], error: false, loading: false, }; @@ -32,17 +31,11 @@ const projectReducer = (state = initialState, action) => { case FETCH_WORKFLOWS: return Object.assign({}, state, { loading: true }); case FETCH_WORKFLOWS_SUCCESS: - return Object.assign({}, state, { - data: Object.assign(state.data, { workflows: action.payload }), - loading: false - }); + return Object.assign({}, state, { workflows: action.payload, loading: false }); case FETCH_TUTORIALS: return Object.assign({}, state, { loading: true }); case FETCH_TUTORIALS_SUCCESS: - return Object.assign({}, state, { - data: Object.assign(state.data, { tutorials: action.payload }), - loading: false - }); + return Object.assign({}, state, { tutorials: action.payload, loading: false }); default: return state; } @@ -61,9 +54,8 @@ const fetchProject = (id) => { }; apiClient.type('projects').get(query) .then(([project]) => { - project.workflows = []; dispatch(fetchWorkflows(project)); - dispatch(fetchTutorials(project)) + dispatch(fetchTutorials(project)); dispatch({ type: FETCH_PROJECT_SUCCESS, payload: project, @@ -92,7 +84,7 @@ function fetchTutorials(project) { dispatch({ type: FETCH_TUTORIALS, }); - apiClient.type('tutorials').get({project_id: project.id}) + apiClient.type('tutorials').get({ project_id: project.id }) .then((tutorials) => { dispatch({ type: FETCH_TUTORIALS_SUCCESS, From 7ef41fb41ee32ddf7a87b861e382e51944828e5f Mon Sep 17 00:00:00 2001 From: Jim O'Donnell Date: Thu, 22 Jun 2017 10:23:36 +0100 Subject: [PATCH 10/10] Fix some linter warnings Add type validation for URL params. Don't return a value from ComponentDidMount. --- src/containers/ProjectContentsContainer.jsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/containers/ProjectContentsContainer.jsx b/src/containers/ProjectContentsContainer.jsx index 256915f9..0b5e18eb 100644 --- a/src/containers/ProjectContentsContainer.jsx +++ b/src/containers/ProjectContentsContainer.jsx @@ -9,9 +9,10 @@ import { DefaultButton } from 'pui-react-buttons'; const propTypes = { actions: PropTypes.object.isRequired, children: PropTypes.node, - contents: PropTypes.object, params: PropTypes.shape({ - project_id: PropTypes.string + project_id: PropTypes.string, + resource_id: PropTypes.string, + resource_type: PropTypes.string }) }; @@ -29,7 +30,7 @@ class ProjectContentsContainer extends Component { const { actions } = this.props; const type = this.props.params.resource_type; const id = type ? this.props.params.resource_id : this.props.params.project_id; - return actions.fetchResource(id, type); + actions.fetchResource(id, type); } handleClick(event) {