diff options
| author | Fabian Mastenbroek <mail.fabianm@gmail.com> | 2021-05-13 16:35:01 +0200 |
|---|---|---|
| committer | Fabian Mastenbroek <mail.fabianm@gmail.com> | 2021-05-17 17:06:50 +0200 |
| commit | 1891a6f3963d3ddeae0ea093f9a7e3608a97b4d7 (patch) | |
| tree | 5fe20a483d7e51e25a7e0759d21981e38844f139 | |
| parent | 24147cba0f5723be3525e8f40d1954144841629b (diff) | |
ui: Simplify projects page
This change simplifies the logic and components of the projects page and
reduces its dependency on Redux for simple operations.
16 files changed, 98 insertions, 159 deletions
diff --git a/opendc-web/opendc-web-ui/src/actions/modals/projects.js b/opendc-web/opendc-web-ui/src/actions/modals/projects.js deleted file mode 100644 index d1043cbb..00000000 --- a/opendc-web/opendc-web-ui/src/actions/modals/projects.js +++ /dev/null @@ -1,14 +0,0 @@ -export const OPEN_NEW_PROJECT_MODAL = 'OPEN_NEW_PROJECT_MODAL' -export const CLOSE_NEW_PROJECT_MODAL = 'CLOSE_PROJECT_MODAL' - -export function openNewProjectModal() { - return { - type: OPEN_NEW_PROJECT_MODAL, - } -} - -export function closeNewProjectModal() { - return { - type: CLOSE_NEW_PROJECT_MODAL, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/projects.js b/opendc-web/opendc-web-ui/src/actions/projects.js index add0f242..15158164 100644 --- a/opendc-web/opendc-web-ui/src/actions/projects.js +++ b/opendc-web/opendc-web-ui/src/actions/projects.js @@ -1,17 +1,9 @@ -export const SET_AUTH_VISIBILITY_FILTER = 'SET_AUTH_VISIBILITY_FILTER' export const ADD_PROJECT = 'ADD_PROJECT' export const ADD_PROJECT_SUCCEEDED = 'ADD_PROJECT_SUCCEEDED' export const DELETE_PROJECT = 'DELETE_PROJECT' export const DELETE_PROJECT_SUCCEEDED = 'DELETE_PROJECT_SUCCEEDED' export const OPEN_PROJECT_SUCCEEDED = 'OPEN_PROJECT_SUCCEEDED' -export function setAuthVisibilityFilter(filter) { - return { - type: SET_AUTH_VISIBILITY_FILTER, - filter, - } -} - export function addProject(name) { return (dispatch, getState) => { const { auth } = getState() diff --git a/opendc-web/opendc-web-ui/src/components/projects/FilterButton.js b/opendc-web/opendc-web-ui/src/components/projects/FilterButton.js deleted file mode 100644 index 664f9b46..00000000 --- a/opendc-web/opendc-web-ui/src/components/projects/FilterButton.js +++ /dev/null @@ -1,24 +0,0 @@ -import classNames from 'classnames' -import PropTypes from 'prop-types' -import React from 'react' - -const FilterButton = ({ active, children, onClick }) => ( - <button - className={classNames('btn btn-secondary', { active: active })} - onClick={() => { - if (!active) { - onClick() - } - }} - > - {children} - </button> -) - -FilterButton.propTypes = { - active: PropTypes.bool.isRequired, - children: PropTypes.node.isRequired, - onClick: PropTypes.func.isRequired, -} - -export default FilterButton diff --git a/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.js b/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.js index 89b483fb..5129c013 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.js +++ b/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.js @@ -1,13 +1,28 @@ import React from 'react' -import FilterLink from '../../containers/projects/FilterLink' +import PropTypes from 'prop-types' +import { Button, ButtonGroup } from 'reactstrap' import { filterPanel } from './FilterPanel.module.scss' -const FilterPanel = () => ( - <div className={`btn-group ${filterPanel} mb-2`}> - <FilterLink filter="SHOW_ALL">All Projects</FilterLink> - <FilterLink filter="SHOW_OWN">My Projects</FilterLink> - <FilterLink filter="SHOW_SHARED">Shared with me</FilterLink> - </div> +export const FILTERS = { SHOW_ALL: 'All Projects', SHOW_OWN: 'My Projects', SHOW_SHARED: 'Shared with me' } + +const FilterPanel = ({ onSelect, activeFilter = 'SHOW_ALL' }) => ( + <ButtonGroup className={`${filterPanel} mb-2`}> + {Object.keys(FILTERS).map((filter) => ( + <Button + color="secondary" + key={filter} + onClick={() => activeFilter === filter || onSelect(filter)} + active={activeFilter === filter} + > + {FILTERS[filter]} + </Button> + ))} + </ButtonGroup> ) +FilterPanel.propTypes = { + onSelect: PropTypes.func.isRequired, + activeFilter: PropTypes.string, +} + export default FilterPanel diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewProjectButtonComponent.js b/opendc-web/opendc-web-ui/src/components/projects/NewProjectButtonComponent.js deleted file mode 100644 index 312671c6..00000000 --- a/opendc-web/opendc-web-ui/src/components/projects/NewProjectButtonComponent.js +++ /dev/null @@ -1,17 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' - -const NewProjectButtonComponent = ({ onClick }) => ( - <div className="bottom-btn-container"> - <div className="btn btn-primary float-right" onClick={onClick}> - <span className="fa fa-plus mr-2" /> - New Project - </div> - </div> -) - -NewProjectButtonComponent.propTypes = { - onClick: PropTypes.func.isRequired, -} - -export default NewProjectButtonComponent diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectActionButtons.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectActionButtons.js index 48cce019..96970fd9 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/ProjectActionButtons.js +++ b/opendc-web/opendc-web-ui/src/components/projects/ProjectActionButtons.js @@ -1,24 +1,29 @@ import PropTypes from 'prop-types' import React from 'react' import Link from 'next/link' +import { Button } from 'reactstrap' const ProjectActionButtons = ({ projectId, onViewUsers, onDelete }) => ( <td className="text-right"> <Link href={`/projects/${projectId}`}> - <a className="btn btn-outline-primary btn-sm mr-2" title="Open this project"> + <Button color="primary" outline size="sm" className="mr-2" title="Open this project"> <span className="fa fa-play" /> - </a> + </Button> </Link> - <div - className="btn btn-outline-success btn-sm disabled mr-2" + <Button + color="success" + outline + size="sm" + disabled + className="mr-2" title="View and edit collaborators (not supported currently)" onClick={() => onViewUsers(projectId)} > <span className="fa fa-users" /> - </div> - <div className="btn btn-outline-danger btn-sm" title="Delete this project" onClick={() => onDelete(projectId)}> + </Button> + <Button color="danger" outline size="sm" title="Delete this project" onClick={() => onDelete(projectId)}> <span className="fa fa-trash" /> - </div> + </Button> </td> ) diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthList.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectList.js index 15147e08..90d42326 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthList.js +++ b/opendc-web/opendc-web-ui/src/components/projects/ProjectList.js @@ -1,9 +1,9 @@ import PropTypes from 'prop-types' import React from 'react' import { Authorization } from '../../shapes' -import ProjectAuthRow from './ProjectAuthRow' +import ProjectRow from './ProjectRow' -const ProjectAuthList = ({ authorizations }) => { +const ProjectList = ({ authorizations }) => { return ( <div className="vertically-expanding-container"> {authorizations.length === 0 ? ( @@ -23,7 +23,7 @@ const ProjectAuthList = ({ authorizations }) => { </thead> <tbody> {authorizations.map((authorization) => ( - <ProjectAuthRow projectAuth={authorization} key={authorization.project._id} /> + <ProjectRow projectAuth={authorization} key={authorization.project._id} /> ))} </tbody> </table> @@ -32,8 +32,8 @@ const ProjectAuthList = ({ authorizations }) => { ) } -ProjectAuthList.propTypes = { +ProjectList.propTypes = { authorizations: PropTypes.arrayOf(Authorization).isRequired, } -export default ProjectAuthList +export default ProjectList diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthRow.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectRow.js index 1c1d5cd8..bc63c805 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthRow.js +++ b/opendc-web/opendc-web-ui/src/components/projects/ProjectRow.js @@ -5,7 +5,7 @@ import { Authorization } from '../../shapes/index' import { AUTH_DESCRIPTION_MAP, AUTH_ICON_MAP } from '../../util/authorizations' import { parseAndFormatDateTime } from '../../util/date-time' -const ProjectAuthRow = ({ projectAuth }) => ( +const ProjectRow = ({ projectAuth }) => ( <tr> <td className="pt-3">{projectAuth.project.name}</td> <td className="pt-3">{parseAndFormatDateTime(projectAuth.project.datetimeLastEdited)}</td> @@ -17,8 +17,8 @@ const ProjectAuthRow = ({ projectAuth }) => ( </tr> ) -ProjectAuthRow.propTypes = { +ProjectRow.propTypes = { projectAuth: Authorization.isRequired, } -export default ProjectAuthRow +export default ProjectRow diff --git a/opendc-web/opendc-web-ui/src/containers/modals/NewProjectModal.js b/opendc-web/opendc-web-ui/src/containers/modals/NewProjectModal.js deleted file mode 100644 index e63ba76b..00000000 --- a/opendc-web/opendc-web-ui/src/containers/modals/NewProjectModal.js +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { closeNewProjectModal } from '../../actions/modals/projects' -import { addProject } from '../../actions/projects' -import TextInputModal from '../../components/modals/TextInputModal' - -const NewProjectModal = (props) => { - const visible = useSelector((state) => state.modals.newProjectModalVisible) - const dispatch = useDispatch() - const callback = (text) => { - if (text) { - dispatch(addProject(text)) - } - dispatch(closeNewProjectModal()) - } - return <TextInputModal title="New Project" label="Project title" show={visible} callback={callback} {...props} /> -} - -export default NewProjectModal diff --git a/opendc-web/opendc-web-ui/src/containers/projects/FilterLink.js b/opendc-web/opendc-web-ui/src/containers/projects/FilterLink.js deleted file mode 100644 index 26f95c55..00000000 --- a/opendc-web/opendc-web-ui/src/containers/projects/FilterLink.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { setAuthVisibilityFilter } from '../../actions/projects' -import FilterButton from '../../components/projects/FilterButton' - -const FilterLink = (props) => { - const active = useSelector((state) => state.projectList.authVisibilityFilter === props.filter) - const dispatch = useDispatch() - - return <FilterButton {...props} onClick={() => dispatch(setAuthVisibilityFilter(props.filter))} active={active} /> -} - -export default FilterLink diff --git a/opendc-web/opendc-web-ui/src/containers/projects/NewProjectButtonContainer.js b/opendc-web/opendc-web-ui/src/containers/projects/NewProjectButtonContainer.js deleted file mode 100644 index b8f6fef5..00000000 --- a/opendc-web/opendc-web-ui/src/containers/projects/NewProjectButtonContainer.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react' -import { useDispatch } from 'react-redux' -import { openNewProjectModal } from '../../actions/modals/projects' -import NewProjectButtonComponent from '../../components/projects/NewProjectButtonComponent' - -const NewProjectButtonContainer = (props) => { - const dispatch = useDispatch() - return <NewProjectButtonComponent {...props} onClick={() => dispatch(openNewProjectModal())} /> -} - -export default NewProjectButtonContainer diff --git a/opendc-web/opendc-web-ui/src/containers/projects/NewProjectContainer.js b/opendc-web/opendc-web-ui/src/containers/projects/NewProjectContainer.js new file mode 100644 index 00000000..5a8a2dcf --- /dev/null +++ b/opendc-web/opendc-web-ui/src/containers/projects/NewProjectContainer.js @@ -0,0 +1,33 @@ +import React, { useState } from 'react' +import { useDispatch } from 'react-redux' +import { addProject } from '../../actions/projects' +import TextInputModal from '../../components/modals/TextInputModal' +import { Button } from 'reactstrap' + +/** + * A container for creating a new project. + */ +const NewProjectContainer = () => { + const [isVisible, setVisible] = useState(false) + const dispatch = useDispatch() + const callback = (text) => { + if (text) { + dispatch(addProject(text)) + } + setVisible(false) + } + + return ( + <> + <div className="bottom-btn-container"> + <Button color="primary" className="float-right" onClick={() => setVisible(true)}> + <span className="fa fa-plus mr-2" /> + New Project + </Button> + </div> + <TextInputModal title="New Project" label="Project title" show={isVisible} callback={callback} /> + </> + ) +} + +export default NewProjectContainer diff --git a/opendc-web/opendc-web-ui/src/containers/projects/VisibleProjectAuthList.js b/opendc-web/opendc-web-ui/src/containers/projects/VisibleProjectAuthList.js index b869775c..8e1d063b 100644 --- a/opendc-web/opendc-web-ui/src/containers/projects/VisibleProjectAuthList.js +++ b/opendc-web/opendc-web-ui/src/containers/projects/VisibleProjectAuthList.js @@ -1,6 +1,7 @@ import React from 'react' +import PropTypes from 'prop-types' import { useSelector } from 'react-redux' -import ProjectList from '../../components/projects/ProjectAuthList' +import ProjectList from '../../components/projects/ProjectList' const getVisibleProjectAuths = (projectAuths, filter) => { switch (filter) { @@ -15,7 +16,7 @@ const getVisibleProjectAuths = (projectAuths, filter) => { } } -const VisibleProjectAuthList = (props) => { +const VisibleProjectAuthList = ({ filter }) => { const authorizations = useSelector((state) => { const denormalizedAuthorizations = state.projectList.authorizationsOfCurrentUser.map((authorizationIds) => { const authorization = state.objects.authorization[authorizationIds] @@ -24,9 +25,13 @@ const VisibleProjectAuthList = (props) => { return authorization }) - return getVisibleProjectAuths(denormalizedAuthorizations, state.projectList.authVisibilityFilter) + return getVisibleProjectAuths(denormalizedAuthorizations, filter) }) - return <ProjectList {...props} authorizations={authorizations} /> + return <ProjectList authorizations={authorizations} /> +} + +VisibleProjectAuthList.propTypes = { + filter: PropTypes.string.isRequired, } export default VisibleProjectAuthList diff --git a/opendc-web/opendc-web-ui/src/pages/projects/index.js b/opendc-web/opendc-web-ui/src/pages/projects/index.js index bea9ad93..dd61751f 100644 --- a/opendc-web/opendc-web-ui/src/pages/projects/index.js +++ b/opendc-web/opendc-web-ui/src/pages/projects/index.js @@ -1,19 +1,21 @@ -import React, { useEffect } from 'react' +import React, { useEffect, useState } from 'react' import Head from 'next/head' import { useDispatch } from 'react-redux' import { fetchAuthorizationsOfCurrentUser } from '../../actions/users' import ProjectFilterPanel from '../../components/projects/FilterPanel' -import NewProjectModal from '../../containers/modals/NewProjectModal' -import NewProjectButtonContainer from '../../containers/projects/NewProjectButtonContainer' +import NewProjectContainer from '../../containers/projects/NewProjectContainer' import VisibleProjectList from '../../containers/projects/VisibleProjectAuthList' import AppNavbarContainer from '../../containers/navigation/AppNavbarContainer' import { useRequireAuth } from '../../auth/hook' +import { Container } from 'reactstrap' function Projects() { + useRequireAuth() + const dispatch = useDispatch() + const [filter, setFilter] = useState('SHOW_ALL') - useRequireAuth() - useEffect(() => dispatch(fetchAuthorizationsOfCurrentUser())) + useEffect(() => dispatch(fetchAuthorizationsOfCurrentUser()), []) return ( <> @@ -22,12 +24,11 @@ function Projects() { </Head> <div className="full-height"> <AppNavbarContainer fullWidth={false} /> - <div className="container text-page-container full-height"> - <ProjectFilterPanel /> - <VisibleProjectList /> - <NewProjectButtonContainer /> - </div> - <NewProjectModal /> + <Container className="text-page-container"> + <ProjectFilterPanel onSelect={setFilter} activeFilter={filter} /> + <VisibleProjectList filter={filter} /> + <NewProjectContainer /> + </Container> </div> </> ) diff --git a/opendc-web/opendc-web-ui/src/reducers/modals.js b/opendc-web/opendc-web-ui/src/reducers/modals.js index e600d556..a58d8708 100644 --- a/opendc-web/opendc-web-ui/src/reducers/modals.js +++ b/opendc-web/opendc-web-ui/src/reducers/modals.js @@ -1,5 +1,4 @@ import { combineReducers } from 'redux' -import { CLOSE_NEW_PROJECT_MODAL, OPEN_NEW_PROJECT_MODAL } from '../actions/modals/projects' import { CLOSE_NEW_TOPOLOGY_MODAL, CLOSE_DELETE_MACHINE_MODAL, @@ -31,7 +30,6 @@ function modal(openAction, closeAction) { } export const modals = combineReducers({ - newProjectModalVisible: modal(OPEN_NEW_PROJECT_MODAL, CLOSE_NEW_PROJECT_MODAL), changeTopologyModalVisible: modal(OPEN_NEW_TOPOLOGY_MODAL, CLOSE_NEW_TOPOLOGY_MODAL), editRoomNameModalVisible: modal(OPEN_EDIT_ROOM_NAME_MODAL, CLOSE_EDIT_ROOM_NAME_MODAL), deleteRoomModalVisible: modal(OPEN_DELETE_ROOM_MODAL, CLOSE_DELETE_ROOM_MODAL), diff --git a/opendc-web/opendc-web-ui/src/reducers/project-list.js b/opendc-web/opendc-web-ui/src/reducers/project-list.js index 1f1aa8d0..ad803db0 100644 --- a/opendc-web/opendc-web-ui/src/reducers/project-list.js +++ b/opendc-web/opendc-web-ui/src/reducers/project-list.js @@ -1,5 +1,5 @@ import { combineReducers } from 'redux' -import { ADD_PROJECT_SUCCEEDED, DELETE_PROJECT_SUCCEEDED, SET_AUTH_VISIBILITY_FILTER } from '../actions/projects' +import { ADD_PROJECT_SUCCEEDED, DELETE_PROJECT_SUCCEEDED } from '../actions/projects' import { FETCH_AUTHORIZATIONS_OF_CURRENT_USER_SUCCEEDED } from '../actions/users' export function authorizationsOfCurrentUser(state = [], action) { @@ -15,16 +15,4 @@ export function authorizationsOfCurrentUser(state = [], action) { } } -export function authVisibilityFilter(state = 'SHOW_ALL', action) { - switch (action.type) { - case SET_AUTH_VISIBILITY_FILTER: - return action.filter - default: - return state - } -} - -export const projectList = combineReducers({ - authorizationsOfCurrentUser, - authVisibilityFilter, -}) +export const projectList = combineReducers({ authorizationsOfCurrentUser }) |
