diff options
43 files changed, 396 insertions, 328 deletions
diff --git a/src/actions/object-stores.js b/src/actions/objects.js index 08f3f0bd..08f3f0bd 100644 --- a/src/actions/object-stores.js +++ b/src/actions/objects.js diff --git a/src/actions/projects.js b/src/actions/projects.js deleted file mode 100644 index efbd15e9..00000000 --- a/src/actions/projects.js +++ /dev/null @@ -1,46 +0,0 @@ -export const SET_AUTH_VISIBILITY_FILTER = "SET_AUTH_VISIBILITY_FILTER"; -export const OPEN_NEW_PROJECT_MODAL = "OPEN_NEW_PROJECT_MODAL"; -export const CLOSE_NEW_PROJECT_MODAL = "CLOSE_PROJECT_POPUP"; -export const ADD_PROJECT = "ADD_PROJECT"; -export const DELETE_PROJECT = "DELETE_PROJECT"; -export const OPEN_PROJECT = "OPEN_PROJECT"; - -export function setAuthVisibilityFilter(filter) { - return { - type: SET_AUTH_VISIBILITY_FILTER, - filter: filter - }; -} - -export function openNewProjectModal() { - return { - type: OPEN_NEW_PROJECT_MODAL - }; -} - -export function closeNewProjectModal() { - return { - type: CLOSE_NEW_PROJECT_MODAL - }; -} - -export function addProject(name) { - return { - type: ADD_PROJECT, - name - }; -} - -export function deleteProject(id) { - return { - type: DELETE_PROJECT, - id - }; -} - -export function openProject(id) { - return { - type: OPEN_PROJECT, - id - }; -} diff --git a/src/actions/simulations.js b/src/actions/simulations.js new file mode 100644 index 00000000..314a2492 --- /dev/null +++ b/src/actions/simulations.js @@ -0,0 +1,58 @@ +export const SET_AUTH_VISIBILITY_FILTER = "SET_AUTH_VISIBILITY_FILTER"; +export const OPEN_NEW_SIMULATION_MODAL = "OPEN_NEW_SIMULATION_MODAL"; +export const CLOSE_NEW_SIMULATION_MODAL = "CLOSE_SIMULATION_POPUP"; +export const ADD_SIMULATION = "ADD_SIMULATION"; +export const ADD_SIMULATION_SUCCEEDED = "ADD_SIMULATION_SUCCEEDED"; +export const DELETE_SIMULATION = "DELETE_SIMULATION"; +export const OPEN_SIMULATION = "OPEN_SIMULATION"; + +export function setAuthVisibilityFilter(filter) { + return { + type: SET_AUTH_VISIBILITY_FILTER, + filter: filter + }; +} + +export function openNewSimulationModal() { + return { + type: OPEN_NEW_SIMULATION_MODAL + }; +} + +export function closeNewSimulationModal() { + return { + type: CLOSE_NEW_SIMULATION_MODAL + }; +} + +export function addSimulation(name) { + return (dispatch, getState) => { + const {auth} = getState(); + dispatch({ + type: ADD_SIMULATION, + name, + userId: auth.userId + }); + }; +} + +export function addSimulationSucceeded(authorization) { + return { + type: ADD_SIMULATION_SUCCEEDED, + authorization + }; +} + +export function deleteSimulation(id) { + return { + type: DELETE_SIMULATION, + id + }; +} + +export function openSimulation(id) { + return { + type: OPEN_SIMULATION, + id + }; +} diff --git a/src/api/sagas/index.js b/src/api/sagas/index.js index ea92533a..f315f377 100644 --- a/src/api/sagas/index.js +++ b/src/api/sagas/index.js @@ -1,9 +1,12 @@ import {takeEvery} from "redux-saga/effects"; import {LOG_IN} from "../../actions/auth"; +import {ADD_SIMULATION} from "../../actions/simulations"; import {FETCH_AUTHORIZATIONS_OF_CURRENT_USER} from "../../actions/users"; -import {fetchAuthorizationsOfCurrentUser, fetchLoggedInUser} from "./users"; +import {onSimulationAdd} from "./simulations"; +import {onFetchAuthorizationsOfCurrentUser, onFetchLoggedInUser} from "./users"; export default function* rootSaga() { - yield takeEvery(LOG_IN, fetchLoggedInUser); - yield takeEvery(FETCH_AUTHORIZATIONS_OF_CURRENT_USER, fetchAuthorizationsOfCurrentUser); + yield takeEvery(LOG_IN, onFetchLoggedInUser); + yield takeEvery(FETCH_AUTHORIZATIONS_OF_CURRENT_USER, onFetchAuthorizationsOfCurrentUser); + yield takeEvery(ADD_SIMULATION, onSimulationAdd); } diff --git a/src/api/sagas/objects.js b/src/api/sagas/objects.js new file mode 100644 index 00000000..9bc98e6f --- /dev/null +++ b/src/api/sagas/objects.js @@ -0,0 +1,24 @@ +import {call, put, select} from "redux-saga/effects"; +import {addToSimulationStore, addToUserStore} from "../../actions/objects"; +import {getSimulation} from "../routes/simulations"; +import {getUser} from "../routes/users"; + +const selectors = { + simulation: state => state.objects.simulations, + user: state => state.objects.users, + authorization: state => state.objects.authorizations, +}; + +function* fetchAndStoreObject(objectType, id, apiCall, addToStore) { + const objectStore = yield select(selectors[objectType]); + if (!objectStore[id]) { + const object = yield apiCall; + yield put(addToStore(object)); + } +} + +export const fetchAndStoreSimulation = (id) => + fetchAndStoreObject("simulation", id, call(getSimulation, id), addToSimulationStore); + +export const fetchAndStoreUser = (id) => + fetchAndStoreObject("user", id, call(getUser, id), addToUserStore); diff --git a/src/api/sagas/simulations.js b/src/api/sagas/simulations.js new file mode 100644 index 00000000..b824d8d5 --- /dev/null +++ b/src/api/sagas/simulations.js @@ -0,0 +1,21 @@ +import {call, put} from "redux-saga/effects"; +import {addToAuthorizationStore, addToSimulationStore} from "../../actions/objects"; +import {addSimulationSucceeded} from "../../actions/simulations"; +import {addSimulation} from "../routes/simulations"; + +export function* onSimulationAdd(action) { + try { + const simulation = yield call(addSimulation, {name: action.name}); + yield put(addToSimulationStore(simulation)); + + const authorization = { + simulationId: simulation.id, + userId: action.userId, + authorizationLevel: "OWN" + }; + yield put(addToAuthorizationStore(authorization)); + yield put(addSimulationSucceeded([authorization.userId, authorization.simulationId])); + } catch (error) { + console.log(error); + } +} diff --git a/src/api/sagas/users.js b/src/api/sagas/users.js index b999b693..d3ef32a6 100644 --- a/src/api/sagas/users.js +++ b/src/api/sagas/users.js @@ -1,12 +1,12 @@ import {call, put} from "redux-saga/effects"; import {logInSucceeded} from "../../actions/auth"; -import {addToAuthorizationStore, addToSimulationStore, addToUserStore} from "../../actions/object-stores"; +import {addToAuthorizationStore} from "../../actions/objects"; import {fetchAuthorizationsOfCurrentUserSucceeded} from "../../actions/users"; import {performTokenSignIn} from "../routes/auth"; -import {getSimulation} from "../routes/simulations"; -import {addUser, getAuthorizationsByUser, getUser} from "../routes/users"; +import {addUser, getAuthorizationsByUser} from "../routes/users"; +import {fetchAndStoreSimulation, fetchAndStoreUser} from "./objects"; -export function* fetchLoggedInUser(action) { +export function* onFetchLoggedInUser(action) { try { const tokenResponse = yield call(performTokenSignIn, action.payload.authToken); let userId = tokenResponse.userId; @@ -22,18 +22,15 @@ export function* fetchLoggedInUser(action) { } } -export function* fetchAuthorizationsOfCurrentUser(action) { +export function* onFetchAuthorizationsOfCurrentUser(action) { try { const authorizations = yield call(getAuthorizationsByUser, action.userId); for (const authorization of authorizations) { yield put(addToAuthorizationStore(authorization)); - const simulation = yield call(getSimulation, authorization.simulationId); - yield put(addToSimulationStore(simulation)); - - const user = yield call(getUser, authorization.userId); - yield put(addToUserStore(user)); + yield fetchAndStoreSimulation(authorization.simulationId); + yield fetchAndStoreUser(authorization.userId); } const authorizationIds = authorizations.map(authorization => ( diff --git a/src/auth/index.js b/src/auth/index.js index 1bfc10c1..8950c529 100644 --- a/src/auth/index.js +++ b/src/auth/index.js @@ -40,7 +40,7 @@ export const authRedirectMiddleware = store => next => action => { switch (action.type) { case LOG_IN_SUCCEEDED: saveAuthLocalStorage(action.payload); - window.location.href = "/projects"; + window.location.href = "/simulations"; break; case LOG_OUT: clearAuthLocalStorage(); diff --git a/src/components/modals/Modal.js b/src/components/modals/Modal.js index c4f10b29..0b3301af 100644 --- a/src/components/modals/Modal.js +++ b/src/components/modals/Modal.js @@ -10,8 +10,10 @@ class Modal extends React.Component { }; static idCounter = 0; - // Local, up-to-date copy of modal visibility for time between close and props update (to prevent duplicate close - // triggers) + /** + * Local, up-to-date copy of modal visibility for time between close event and a props update (to prevent duplicate + * close triggers). + */ visible = false; constructor() { @@ -23,6 +25,11 @@ class Modal extends React.Component { this.visible = this.props.show; this.openOrCloseModal(); + // Trigger auto-focus + window["$"]("#" + this.id).on("shown.bs.modal", function () { + window["$"](this).find("input").first().focus(); + }); + window["$"]("#" + this.id).on("hide.bs.modal", () => { if (this.visible) { this.props.onCancel(); diff --git a/src/components/modals/TextInputModal.js b/src/components/modals/TextInputModal.js index 4acf25b3..90a5db12 100644 --- a/src/components/modals/TextInputModal.js +++ b/src/components/modals/TextInputModal.js @@ -27,10 +27,14 @@ class TextInputModal extends React.Component { show={this.props.show} onSubmit={this.onSubmit.bind(this)} onCancel={this.onCancel.bind(this)}> - <form> + <form onSubmit={e => { + e.preventDefault(); + this.onSubmit(); + }}> <div className="form-group"> <label className="form-control-label">{this.props.label}:</label> - <input type="text" className="form-control" ref="textInput" value={this.props.initialValue}/> + <input type="text" className="form-control" ref="textInput" value={this.props.initialValue} + autoFocus/> </div> </form> </Modal> diff --git a/src/components/navigation/Navbar.js b/src/components/navigation/Navbar.js index 96dd93b4..ce020fef 100644 --- a/src/components/navigation/Navbar.js +++ b/src/components/navigation/Navbar.js @@ -17,7 +17,7 @@ class Navbar extends Component { </div> </Link> <div className="navigation navbar-button-group"> - <Link className="projects" title="Projects" to="/projects">Projects</Link> + <Link className="simulations" title="Simulations" to="/simulations">Simulations</Link> </div> <div className="user-controls navbar-button-group"> <Mailto className="support" title="Support" email="opendc.tudelft@gmail.com" diff --git a/src/components/navigation/Navbar.sass b/src/components/navigation/Navbar.sass index 8be622db..e9a83301 100644 --- a/src/components/navigation/Navbar.sass +++ b/src/components/navigation/Navbar.sass @@ -29,6 +29,9 @@ +transition(background, $transition-length) + &:hover + color: #fff + img display: inline-block float: left @@ -73,16 +76,16 @@ .navigation margin-left: 30px - .projects + .simulations float: left padding: 0 20px font-size: 12pt - .projects:hover + .simulations:hover background: #606060 - .projects:active + .simulations:active background: #161616 .user-controls diff --git a/src/components/projects/FilterPanel.js b/src/components/projects/FilterPanel.js deleted file mode 100644 index 050bf0aa..00000000 --- a/src/components/projects/FilterPanel.js +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; -import FilterLink from "../../containers/projects/FilterLink"; -import "./FilterPanel.css"; - -const ProjectFilterPanel = () => ( - <div className="filter-menu"> - <div className="project-filters"> - <FilterLink filter="SHOW_ALL">All Projects</FilterLink> - <FilterLink filter="SHOW_OWN">My Projects</FilterLink> - <FilterLink filter="SHOW_SHARED">Projects shared with me</FilterLink> - </div> - </div> -); - -export default ProjectFilterPanel; diff --git a/src/components/projects/NewProjectButton.js b/src/components/projects/NewProjectButton.js deleted file mode 100644 index 9eaf6df4..00000000 --- a/src/components/projects/NewProjectButton.js +++ /dev/null @@ -1,16 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import './NewProjectButton.css'; - -const NewProjectButton = ({onClick}) => ( - <div className="new-project-btn" onClick={onClick}> - <span className="fa fa-plus"/> - New Project - </div> -); - -NewProjectButton.propTypes = { - onClick: PropTypes.func.isRequired, -}; - -export default NewProjectButton; diff --git a/src/components/projects/NoProjectsAlert.js b/src/components/projects/NoProjectsAlert.js deleted file mode 100644 index 957435c7..00000000 --- a/src/components/projects/NoProjectsAlert.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import "./NoProjectsAlert.css"; - -const NoProjectsAlert = () => ( - <div className="no-projects-alert alert alert-info"> - <span className="info-icon fa fa-2x fa-question-circle"/> - <strong>No projects here yet...</strong> Add some with the 'New Project' button! - </div> -); - -export default NoProjectsAlert; diff --git a/src/components/projects/ProjectActionButtons.js b/src/components/projects/ProjectActionButtons.js deleted file mode 100644 index 66eb8bfa..00000000 --- a/src/components/projects/ProjectActionButtons.js +++ /dev/null @@ -1,26 +0,0 @@ -import PropTypes from "prop-types"; -import React from 'react'; - -const ProjectActionButtons = ({projectId, onOpen, onViewUsers, onDelete}) => ( - <div className="project-icons"> - <div className="open" title="Open this project" onClick={() => onOpen(projectId)}> - <span className="fa fa-play"/> - </div> - <div className="users" title="View and edit collaborators on this project" - onClick={() => onViewUsers(projectId)}> - <span className="fa fa-users"/> - </div> - <div className="delete" title="Delete this project" onClick={() => onDelete(projectId)}> - <span className="fa fa-trash"/> - </div> - </div> -); - -ProjectActionButtons.propTypes = { - projectId: PropTypes.number.isRequired, - onOpen: PropTypes.func, - onViewUsers: PropTypes.func, - onDelete: PropTypes.func, -}; - -export default ProjectActionButtons; diff --git a/src/components/projects/ProjectAuth.js b/src/components/projects/ProjectAuth.js deleted file mode 100644 index 10cfd252..00000000 --- a/src/components/projects/ProjectAuth.js +++ /dev/null @@ -1,24 +0,0 @@ -import classNames from 'classnames'; -import React from 'react'; -import ProjectActions from "../../containers/projects/ProjectActions"; -import Shapes from "../../shapes/index"; -import {AUTH_DESCRIPTION_MAP, AUTH_ICON_MAP} from "../../util/authorizations"; -import {parseAndFormatDateTime} from "../../util/date-time"; - -const ProjectAuth = ({projectAuth}) => ( - <div className="project-row"> - <div>{projectAuth.simulation.name}</div> - <div>{parseAndFormatDateTime(projectAuth.simulation.datetimeLastEdited)}</div> - <div> - <span className={classNames("fa", "fa-" + AUTH_ICON_MAP[projectAuth.authorizationLevel])}/> - {AUTH_DESCRIPTION_MAP[projectAuth.authorizationLevel]} - </div> - <ProjectActions projectId={projectAuth.simulation.id}/> - </div> -); - -ProjectAuth.propTypes = { - projectAuth: Shapes.Authorization.isRequired, -}; - -export default ProjectAuth; diff --git a/src/components/projects/FilterButton.js b/src/components/simulations/FilterButton.js index 8d6b7146..2105d281 100644 --- a/src/components/projects/FilterButton.js +++ b/src/components/simulations/FilterButton.js @@ -4,7 +4,7 @@ import React from 'react'; import "./FilterButton.css"; const FilterButton = ({active, children, onClick}) => ( - <div className={classNames("project-filter-button", {"active": active})} + <div className={classNames("simulation-filter-button", {"active": active})} onClick={() => { if (!active) { onClick(); diff --git a/src/components/projects/FilterButton.sass b/src/components/simulations/FilterButton.sass index 0cad68e3..60f6faca 100644 --- a/src/components/projects/FilterButton.sass +++ b/src/components/simulations/FilterButton.sass @@ -1,7 +1,7 @@ @import ../../style-globals/_mixins.sass @import ../../style-globals/_variables.sass -.project-filter-button +.simulation-filter-button display: inline-block width: 33.3% //margin-right: -4px @@ -13,11 +13,11 @@ +clickable +transition(background, $transition-length) -.project-filter-button:last-of-type +.simulation-filter-button:last-of-type border: 0 -.project-filter-button:hover +.simulation-filter-button:hover background: #0c60bf -.project-filter-button:active, .project-filter-button.active +.simulation-filter-button:active, .simulation-filter-button.active background: #073d7d diff --git a/src/components/simulations/FilterPanel.js b/src/components/simulations/FilterPanel.js new file mode 100644 index 00000000..b43139c1 --- /dev/null +++ b/src/components/simulations/FilterPanel.js @@ -0,0 +1,15 @@ +import React from 'react'; +import FilterLink from "../../containers/simulations/FilterLink"; +import "./FilterPanel.css"; + +const FilterPanel = () => ( + <div className="filter-menu"> + <div className="simulation-filters"> + <FilterLink filter="SHOW_ALL">All Simulations</FilterLink> + <FilterLink filter="SHOW_OWN">My Simulations</FilterLink> + <FilterLink filter="SHOW_SHARED">Simulations shared with me</FilterLink> + </div> + </div> +); + +export default FilterPanel; diff --git a/src/components/projects/FilterPanel.sass b/src/components/simulations/FilterPanel.sass index a70c7a90..a59ffdfd 100644 --- a/src/components/projects/FilterPanel.sass +++ b/src/components/simulations/FilterPanel.sass @@ -15,7 +15,7 @@ margin-bottom: 20px - .project-filters + .simulation-filters display: block overflow: hidden margin: 0 -1px diff --git a/src/components/simulations/NewSimulationButton.js b/src/components/simulations/NewSimulationButton.js new file mode 100644 index 00000000..468f7678 --- /dev/null +++ b/src/components/simulations/NewSimulationButton.js @@ -0,0 +1,16 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import './NewSimulationButton.css'; + +const NewSimulationButton = ({onClick}) => ( + <div className="new-simulation-btn" onClick={onClick}> + <span className="fa fa-plus"/> + New Simulation + </div> +); + +NewSimulationButton.propTypes = { + onClick: PropTypes.func.isRequired, +}; + +export default NewSimulationButton; diff --git a/src/components/projects/NewProjectButton.sass b/src/components/simulations/NewSimulationButton.sass index 89435902..9bf82c49 100644 --- a/src/components/projects/NewProjectButton.sass +++ b/src/components/simulations/NewSimulationButton.sass @@ -1,7 +1,7 @@ @import ../../style-globals/_mixins.sass @import ../../style-globals/_variables.sass -.new-project-btn +.new-simulation-btn $button-height: 35px display: inline-block @@ -24,8 +24,8 @@ span margin-right: 10px -.new-project-btn:hover +.new-simulation-btn:hover background: #73ac45 -.new-project-btn:active +.new-simulation-btn:active background: #5c8835 diff --git a/src/components/simulations/NoSimulationsAlert.js b/src/components/simulations/NoSimulationsAlert.js new file mode 100644 index 00000000..d688ae56 --- /dev/null +++ b/src/components/simulations/NoSimulationsAlert.js @@ -0,0 +1,11 @@ +import React from 'react'; +import "./NoSimulationsAlert.css"; + +const NoSimulationsAlert = () => ( + <div className="no-simulations-alert alert alert-info"> + <span className="info-icon fa fa-2x fa-question-circle"/> + <strong>No simulations here yet...</strong> Add some with the 'New Simulation' button! + </div> +); + +export default NoSimulationsAlert; diff --git a/src/components/projects/NoProjectsAlert.sass b/src/components/simulations/NoSimulationsAlert.sass index a526f9ad..10e89e51 100644 --- a/src/components/projects/NoProjectsAlert.sass +++ b/src/components/simulations/NoSimulationsAlert.sass @@ -1,4 +1,4 @@ -.no-projects-alert +.no-simulations-alert position: relative padding-left: 50px diff --git a/src/components/simulations/SimulationActionButtons.js b/src/components/simulations/SimulationActionButtons.js new file mode 100644 index 00000000..d48b4bcf --- /dev/null +++ b/src/components/simulations/SimulationActionButtons.js @@ -0,0 +1,26 @@ +import PropTypes from "prop-types"; +import React from 'react'; + +const SimulationActionButtons = ({simulationId, onOpen, onViewUsers, onDelete}) => ( + <div className="simulation-icons"> + <div className="open" title="Open this simulation" onClick={() => onOpen(simulationId)}> + <span className="fa fa-play"/> + </div> + <div className="users" title="View and edit collaborators on this simulation" + onClick={() => onViewUsers(simulationId)}> + <span className="fa fa-users"/> + </div> + <div className="delete" title="Delete this simulation" onClick={() => onDelete(simulationId)}> + <span className="fa fa-trash"/> + </div> + </div> +); + +SimulationActionButtons.propTypes = { + simulationId: PropTypes.number.isRequired, + onOpen: PropTypes.func, + onViewUsers: PropTypes.func, + onDelete: PropTypes.func, +}; + +export default SimulationActionButtons; diff --git a/src/components/simulations/SimulationAuth.js b/src/components/simulations/SimulationAuth.js new file mode 100644 index 00000000..9b887991 --- /dev/null +++ b/src/components/simulations/SimulationAuth.js @@ -0,0 +1,24 @@ +import classNames from 'classnames'; +import React from 'react'; +import SimulationActions from "../../containers/simulations/SimulationActions"; +import Shapes from "../../shapes/index"; +import {AUTH_DESCRIPTION_MAP, AUTH_ICON_MAP} from "../../util/authorizations"; +import {parseAndFormatDateTime} from "../../util/date-time"; + +const SimulationAuth = ({simulationAuth}) => ( + <div className="simulation-row"> + <div>{simulationAuth.simulation.name}</div> + <div>{parseAndFormatDateTime(simulationAuth.simulation.datetimeLastEdited)}</div> + <div> + <span className={classNames("fa", "fa-" + AUTH_ICON_MAP[simulationAuth.authorizationLevel])}/> + {AUTH_DESCRIPTION_MAP[simulationAuth.authorizationLevel]} + </div> + <SimulationActions simulationId={simulationAuth.simulation.id}/> + </div> +); + +SimulationAuth.propTypes = { + simulationAuth: Shapes.Authorization.isRequired, +}; + +export default SimulationAuth; diff --git a/src/components/projects/ProjectAuthList.js b/src/components/simulations/SimulationAuthList.js index 94e7abad..7653f178 100644 --- a/src/components/projects/ProjectAuthList.js +++ b/src/components/simulations/SimulationAuthList.js @@ -1,33 +1,33 @@ import PropTypes from 'prop-types'; import React from 'react'; import Shapes from "../../shapes/index"; -import NoProjectsAlert from "./NoProjectsAlert"; -import ProjectAuth from "./ProjectAuth"; -import "./ProjectAuthList.css"; +import NoSimulationsAlert from "./NoSimulationsAlert"; +import SimulationAuth from "./SimulationAuth"; +import "./SimulationAuthList.css"; -const ProjectAuthList = ({authorizations}) => { +const SimulationAuthList = ({authorizations}) => { if (authorizations.length === 0) { - return <NoProjectsAlert/>; + return <NoSimulationsAlert/>; } return ( - <div className="project-list"> + <div className="simulation-list"> <div className="list-head"> - <div>Project name</div> + <div>Simulation name</div> <div>Last edited</div> <div>Access rights</div> </div> <div className="list-body"> {authorizations.map(authorization => ( - <ProjectAuth projectAuth={authorization} key={authorization.simulation.id}/> + <SimulationAuth simulationAuth={authorization} key={authorization.simulation.id}/> ))} </div> </div> ); }; -ProjectAuthList.propTypes = { +SimulationAuthList.propTypes = { authorizations: PropTypes.arrayOf(Shapes.Authorization).isRequired, }; -export default ProjectAuthList; +export default SimulationAuthList; diff --git a/src/components/projects/ProjectAuthList.sass b/src/components/simulations/SimulationAuthList.sass index 5cdfacaa..4edfb99e 100644 --- a/src/components/projects/ProjectAuthList.sass +++ b/src/components/simulations/SimulationAuthList.sass @@ -1,16 +1,16 @@ @import ../../style-globals/_mixins.sass @import ../../style-globals/_variables.sass -.project-list +.simulation-list display: block font-size: 12pt border: 0 - .list-head, .list-body .project-row + .list-head, .list-body .simulation-row display: block position: relative - .list-head div, .list-body .project-row div + .list-head div, .list-body .simulation-row div padding: 0 10px display: inline-block @@ -21,18 +21,18 @@ div margin-right: -4px -.project-row +.simulation-row background: #f8f8f8 border: 1px solid #b6b6b6 height: 40px line-height: 40px clear: both -.project-row:not(:first-of-type) +.simulation-row:not(:first-of-type) margin-top: -1px // Sizing of table columns -.project-row, .project-list .list-head +.simulation-row, .simulation-list .list-head div:first-of-type width: 40% @@ -48,10 +48,10 @@ span margin-right: 10px -.project-row .project-icons +.simulation-row .simulation-icons text-align: right -.project-row .project-icons div +.simulation-row .simulation-icons div display: inline position: relative top: 4px diff --git a/src/containers/projects/ProjectActions.js b/src/containers/projects/ProjectActions.js deleted file mode 100644 index 098d9966..00000000 --- a/src/containers/projects/ProjectActions.js +++ /dev/null @@ -1,24 +0,0 @@ -import {connect} from "react-redux"; -import {deleteProject, openProject} from "../../actions/projects"; -import ProjectActionButtons from "../../components/projects/ProjectActionButtons"; - -const mapStateToProps = (state, ownProps) => { - return { - projectId: ownProps.projectId - }; -}; - -const mapDispatchToProps = dispatch => { - return { - onOpen: (id) => dispatch(openProject(id)), - onViewUsers: (id) => {}, - onDelete: (id) => dispatch(deleteProject(id)), - }; -}; - -const ProjectActions = connect( - mapStateToProps, - mapDispatchToProps -)(ProjectActionButtons); - -export default ProjectActions; diff --git a/src/containers/projects/VisibleProjectAuthList.js b/src/containers/projects/VisibleProjectAuthList.js deleted file mode 100644 index f668711e..00000000 --- a/src/containers/projects/VisibleProjectAuthList.js +++ /dev/null @@ -1,32 +0,0 @@ -import {connect} from "react-redux"; -import ProjectList from "../../components/projects/ProjectAuthList"; - -const getVisibleProjectAuths = (projectAuths, filter) => { - switch (filter) { - case 'SHOW_ALL': - return projectAuths; - case 'SHOW_OWN': - return projectAuths.filter(projectAuth => projectAuth.authorizationLevel === "OWN"); - case 'SHOW_SHARED': - return projectAuths.filter(projectAuth => projectAuth.authorizationLevel !== "OWN"); - default: - return projectAuths; - } -}; - -const mapStateToProps = state => { - const denormalizedAuthorizations = state.authorizationsOfCurrentUser.map(authorizationIds => { - const authorization = Object.assign({}, state.objects.authorizations[authorizationIds]); - authorization.simulation = state.objects.simulations[authorization.simulationId]; - authorization.user = state.objects.users[authorization.userId]; - return authorization; - }); - - return { - authorizations: getVisibleProjectAuths(denormalizedAuthorizations, state.authVisibilityFilter) - }; -}; - -const VisibleProjectAuthList = connect(mapStateToProps)(ProjectList); - -export default VisibleProjectAuthList; diff --git a/src/containers/projects/FilterLink.js b/src/containers/simulations/FilterLink.js index e9a13436..dff01ab2 100644 --- a/src/containers/projects/FilterLink.js +++ b/src/containers/simulations/FilterLink.js @@ -1,6 +1,6 @@ import {connect} from "react-redux"; -import {setAuthVisibilityFilter} from "../../actions/projects"; -import FilterButton from "../../components/projects/FilterButton"; +import {setAuthVisibilityFilter} from "../../actions/simulations"; +import FilterButton from "../../components/simulations/FilterButton"; const mapStateToProps = (state, ownProps) => { return { diff --git a/src/containers/projects/NewProjectModal.js b/src/containers/simulations/NewSimulationModal.js index affaf794..a4a3d2a8 100644 --- a/src/containers/projects/NewProjectModal.js +++ b/src/containers/simulations/NewSimulationModal.js @@ -1,17 +1,17 @@ import React from "react"; import {connect} from "react-redux"; -import {addProject, closeNewProjectModal} from "../../actions/projects"; +import {addSimulation, closeNewSimulationModal} from "../../actions/simulations"; import TextInputModal from "../../components/modals/TextInputModal"; -const NewProjectModalComponent = ({visible, callback}) => ( - <TextInputModal title="New Project" label="Project title" +const NewSimulationModalComponent = ({visible, callback}) => ( + <TextInputModal title="New Simulation" label="Simulation title" show={visible} callback={callback}/> ); const mapStateToProps = state => { return { - visible: state.newProjectModalVisible + visible: state.newSimulationModalVisible }; }; @@ -19,16 +19,16 @@ const mapDispatchToProps = dispatch => { return { callback: (text) => { if (text) { - dispatch(addProject(text)); + dispatch(addSimulation(text)); } - dispatch(closeNewProjectModal()); + dispatch(closeNewSimulationModal()); } }; }; -const NewProjectModal = connect( +const NewSimulationModal = connect( mapStateToProps, mapDispatchToProps -)(NewProjectModalComponent); +)(NewSimulationModalComponent); -export default NewProjectModal; +export default NewSimulationModal; diff --git a/src/containers/simulations/SimulationActions.js b/src/containers/simulations/SimulationActions.js new file mode 100644 index 00000000..e2ca2795 --- /dev/null +++ b/src/containers/simulations/SimulationActions.js @@ -0,0 +1,24 @@ +import {connect} from "react-redux"; +import {deleteSimulation, openSimulation} from "../../actions/simulations"; +import SimulationActionButtons from "../../components/simulations/SimulationActionButtons"; + +const mapStateToProps = (state, ownProps) => { + return { + simulationId: ownProps.simulationId + }; +}; + +const mapDispatchToProps = dispatch => { + return { + onOpen: (id) => dispatch(openSimulation(id)), + onViewUsers: (id) => {}, + onDelete: (id) => dispatch(deleteSimulation(id)), + }; +}; + +const SimulationActions = connect( + mapStateToProps, + mapDispatchToProps +)(SimulationActionButtons); + +export default SimulationActions; diff --git a/src/containers/simulations/VisibleSimulationAuthList.js b/src/containers/simulations/VisibleSimulationAuthList.js new file mode 100644 index 00000000..07740435 --- /dev/null +++ b/src/containers/simulations/VisibleSimulationAuthList.js @@ -0,0 +1,32 @@ +import {connect} from "react-redux"; +import SimulationList from "../../components/simulations/SimulationAuthList"; + +const getVisibleSimulationAuths = (simulationAuths, filter) => { + switch (filter) { + case 'SHOW_ALL': + return simulationAuths; + case 'SHOW_OWN': + return simulationAuths.filter(simulationAuth => simulationAuth.authorizationLevel === "OWN"); + case 'SHOW_SHARED': + return simulationAuths.filter(simulationAuth => simulationAuth.authorizationLevel !== "OWN"); + default: + return simulationAuths; + } +}; + +const mapStateToProps = state => { + const denormalizedAuthorizations = state.authorizationsOfCurrentUser.map(authorizationIds => { + const authorization = Object.assign({}, state.objects.authorizations[authorizationIds]); + authorization.simulation = state.objects.simulations[authorization.simulationId]; + authorization.user = state.objects.users[authorization.userId]; + return authorization; + }); + + return { + authorizations: getVisibleSimulationAuths(denormalizedAuthorizations, state.authVisibilityFilter) + }; +}; + +const VisibleSimulationAuthList = connect(mapStateToProps)(SimulationList); + +export default VisibleSimulationAuthList; diff --git a/src/pages/Projects.js b/src/pages/Projects.js deleted file mode 100644 index 06655768..00000000 --- a/src/pages/Projects.js +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react'; -import {connect} from "react-redux"; -import {addProject, openNewProjectModal} from "../actions/projects"; -import {fetchAuthorizationsOfCurrentUser} from "../actions/users"; -import Navbar from "../components/navigation/Navbar"; -import ProjectFilterPanel from "../components/projects/FilterPanel"; -import NewProjectButton from "../components/projects/NewProjectButton"; -import Login from "../containers/auth/Login"; -import NewProjectModal from "../containers/projects/NewProjectModal"; -import VisibleProjectList from "../containers/projects/VisibleProjectAuthList"; -import "./Projects.css"; - -class ProjectsContainer extends React.Component { - componentDidMount() { - this.props.fetchAuthorizationsOfCurrentUser(); - } - - onInputSubmission(text) { - this.props.dispatch(addProject(text)); - } - - render() { - return ( - <div className="full-height"> - <Navbar/> - <div className="container project-page-container full-height"> - <ProjectFilterPanel/> - <VisibleProjectList/> - <NewProjectButton onClick={() => {this.props.openNewProjectModal()}}/> - </div> - <NewProjectModal/> - <Login visible={false}/> - </div> - ); - } -} - -const mapDispatchToProps = dispatch => { - return { - fetchAuthorizationsOfCurrentUser: () => dispatch(fetchAuthorizationsOfCurrentUser()), - openNewProjectModal: () => dispatch(openNewProjectModal()), - }; -}; - -const Projects = connect( - undefined, - mapDispatchToProps -)(ProjectsContainer); - -export default Projects; diff --git a/src/pages/Projects.sass b/src/pages/Projects.sass deleted file mode 100644 index 11a52e1a..00000000 --- a/src/pages/Projects.sass +++ /dev/null @@ -1,2 +0,0 @@ -.project-page-container - padding-top: 2rem diff --git a/src/pages/Simulations.js b/src/pages/Simulations.js new file mode 100644 index 00000000..c46cb621 --- /dev/null +++ b/src/pages/Simulations.js @@ -0,0 +1,51 @@ +import React from 'react'; +import {connect} from "react-redux"; +import {addSimulation, openNewSimulationModal} from "../actions/simulations"; +import {fetchAuthorizationsOfCurrentUser} from "../actions/users"; +import Navbar from "../components/navigation/Navbar"; +import SimulationFilterPanel from "../components/simulations/FilterPanel"; +import NewSimulationButton from "../components/simulations/NewSimulationButton"; +import Login from "../containers/auth/Login"; +import NewSimulationModal from "../containers/simulations/NewSimulationModal"; +import VisibleSimulationList from "../containers/simulations/VisibleSimulationAuthList"; +import "./Simulations.css"; + +class SimulationsContainer extends React.Component { + componentDidMount() { + this.props.fetchAuthorizationsOfCurrentUser(); + } + + onInputSubmission(text) { + this.props.addSimulation(text); + } + + render() { + return ( + <div className="full-height"> + <Navbar/> + <div className="container simulation-page-container full-height"> + <SimulationFilterPanel/> + <VisibleSimulationList/> + <NewSimulationButton onClick={() => {this.props.openNewSimulationModal()}}/> + </div> + <NewSimulationModal/> + <Login visible={false}/> + </div> + ); + } +} + +const mapDispatchToProps = dispatch => { + return { + fetchAuthorizationsOfCurrentUser: () => dispatch(fetchAuthorizationsOfCurrentUser()), + openNewSimulationModal: () => dispatch(openNewSimulationModal()), + addSimulation: (text) => dispatch(addSimulation(text)), + }; +}; + +const Simulations = connect( + undefined, + mapDispatchToProps +)(SimulationsContainer); + +export default Simulations; diff --git a/src/pages/Simulations.sass b/src/pages/Simulations.sass new file mode 100644 index 00000000..d639ba83 --- /dev/null +++ b/src/pages/Simulations.sass @@ -0,0 +1,2 @@ +.simulation-page-container + padding-top: 2rem diff --git a/src/reducers/index.js b/src/reducers/index.js index 71379a6f..bfafaedd 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -1,13 +1,13 @@ import {combineReducers} from "redux"; import {auth} from "./auth"; import {objects} from "./objects"; -import {authorizationsOfCurrentUser, authVisibilityFilter, newProjectModalVisible} from "./projects"; +import {authorizationsOfCurrentUser, authVisibilityFilter, newSimulationModalVisible} from "./simulations"; const rootReducer = combineReducers({ auth, objects, authorizationsOfCurrentUser, - newProjectModalVisible, + newSimulationModalVisible, authVisibilityFilter, }); diff --git a/src/reducers/objects.js b/src/reducers/objects.js index 69e68ca7..40bd3524 100644 --- a/src/reducers/objects.js +++ b/src/reducers/objects.js @@ -1,5 +1,5 @@ import {combineReducers} from "redux"; -import {ADD_TO_AUTHORIZATION_STORE, ADD_TO_SIMULATION_STORE, ADD_TO_USER_STORE} from "../actions/object-stores"; +import {ADD_TO_AUTHORIZATION_STORE, ADD_TO_SIMULATION_STORE, ADD_TO_USER_STORE} from "../actions/objects"; export const objects = combineReducers({ simulations, diff --git a/src/reducers/projects.js b/src/reducers/simulations.js index ba3c792d..5a34ee7f 100644 --- a/src/reducers/projects.js +++ b/src/reducers/simulations.js @@ -1,37 +1,33 @@ import { - ADD_PROJECT, - CLOSE_NEW_PROJECT_MODAL, - DELETE_PROJECT, - OPEN_NEW_PROJECT_MODAL, + ADD_SIMULATION_SUCCEEDED, + CLOSE_NEW_SIMULATION_MODAL, + DELETE_SIMULATION, + OPEN_NEW_SIMULATION_MODAL, SET_AUTH_VISIBILITY_FILTER -} from "../actions/projects"; +} from "../actions/simulations"; import {FETCH_AUTHORIZATIONS_OF_CURRENT_USER_SUCCEEDED} from "../actions/users"; export function authorizationsOfCurrentUser(state = [], action) { switch (action.type) { case FETCH_AUTHORIZATIONS_OF_CURRENT_USER_SUCCEEDED: return action.authorizationsOfCurrentUser; - case ADD_PROJECT: + case ADD_SIMULATION_SUCCEEDED: return [ ...state, - { - userId: -1, - simulation: {name: action.name, datetimeLastEdited: "2017-08-06T12:43:00", id: state.length}, - authorizationLevel: "OWN" - } + action.authorization ]; - case DELETE_PROJECT: + case DELETE_SIMULATION: return []; default: return state; } } -export function newProjectModalVisible(state = false, action) { +export function newSimulationModalVisible(state = false, action) { switch (action.type) { - case OPEN_NEW_PROJECT_MODAL: + case OPEN_NEW_SIMULATION_MODAL: return true; - case CLOSE_NEW_PROJECT_MODAL: + case CLOSE_NEW_SIMULATION_MODAL: return false; default: return state; diff --git a/src/routes/index.js b/src/routes/index.js index 6b3a454d..6257017e 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -3,15 +3,15 @@ import {BrowserRouter, Redirect, Route, Switch} from "react-router-dom"; import {userIsLoggedIn} from "../auth/index"; import Home from "../pages/Home"; import NotFound from "../pages/NotFound"; -import Projects from "../pages/Projects"; +import Simulations from "../pages/Simulations"; const Routes = () => ( <BrowserRouter> <Switch> <Route exact path="/" component={Home}/> - <Route exact path="/projects" render={() => ( + <Route exact path="/simulations" render={() => ( userIsLoggedIn() ? ( - <Projects/> + <Simulations/> ) : ( <Redirect to="/"/> ) |
