aboutsummaryrefslogtreecommitdiffstats
path: root/web/src/js/components
diff options
context:
space:
mode:
Diffstat (limited to 'web/src/js/components')
-rw-r--r--web/src/js/components/Modal/Option.jsx141
-rw-r--r--web/src/js/components/Modal/OptionMaster.jsx119
-rw-r--r--web/src/js/components/Modal/OptionModal.jsx57
3 files changed, 177 insertions, 140 deletions
diff --git a/web/src/js/components/Modal/Option.jsx b/web/src/js/components/Modal/Option.jsx
new file mode 100644
index 00000000..58b863d1
--- /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)}
+ selected={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/OptionMaster.jsx b/web/src/js/components/Modal/OptionMaster.jsx
deleted file mode 100644
index c25dda72..00000000
--- a/web/src/js/components/Modal/OptionMaster.jsx
+++ /dev/null
@@ -1,119 +0,0 @@
-import PropTypes from 'prop-types'
-
-PureBooleanOption.PropTypes = {
- value: PropTypes.bool.isRequired,
- onChange: PropTypes.func.isRequired,
-}
-
-function PureBooleanOption({ value, onChange, name, help}) {
- return (
- <label>
- { name }
- <input type="checkbox"
- checked={value}
- onChange={onChange}
- title={help}
- />
- </label>
- )
-}
-
-PureStringOption.PropTypes = {
- value: PropTypes.string.isRequired,
- onChange: PropTypes.func.isRequired,
-}
-
-function PureStringOption( { value, onChange, name, help }) {
- let onKeyDown = (e) => {e.stopPropagation()}
- return (
- <label>
- { name }
- <input type="text"
- value={value}
- onChange={onChange}
- title={help}
- onKeyDown={onKeyDown}
- />
- </label>
- )
-}
-
-PureNumberOption.PropTypes = {
- value: PropTypes.number.isRequired,
- onChange: PropTypes.func.isRequired,
-}
-
-function PureNumberOption( {value, onChange, name, help }) {
- let onKeyDown = (e) => {e.stopPropagation()}
- return (
- <label>
- { name }
- <input type="number"
- value={value}
- onChange={onChange}
- title={help}
- onKeyDown={onKeyDown}
- />
- </label>
- )
-}
-
-PureChoicesOption.PropTypes = {
- value: PropTypes.string.isRequired,
- onChange: PropTypes.func.isRequired,
-}
-
-function PureChoicesOption( { value, onChange, name, help, choices }) {
- return (
- <label htmlFor="">
- { name }
- <select name={name} onChange={onChange} title={help} selected={value}>
- { choices.map((choice, index) => (
- <option key={index} value={choice}> {choice} </option>
- ))}
- </select>
- </label>
- )
-}
-
-const OptionTypes = {
- bool: PureBooleanOption,
- str: PureStringOption,
- int: PureNumberOption,
- "optional str": PureStringOption,
- "sequence of str": PureStringOption,
-}
-
-export default function OptionMaster({option, name, updateOptions, ...props}) {
- let WrappedComponent = null
- if (option.choices) {
- WrappedComponent = PureChoicesOption
- } else {
- WrappedComponent = OptionTypes[option.type]
- }
-
- let onChange = (e) => {
- switch (option.type) {
- case 'bool' :
- updateOptions({[name]: !option.value})
- break
- case 'int':
- updateOptions({[name]: parseInt(e.target.value)})
- break
- default:
- updateOptions({[name]: e.target.value})
- }
- }
- return (
- <div className="menu-entry">
- <WrappedComponent
- children={props.children}
- value={option.value}
- onChange={onChange}
- name={name}
- help={option.help}
- choices={option.choices}
- />
- </div>
- )
-}
diff --git a/web/src/js/components/Modal/OptionModal.jsx b/web/src/js/components/Modal/OptionModal.jsx
index ef3a224a..5741ee8c 100644
--- a/web/src/js/components/Modal/OptionModal.jsx
+++ b/web/src/js/components/Modal/OptionModal.jsx
@@ -1,8 +1,22 @@
-import React, { Component } from 'react'
-import { connect } from 'react-redux'
-import * as modalAction from '../../ducks/ui/modal'
-import { update as updateOptions } from '../../ducks/options'
-import Option from './OptionMaster'
+import React, { Component } from "react"
+import { connect } from "react-redux"
+import * as modalAction from "../../ducks/ui/modal"
+import Option from "./Option"
+
+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);
class PureOptionModal extends Component {
@@ -28,23 +42,25 @@ class PureOptionModal extends Component {
</div>
<div className="modal-body">
- {
- Object.keys(options).sort()
- .map((key, index) => {
- let option = options[key];
- return (
- <Option
- key={index}
- name={key}
- updateOptions={updateOptions}
- option={option}
- />)
- })
- }
+ <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}/>
+ </div>
+ </div>
+ )
+ }
+ </div>
</div>
<div className="modal-footer">
- <button type="button" className="btn btn-primary">Save Changes</button>
</div>
</div>
)
@@ -53,10 +69,9 @@ class PureOptionModal extends Component {
export default connect(
state => ({
- options: state.options
+ options: Object.keys(state.options).sort()
}),
{
hideModal: modalAction.hideModal,
- updateOptions: updateOptions,
}
)(PureOptionModal)