Cleanup flow types and fix eslint errors

This commit is contained in:
uwap 2018-01-19 16:20:10 +01:00
parent ef41de4349
commit c1ed43ee6c
4 changed files with 118 additions and 147 deletions

View file

@ -7,7 +7,7 @@ import parseIconName, { controlGetIcon } from "utils/parseIconName";
export type Point = [number, number]; export type Point = [number, number];
const convertPoint = ([y,x]: Point): Point => [-x, y]; const convertPoint = ([y, x]: Point): Point => [-x, y];
export type ControlMapProps = { export type ControlMapProps = {
width: number, width: number,
@ -58,12 +58,10 @@ export default class ControlMap extends React.Component<ControlMapProps> {
} }
iconColor(control: Control): string { 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) { if (control.iconColor != null) {
return control.iconColor( return control.iconColor(ints, acts, this.props.state);
_.mapValues(this.props.state, (x) => x.internal || x.actual),
_.mapValues(this.props.state, (x) => x.actual),
this.props.state
);
} }
return "#000"; return "#000";
} }

View file

@ -21,7 +21,7 @@ type UiItemProps<I> = {
onChangeState: (topic: string, nextState: any) => void onChangeState: (topic: string, nextState: any) => void
}; };
export default class UiItem<I> extends React.Component<UiItemProps<I>> { export default class UiItem<I:Object> extends React.Component<UiItemProps<I>> {
constructor(props: UiItemProps<I>) { constructor(props: UiItemProps<I>) {
super(props); super(props);
} }
@ -30,48 +30,79 @@ export default class UiItem<I> extends React.Component<UiItemProps<I>> {
} }
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<I: UIControl> extends UiItem<I> {
constructor(props: UiItemProps<I>) {
super(props);
}
changeState(next: any) { changeState(next: any) {
if (this.props.item.topic == null) { if (this.props.item.topic == null) {
throw new Error( 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); this.props.onChangeState(this.props.item.topic, next);
} }
getValue() { getValue() {
const topic: string = this.props.item.topic || ""; const control = this.props.item;
const topic: string = control.topic || "";
const value = this.props.state[topic]; const value = this.props.state[topic];
if (value == null) { if (value == null) {
const control = this.props.item; if (topic === "") {
throw new Error( throw new Error(
`Unknown topic "${control.topic}" in ${control.type} "${control.text}"` `Missing topic in ${control.type} "${control.text}"`
);
}
throw new Error(
`Unknown topic "${topic}" in ${control.type} "${control.text}"`
); );
} }
return value; return value;
} }
isEnabled() { isEnabled() {
if (Object.keys(this.props.item).includes("enableCondition") &&
typeof this.props.item.enableCondition == "function") {
const enableCondition = this.props.item.enableCondition; const enableCondition = this.props.item.enableCondition;
if (enableCondition == null) {
return true;
} else {
const value = this.getValue(); const value = this.getValue();
return enableCondition( return enableCondition(
value.internal || value.actual, value.actual, this.props.state); value.internal || value.actual, value.actual, this.props.state);
} else {
return true;
} }
} }
render() {
return null;
}
} }
export class Toggle extends UiItem<UIToggle> { export class Toggle extends UiControl<UIToggle> {
isToggled = () => { isToggled = () => {
const value = this.getValue(); const value = this.getValue();
const control = this.props.item; 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( const checked = isChecked(
value.internal || value.actual, value.actual, this.props.state); value.internal || value.actual, value.actual, this.props.state);
return checked; return checked;
@ -99,7 +130,7 @@ export class Toggle extends UiItem<UIToggle> {
} }
} }
export class DropDown extends UiItem<UIDropDown> { export class DropDown extends UiControl<UIDropDown> {
runPrimaryAction = (next?: any) => { runPrimaryAction = (next?: any) => {
if (this.isEnabled()) { if (this.isEnabled()) {
const control = this.props.item; const control = this.props.item;
@ -117,7 +148,7 @@ export class DropDown extends UiItem<UIDropDown> {
render() { render() {
const control = this.props.item; const control = this.props.item;
const value = this.getValue(); const value = this.getValue();
const id = `${control.topic}-${control.name}`; const id = `${control.topic}-${control.text}`;
const options = control.options; const options = control.options;
if (options == null) { if (options == null) {
throw new Error( throw new Error(
@ -173,7 +204,7 @@ export class Section extends UiItem<UISection> {
} }
} }
export class Text extends UiItem<UIText> { export class Text extends UiControl<UIText> {
render() { render() {
return [ return [
<ListItemText key="label" primary={this.props.item.text} />, <ListItemText key="label" primary={this.props.item.text} />,

View file

@ -5,16 +5,9 @@ import {
ListItem, ListItem,
ListItemIcon, ListItemIcon,
ListItemSecondaryAction, ListItemSecondaryAction,
ListItemText, ListItemText
ListSubheader
} from "material-ui/List"; } from "material-ui/List";
import Switch from "material-ui/Switch";
import { renderIcon } from "utils/parseIconName"; 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 // TODO: Use something else
import Slider from "material-ui-old/Slider"; import Slider from "material-ui-old/Slider";
@ -91,18 +84,7 @@ export default class UiItemList extends React.Component<UiItemListProps> {
} }
} }
isEnabled(control: ControlUI) { getValue(control: UIControl) {
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) {
const value = this.props.state[control.topic]; const value = this.props.state[control.topic];
if (value == null) { if (value == null) {
throw new Error( throw new Error(
@ -112,77 +94,7 @@ export default class UiItemList extends React.Component<UiItemListProps> {
return value; return value;
} }
toggleSwitch(control: ControlUI, newState: boolean) { renderSlider(control: UISlider) {
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 [
<ListItemText key="label" primary={control.text} />,
<ListItemSecondaryAction key="action">
<Switch label={control.text}
checked={checked}
onChange={(_event, state) => this.toggleSwitch(control, state)}
disabled={!this.isEnabled(control)} />
</ListItemSecondaryAction>
];
}
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 (
<FormControl>
<InputLabel htmlFor={id}>{control.text}</InputLabel>
<Select value={value.internal || value.actual}
onChange={(event) => this.changeDropDown(control, event.target.value)}
disabled={!this.isEnabled(control)}
input={<Input id={id} />}
>
{_.map(options, (v, k) => <MenuItem value={k} key={k}>{v}</MenuItem>)}
</Select>
</FormControl>
);
}
renderSection(control: ControlUI) {
return (
<ListSubheader key={control.text}>{control.text}</ListSubheader>
);
}
renderLink(control: ControlUI) {
if (control.link == null) {
throw new Error(
`Parameter "link" missing for ${control.type} "${control.text}"`
);
}
return (
<Button raised
onClick={() => window.open(control.link, "_blank")}
color="primary"
>
{control.text}
</Button>
);
}
renderSlider(control: ControlUI) {
const value = this.getValue(control); const value = this.getValue(control);
return [ return [
<ListItemText primary={control.text} key="text" />, <ListItemText primary={control.text} key="text" />,

View file

@ -13,46 +13,76 @@ declare type Topic = {
}; };
declare type Topics = Map<string,Topic>; declare type Topics = Map<string,Topic>;
declare type UIBase = { declare type TopicDependentOption<T> = (
text: string, internal: string, actual: any, state: State
topic?: string, ) => T;
icon?: string, declare type StateDependentOption<T> = (
enableCondition?: (internal: string, actual: any, state: State) => boolean internals: Map<string, string>, actuals: Map<string, any>, state: State
} ) => T;
declare type UIToggle = { interface UIControl {
+type: string,
+text: string,
+topic: string
};
interface Enableable {
enableCondition?: TopicDependentOption<boolean>
};
declare type UIToggle = $ReadOnly<{|
type: "toggle", type: "toggle",
text: string,
topic: string,
icon?: string,
enableCondition?: TopicDependentOption<boolean>,
on?: string, on?: string,
off?: string, off?: string,
toggled?: (internal: string, actual: any, state: State) => boolean, toggled?: TopicDependentOption<boolean>
} & UIBase; |}>;
declare type UIDropDown = { declare type UIDropDown = $ReadOnly<{|
type: "dropDown", type: "dropDown",
text: string,
topic: string,
icon?: string,
enableCondition?: TopicDependentOption<boolean>,
options: Map<string, any>, options: Map<string, any>,
renderValue?: (value: string) => string renderValue?: (value: string) => string
} & UIBase; |}>;
declare type UISlider = { declare type UISlider = $ReadOnly<{|
type: "slider", type: "slider",
text: string,
topic: string,
icon?: string,
enableCondition?: TopicDependentOption<boolean>,
min?: number, min?: number,
max?: number, max?: number,
step?: number step?: number
} & UIBase; |}>;
declare type UISection = { declare type UISection = $ReadOnly<{|
type: "section", type: "section",
text: string text: string
}; |}>;
declare type UILink = { declare type UILink = $ReadOnly<{|
type: "link", type: "link",
link: string text: string,
} & UIBase; link: string,
enableCondition?: StateDependentOption<boolean>,
declare type UIText = { // TODO: check if both the following options are implemented
type: "text" icon?: string
} & UIBase; |}>;
declare type UIText = $ReadOnly<{|
type: "text",
text: string,
topic: string,
icon?: string
|}>;
declare type ControlUI = declare type ControlUI =
UIToggle UIToggle
@ -64,7 +94,7 @@ declare type ControlUI =
declare type Control = { declare type Control = {
name: string, name: string,
position: Array<number>, position: [number, number],
icon: string | ( icon: string | (
internals: Map<string, string>, internals: Map<string, string>,
actuals: Map<string, any>, actuals: Map<string, any>,