summaryrefslogtreecommitdiff
path: root/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/components')
-rw-r--r--src/components/modals/Modal.js82
-rw-r--r--src/components/modals/TextInputModal.js41
-rw-r--r--src/components/navigation/Navbar.js6
-rw-r--r--src/components/navigation/Navbar.sass4
-rw-r--r--src/components/not-found/BlinkingCursor.js8
-rw-r--r--src/components/not-found/BlinkingCursor.sass35
-rw-r--r--src/components/not-found/CodeBlock.js30
-rw-r--r--src/components/not-found/CodeBlock.sass3
-rw-r--r--src/components/not-found/TerminalWindow.js26
-rw-r--r--src/components/not-found/TerminalWindow.sass70
-rw-r--r--src/components/projects/FilterButton.js23
-rw-r--r--src/components/projects/FilterButton.sass23
-rw-r--r--src/components/projects/FilterPanel.js15
-rw-r--r--src/components/projects/FilterPanel.sass21
-rw-r--r--src/components/projects/NewProjectButton.js16
-rw-r--r--src/components/projects/NewProjectButton.sass31
-rw-r--r--src/components/projects/NoProjectsAlert.js11
-rw-r--r--src/components/projects/NoProjectsAlert.sass10
-rw-r--r--src/components/projects/ProjectAuth.js22
-rw-r--r--src/components/projects/ProjectAuthList.js31
-rw-r--r--src/components/projects/ProjectAuthList.sass98
21 files changed, 601 insertions, 5 deletions
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 (
+ <div className="modal" id={this.id} role="dialog">
+ <div className="modal-dialog" role="document">
+ <div className="modal-content">
+ <div className="modal-header">
+ <h5 className="modal-title">{this.props.title}</h5>
+ <button type="button" className="close" onClick={this.onCancel.bind(this)}
+ aria-label="Close">
+ <span aria-hidden="true">&times;</span>
+ </button>
+ </div>
+ <div className="modal-body">
+ {this.props.children}
+ </div>
+ <div className="modal-footer">
+ <button type="button" className="btn btn-secondary" onClick={this.onCancel.bind(this)}>
+ Close
+ </button>
+ <button type="button" className="btn btn-primary" onClick={this.onSubmit.bind(this)}>
+ Save
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
+
+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 (
+ <Modal title={this.props.title}
+ show={this.props.show}
+ onSubmit={this.onSubmit.bind(this)}
+ onCancel={this.onCancel.bind(this)}>
+ <form>
+ <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}/>
+ </div>
+ </form>
+ </Modal>
+ );
+ }
+}
+
+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 {
</div>
<div className="user-controls navbar-button-group">
<Mailto className="support" title="Support" email="opendc.tudelft@gmail.com"
- headers={{subject: "OpenDC%20Support"}}>
+ headers={{subject: "OpenDC Support"}}>
<FontAwesome name="question-circle" size="lg"/>
</Mailto>
<Link className="username" title="My Profile" to="/profile">Profile</Link>
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 = () => (
+ <span className="blinking-cursor">_</span>
+);
+
+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 <br/>" +
+ " oo oo oo oo <br/>" +
+ " oo oo oo oo <br/>" +
+ " oooooo oo oo oooooo <br/>" +
+ " oo oo oo oo <br/>" +
+ " oo oooo oo <br/>";
+ 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 (
+ <div className="code-block" dangerouslySetInnerHTML={{__html: textBlock}}/>
+ );
+};
+
+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 = () => (
+ <div className="terminal-window">
+ <div className="terminal-header">Terminal -- bash</div>
+ <div className="terminal-body">
+ <div className="segfault">$ status<br/>
+ opendc[4264]: segfault at 0000051497be459d1 err 12 in libopendc.9.0.4<br/>
+ opendc[4269]: segfault at 000004234855fc2db err 3 in libopendc.9.0.4<br/>
+ opendc[4270]: STDERR Page does not exist<br/>
+ </div>
+ <CodeBlock/>
+ <div className="sub-title">Got lost?<BlinkingCursor/></div>
+ <Link to="/" className="home-btn">
+ <span className="fa fa-home"/> GET ME BACK TO OPENDC
+ </Link>
+ </div>
+ </div>
+);
+
+
+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}) => (
+ <div className={classNames("project-filter-button", {"active": active})}
+ onClick={() => {
+ if (!active) {
+ onClick();
+ }
+ }}>
+ {children}
+ </div>
+);
+
+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 = () => (
+ <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/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}) => (
+ <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/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 = () => (
+ <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/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}) => (
+ <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>
+ </div>
+);
+
+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}) => (
+ <div className="project-list">
+ <div className="list-head">
+ <div>Project name</div>
+ <div>Last edited</div>
+ <div>Access rights</div>
+ </div>
+ <div className="list-body">
+ {authorizations.length === 0 ?
+ <NoProjectsAlert/> :
+ authorizations.map(authorization => (
+ <ProjectAuth projectAuth={authorization} key={authorization.simulation.id}/>
+ ))
+ }
+ </div>
+ </div>
+);
+
+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