Clean up refactor branch
This commit is contained in:
parent
a33474d893
commit
4960da7ec6
7 changed files with 69 additions and 455 deletions
149
src/UiItems.js
149
src/UiItems.js
|
|
@ -1,149 +0,0 @@
|
|||
// @flow
|
||||
import React from "react";
|
||||
import Switch from "material-ui/Switch";
|
||||
import Select from "material-ui/Select";
|
||||
import { MenuItem } from "material-ui/Menu";
|
||||
import Slider from "material-ui-old/Slider";
|
||||
import MuiThemeProvider from "material-ui-old/styles/MuiThemeProvider";
|
||||
import Config from "./config";
|
||||
import Input, { InputLabel } from "material-ui/Input";
|
||||
import { FormControl } from "material-ui/Form";
|
||||
import R from "ramda";
|
||||
import {
|
||||
ListItem,
|
||||
ListItemIcon,
|
||||
ListItemSecondaryAction,
|
||||
ListItemText,
|
||||
ListSubheader
|
||||
} from "material-ui/List";
|
||||
import Button from "material-ui/Button";
|
||||
|
||||
const enabled = (props: ControlUI, state: State) => {
|
||||
if (props.enableCondition == null) {
|
||||
return true;
|
||||
} else {
|
||||
const val = state.values[props.topic];
|
||||
return props.enableCondition(
|
||||
val.internal == null ? val.actual : val.internal, val.actual,
|
||||
R.map((x) => (x.internal == null ? x.actual
|
||||
: x.internal), state.values == null ? {} : state.values));
|
||||
}
|
||||
};
|
||||
|
||||
const getValue = (topic: string, val: string) =>
|
||||
Config.topics[topic].values[val];
|
||||
|
||||
const renderIcon = (icon: string) => {
|
||||
if (icon != null) {
|
||||
return (
|
||||
<ListItemIcon>
|
||||
<i className={`mdi mdi-${icon} mdi-24px`}></i>
|
||||
</ListItemIcon>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const onSwitch = (topic: string, props: ControlUI, state: State) =>
|
||||
(x, toggled: boolean) => {
|
||||
if (state.mqtt != null) {
|
||||
state.mqtt.publish(Config.topics[topic].command,
|
||||
toggled ? getValue(topic, R.propOr("on", "on", props))
|
||||
: getValue(topic, R.propOr("off", "off", props)));
|
||||
}
|
||||
};
|
||||
|
||||
export const isToggled = (state: State, props: ControlUI) => {
|
||||
const val = state.values[props.topic];
|
||||
if (props.toggled != null) {
|
||||
return props.toggled(val.internal == null ? val.actual : val.internal,
|
||||
val.actual);
|
||||
} else {
|
||||
return val.internal === R.propOr("on", "on", props);
|
||||
}
|
||||
};
|
||||
|
||||
export const toggle = (state: State, props: ControlUI) => {
|
||||
return (
|
||||
<ListItem>
|
||||
{renderIcon(props.icon)}
|
||||
<ListItemText primary={props.text} />
|
||||
<ListItemSecondaryAction>
|
||||
<Switch label={props.text}
|
||||
checked={isToggled(state, props)}
|
||||
onChange={onSwitch(props.topic, props, state)}
|
||||
disabled={!(enabled(props, state))} />
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
);
|
||||
};
|
||||
|
||||
const onDropDownChange = (topic: string, props: ControlUI, state: State) =>
|
||||
(event) => {
|
||||
if (state.mqtt != null) {
|
||||
state.mqtt.publish(Config.topics[topic].command, event.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
const dropDownItem = (topic: string) => (text: string, key: string) => (
|
||||
<MenuItem value={Config.topics[topic].values[key]} key={key}>{text}</MenuItem>
|
||||
);
|
||||
|
||||
export const dropDown = (state: State, props: ControlUI) => {
|
||||
const id = `${props.topic}.${Object.keys(props.options)
|
||||
.reduce((v, r) => v + "." + r)}`;
|
||||
return (
|
||||
<ListItem>
|
||||
{renderIcon(props.icon)}
|
||||
<FormControl>
|
||||
<InputLabel htmlFor={id}>{props.text}</InputLabel>
|
||||
<Select value={state.values[props.topic].actual}
|
||||
onChange={onDropDownChange(props.topic, props, state)}
|
||||
disabled={!(enabled(props, state))}
|
||||
input={<Input id={id} />}
|
||||
>
|
||||
{R.values(R.mapObjIndexed(dropDownItem(props.topic), props.options))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</ListItem>
|
||||
);
|
||||
};
|
||||
|
||||
const onSliderChange = (state: State, props: ControlUI) =>
|
||||
(event, value) => {
|
||||
if (state.mqtt != null) {
|
||||
state.mqtt.publish(Config.topics[props.topic].command, value.toString());
|
||||
}
|
||||
};
|
||||
|
||||
export const slider = (state: State, props: ControlUI) => (
|
||||
<ListItem>
|
||||
{renderIcon(props.icon)}
|
||||
<ListItemText primary={props.text} />
|
||||
<ListItemSecondaryAction>
|
||||
<MuiThemeProvider>
|
||||
<Slider value={state.values[props.topic].actual}
|
||||
min={props.min == null ? 0 : props.min}
|
||||
max={props.max == null ? 1 : props.max}
|
||||
step={props.step == null ? 1 : props.step}
|
||||
onChange={onSliderChange(state, props)}
|
||||
style={{width: 100}}
|
||||
/></MuiThemeProvider>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
);
|
||||
|
||||
export const section = (state: State, props: ControlUI) => (
|
||||
<ListSubheader>{props.text}</ListSubheader>
|
||||
);
|
||||
|
||||
export const link = (state: State, props: ControlUI) => (
|
||||
<ListItem>
|
||||
<Button raised
|
||||
onClick={() => window.open(props.link, "_blank")}
|
||||
color="primary"
|
||||
>
|
||||
{props.text}
|
||||
</Button>
|
||||
</ListItem>
|
||||
);
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
// @flow
|
||||
import React from "react";
|
||||
import AppBar from "material-ui/AppBar";
|
||||
import Toolbar from "material-ui/Toolbar";
|
||||
import { CircularProgress } from "material-ui/Progress";
|
||||
import IconButton from "material-ui/IconButton";
|
||||
import Typography from "material-ui/Typography";
|
||||
|
||||
const TopBarLayerSelector = (_props: Object) => (
|
||||
<IconButton>
|
||||
<i className="mdi mdi-layers"></i>
|
||||
</IconButton>
|
||||
);
|
||||
|
||||
const TopBarIndicatorMenu = (props: Object) => (
|
||||
<IconButton>
|
||||
{props.mqtt.connected ?
|
||||
(<i style={{fontSize: 48}} className="mdi mdi-map"></i>) :
|
||||
(<i style={{fontSize: 48}} className="mdi mdi-lan-disconnect"></i>)}
|
||||
</IconButton>
|
||||
);
|
||||
|
||||
|
||||
const TopBarIndicator = (props: Object) => {
|
||||
if (props.mqtt == null || props.mqtt.reconnecting) {
|
||||
return (<CircularProgress size={48}
|
||||
style={{color: "rgba(0, 0, 0, 0.54)"}} />);
|
||||
} else {
|
||||
return (<TopBarIndicatorMenu {...props} />);
|
||||
}
|
||||
};
|
||||
|
||||
const TopBar = (props: Object) => (
|
||||
<AppBar position="static">
|
||||
<Toolbar>
|
||||
<TopBarIndicator {...props} />
|
||||
<Typography type="title">{props.title}</Typography>
|
||||
{false && <TopBarLayerSelector {...props} />}
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
);
|
||||
|
||||
export default TopBar;
|
||||
83
src/map.js
83
src/map.js
|
|
@ -1,83 +0,0 @@
|
|||
// @flow
|
||||
import React from "react";
|
||||
import { Map, ImageOverlay, Marker, LayersControl } from "react-leaflet";
|
||||
import Leaflet from "leaflet";
|
||||
import R from "ramda";
|
||||
import Config from "./config";
|
||||
import { Actions } from "./state";
|
||||
import { store } from "./state";
|
||||
|
||||
// convert width/height coordinates to -height/width coordinates
|
||||
const c = (p) => [-p[1], p[0]];
|
||||
|
||||
const color = (iconColor, state: State) => {
|
||||
// TODO: give iconColor not only internal but also actual values
|
||||
return iconColor == null ? "#000000" :
|
||||
iconColor(
|
||||
R.map((x) => (x.internal == null ?
|
||||
x.actual : x.internal), state.values == null ? {} : state.values)
|
||||
);
|
||||
};
|
||||
const iconHtml = (el, state: State) =>
|
||||
"<i class=\"mdi mdi-" + el.icon + " mdi-36px\" style=\""
|
||||
+ "color:" + color(el.iconColor, state) + ";\">"
|
||||
+ "</i>";
|
||||
|
||||
const Markers = (props) => R.values(R.mapObjIndexed((el, key) => (
|
||||
<Marker position={c(el.position)} key={el.name}
|
||||
icon={Leaflet.divIcon(
|
||||
{
|
||||
html: iconHtml(el, props.state),
|
||||
iconSize: Leaflet.point(36, 36),
|
||||
iconAnchor: Leaflet.point(18, 18)
|
||||
})}
|
||||
onClick={(e) => store.dispatch({
|
||||
type: Actions.CHANGE_UI,
|
||||
payload: key,
|
||||
toggle: e.originalEvent.ctrlKey})}>
|
||||
</Marker>
|
||||
), R.propOr({}, "controls", Config)));
|
||||
|
||||
type SpaceMapProps = {
|
||||
state: State,
|
||||
width: number,
|
||||
height: number,
|
||||
zoom: number,
|
||||
image: string
|
||||
};
|
||||
|
||||
class SpaceMap extends React.Component<SpaceMapProps> {
|
||||
|
||||
constructor(props: SpaceMapProps) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
const props = this.props;
|
||||
return (
|
||||
<Map center={c([props.width / 2, props.height / 2])} zoom={props.zoom}
|
||||
crs={Leaflet.CRS.Simple}>
|
||||
{Markers(props)}
|
||||
<LayersControl position="topright">
|
||||
{Config.layers.map((x) =>
|
||||
this.renderLayer(x, [c([0, 0]), c([props.width, props.height])]))}
|
||||
</LayersControl>
|
||||
</Map>
|
||||
);
|
||||
}
|
||||
|
||||
renderLayer(layer, bounds) {
|
||||
const LayersControlType =
|
||||
layer.baseLayer ? LayersControl.BaseLayer : LayersControl.Overlay;
|
||||
return (
|
||||
<LayersControlType name={layer.name}
|
||||
checked={layer.defaultVisibility === "visible"}>
|
||||
<ImageOverlay url={layer.image}
|
||||
bounds={bounds}
|
||||
opacity={layer.opacity} />
|
||||
</LayersControlType>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SpaceMap;
|
||||
81
src/state.js
81
src/state.js
|
|
@ -1,81 +0,0 @@
|
|||
// @flow
|
||||
import R from "ramda";
|
||||
import { createStore } from "redux";
|
||||
import Config from "./config";
|
||||
import { keyOf } from "./util";
|
||||
import { onSwitch, isToggled } from "./UiItems";
|
||||
|
||||
export const Actions = Object.freeze({
|
||||
MQTT_CONNECT: "CONNECT",
|
||||
MQTT_MESSAGE: "MESSAGE",
|
||||
CHANGE_UI: "UI_POPUP"
|
||||
});
|
||||
|
||||
const initState : State = {
|
||||
mqtt: null,
|
||||
uiOpened: null,
|
||||
values: R.map(
|
||||
(topic) => {
|
||||
return {
|
||||
internal: keyOf(topic.values, topic.defaultValue),
|
||||
actual: topic.defaultValue
|
||||
};
|
||||
}, Config.topics),
|
||||
visibleLayers: []
|
||||
};
|
||||
|
||||
const onMessage = (state: State, action: StateAction) => {
|
||||
if (action.payload == null) {
|
||||
return state;
|
||||
}
|
||||
|
||||
/*
|
||||
* action.payload.topic is the mqtt topic
|
||||
* topics is the list of all internal topic references
|
||||
* that have their state topic set to action.payload.topic
|
||||
*/
|
||||
const payload = action.payload == null ? { topic: "", message: {} }
|
||||
: action.payload; // thx flow </3
|
||||
const topics = R.keys(R.pickBy(
|
||||
(val) => val.state === payload.topic, Config.topics));
|
||||
const message = payload.message;
|
||||
const parsedMessage = (topic: string) => {
|
||||
let parseFunction = Config.topics[topic].parseState;
|
||||
if (parseFunction == null) {
|
||||
return message.toString();
|
||||
} else {
|
||||
return parseFunction(message);
|
||||
}
|
||||
};
|
||||
const newValue = (topic: string) => {
|
||||
return {
|
||||
actual: parsedMessage(topic),
|
||||
internal: keyOf(Config.topics[topic].values, parsedMessage(topic))
|
||||
};
|
||||
};
|
||||
return R.mergeDeepRight(state, R.objOf("values", R.mergeAll(
|
||||
R.map((topic) => R.objOf(topic, newValue(topic)), topics)
|
||||
)));
|
||||
};
|
||||
|
||||
const match = (value: any, array: Map<any, any>) => array[value];
|
||||
const handleEvent = (state: State = initState, action: StateAction) => {
|
||||
return match(action.type, {
|
||||
[Actions.MQTT_CONNECT]: R.merge(state, { mqtt: action.payload }),
|
||||
[Actions.MQTT_MESSAGE]: onMessage(state, action),
|
||||
[Actions.CHANGE_UI]: (() => {
|
||||
const control = Config.controls[action.payload];
|
||||
if (action.toggle && control.ui.length > 0
|
||||
&& control.ui[0].type === "toggle") {
|
||||
const props = control.ui[0];
|
||||
onSwitch(props.topic, props, state)(null, !isToggled(state, props));
|
||||
return state;
|
||||
} else {
|
||||
return R.merge(state, { uiOpened: action.payload });
|
||||
}
|
||||
})(),
|
||||
[null]: state
|
||||
});
|
||||
};
|
||||
|
||||
export const store = createStore(handleEvent, initState);
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
// @flow
|
||||
import R from "ramda";
|
||||
|
||||
export const keyOf = <a, b> (map: Map<b, a>, value: a): ?b =>
|
||||
((keys) => keys[R.findIndex((k) => map[k] === value, keys)])(R.keys(map));
|
||||
Loading…
Add table
Add a link
Reference in a new issue