aboutsummaryrefslogtreecommitdiffstats
path: root/web/src/js/components/Modal
diff options
context:
space:
mode:
Diffstat (limited to 'web/src/js/components/Modal')
-rw-r--r--web/src/js/components/Modal/Modal.jsx24
-rw-r--r--web/src/js/components/Modal/ModalLayout.jsx16
-rw-r--r--web/src/js/components/Modal/ModalList.jsx13
-rw-r--r--web/src/js/components/Modal/Option.jsx141
-rw-r--r--web/src/js/components/Modal/OptionModal.jsx110
5 files changed, 304 insertions, 0 deletions
diff --git a/web/src/js/components/Modal/Modal.jsx b/web/src/js/components/Modal/Modal.jsx
new file mode 100644
index 00000000..88e81156
--- /dev/null
+++ b/web/src/js/components/Modal/Modal.jsx
@@ -0,0 +1,24 @@
+import React, { Component } from 'react'
+import { connect } from 'react-redux'
+import ModalList from './ModalList'
+
+class PureModal extends Component {
+
+ constructor(props, context) {
+ super(props, context)
+ }
+
+ render() {
+ const { activeModal } = this.props
+ const ActiveModal = ModalList.find(m => m.name === activeModal )
+ return(
+ activeModal ? <ActiveModal/> : <div/>
+ )
+ }
+}
+
+export default connect(
+ state => ({
+ activeModal: state.ui.modal.activeModal
+ })
+)(PureModal)
diff --git a/web/src/js/components/Modal/ModalLayout.jsx b/web/src/js/components/Modal/ModalLayout.jsx
new file mode 100644
index 00000000..cf357b2b
--- /dev/null
+++ b/web/src/js/components/Modal/ModalLayout.jsx
@@ -0,0 +1,16 @@
+import React from 'react'
+
+export default function ModalLayout ({ children }) {
+ return (
+ <div>
+ <div className="modal-backdrop fade in"></div>
+ <div className="modal modal-visible" id="optionsModal" tabIndex="-1" role="dialog" aria-labelledby="options">
+ <div className="modal-dialog modal-lg" role="document">
+ <div className="modal-content">
+ {children}
+ </div>
+ </div>
+ </div>
+ </div>
+ )
+}
diff --git a/web/src/js/components/Modal/ModalList.jsx b/web/src/js/components/Modal/ModalList.jsx
new file mode 100644
index 00000000..1175d5ea
--- /dev/null
+++ b/web/src/js/components/Modal/ModalList.jsx
@@ -0,0 +1,13 @@
+import React from 'react'
+import ModalLayout from './ModalLayout'
+import OptionContent from './OptionModal'
+
+function OptionModal() {
+ return (
+ <ModalLayout>
+ <OptionContent/>
+ </ModalLayout>
+ )
+}
+
+export default [ OptionModal ]
diff --git a/web/src/js/components/Modal/Option.jsx b/web/src/js/components/Modal/Option.jsx
new file mode 100644
index 00000000..38e2f239
--- /dev/null
+++ b/web/src/js/components/Modal/Option.jsx
@@ -0,0 +1,141 @@
+import React, { Component } from "react"
+import PropTypes from "prop-types"
+import { connect } from "react-redux"
+import { update as updateOptions } from "../../ducks/options"
+import { Key } from "../../utils"
+import classnames from 'classnames'
+
+const stopPropagation = e => {
+ if (e.keyCode !== Key.ESC) {
+ e.stopPropagation()
+ }
+}
+
+BooleanOption.PropTypes = {
+ value: PropTypes.bool.isRequired,
+ onChange: PropTypes.func.isRequired,
+}
+function BooleanOption({ value, onChange, ...props }) {
+ return (
+ <div className="checkbox">
+ <label>
+ <input type="checkbox"
+ checked={value}
+ onChange={e => onChange(e.target.checked)}
+ {...props}
+ />
+ Enable
+ </label>
+ </div>
+ )
+}
+
+StringOption.PropTypes = {
+ value: PropTypes.string.isRequired,
+ onChange: PropTypes.func.isRequired,
+}
+function StringOption({ value, onChange, ...props }) {
+ return (
+ <input type="text"
+ value={value || ""}
+ onChange={e => onChange(e.target.value)}
+ {...props}
+ />
+ )
+}
+function Optional(Component) {
+ return function ({ onChange, ...props }) {
+ return <Component
+ onChange={x => onChange(x ? x : null)}
+ {...props}
+ />
+ }
+}
+
+NumberOption.PropTypes = {
+ value: PropTypes.number.isRequired,
+ onChange: PropTypes.func.isRequired,
+}
+function NumberOption({ value, onChange, ...props }) {
+ return (
+ <input type="number"
+ value={value}
+ onChange={(e) => onChange(parseInt(e.target.value))}
+ {...props}
+ />
+ )
+}
+
+ChoicesOption.PropTypes = {
+ value: PropTypes.string.isRequired,
+ onChange: PropTypes.func.isRequired,
+}
+export function ChoicesOption({ value, onChange, choices, ...props }) {
+ return (
+ <select
+ onChange={(e) => onChange(e.target.value)}
+ value={value}
+ {...props}
+ >
+ { choices.map(
+ choice => (
+ <option key={choice} value={choice}>{choice}</option>
+ )
+ )}
+ </select>
+ )
+}
+
+StringSequenceOption.PropTypes = {
+ value: PropTypes.string.isRequired,
+ onChange: PropTypes.func.isRequired,
+}
+function StringSequenceOption({ value, onChange, ...props }) {
+ const height = Math.max(value.length, 1)
+ return <textarea
+ rows={height}
+ value={value.join('\n')}
+ onChange={e => onChange(e.target.value.split("\n"))}
+ {...props}
+ />
+}
+
+export const Options = {
+ "bool": BooleanOption,
+ "str": StringOption,
+ "int": NumberOption,
+ "optional str": Optional(StringOption),
+ "sequence of str": StringSequenceOption,
+}
+
+function PureOption({ choices, type, value, onChange, name, error }) {
+ let Opt, props = {}
+ if (choices) {
+ Opt = ChoicesOption;
+ props.choices = choices
+ } else {
+ Opt = Options[type]
+ }
+ if (Opt !== BooleanOption) {
+ props.className = "form-control"
+ }
+
+ return <div className={classnames({'has-error':error})}>
+ <Opt
+ name={name}
+ value={value}
+ onChange={onChange}
+ onKeyDown={stopPropagation}
+ {...props}
+ />
+ </div>
+}
+export default connect(
+ (state, { name }) => ({
+ ...state.options[name],
+ ...state.ui.optionsEditor[name]
+ }),
+ (dispatch, { name }) => ({
+ onChange: value => dispatch(updateOptions(name, value))
+ })
+)(PureOption)
diff --git a/web/src/js/components/Modal/OptionModal.jsx b/web/src/js/components/Modal/OptionModal.jsx
new file mode 100644
index 00000000..fed0048d
--- /dev/null
+++ b/web/src/js/components/Modal/OptionModal.jsx
@@ -0,0 +1,110 @@
+import React, { Component } from "react"
+import { connect } from "react-redux"
+import * as modalAction from "../../ducks/ui/modal"
+import * as optionAction from "../../ducks/options"
+import Option from "./Option"
+import _ from "lodash"
+
+function PureOptionHelp({help}){
+ return <div className="help-block small">{help}</div>;
+}
+const OptionHelp = connect((state, {name}) => ({
+ help: state.options[name].help,
+}))(PureOptionHelp);
+
+function PureOptionError({error}){
+ if(!error) return null;
+ return <div className="small text-danger">{error}</div>;
+}
+const OptionError = connect((state, {name}) => ({
+ error: state.ui.optionsEditor[name] && state.ui.optionsEditor[name].error
+}))(PureOptionError);
+
+export function PureOptionDefault({value, defaultVal}){
+ if( value === defaultVal ) {
+ return null
+ } else {
+ if (typeof(defaultVal) === 'boolean') {
+ defaultVal = defaultVal ? 'true' : 'false'
+ } else if (Array.isArray(defaultVal)){
+ if (_.isEmpty(_.compact(value)) && // filter the empty string in array
+ _.isEmpty(defaultVal)){
+ return null
+ }
+ defaultVal = '[ ]'
+ } else if (defaultVal === ''){
+ defaultVal = '\"\"'
+ } else if (defaultVal === null){
+ defaultVal = 'null'
+ }
+ return <div className="small">Default: <strong> {defaultVal} </strong> </div>
+ }
+}
+const OptionDefault = connect((state, {name}) => ({
+ value: state.options[name].value,
+ defaultVal: state.options[name].default
+}))(PureOptionDefault)
+
+class PureOptionModal extends Component {
+
+ constructor(props, context) {
+ super(props, context)
+ this.state = { title: 'Options' }
+ }
+
+ componentWillUnmount(){
+ // this.props.save()
+ }
+
+ render() {
+ const { hideModal, options } = this.props
+ const { title } = this.state
+ return (
+ <div>
+ <div className="modal-header">
+ <button type="button" className="close" data-dismiss="modal" onClick={() => {
+ hideModal()
+ }}>
+ <i className="fa fa-fw fa-times"></i>
+ </button>
+ <div className="modal-title">
+ <h4>{ title }</h4>
+ </div>
+ </div>
+
+ <div className="modal-body">
+ <div className="form-horizontal">
+ {
+ options.map(name =>
+ <div key={name} className="form-group">
+ <div className="col-xs-6">
+ <label htmlFor={name}>{name}</label>
+ <OptionHelp name={name}/>
+ </div>
+ <div className="col-xs-6">
+ <Option name={name}/>
+ <OptionError name={name}/>
+ <OptionDefault name={name}/>
+ </div>
+ </div>
+ )
+ }
+ </div>
+ </div>
+
+ <div className="modal-footer">
+ </div>
+ </div>
+ )
+ }
+}
+
+export default connect(
+ state => ({
+ options: Object.keys(state.options).sort()
+ }),
+ {
+ hideModal: modalAction.hideModal,
+ save: optionAction.save,
+ }
+)(PureOptionModal)