diff --git a/src/components/ControlMap.js b/src/components/ControlMap.js index 81dafcd..7c08afe 100644 --- a/src/components/ControlMap.js +++ b/src/components/ControlMap.js @@ -7,7 +7,7 @@ import parseIconName, { controlGetIcon } from "utils/parseIconName"; export type Point = [number, number]; -const convertPoint = ([y,x]: Point): Point => [-x, y]; +const convertPoint = ([y, x]: Point): Point => [-x, y]; export type ControlMapProps = { width: number, @@ -58,12 +58,10 @@ export default class ControlMap extends React.Component { } iconColor(control: Control): string { + const ints = _.mapValues(this.props.state, (x) => x.internal || x.actual); + const acts = _.mapValues(this.props.state, (x) => x.actual); if (control.iconColor != null) { - return control.iconColor( - _.mapValues(this.props.state, (x) => x.internal || x.actual), - _.mapValues(this.props.state, (x) => x.actual), - this.props.state - ); + return control.iconColor(ints, acts, this.props.state); } return "#000"; } diff --git a/src/components/UiItemList/UiItem.js b/src/components/UiItemList/UiItem.js index 8cb7d01..983495d 100644 --- a/src/components/UiItemList/UiItem.js +++ b/src/components/UiItemList/UiItem.js @@ -21,7 +21,7 @@ type UiItemProps = { onChangeState: (topic: string, nextState: any) => void }; -export default class UiItem extends React.Component> { +export default class UiItem extends React.Component> { constructor(props: UiItemProps) { super(props); } @@ -30,48 +30,79 @@ export default class UiItem extends React.Component> { } + render() { + return null; + } + + /* + * TODO: The type system can't really check if the enableCondition is of + * any function type or if it is a TopicDependentOption or a + * StateDependentOption. This should be fixed. + */ + isEnabled() { + if (Object.keys(this.props.item).includes("enableCondition") && + typeof this.props.item.enableCondition == "function") { + const enableCondition = this.props.item.enableCondition; + const state = this.props.state; + const internals = _.mapValues(state, (x) => x.internal); + const actuals = _.mapValues(state, (x) => x.actual); + return enableCondition(internals, actuals, state); + } else { + return true; + } + } +} + +export class UiControl extends UiItem { + constructor(props: UiItemProps) { + super(props); + } + changeState(next: any) { if (this.props.item.topic == null) { throw new Error( - `Undefined topic in ${this.props.item.type} "${this.props.item.text}"` + `Missing topic in ${this.props.item.type} "${this.props.item.text}"` ); } this.props.onChangeState(this.props.item.topic, next); } getValue() { - const topic: string = this.props.item.topic || ""; + const control = this.props.item; + const topic: string = control.topic || ""; const value = this.props.state[topic]; if (value == null) { - const control = this.props.item; + if (topic === "") { + throw new Error( + `Missing topic in ${control.type} "${control.text}"` + ); + } throw new Error( - `Unknown topic "${control.topic}" in ${control.type} "${control.text}"` + `Unknown topic "${topic}" in ${control.type} "${control.text}"` ); } return value; } isEnabled() { - const enableCondition = this.props.item.enableCondition; - if (enableCondition == null) { - return true; - } else { + if (Object.keys(this.props.item).includes("enableCondition") && + typeof this.props.item.enableCondition == "function") { + const enableCondition = this.props.item.enableCondition; const value = this.getValue(); return enableCondition( value.internal || value.actual, value.actual, this.props.state); + } else { + return true; } } - - render() { - return null; - } } -export class Toggle extends UiItem { +export class Toggle extends UiControl { isToggled = () => { const value = this.getValue(); const control = this.props.item; - const isChecked = control.toggled || ((i) => i === (control.on || "on")); + const isChecked = control.toggled || + ((i, _a, _s) => i === (control.on || "on")); const checked = isChecked( value.internal || value.actual, value.actual, this.props.state); return checked; @@ -99,7 +130,7 @@ export class Toggle extends UiItem { } } -export class DropDown extends UiItem { +export class DropDown extends UiControl { runPrimaryAction = (next?: any) => { if (this.isEnabled()) { const control = this.props.item; @@ -117,7 +148,7 @@ export class DropDown extends UiItem { render() { const control = this.props.item; const value = this.getValue(); - const id = `${control.topic}-${control.name}`; + const id = `${control.topic}-${control.text}`; const options = control.options; if (options == null) { throw new Error( @@ -173,7 +204,7 @@ export class Section extends UiItem { } } -export class Text extends UiItem { +export class Text extends UiControl { render() { return [ , diff --git a/src/components/UiItemList/index.js b/src/components/UiItemList/index.js index d6e3d8f..f089113 100644 --- a/src/components/UiItemList/index.js +++ b/src/components/UiItemList/index.js @@ -5,16 +5,9 @@ import { ListItem, ListItemIcon, ListItemSecondaryAction, - ListItemText, - ListSubheader + ListItemText } from "material-ui/List"; -import Switch from "material-ui/Switch"; import { renderIcon } from "utils/parseIconName"; -import Input, { InputLabel } from "material-ui/Input"; -import { FormControl } from "material-ui/Form"; -import Select from "material-ui/Select"; -import { MenuItem } from "material-ui/Menu"; -import Button from "material-ui/Button"; // TODO: Use something else import Slider from "material-ui-old/Slider"; @@ -57,31 +50,31 @@ export default class UiItemList extends React.Component { switch (control.type) { case "toggle": { return ; + state={this.props.state} + onChangeState={this.props.onChangeState} />; } case "dropDown": { return ; + state={this.props.state} + onChangeState={this.props.onChangeState} />; } case "section": { return
; + state={this.props.state} + onChangeState={this.props.onChangeState} />; } case "link": { return ; + state={this.props.state} + onChangeState={this.props.onChangeState} />; } case "slider": { return this.renderSlider(control); } case "text": { return ; + state={this.props.state} + onChangeState={this.props.onChangeState} />; } default: { throw new Error( @@ -91,18 +84,7 @@ export default class UiItemList extends React.Component { } } - isEnabled(control: ControlUI) { - const enableCondition = control.enableCondition; - if (enableCondition == null) { - return true; - } else { - const value = this.getValue(control); - return enableCondition( - value.internal || value.actual, value.actual, this.props.state); - } - } - - getValue(control: ControlUI) { + getValue(control: UIControl) { const value = this.props.state[control.topic]; if (value == null) { throw new Error( @@ -112,77 +94,7 @@ export default class UiItemList extends React.Component { return value; } - toggleSwitch(control: ControlUI, newState: boolean) { - this.props.onChangeState(control.topic, - newState ? (control.on || "on") : (control.off || "off")); - } - - renderToggle(control: ControlUI) { - const value = this.getValue(control); - const isToggled = control.toggled || ((i) => i === (control.on || "on")); - const checked = isToggled( - value.internal || value.actual, value.actual, this.props.state); - return [ - , - - this.toggleSwitch(control, state)} - disabled={!this.isEnabled(control)} /> - - ]; - } - - changeDropDown(control: ControlUI, newState: string) { - this.props.onChangeState(control.topic, newState); - } - - renderDropDown(control: ControlUI) { - const value = this.getValue(control); - const id = `${control.topic}-${control.name}`; - const options = control.options; - if (options == null) { - throw new Error( - `Parameter "options" missing for ${control.type} "${control.text}"` - ); - } - return ( - - {control.text} - } - > - {_.map(options, (v, k) => {v})} - - - ); - } - - renderSection(control: ControlUI) { - return ( - {control.text} - ); - } - - renderLink(control: ControlUI) { - if (control.link == null) { - throw new Error( - `Parameter "link" missing for ${control.type} "${control.text}"` - ); - } - return ( - - ); - } - - renderSlider(control: ControlUI) { + renderSlider(control: UISlider) { const value = this.getValue(control); return [ , diff --git a/types/types.js b/types/types.js index caf4ea3..d9b7785 100644 --- a/types/types.js +++ b/types/types.js @@ -13,46 +13,76 @@ declare type Topic = { }; declare type Topics = Map; -declare type UIBase = { - text: string, - topic?: string, - icon?: string, - enableCondition?: (internal: string, actual: any, state: State) => boolean -} +declare type TopicDependentOption = ( + internal: string, actual: any, state: State + ) => T; +declare type StateDependentOption = ( + internals: Map, actuals: Map, state: State + ) => T; -declare type UIToggle = { +interface UIControl { + +type: string, + +text: string, + +topic: string +}; + +interface Enableable { + enableCondition?: TopicDependentOption +}; + +declare type UIToggle = $ReadOnly<{| type: "toggle", + text: string, + topic: string, + icon?: string, + enableCondition?: TopicDependentOption, on?: string, off?: string, - toggled?: (internal: string, actual: any, state: State) => boolean, -} & UIBase; + toggled?: TopicDependentOption +|}>; -declare type UIDropDown = { +declare type UIDropDown = $ReadOnly<{| type: "dropDown", + text: string, + topic: string, + icon?: string, + enableCondition?: TopicDependentOption, options: Map, renderValue?: (value: string) => string -} & UIBase; +|}>; -declare type UISlider = { +declare type UISlider = $ReadOnly<{| type: "slider", + text: string, + topic: string, + icon?: string, + enableCondition?: TopicDependentOption, min?: number, max?: number, step?: number -} & UIBase; +|}>; -declare type UISection = { +declare type UISection = $ReadOnly<{| type: "section", text: string -}; +|}>; -declare type UILink = { +declare type UILink = $ReadOnly<{| type: "link", - link: string -} & UIBase; + text: string, + link: string, + enableCondition?: StateDependentOption, + + // TODO: check if both the following options are implemented + icon?: string +|}>; -declare type UIText = { - type: "text" -} & UIBase; +declare type UIText = $ReadOnly<{| + type: "text", + text: string, + topic: string, + icon?: string +|}>; declare type ControlUI = UIToggle @@ -64,7 +94,7 @@ declare type ControlUI = declare type Control = { name: string, - position: Array, + position: [number, number], icon: string | ( internals: Map, actuals: Map,