diff options
Diffstat (limited to 'frontend/src/components/modals')
4 files changed, 331 insertions, 0 deletions
diff --git a/frontend/src/components/modals/ConfirmationModal.js b/frontend/src/components/modals/ConfirmationModal.js new file mode 100644 index 00000000..abdce5ac --- /dev/null +++ b/frontend/src/components/modals/ConfirmationModal.js @@ -0,0 +1,37 @@ +import PropTypes from "prop-types"; +import React from "react"; +import Modal from "./Modal"; + +class ConfirmationModal extends React.Component { + static propTypes = { + title: PropTypes.string.isRequired, + message: PropTypes.string.isRequired, + show: PropTypes.bool.isRequired, + callback: PropTypes.func.isRequired + }; + + onConfirm() { + this.props.callback(true); + } + + onCancel() { + this.props.callback(false); + } + + render() { + return ( + <Modal + title={this.props.title} + show={this.props.show} + onSubmit={this.onConfirm.bind(this)} + onCancel={this.onCancel.bind(this)} + submitButtonType="danger" + submitButtonText="Confirm" + > + {this.props.message} + </Modal> + ); + } +} + +export default ConfirmationModal; diff --git a/frontend/src/components/modals/Modal.js b/frontend/src/components/modals/Modal.js new file mode 100644 index 00000000..19337db8 --- /dev/null +++ b/frontend/src/components/modals/Modal.js @@ -0,0 +1,132 @@ +import classNames from "classnames"; +import PropTypes from "prop-types"; +import React from "react"; +import jQuery from "../../util/jquery"; + +class Modal extends React.Component { + static propTypes = { + title: PropTypes.string.isRequired, + show: PropTypes.bool.isRequired, + onSubmit: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, + submitButtonType: PropTypes.string, + submitButtonText: PropTypes.string + }; + static defaultProps = { + submitButtonType: "primary", + submitButtonText: "Save" + }; + static idCounter = 0; + + // 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(props) { + super(props); + this.id = "modal-" + Modal.idCounter++; + } + + componentDidMount() { + this.visible = this.props.show; + this.openOrCloseModal(); + + // Trigger auto-focus + jQuery("#" + this.id) + .on("shown.bs.modal", function() { + jQuery(this) + .find("input") + .first() + .focus(); + }) + .on("hide.bs.modal", () => { + if (this.visible) { + this.props.onCancel(); + } + }) + .on("keydown", e => { + e.stopPropagation(); + }); + } + + componentDidUpdate() { + this.visible = this.props.show; + this.openOrCloseModal(); + } + + onSubmit() { + if (this.visible) { + this.props.onSubmit(); + this.visible = false; + this.closeModal(); + } + } + + onCancel() { + if (this.visible) { + this.props.onCancel(); + this.visible = false; + this.closeModal(); + } + } + + openModal() { + jQuery("#" + this.id).modal("show"); + } + + closeModal() { + jQuery("#" + this.id).modal("hide"); + } + + openOrCloseModal() { + if (this.visible) { + this.openModal(); + } else { + this.closeModal(); + } + } + + render() { + return ( + <div className="modal fade" 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>×</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={classNames( + "btn", + "btn-" + this.props.submitButtonType + )} + onClick={this.onSubmit.bind(this)} + > + {this.props.submitButtonText} + </button> + </div> + </div> + </div> + </div> + ); + } +} + +export default Modal; diff --git a/frontend/src/components/modals/TextInputModal.js b/frontend/src/components/modals/TextInputModal.js new file mode 100644 index 00000000..cc16f8e1 --- /dev/null +++ b/frontend/src/components/modals/TextInputModal.js @@ -0,0 +1,58 @@ +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 + }; + + componentDidUpdate() { + if (this.props.initialValue) { + this.textInput.value = this.props.initialValue; + } + } + + onSubmit() { + this.props.callback(this.textInput.value); + this.textInput.value = ""; + } + + onCancel() { + this.props.callback(undefined); + this.textInput.value = ""; + } + + render() { + return ( + <Modal + title={this.props.title} + show={this.props.show} + onSubmit={this.onSubmit.bind(this)} + onCancel={this.onCancel.bind(this)} + > + <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 => (this.textInput = textInput)} + /> + </div> + </form> + </Modal> + ); + } +} + +export default TextInputModal; diff --git a/frontend/src/components/modals/custom-components/NewExperimentModalComponent.js b/frontend/src/components/modals/custom-components/NewExperimentModalComponent.js new file mode 100644 index 00000000..e356fe96 --- /dev/null +++ b/frontend/src/components/modals/custom-components/NewExperimentModalComponent.js @@ -0,0 +1,104 @@ +import PropTypes from "prop-types"; +import React from "react"; +import Shapes from "../../../shapes"; +import Modal from "../Modal"; + +class NewExperimentModalComponent extends React.Component { + static propTypes = { + show: PropTypes.bool.isRequired, + paths: PropTypes.arrayOf(Shapes.Path), + schedulers: PropTypes.arrayOf(Shapes.Scheduler), + traces: PropTypes.arrayOf(Shapes.Trace), + callback: PropTypes.func.isRequired + }; + + reset() { + this.textInput.value = ""; + this.pathSelect.selectedIndex = 0; + this.traceSelect.selectedIndex = 0; + this.schedulerSelect.selectedIndex = 0; + } + + onSubmit() { + this.props.callback( + this.textInput.value, + parseInt(this.pathSelect.value, 10), + parseInt(this.traceSelect.value, 10), + this.schedulerSelect.value + ); + this.reset(); + } + + onCancel() { + this.props.callback(undefined); + this.reset(); + } + + render() { + return ( + <Modal + title="New Experiment" + show={this.props.show} + onSubmit={this.onSubmit.bind(this)} + onCancel={this.onCancel.bind(this)} + > + <form + onSubmit={e => { + e.preventDefault(); + this.onSubmit(); + }} + > + <div className="form-group"> + <label className="form-control-label">Name</label> + <input + type="text" + className="form-control" + ref={textInput => (this.textInput = textInput)} + /> + </div> + <div className="form-group"> + <label className="form-control-label">Path</label> + <select + className="form-control" + ref={pathSelect => (this.pathSelect = pathSelect)} + > + {this.props.paths.map(path => ( + <option value={path.id} key={path.id}> + {path.name ? path.name : "Path " + path.id} + </option> + ))} + </select> + </div> + <div className="form-group"> + <label className="form-control-label">Trace</label> + <select + className="form-control" + ref={traceSelect => (this.traceSelect = traceSelect)} + > + {this.props.traces.map(trace => ( + <option value={trace.id} key={trace.id}> + {trace.name} + </option> + ))} + </select> + </div> + <div className="form-group"> + <label className="form-control-label">Scheduler</label> + <select + className="form-control" + ref={schedulerSelect => (this.schedulerSelect = schedulerSelect)} + > + {this.props.schedulers.map(scheduler => ( + <option value={scheduler.name} key={scheduler.name}> + {scheduler.name} + </option> + ))} + </select> + </div> + </form> + </Modal> + ); + } +} + +export default NewExperimentModalComponent; |
