From 67a771cbb02ec9da3c60704901f3150b46a7262b Mon Sep 17 00:00:00 2001 From: Georgios Andreadis Date: Wed, 9 Aug 2017 14:29:14 +0300 Subject: Create basic projects page with add-button --- package.json | 10 ++- public/index.html | 12 ++- src/actions/projects.js | 38 +++++++++ src/components/modals/Modal.js | 82 +++++++++++++++++++ src/components/modals/TextInputModal.js | 41 ++++++++++ src/components/navigation/Navbar.js | 6 +- src/components/navigation/Navbar.sass | 4 +- src/components/not-found/BlinkingCursor.js | 8 ++ src/components/not-found/BlinkingCursor.sass | 35 ++++++++ src/components/not-found/CodeBlock.js | 30 +++++++ src/components/not-found/CodeBlock.sass | 3 + src/components/not-found/TerminalWindow.js | 26 ++++++ src/components/not-found/TerminalWindow.sass | 70 ++++++++++++++++ src/components/projects/FilterButton.js | 23 ++++++ src/components/projects/FilterButton.sass | 23 ++++++ src/components/projects/FilterPanel.js | 15 ++++ src/components/projects/FilterPanel.sass | 21 +++++ src/components/projects/NewProjectButton.js | 16 ++++ src/components/projects/NewProjectButton.sass | 31 +++++++ src/components/projects/NoProjectsAlert.js | 11 +++ src/components/projects/NoProjectsAlert.sass | 10 +++ src/components/projects/ProjectAuth.js | 22 +++++ src/components/projects/ProjectAuthList.js | 31 +++++++ src/components/projects/ProjectAuthList.sass | 98 +++++++++++++++++++++++ src/containers/projects/FilterLink.js | 22 +++++ src/containers/projects/NewProjectModal.js | 34 ++++++++ src/containers/projects/VisibleProjectAuthList.js | 25 ++++++ src/index.js | 19 ++--- src/index.sass | 7 +- src/pages/NotFound.js | 11 +++ src/pages/Projects.js | 35 +++++++- src/pages/Projects.sass | 2 + src/reducers/index.js | 10 +++ src/reducers/projects.js | 42 ++++++++++ src/routes/index.js | 17 ++++ src/shapes/index.js | 28 +++++++ src/store/configureStore.js | 16 ++++ src/style-globals/_mixins.sass | 21 +++++ src/style-globals/_variables.sass | 25 ++++++ src/style-globals/mixins.sass | 21 ----- src/style-globals/variables.sass | 25 ------ src/util/authorizations.js | 11 +++ src/util/date-time.js | 32 +++++--- 43 files changed, 985 insertions(+), 84 deletions(-) create mode 100644 src/actions/projects.js create mode 100644 src/components/modals/Modal.js create mode 100644 src/components/modals/TextInputModal.js create mode 100644 src/components/not-found/BlinkingCursor.js create mode 100644 src/components/not-found/BlinkingCursor.sass create mode 100644 src/components/not-found/CodeBlock.js create mode 100644 src/components/not-found/CodeBlock.sass create mode 100644 src/components/not-found/TerminalWindow.js create mode 100644 src/components/not-found/TerminalWindow.sass create mode 100644 src/components/projects/FilterButton.js create mode 100644 src/components/projects/FilterButton.sass create mode 100644 src/components/projects/FilterPanel.js create mode 100644 src/components/projects/FilterPanel.sass create mode 100644 src/components/projects/NewProjectButton.js create mode 100644 src/components/projects/NewProjectButton.sass create mode 100644 src/components/projects/NoProjectsAlert.js create mode 100644 src/components/projects/NoProjectsAlert.sass create mode 100644 src/components/projects/ProjectAuth.js create mode 100644 src/components/projects/ProjectAuthList.js create mode 100644 src/components/projects/ProjectAuthList.sass create mode 100644 src/containers/projects/FilterLink.js create mode 100644 src/containers/projects/NewProjectModal.js create mode 100644 src/containers/projects/VisibleProjectAuthList.js create mode 100644 src/pages/NotFound.js create mode 100644 src/pages/Projects.sass create mode 100644 src/reducers/index.js create mode 100644 src/reducers/projects.js create mode 100644 src/routes/index.js create mode 100644 src/shapes/index.js create mode 100644 src/store/configureStore.js create mode 100644 src/style-globals/_mixins.sass create mode 100644 src/style-globals/_variables.sass delete mode 100644 src/style-globals/mixins.sass delete mode 100644 src/style-globals/variables.sass create mode 100644 src/util/authorizations.js diff --git a/package.json b/package.json index 7178e213..03fbeb84 100644 --- a/package.json +++ b/package.json @@ -4,16 +4,24 @@ "private": true, "dependencies": { "bootstrap": "4.0.0-alpha.6", + "classnames": "^2.2.5", "history": "^4.6.3", + "isomorphic-fetch": "^2.2.1", "node-sass-chokidar": "^0.0.3", + "normalizr": "^3.2.3", "npm-run-all": "^4.0.2", + "prop-types": "^15.5.10", "react": "^15.6.1", "react-dom": "^15.6.1", "react-fontawesome": "^1.6.1", "react-google-login": "^2.9.2", "react-mailto": "^0.4.0", + "react-redux": "^5.0.5", "react-router-dom": "^4.1.1", - "react-scripts": "1.0.10" + "react-scripts": "1.0.10", + "redux": "^3.7.2", + "redux-logger": "^3.0.6", + "redux-thunk": "^2.2.0" }, "scripts": { "build-css": "node-sass-chokidar src/ -o src/", diff --git a/public/index.html b/public/index.html index 8429885a..54689af2 100644 --- a/public/index.html +++ b/public/index.html @@ -18,7 +18,8 @@ content="311799954046-jv2inpg9nu7m0avcg6gulvkuvfgbtgb4.apps.googleusercontent.com"> + integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous"> + @@ -27,10 +28,13 @@
+ integrity="sha384-A7FZj7v+d/sdmMqp/nOQwliLvUsJfDHW+k9Omg/a/EheAdgtzNs3hpfag6Ed950n" + crossorigin="anonymous"> + integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb" + crossorigin="anonymous"> + integrity="sha384-vBWWzlZJ8ea9aCX4pEW3rVHjgjt7zpkNpZk+02D9phzyeVkE+jo0ieGizqPLForn" + crossorigin="anonymous"> diff --git a/src/actions/projects.js b/src/actions/projects.js new file mode 100644 index 00000000..57304326 --- /dev/null +++ b/src/actions/projects.js @@ -0,0 +1,38 @@ +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 setAuthVisibilityFilter = (filter) => { + return { + type: SET_AUTH_VISIBILITY_FILTER, + filter: filter + }; +}; + +export const openNewProjectModal = () => { + return { + type: OPEN_NEW_PROJECT_MODAL + }; +}; + +export const closeNewProjectModal = () => { + return { + type: CLOSE_NEW_PROJECT_MODAL + }; +}; + +export const addProject = (name) => { + return { + type: ADD_PROJECT, + name + }; +}; + +export const deleteProject = (id) => { + return { + type: DELETE_PROJECT, + id + }; +}; diff --git a/src/components/modals/Modal.js b/src/components/modals/Modal.js new file mode 100644 index 00000000..e2d19fcb --- /dev/null +++ b/src/components/modals/Modal.js @@ -0,0 +1,82 @@ +import PropTypes from "prop-types"; +import React from "react"; + +class Modal extends React.Component { + static propTypes = { + title: PropTypes.string.isRequired, + show: PropTypes.bool.isRequired, + onSubmit: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, + }; + static idCounter = 0; + + constructor() { + super(); + this.id = "modal-" + Modal.idCounter; + } + + componentDidMount() { + this.openOrCloseModal(); + } + + componentDidUpdate() { + this.openOrCloseModal(); + } + + onSubmit() { + this.props.onSubmit(); + this.closeModal(); + } + + onCancel() { + this.props.onCancel(); + this.closeModal(); + } + + openModal() { + window["$"]("#" + this.id).modal("show"); + } + + closeModal() { + window["$"]("#" + this.id).modal("hide"); + } + + openOrCloseModal() { + if (this.props.show) { + this.openModal(); + } else { + this.closeModal(); + } + } + + render() { + return ( + + ); + } +} + +export default Modal; diff --git a/src/components/modals/TextInputModal.js b/src/components/modals/TextInputModal.js new file mode 100644 index 00000000..4acf25b3 --- /dev/null +++ b/src/components/modals/TextInputModal.js @@ -0,0 +1,41 @@ +import PropTypes from "prop-types"; +import React from "react"; +import Modal from "./Modal"; + +class TextInputModal extends React.Component { + static propTypes = { + title: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + show: PropTypes.bool.isRequired, + callback: PropTypes.func.isRequired, + initialValue: PropTypes.string, + }; + + onSubmit() { + this.props.callback(this.refs.textInput.value); + this.refs.textInput.value = ""; + } + + onCancel() { + this.props.callback(undefined); + this.refs.textInput.value = ""; + } + + render() { + return ( + +
+
+ + +
+
+
+ ); + } +} + +export default TextInputModal; diff --git a/src/components/navigation/Navbar.js b/src/components/navigation/Navbar.js index a5f0510f..bd6fd750 100644 --- a/src/components/navigation/Navbar.js +++ b/src/components/navigation/Navbar.js @@ -1,8 +1,8 @@ import React, {Component} from 'react'; +import FontAwesome from "react-fontawesome"; +import Mailto from "react-mailto"; import {Link} from "react-router-dom"; import "./Navbar.css"; -import Mailto from "react-mailto"; -import FontAwesome from "react-fontawesome"; class Navbar extends Component { render() { @@ -19,7 +19,7 @@ class Navbar extends Component {
+ headers={{subject: "OpenDC Support"}}> Profile diff --git a/src/components/navigation/Navbar.sass b/src/components/navigation/Navbar.sass index d40eecfb..a592eab0 100644 --- a/src/components/navigation/Navbar.sass +++ b/src/components/navigation/Navbar.sass @@ -1,5 +1,5 @@ -@import ../../style-globals/mixins.sass -@import ../../style-globals/variables.sass +@import ../../style-globals/_mixins.sass +@import ../../style-globals/_variables.sass .opendc-navbar position: relative diff --git a/src/components/not-found/BlinkingCursor.js b/src/components/not-found/BlinkingCursor.js new file mode 100644 index 00000000..f6c9768c --- /dev/null +++ b/src/components/not-found/BlinkingCursor.js @@ -0,0 +1,8 @@ +import React from "react"; +import "./BlinkingCursor.css"; + +const BlinkingCursor = () => ( + _ +); + +export default BlinkingCursor; diff --git a/src/components/not-found/BlinkingCursor.sass b/src/components/not-found/BlinkingCursor.sass new file mode 100644 index 00000000..6be1476d --- /dev/null +++ b/src/components/not-found/BlinkingCursor.sass @@ -0,0 +1,35 @@ +.blinking-cursor + -webkit-animation: 1s blink step-end infinite + -moz-animation: 1s blink step-end infinite + -o-animation: 1s blink step-end infinite + animation: 1s blink step-end infinite + +@keyframes blink + from, to + color: #eeeeee + 50% + color: #333333 + +@-moz-keyframes blink + from, to + color: #eeeeee + 50% + color: #333333 + +@-webkit-keyframes blink + from, to + color: #eeeeee + 50% + color: #333333 + +@-ms-keyframes blink + from, to + color: #eeeeee + 50% + color: #333333 + +@-o-keyframes blink + from, to + color: #eeeeee + 50% + color: #333333 diff --git a/src/components/not-found/CodeBlock.js b/src/components/not-found/CodeBlock.js new file mode 100644 index 00000000..24d100cc --- /dev/null +++ b/src/components/not-found/CodeBlock.js @@ -0,0 +1,30 @@ +import React from "react"; +import "./CodeBlock.css"; + +const CodeBlock = () => { + const textBlock = + " oo oooo oo
" + + " oo oo oo oo
" + + " oo oo oo oo
" + + " oooooo oo oo oooooo
" + + " oo oo oo oo
" + + " oo oooo oo
"; + const charList = textBlock.split(''); + + // Binary representation of the string "OpenDC!" ;) + const binaryString = "01001111011100000110010101101110010001000100001100100001"; + + let binaryIndex = 0; + for (let i = 0; i < charList.length; i++) { + if (charList[i] === "o") { + charList[i] = binaryString[binaryIndex]; + binaryIndex++; + } + } + + return ( +
+ ); +}; + +export default CodeBlock; diff --git a/src/components/not-found/CodeBlock.sass b/src/components/not-found/CodeBlock.sass new file mode 100644 index 00000000..51a3d3d0 --- /dev/null +++ b/src/components/not-found/CodeBlock.sass @@ -0,0 +1,3 @@ +.code-block + white-space: pre-wrap + margin-top: 60px diff --git a/src/components/not-found/TerminalWindow.js b/src/components/not-found/TerminalWindow.js new file mode 100644 index 00000000..52d3c062 --- /dev/null +++ b/src/components/not-found/TerminalWindow.js @@ -0,0 +1,26 @@ +import React from "react"; +import {Link} from "react-router-dom"; +import BlinkingCursor from "./BlinkingCursor"; +import CodeBlock from "./CodeBlock"; +import "./TerminalWindow.css"; + +const TerminalWindow = () => ( +
+
Terminal -- bash
+
+
$ status
+ opendc[4264]: segfault at 0000051497be459d1 err 12 in libopendc.9.0.4
+ opendc[4269]: segfault at 000004234855fc2db err 3 in libopendc.9.0.4
+ opendc[4270]: STDERR Page does not exist
+
+ +
Got lost?
+ + GET ME BACK TO OPENDC + +
+
+); + + +export default TerminalWindow; diff --git a/src/components/not-found/TerminalWindow.sass b/src/components/not-found/TerminalWindow.sass new file mode 100644 index 00000000..4f51a77f --- /dev/null +++ b/src/components/not-found/TerminalWindow.sass @@ -0,0 +1,70 @@ +.terminal-window + width: 600px + height: 400px + display: block + + position: absolute + top: 0 + bottom: 0 + left: 0 + right: 0 + + margin: auto + + -webkit-user-select: none + -moz-user-select: none + -ms-user-select: none + user-select: none + cursor: default + + overflow: hidden + + box-shadow: 5px 5px 20px #444444 + +.terminal-header + font-family: monospace + background: #cccccc + color: #444444 + height: 30px + line-height: 30px + padding-left: 10px + + border-top-left-radius: 7px + border-top-right-radius: 7px + +.terminal-body + font-family: monospace + text-align: center + background-color: #333333 + color: #eeeeee + padding: 10px + + height: 100% + +.segfault + text-align: left + +.sub-title + margin-top: 20px + +.home-btn + margin-top: 10px + padding: 5px + display: inline-block + border: 1px solid #eeeeee + color: #eeeeee + text-decoration: none + cursor: pointer + + -webkit-transition: all 200ms + -moz-transition: all 200ms + -o-transition: all 200ms + transition: all 200ms + +.home-btn:hover + background: #eeeeee + color: #333333 + +.home-btn:active + background: #333333 + color: #eeeeee diff --git a/src/components/projects/FilterButton.js b/src/components/projects/FilterButton.js new file mode 100644 index 00000000..8d6b7146 --- /dev/null +++ b/src/components/projects/FilterButton.js @@ -0,0 +1,23 @@ +import classNames from 'classnames'; +import PropTypes from 'prop-types'; +import React from 'react'; +import "./FilterButton.css"; + +const FilterButton = ({active, children, onClick}) => ( +
{ + if (!active) { + onClick(); + } + }}> + {children} +
+); + +FilterButton.propTypes = { + active: PropTypes.bool.isRequired, + children: PropTypes.node.isRequired, + onClick: PropTypes.func.isRequired +}; + +export default FilterButton; diff --git a/src/components/projects/FilterButton.sass b/src/components/projects/FilterButton.sass new file mode 100644 index 00000000..0cad68e3 --- /dev/null +++ b/src/components/projects/FilterButton.sass @@ -0,0 +1,23 @@ +@import ../../style-globals/_mixins.sass +@import ../../style-globals/_variables.sass + +.project-filter-button + display: inline-block + width: 33.3% + //margin-right: -4px + padding: 10px $global-padding + + font-size: 12pt + border-right: 1px solid #06326b + + +clickable + +transition(background, $transition-length) + +.project-filter-button:last-of-type + border: 0 + +.project-filter-button:hover + background: #0c60bf + +.project-filter-button:active, .project-filter-button.active + background: #073d7d diff --git a/src/components/projects/FilterPanel.js b/src/components/projects/FilterPanel.js new file mode 100644 index 00000000..050bf0aa --- /dev/null +++ b/src/components/projects/FilterPanel.js @@ -0,0 +1,15 @@ +import React from 'react'; +import FilterLink from "../../containers/projects/FilterLink"; +import "./FilterPanel.css"; + +const ProjectFilterPanel = () => ( +
+
+ All Projects + My Projects + Projects shared with me +
+
+); + +export default ProjectFilterPanel; diff --git a/src/components/projects/FilterPanel.sass b/src/components/projects/FilterPanel.sass new file mode 100644 index 00000000..a70c7a90 --- /dev/null +++ b/src/components/projects/FilterPanel.sass @@ -0,0 +1,21 @@ +@import ../../style-globals/_mixins.sass +@import ../../style-globals/_variables.sass + +.filter-menu + display: block + + background: #0761b1 + border: 1px solid #06326b + color: #eee + + text-align: center + + +border-radius($standard-border-radius) + overflow: hidden + + margin-bottom: 20px + + .project-filters + display: block + overflow: hidden + margin: 0 -1px diff --git a/src/components/projects/NewProjectButton.js b/src/components/projects/NewProjectButton.js new file mode 100644 index 00000000..9eaf6df4 --- /dev/null +++ b/src/components/projects/NewProjectButton.js @@ -0,0 +1,16 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import './NewProjectButton.css'; + +const NewProjectButton = ({onClick}) => ( +
+ + New Project +
+); + +NewProjectButton.propTypes = { + onClick: PropTypes.func.isRequired, +}; + +export default NewProjectButton; diff --git a/src/components/projects/NewProjectButton.sass b/src/components/projects/NewProjectButton.sass new file mode 100644 index 00000000..89435902 --- /dev/null +++ b/src/components/projects/NewProjectButton.sass @@ -0,0 +1,31 @@ +@import ../../style-globals/_mixins.sass +@import ../../style-globals/_variables.sass + +.new-project-btn + $button-height: 35px + + display: inline-block + position: absolute + bottom: $navbar-height + 40px + right: 40px + padding: 0 10px + height: $button-height + line-height: $button-height + font-size: 14pt + + background: #679436 + color: #eee + border: 1px solid #507830 + + +border-radius($standard-border-radius) + +clickable + +transition(all, $transition-length) + + span + margin-right: 10px + +.new-project-btn:hover + background: #73ac45 + +.new-project-btn:active + background: #5c8835 diff --git a/src/components/projects/NoProjectsAlert.js b/src/components/projects/NoProjectsAlert.js new file mode 100644 index 00000000..957435c7 --- /dev/null +++ b/src/components/projects/NoProjectsAlert.js @@ -0,0 +1,11 @@ +import React from 'react'; +import "./NoProjectsAlert.css"; + +const NoProjectsAlert = () => ( +
+ + No projects here yet... Add some with the 'New Project' button! +
+); + +export default NoProjectsAlert; diff --git a/src/components/projects/NoProjectsAlert.sass b/src/components/projects/NoProjectsAlert.sass new file mode 100644 index 00000000..a526f9ad --- /dev/null +++ b/src/components/projects/NoProjectsAlert.sass @@ -0,0 +1,10 @@ +.no-projects-alert + position: relative + padding-left: 50px + + .info-icon + position: absolute + top: 11px + left: 15px + bottom: 10px + font-size: 20pt diff --git a/src/components/projects/ProjectAuth.js b/src/components/projects/ProjectAuth.js new file mode 100644 index 00000000..7e3abae1 --- /dev/null +++ b/src/components/projects/ProjectAuth.js @@ -0,0 +1,22 @@ +import classNames from 'classnames'; +import React from 'react'; +import Shapes from "../../shapes/index"; +import {AUTH_DESCRIPTION_MAP, AUTH_ICON_MAP} from "../../util/authorizations"; +import {parseAndFormatDateTime} from "../../util/date-time"; + +const ProjectAuth = ({projectAuth}) => ( +
+
{projectAuth.simulation.name}
+
{parseAndFormatDateTime(projectAuth.simulation.datetimeLastEdited)}
+
+ + {AUTH_DESCRIPTION_MAP[projectAuth.authorizationLevel]} +
+
+); + +ProjectAuth.propTypes = { + projectAuth: Shapes.Authorization.isRequired, +}; + +export default ProjectAuth; diff --git a/src/components/projects/ProjectAuthList.js b/src/components/projects/ProjectAuthList.js new file mode 100644 index 00000000..093b3279 --- /dev/null +++ b/src/components/projects/ProjectAuthList.js @@ -0,0 +1,31 @@ +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"; + +const ProjectAuthList = ({authorizations, onProjectClick}) => ( +
+
+
Project name
+
Last edited
+
Access rights
+
+
+ {authorizations.length === 0 ? + : + authorizations.map(authorization => ( + + )) + } +
+
+); + +ProjectAuthList.propTypes = { + authorizations: PropTypes.arrayOf(Shapes.Authorization).isRequired, + onProjectClick: PropTypes.func.isRequired, +}; + +export default ProjectAuthList; diff --git a/src/components/projects/ProjectAuthList.sass b/src/components/projects/ProjectAuthList.sass new file mode 100644 index 00000000..86e1123c --- /dev/null +++ b/src/components/projects/ProjectAuthList.sass @@ -0,0 +1,98 @@ +@import ../../style-globals/_mixins.sass +@import ../../style-globals/_variables.sass + +.project-list + display: block + font-size: 12pt + border: 0 + + .list-head, .list-body .project-row + display: block + position: relative + + .list-head div, .list-body .project-row div + padding: 0 10px + display: inline-block + + .list-head + font-weight: bold + + // Address default margin between inline-blocks + div + margin-right: -4px + +.project-row + background: #f8f8f8 + border: 1px solid #b6b6b6 + height: 40px + line-height: 40px + clear: both + + +transition(background, $transition-length) + +clickable + +.project-row:hover + background: #fff + +.project-row:active + background: #cccccc + +.project-row:not(:first-of-type) + margin-top: -1px + +// Sizing of table columns +.project-row, .project-list .list-head + div:first-of-type + width: 50% + + div:nth-of-type(2) + width: 30% + + div:last-of-type + width: 20% + + span + margin-right: 10px + +.project-row.active + border-bottom: 0 + background: #3442b1 + color: #eee + +.project-view + padding: 10px + overflow: hidden + border: 1px solid #b6b6b6 + border-top: 0 + + background: #3442b1 + color: #eee + + .participants + display: inline-block + float: left + + .access-buttons + display: inline-block + float: right + + .inline-btn + margin-left: 10px + + .open + background: #e38829 + + .open:hover + background: #ff992e + + .open:active + background: #ba6f21 + + .edit + background: #2c3897 + + .edit:hover + background: #3a4ac8 + + .edit:active + background: #242d7a diff --git a/src/containers/projects/FilterLink.js b/src/containers/projects/FilterLink.js new file mode 100644 index 00000000..e9a13436 --- /dev/null +++ b/src/containers/projects/FilterLink.js @@ -0,0 +1,22 @@ +import {connect} from "react-redux"; +import {setAuthVisibilityFilter} from "../../actions/projects"; +import FilterButton from "../../components/projects/FilterButton"; + +const mapStateToProps = (state, ownProps) => { + return { + active: state.authVisibilityFilter === ownProps.filter + }; +}; + +const mapDispatchToProps = (dispatch, ownProps) => { + return { + onClick: () => dispatch(setAuthVisibilityFilter(ownProps.filter)) + }; +}; + +const FilterLink = connect( + mapStateToProps, + mapDispatchToProps +)(FilterButton); + +export default FilterLink; diff --git a/src/containers/projects/NewProjectModal.js b/src/containers/projects/NewProjectModal.js new file mode 100644 index 00000000..7321cb4d --- /dev/null +++ b/src/containers/projects/NewProjectModal.js @@ -0,0 +1,34 @@ +import React from "react"; +import {connect} from "react-redux"; +import {addProject, closeNewProjectModal} from "../../actions/projects"; +import TextInputModal from "../../components/modals/TextInputModal"; + +const NewProjectModalComponent = ({visible, callback}) => ( + +); + +const mapStateToProps = state => { + return { + visible: state.newProjectModalVisible + }; +}; + +const mapDispatchToProps = dispatch => { + return { + callback: (text) => { + if (text) { + dispatch(addProject(text)); + dispatch(closeNewProjectModal()); + } + } + }; +}; + +const NewProjectModal = connect( + mapStateToProps, + mapDispatchToProps +)(NewProjectModalComponent); + +export default NewProjectModal; diff --git a/src/containers/projects/VisibleProjectAuthList.js b/src/containers/projects/VisibleProjectAuthList.js new file mode 100644 index 00000000..746380f6 --- /dev/null +++ b/src/containers/projects/VisibleProjectAuthList.js @@ -0,0 +1,25 @@ +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 => { + return { + authorizations: getVisibleProjectAuths(state.authorizations, state.authVisibilityFilter) + }; +}; + +const VisibleProjectAuthList = connect(mapStateToProps)(ProjectList); + +export default VisibleProjectAuthList; diff --git a/src/index.js b/src/index.js index 25c33e82..1176224e 100644 --- a/src/index.js +++ b/src/index.js @@ -1,20 +1,17 @@ import React from "react"; import ReactDOM from "react-dom"; -import {BrowserRouter, Route, Switch} from "react-router-dom"; +import {Provider} from "react-redux"; import "./index.css"; import registerServiceWorker from "./registerServiceWorker"; -import Home from "./pages/Home"; -import Projects from "./pages/Projects"; -import NotFound from "./pages/NotFound"; +import Routes from "./routes"; +import configureStore from "./store/configureStore"; + +const store = configureStore(); ReactDOM.render( - - - - - - - , + + + , document.getElementById('root') ); diff --git a/src/index.sass b/src/index.sass index 1ea3b0c5..80c72a77 100644 --- a/src/index.sass +++ b/src/index.sass @@ -1,12 +1,15 @@ -body +html, body, #root margin: 0 padding: 0 width: 100% height: 100% - font-family: Helvetica, Verdana, sans-serif + font-family: Roboto, Helvetica, Verdana, sans-serif overflow: hidden background: #eee +.full-height + height: 100% + a, a:hover text-decoration: none diff --git a/src/pages/NotFound.js b/src/pages/NotFound.js new file mode 100644 index 00000000..51141c3e --- /dev/null +++ b/src/pages/NotFound.js @@ -0,0 +1,11 @@ +import React from 'react'; +import TerminalWindow from "../components/not-found/TerminalWindow"; +import './NotFound.css'; + +const NotFound = () => ( +
+ +
+); + +export default NotFound; diff --git a/src/pages/Projects.js b/src/pages/Projects.js index 6d377e92..40902d97 100644 --- a/src/pages/Projects.js +++ b/src/pages/Projects.js @@ -1,8 +1,35 @@ import React from 'react'; +import {connect} from "react-redux"; +import {addProject, openNewProjectModal} from "../actions/projects"; import Navbar from "../components/navigation/Navbar"; +import ProjectFilterPanel from "../components/projects/FilterPanel"; +import NewProjectButton from "../components/projects/NewProjectButton"; +import NewProjectModal from "../containers/projects/NewProjectModal"; +import VisibleProjectList from "../containers/projects/VisibleProjectAuthList"; +import "./Projects.css"; -const Projects = () => ( - -); +class Projects extends React.Component { + componentDidMount() { + // TODO perform initial fetch + } -export default Projects; + onInputSubmission(text) { + this.props.dispatch(addProject(text)); + } + + render() { + return ( +
+ +
+ + + {this.props.dispatch(openNewProjectModal())}}/> +
+ +
+ ); + } +} + +export default connect()(Projects); diff --git a/src/pages/Projects.sass b/src/pages/Projects.sass new file mode 100644 index 00000000..11a52e1a --- /dev/null +++ b/src/pages/Projects.sass @@ -0,0 +1,2 @@ +.project-page-container + padding-top: 2rem diff --git a/src/reducers/index.js b/src/reducers/index.js new file mode 100644 index 00000000..3974ce4a --- /dev/null +++ b/src/reducers/index.js @@ -0,0 +1,10 @@ +import {combineReducers} from "redux"; +import {authorizations, authVisibilityFilter, newProjectModalVisible} from "./projects"; + +const rootReducer = combineReducers({ + authorizations, + newProjectModalVisible, + authVisibilityFilter, +}); + +export default rootReducer; diff --git a/src/reducers/projects.js b/src/reducers/projects.js new file mode 100644 index 00000000..20f17a3c --- /dev/null +++ b/src/reducers/projects.js @@ -0,0 +1,42 @@ +import { + ADD_PROJECT, + CLOSE_NEW_PROJECT_MODAL, + OPEN_NEW_PROJECT_MODAL, + SET_AUTH_VISIBILITY_FILTER +} from "../actions/projects"; + +export const authorizations = (state = [], action) => { + switch (action.type) { + case ADD_PROJECT: + return [ + ...state, + { + userId: -1, + simulation: {name: action.name, datetimeLastEdited: "2017-08-06T12:43:00"}, + authorizationLevel: "OWN" + } + ]; + default: + return state; + } +}; + +export const newProjectModalVisible = (state = false, action) => { + switch (action.type) { + case OPEN_NEW_PROJECT_MODAL: + return true; + case CLOSE_NEW_PROJECT_MODAL: + return false; + default: + return state; + } +}; + +export const authVisibilityFilter = (state = "SHOW_ALL", action) => { + switch (action.type) { + case SET_AUTH_VISIBILITY_FILTER: + return action.filter; + default: + return state; + } +}; diff --git a/src/routes/index.js b/src/routes/index.js new file mode 100644 index 00000000..54dc0703 --- /dev/null +++ b/src/routes/index.js @@ -0,0 +1,17 @@ +import React from 'react'; +import {BrowserRouter, Route, Switch} from "react-router-dom"; +import Home from "../pages/Home"; +import NotFound from "../pages/NotFound"; +import Projects from "../pages/Projects"; + +const Routes = () => ( + + + + + + + +); + +export default Routes; diff --git a/src/shapes/index.js b/src/shapes/index.js new file mode 100644 index 00000000..72153f54 --- /dev/null +++ b/src/shapes/index.js @@ -0,0 +1,28 @@ +import PropTypes from 'prop-types'; + +const Shapes = {}; + +Shapes.User = PropTypes.shape({ + id: PropTypes.number.isRequired, + googleId: PropTypes.number.isRequired, + email: PropTypes.string.isRequired, + givenName: PropTypes.string.isRequired, + familyName: PropTypes.string.isRequired, +}); + +Shapes.Simulation = PropTypes.shape({ + id: PropTypes.number.isRequired, + name: PropTypes.string.isRequired, + datetimeCreated: PropTypes.string.isRequired, + datetimeLastEdited: PropTypes.string.isRequired, +}); + +Shapes.Authorization = PropTypes.shape({ + userId: PropTypes.number.isRequired, + user: Shapes.User, + simulationId: PropTypes.number.isRequired, + simulation: Shapes.Simulation, + authorizationLevel: PropTypes.string.isRequired, +}); + +export default Shapes; \ No newline at end of file diff --git a/src/store/configureStore.js b/src/store/configureStore.js new file mode 100644 index 00000000..9584f591 --- /dev/null +++ b/src/store/configureStore.js @@ -0,0 +1,16 @@ +import {applyMiddleware, createStore} from "redux"; +import {createLogger} from "redux-logger"; +import thunkMiddleware from "redux-thunk"; +import rootReducer from "../reducers/index"; + +const logger = createLogger(); + +const configureStore = () => createStore( + rootReducer, + applyMiddleware( + thunkMiddleware, + logger, + ) +); + +export default configureStore; diff --git a/src/style-globals/_mixins.sass b/src/style-globals/_mixins.sass new file mode 100644 index 00000000..4ac5a9bc --- /dev/null +++ b/src/style-globals/_mixins.sass @@ -0,0 +1,21 @@ +=transition($property, $time) + -webkit-transition: $property $time + -moz-transition: $property $time + -o-transition: $property $time + transition: $property $time + +=user-select + -webkit-user-select: none + -moz-user-select: none + -ms-user-select: none + user-select: none + +=border-radius($length) + -webkit-border-radius: $length + -moz-border-radius: $length + border-radius: $length + +/* General Button Abstractions */ +=clickable + cursor: pointer + +user-select diff --git a/src/style-globals/_variables.sass b/src/style-globals/_variables.sass new file mode 100644 index 00000000..4386059d --- /dev/null +++ b/src/style-globals/_variables.sass @@ -0,0 +1,25 @@ +// Sizes and Margins +$document-padding: 20px +$inter-element-margin: 5px +$standard-border-radius: 5px +$side-menu-width: 350px +$color-indicator-width: 140px + +$global-padding: 30px +$side-bar-width: 250px +$navbar-height: 50px +$navbar-padding: 10px + +// Durations +$transition-length: 150ms + +// Colors +$gray-dark: #aaa +$gray-semi-dark: #bbb +$gray-semi-light: #ccc +$gray-light: #ddd +$gray-very-light: #eee +$blue: #00A6D6 +$blue-dark: #0087b5 +$blue-very-dark: #006182 +$blue-light: #deebf7 diff --git a/src/style-globals/mixins.sass b/src/style-globals/mixins.sass deleted file mode 100644 index 40c07a6d..00000000 --- a/src/style-globals/mixins.sass +++ /dev/null @@ -1,21 +0,0 @@ -=transition($property, $time) - -webkit-transition: $property $time - -moz-transition: $property $time - -o-transition: $property $time - transition: $property $time - -=user-select-def - -webkit-user-select: none - -moz-user-select: none - -ms-user-select: none - user-select: none - -=border-radius-def($length) - -webkit-border-radius: $length - -moz-border-radius: $length - border-radius: $length - -/* General Button Abstractions */ -=clickable - cursor: pointer - +user-select-def diff --git a/src/style-globals/variables.sass b/src/style-globals/variables.sass deleted file mode 100644 index 4386059d..00000000 --- a/src/style-globals/variables.sass +++ /dev/null @@ -1,25 +0,0 @@ -// Sizes and Margins -$document-padding: 20px -$inter-element-margin: 5px -$standard-border-radius: 5px -$side-menu-width: 350px -$color-indicator-width: 140px - -$global-padding: 30px -$side-bar-width: 250px -$navbar-height: 50px -$navbar-padding: 10px - -// Durations -$transition-length: 150ms - -// Colors -$gray-dark: #aaa -$gray-semi-dark: #bbb -$gray-semi-light: #ccc -$gray-light: #ddd -$gray-very-light: #eee -$blue: #00A6D6 -$blue-dark: #0087b5 -$blue-very-dark: #006182 -$blue-light: #deebf7 diff --git a/src/util/authorizations.js b/src/util/authorizations.js new file mode 100644 index 00000000..9a7d4e36 --- /dev/null +++ b/src/util/authorizations.js @@ -0,0 +1,11 @@ +export const AUTH_ICON_MAP = { + "OWN": "home", + "EDIT": "pencil", + "VIEW": "eye", +}; + +export const AUTH_DESCRIPTION_MAP = { + "OWN": "Own", + "EDIT": "Can Edit", + "VIEW": "Can View", +}; diff --git a/src/util/date-time.js b/src/util/date-time.js index f8a2ac45..0093e846 100644 --- a/src/util/date-time.js +++ b/src/util/date-time.js @@ -1,3 +1,15 @@ +/** + * Parses and formats the given date-time string representation. + * + * The format assumed is "YYYY-MM-DDTHH:MM:SS". + * + * @param dateTimeString A string expressing a date and a time, in the above mentioned format. + * @returns {string} A human-friendly string version of that date and time. + */ +export function parseAndFormatDateTime(dateTimeString) { + return formatDateTime(parseDateTime(dateTimeString)); +} + /** * Parses date-time string representations and returns a parsed object. * @@ -13,30 +25,28 @@ export function parseDateTime(dateTimeString) { day: 0, hour: 0, minute: 0, - second: 0 + second: 0, }; const dateAndTime = dateTimeString.split("T"); const dateComponents = dateAndTime[0].split("-"); - output.year = parseInt(dateComponents[0]); - output.month = parseInt(dateComponents[1]); - output.day = parseInt(dateComponents[2]); + output.year = parseInt(dateComponents[0], 10); + output.month = parseInt(dateComponents[1], 10); + output.day = parseInt(dateComponents[2], 10); const timeComponents = dateAndTime[1].split(":"); - output.hour = parseInt(timeComponents[0]); - output.minute = parseInt(timeComponents[1]); - output.second = parseInt(timeComponents[2]); + output.hour = parseInt(timeComponents[0], 10); + output.minute = parseInt(timeComponents[1], 10); + output.second = parseInt(timeComponents[2], 10); return output; } /** - * Serializes the given date and time value to a string. - * - * The format assumed is "YYYY-MM-DDTHH:MM:SS". + * Serializes the given date and time value to a human-friendly string. * * @param dateTime An object representation of a date and time. - * @returns {string} A string representation of that date and time. + * @returns {string} A human-friendly string version of that date and time. */ export function formatDateTime(dateTime) { let date; -- cgit v1.2.3