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/ProjectDashboard.jsx b/src/components/ProjectDashboard.jsx
new file mode 100644
index 00000000..38c2ad92
--- /dev/null
+++ b/src/components/ProjectDashboard.jsx
@@ -0,0 +1,54 @@
+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,
+ tutorials: PropTypes.array.isRequired,
+ workflows: PropTypes.array.isRequired,
+};
+
+function ProjectDashboard(props) {
+ const { project } = props;
+ return (
+
+
+
+
+
Workflows
+
+ {props.workflows.map((workflow) => {
+ return (
+ -
+ {workflow.display_name}
+
+ );
+ })}
+
+
Tutorials
+
+ {props.tutorials.map((tutorial) => {
+ return (
+ -
+ {tutorial.id}: {tutorial.display_name}
+
+ );
+ })}
+
+
+ );
+}
+
+ProjectDashboard.propTypes = propTypes;
+
+ProjectDashboard.defaultProps = {
+ project: {
+ tutorials: [],
+ workflows: []
+ }
+};
+
+export default fixIt(ProjectDashboard);
diff --git a/src/components/ProjectList.jsx b/src/components/ProjectList.jsx
index e1ff54e5..3afacbd8 100644
--- a/src/components/ProjectList.jsx
+++ b/src/components/ProjectList.jsx
@@ -22,7 +22,7 @@ class ProjectList extends Component {
Projects
- {projects.data.map(project => )}
+ {projects.data.map(project => )}
);
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..7d835680
--- /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.original.length ? contents.original[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/components/WorkflowContents.jsx b/src/components/WorkflowContents.jsx
new file mode 100644
index 00000000..9027a0ce
--- /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.original.length ? contents.original[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 5db3bf50..0b5e18eb 100644
--- a/src/containers/ProjectContentsContainer.jsx
+++ b/src/containers/ProjectContentsContainer.jsx
@@ -2,14 +2,18 @@ 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 ProjectContents from '../components/ProjectContents';
+import * as contentsActions from '../ducks/resource';
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,
+ params: PropTypes.shape({
+ project_id: PropTypes.string,
+ resource_id: PropTypes.string,
+ resource_type: PropTypes.string
+ })
};
class ProjectContentsContainer extends Component {
@@ -24,7 +28,9 @@ class ProjectContentsContainer extends Component {
componentDidMount() {
const { actions } = this.props;
- return actions.fetchProjectContents(this.props.params.project_id);
+ const type = this.props.params.resource_type;
+ const id = type ? this.props.params.resource_id : this.props.params.project_id;
+ actions.fetchResource(id, type);
}
handleClick(event) {
@@ -35,7 +41,7 @@ class ProjectContentsContainer extends Component {
}
render() {
- const { contents } = this.props;
+ const { resource } = this.props;
return (
-
+ {React.cloneElement(this.props.children, { contents: resource })}
);
@@ -63,7 +69,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/containers/ProjectDashboardContainer.jsx b/src/containers/ProjectDashboardContainer.jsx
new file mode 100644
index 00000000..cb2b9879
--- /dev/null
+++ b/src/containers/ProjectDashboardContainer.jsx
@@ -0,0 +1,58 @@
+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,
+ project: PropTypes.shape({
+ data: PropTypes.object,
+ tutorials: PropTypes.array,
+ workflows: PropTypes.array
+ }),
+ 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;
+ const { workflows, tutorials } = this.props.project;
+ return (
+
+
Project Dashboard
+ {React.cloneElement(this.props.children, { project, workflows, tutorials })}
+
+ );
+ }
+}
+
+const mapStateToProps = (state) => ({
+ project: state.project
+});
+const mapDispatchToProps = (dispatch) => ({
+ actions: bindActionCreators(projectActions, dispatch),
+});
+
+ProjectDashboardContainer.propTypes = propTypes;
+ProjectDashboardContainer.defaultProps = {
+ children: null,
+ project: {
+ data: null,
+ tutorials: [],
+ workflows: []
+ }
+};
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps,
+)(ProjectDashboardContainer);
diff --git a/src/ducks/project.js b/src/ducks/project.js
new file mode 100644
index 00000000..92a0f15a
--- /dev/null
+++ b/src/ducks/project.js
@@ -0,0 +1,102 @@
+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';
+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,
+ 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 });
+ case FETCH_WORKFLOWS:
+ return Object.assign({}, state, { loading: true });
+ case FETCH_WORKFLOWS_SUCCESS:
+ 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, { tutorials: 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'],
+ include: ['workflows']
+ };
+ apiClient.type('projects').get(query)
+ .then(([project]) => {
+ dispatch(fetchWorkflows(project));
+ dispatch(fetchTutorials(project));
+ dispatch({
+ type: FETCH_PROJECT_SUCCESS,
+ payload: project,
+ });
+ });
+ };
+};
+
+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,
+ });
+ });
+ };
+}
+
+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;
+
+export {
+ fetchProject
+};
diff --git a/src/ducks/projects.js b/src/ducks/projects.js
index 1c6f6215..3a23e892 100644
--- a/src/ducks/projects.js
+++ b/src/ducks/projects.js
@@ -33,7 +33,7 @@ const fetchProjects = () => {
});
const query = {
current_user_roles: ['owner', 'translator']
- }
+ };
apiClient.type('projects').get(query)
.then((projects) => {
dispatch({
diff --git a/src/ducks/reducer.js b/src/ducks/reducer.js
index 47f6349e..2ee57a40 100644
--- a/src/ducks/reducer.js
+++ b/src/ducks/reducer.js
@@ -1,10 +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,
- projects
+ project,
+ projects,
+ resource
});
diff --git a/src/ducks/resource.js b/src/ducks/resource.js
new file mode 100644
index 00000000..78e5c444
--- /dev/null
+++ b/src/ducks/resource.js
@@ -0,0 +1,107 @@
+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';
+export const CREATE_TRANSLATION_SUCCESS = 'CREATE_TRANSLATION_SUCCESS';
+
+// Reducer
+const initialState = {
+ original: [],
+ translation: null,
+ 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, { 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;
+ }
+};
+
+// Action Creators
+const fetchResource = (id, type) => {
+ 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,
+ });
+ 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).get(query)
+ .then((resource) => {
+ dispatch({
+ type: FETCH_RESOURCE_SUCCESS,
+ payload: resource,
+ });
+ });
+ };
+}
+
+const createTranslation = (type, lang) =>
+ (dispatch, getState) => {
+ 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));
+ };
+
+// Exports
+export default resourceReducer;
+
+export {
+ createTranslation,
+ fetchResource,
+};
diff --git a/src/index.jsx b/src/index.jsx
index c7a47806..2f108778 100644
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -8,8 +8,11 @@ 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 ProjectDashboardContainer from './containers/ProjectDashboardContainer';
+import ProjectDashboard from './components/ProjectDashboard';
import ProjectList from './containers/ProjectListContainer';
+import Resource from './components/Resource';
// Todo: let's find a better way to include Styles,
// currently Styles looks like an unused var to eslint
@@ -24,7 +27,12 @@ oauth.init(config.panoptesAppId)
-
+
+
+
+
+
+