mqtt-control-map/src/components/App.js
uwap 4d40321975 Proper drawer behaviour
* Move the content to the left when the drawer is opened
2018-11-12 05:20:44 +01:00

214 lines
6.5 KiB
JavaScript

// @flow
import * as React from "react";
import map from "lodash/map";
import mapValues from "lodash/mapValues";
import filter from "lodash/filter";
import keys from "lodash/keys";
import merge from "lodash/merge";
import throttle from "lodash/throttle";
import type { Config, Control, Topics } from "config/flowtypes";
import MuiThemeProvider from "@material-ui/core/styles/MuiThemeProvider";
import createMuiTheme from "@material-ui/core/styles/createMuiTheme";
import withStyles from "@material-ui/core/styles/withStyles";
import * as Colors from "@material-ui/core/colors";
import Snackbar from "@material-ui/core/Snackbar";
import IconButton from "@material-ui/core/IconButton";
import Typography from "@material-ui/core/Typography";
import SideBar from "components/SideBar";
import ControlMap from "components/ControlMap";
import TopBar from "components/TopBar";
import UiItemList from "components/UiItemList";
import MqttContext from "mqtt/context";
import connectMqtt from "../connectMqtt";
export type AppProps = {
config: Config
};
export type AppState = {
selectedControl: ?Control,
drawerOpened: boolean,
mqttState: State,
mqttSend: (topic: string, value: Buffer) => void,
mqttConnected: boolean,
search: string,
error: ?string
};
class App extends React.PureComponent<AppProps & Classes, AppState> {
controlMap: React.Node
constructor(props: AppProps & Classes) {
super(props);
this.state = {
selectedControl: null,
drawerOpened: false,
mqttState: mapValues(this.topics, (topic) => topic.defaultValue),
mqttSend: connectMqtt(props.config.space.mqtt, {
onMessage: this.receiveMessage.bind(this),
onConnect: () => this.setState({ mqttConnected: true }),
onReconnect: () => this.setState({ mqttConnected: false }),
onDisconnect: () => this.setState({ mqttConnected: false }),
subscribe: map(
filter(keys(this.topics), (x) => this.topics[x].state != null),
(x) => this.topics[x].state.name)
}),
mqttConnected: false,
search: "",
error: null
};
}
controlMap = (search: string) =>
<ControlMap width={1000} height={700} zoom={0}
layers={this.props.config.layers}
controls={this.props.config.controls}
onChangeControl={this.changeControl}
search={search}
/>;
get topics(): Topics {
return Array.isArray(this.props.config.topics) ?
Object.assign({}, ...this.props.config.topics) : this.props.config.topics;
}
static styles(theme) {
return {
contentElement: {
transition: theme.transitions.create(["width"], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen
})
},
contentElementShifted: {
width: "calc(100% - 340px)",
transition: theme.transitions.create(["width"], {
easing: theme.transitions.easing.easeOut,
duration: theme.transitions.duration.enteringScreen
})
}
};
}
static theme(config: Config) {
return createMuiTheme({
palette: {
primary: Colors[config.space.color]
}
});
}
receiveMessage(rawTopic: string, message: Buffer) {
try {
const topics = filter(
keys(this.topics),
(k) => this.topics[k].state != null &&
this.topics[k].state.name === rawTopic
);
if (topics.length === 0) {
return;
}
for (let i in topics) {
const topic = topics[i];
const stateTopic = this.topics[topic].state;
const parseVal = stateTopic ? stateTopic.type : null;
const val = parseVal == null ? message.toString() : parseVal(message);
this.setMqttStateDebounced(
{mqttState: Object.assign({},
merge(this.state.mqttState, { [topic]: val}))});
}
} catch (err) {
this.setState({ error: err.toString() });
}
}
setMqttStateDebounced = throttle(this.setState, 16);
changeControl = (control: ?Control = null) => {
this.setState({selectedControl: control, drawerOpened: control != null});
}
closeDrawer = () => {
this.setState({drawerOpened: false});
}
changeState = (topic: string, value: string) => {
try {
if (this.topics[topic].command == null) {
return;
}
const rawTopic = this.topics[topic].command.name;
const transformValue = this.topics[topic].command.type;
const val =
transformValue == null ? value : transformValue(Buffer.from(value));
this.state.mqttSend(rawTopic, Buffer.from(val));
} catch (err) {
this.setState({ error: err.toString() });
}
}
render() {
return (
<MqttContext.Provider value={{
state: this.state.mqttState,
changeState: this.changeState
}}>
<div className={
this.state.drawerOpened
? this.props.classes.contentElementShifted
: this.props.classes.contentElement
}>
<TopBar connected={this.state.mqttConnected}
onSearch={(s) => this.setState({ search: s })} />
{this.controlMap(this.state.search)}
</div>
<SideBar open={this.state.drawerOpened}
control={this.state.selectedControl}
onCloseRequest={this.closeDrawer}
icon={this.state.selectedControl == null ? null :
this.state.selectedControl.icon(this.state.mqttState)}
>
{this.state.selectedControl == null
|| <UiItemList controls={this.state.selectedControl.ui} />}
</SideBar>
<Snackbar
anchorOrigin={{
vertical: "bottom",
horizontal: "center"
}}
open={this.state.error != null}
autoHideDuration={6000}
onClose={() => this.setState({ error: null })}
ContentProps={{
"aria-describedby": "errormsg"
}}
message={
<Typography color="error" id="errormsg">
{this.state.error}
</Typography>}
action={
<IconButton
key="close"
aria-label="Close"
color="inherit"
onClick={() => this.setState({ error: null })}>
<i className="mdi mdi-close" />
</IconButton>
} />
</MqttContext.Provider>
);
}
}
export default (props: AppProps) => {
const StyledApp = withStyles(App.styles)(App);
return (
<MuiThemeProvider theme={App.theme(props.config)}>
<StyledApp {...props} />
</MuiThemeProvider>
);
};