First commit
This commit is contained in:
commit
9d69521b26
14 changed files with 3636 additions and 0 deletions
20
src/UiItems.js
Normal file
20
src/UiItems.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// @flow
|
||||
import React from "react";
|
||||
import Toggle from "material-ui/Toggle";
|
||||
import Config from "./config";
|
||||
import R from "ramda";
|
||||
|
||||
const onToggle = (topic, props, state) => (x, toggled) =>
|
||||
state.mqtt.publish(Config.topics[topic].command,
|
||||
toggled ? Config.topics[topic].values[R.propOr("on", "on", props)]
|
||||
: Config.topics[topic].values[R.propOr("off", "off", props)]);
|
||||
|
||||
export const toggle = (props: Object) => (
|
||||
<Toggle label={props.text}
|
||||
toggled={
|
||||
props.state.values[props.topic] ===
|
||||
Config.topics[props.topic].values[R.propOr("on", "on", props)]
|
||||
}
|
||||
onToggle={onToggle(props.topic, props, props.state)}
|
||||
/>
|
||||
);
|
||||
37
src/appbar.js
Normal file
37
src/appbar.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
// @flow
|
||||
import React from "react";
|
||||
import AppBar from "material-ui/AppBar";
|
||||
import CircularProgress from "material-ui/CircularProgress";
|
||||
import DoneIcon from "material-ui/svg-icons/action/done";
|
||||
import IconMenu from "material-ui/IconMenu";
|
||||
import IconButton from "material-ui/IconButton";
|
||||
import MenuItem from "material-ui/MenuItem";
|
||||
import { orange400, grey50 } from "material-ui/styles/colors";
|
||||
|
||||
const TopBarIndicatorMenu = (props: Object) => (
|
||||
<IconMenu
|
||||
iconButtonElement={
|
||||
<IconButton style={{width:48, height:48, padding: 0}}
|
||||
iconStyle={{width:48, height: 48}}
|
||||
tooltip="Connected!">
|
||||
<DoneIcon color={grey50} />
|
||||
</IconButton>}
|
||||
style={{width:48, height:48}}>
|
||||
<MenuItem primaryText="Reconnect" />
|
||||
</IconMenu>
|
||||
);
|
||||
|
||||
|
||||
const TopBarIndicator = (props: Object) => (
|
||||
props.mqtt == null ? <CircularProgress size={48} color={grey50} />
|
||||
: <TopBarIndicatorMenu {...props} />
|
||||
);
|
||||
|
||||
const TopBar = (props: Object) => (
|
||||
<AppBar title={props.title}
|
||||
style={{background:orange400}}
|
||||
iconElementLeft={<TopBarIndicator {...props} />}
|
||||
className="nav"
|
||||
/>);
|
||||
|
||||
export default TopBar;
|
||||
126
src/config.js
Normal file
126
src/config.js
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
// @flow
|
||||
const config : Config = {
|
||||
topics: {
|
||||
led_stahltraeger: {
|
||||
state: "/service/openhab/out/pca301_ledstrips/state",
|
||||
command: "/service/openhab/in/pca301_ledstrips/command",
|
||||
value: "OFF", # defaultValue
|
||||
values: { on: "ON", off: "OFF" }
|
||||
},
|
||||
snackbar: {
|
||||
state: "/service/openhab/out/pca301_snackbar/state",
|
||||
command: "/service/openhab/in/pca301_snackbar/command",
|
||||
value: "OFF",
|
||||
values: { on: "ON", off: "OFF" }
|
||||
},
|
||||
twinkle: {
|
||||
state: "/service/openhab/out/pca301_twinkle/state",
|
||||
command: "/service/openhab/in/pca301_twinkle/command",
|
||||
value: "OFF",
|
||||
values: { on: "ON", off: "OFF" }
|
||||
},
|
||||
flyfry: {
|
||||
state: "/service/openhab/out/wifi_flyfry/state",
|
||||
command: "/service/openhab/in/wifi_flyfry/command",
|
||||
value: "OFF",
|
||||
values: { on: "ON", off: "OFF" }
|
||||
},
|
||||
artnet: {
|
||||
state: "/artnet/state",
|
||||
command: "/artnet/push",
|
||||
value: "blackout",
|
||||
values: { off: "blackout", yellow: "yellow", purple: "purple",
|
||||
blue: "blue", green: "green", red: "red", random: "random",
|
||||
cycle: "cycle-random" }
|
||||
}
|
||||
},
|
||||
controls: {
|
||||
led_stahltrager: {
|
||||
name: "LED Stahlträger",
|
||||
position: [360, 80],
|
||||
icon: "",
|
||||
ui: [
|
||||
{
|
||||
type: "toggle",
|
||||
text: "Stahlträger LED",
|
||||
topic: "led_stahltraeger"
|
||||
},
|
||||
]
|
||||
},
|
||||
snackbar: {
|
||||
name: "Snackbar",
|
||||
position: [550, 200],
|
||||
icon: "",
|
||||
ui: [
|
||||
{
|
||||
type: "toggle",
|
||||
text: "Snackbar",
|
||||
topic: "snackbar"
|
||||
}
|
||||
]
|
||||
},
|
||||
twinkle: {
|
||||
name: "Twinkle",
|
||||
position: [500, 280],
|
||||
icon: "",
|
||||
ui: [
|
||||
{
|
||||
type: "toggle",
|
||||
text: "Twinkle",
|
||||
topic: "twinkle"
|
||||
}
|
||||
]
|
||||
},
|
||||
flyfry: {
|
||||
name: "Fliegenbratgerät",
|
||||
position: [450, 320],
|
||||
icon: "",
|
||||
ui: [
|
||||
{
|
||||
type: "toggle",
|
||||
text: "Fliegenbratgerät",
|
||||
topic: "flyfry"
|
||||
}
|
||||
]
|
||||
},
|
||||
artnet: {
|
||||
name: "Artnet",
|
||||
position: [550,150],
|
||||
icon: "",
|
||||
ui: [
|
||||
{
|
||||
type: "toggle",
|
||||
text: "Gelb",
|
||||
topic: "artnet",
|
||||
on: "yellow"
|
||||
},
|
||||
{
|
||||
type: "toggle",
|
||||
text: "Rot",
|
||||
topic: "artnet",
|
||||
on: "red"
|
||||
},
|
||||
{
|
||||
type: "toggle",
|
||||
text: "Pink",
|
||||
topic: "artnet",
|
||||
on: "purple"
|
||||
},
|
||||
{
|
||||
type: "toggle",
|
||||
text: "Grün",
|
||||
topic: "artnet",
|
||||
on: "green"
|
||||
},
|
||||
{
|
||||
type: "toggle",
|
||||
text: "Cycle Random",
|
||||
topic: "artnet",
|
||||
on: "cycle"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
92
src/index.jsx
Normal file
92
src/index.jsx
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
// @flow
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
|
||||
import Drawer from 'material-ui/Drawer';
|
||||
import injectTapEventPlugin from 'react-tap-event-plugin';
|
||||
import { createStore } from "redux";
|
||||
import { MQTT_CONNECT } from "./stateActions";
|
||||
import connectMqtt from "./mqtt";
|
||||
import AppBar from "./appbar";
|
||||
import Toggle from "material-ui/Toggle";
|
||||
import * as UiItems from "./UiItems.js";
|
||||
import SpaceMap from "./map.js";
|
||||
import R from "ramda";
|
||||
import Config from "./config";
|
||||
import { Toolbar, ToolbarGroup, ToolbarTitle } from "material-ui/Toolbar";
|
||||
|
||||
injectTapEventPlugin();
|
||||
|
||||
const appState : State = {
|
||||
mqtt: null,
|
||||
ui: null,
|
||||
values: R.map(R.prop("value"), Config.topics)
|
||||
};
|
||||
|
||||
console.log(appState.values);
|
||||
|
||||
const handleEvent = (state = appState, action) => {
|
||||
switch (action.type) {
|
||||
case "CONNECT":
|
||||
return R.merge(state, { mqtt: action.mqtt });
|
||||
case "uiopen":
|
||||
return R.merge(state, { ui: action.ui });
|
||||
case "uiclose":
|
||||
return R.merge(state, { ui: null });
|
||||
case "mqtt_message":
|
||||
console.log(action.topic + ": " + action.message.toString());
|
||||
const val = (topic: string) =>
|
||||
Config.topics[topic].parseState == null ?
|
||||
action.message.toString() :
|
||||
Config.topics[topic].parseState(action.message);
|
||||
const keysToUpdate = R.keys(R.pickBy(val => val.state == action.topic,
|
||||
Config.topics));
|
||||
return R.mergeDeepRight(state, R.objOf("values", R.mergeAll(R.map(
|
||||
k => R.objOf(k, val(k)), keysToUpdate))));
|
||||
/*
|
||||
return R.merge(state, R.objOf("topics", R.merge(state.topics,
|
||||
R.map(R.merge(R.__, { value: val }),
|
||||
R.pickBy(val => val.state == action.topic, Config.topics)))));
|
||||
*/
|
||||
}
|
||||
return state;
|
||||
};
|
||||
|
||||
const store = createStore(handleEvent);
|
||||
|
||||
const UiItem = (state) => (props) =>
|
||||
UiItems[props.type](R.merge(props, {state:state}));
|
||||
|
||||
const renderUi = (state, key) => key != null && Config.controls[key] != null ?
|
||||
R.map(UiItem(state), Config.controls[key].ui) : null;
|
||||
|
||||
const App = (state: State) => (
|
||||
<div>
|
||||
<MuiThemeProvider>
|
||||
<div>
|
||||
<AppBar title="RZL Map" {...state} />
|
||||
<Drawer open={state.ui != null} openSecondary={true} disableSwipeToOpen={true}>
|
||||
<Toolbar>
|
||||
<ToolbarGroup firstChild={true}>
|
||||
<ToolbarTitle text={
|
||||
state.ui == null ? "" : Config.controls[state.ui].name}
|
||||
style={{"marginLeft": 10}} />
|
||||
</ToolbarGroup>
|
||||
</Toolbar>
|
||||
<div id="drawer_uiComponents">
|
||||
{renderUi(state, state.ui)}
|
||||
</div>
|
||||
</Drawer>
|
||||
</div>
|
||||
</MuiThemeProvider>
|
||||
<SpaceMap width={640} height={400} image="rzl.svg" zoom={0.1}
|
||||
store={store} state={state} />
|
||||
</div>
|
||||
);
|
||||
|
||||
store.subscribe(() => ReactDOM.render(<App {...store.getState()} />, document.getElementById("content")));
|
||||
|
||||
store.dispatch({type: null});
|
||||
store.dispatch({type: "mqtt_message", topic: "/service/openhab/out/pca301_ledstrips/state", message: "ON"});
|
||||
|
||||
connectMqtt("ws://172.22.36.207:1884", store); // wss://mqtt.starletp9.de/mqtt", store);
|
||||
29
src/map.js
Normal file
29
src/map.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// @flow
|
||||
import React from "react";
|
||||
import { Map, ImageOverlay, Marker, Popup } from "react-leaflet";
|
||||
import Leaflet from "leaflet";
|
||||
import R from "ramda";
|
||||
import Config from "./config";
|
||||
|
||||
// convert width/height coordinates to -height/width coordinates
|
||||
const c = (p) => [-p[1], p[0]]
|
||||
|
||||
const Markers = (props) => R.values(R.mapObjIndexed((el,key) => (
|
||||
<Marker position={c(el.position)} key={el.name}>
|
||||
<Popup onOpen={() => props.store.dispatch({type: "uiopen", ui: key})}
|
||||
onClose={() => props.store.dispatch({type: "uiclose"})}>
|
||||
<span>{el.name}</span>
|
||||
</Popup>
|
||||
</Marker>
|
||||
), R.propOr({}, "controls", Config)));
|
||||
|
||||
const SpaceMap = (props: Object) => (
|
||||
<Map center={c([props.width / 2, props.height / 2])} zoom={props.zoom}
|
||||
crs={Leaflet.CRS.Simple}>
|
||||
<ImageOverlay url={props.image}
|
||||
bounds={[c([0,0]), c([props.width, props.height])]} />
|
||||
{Markers(props)}
|
||||
</Map>
|
||||
);
|
||||
|
||||
export default SpaceMap;
|
||||
22
src/mqtt.js
Normal file
22
src/mqtt.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// @flow
|
||||
import mqtt from "mqtt";
|
||||
import { MQTT_MESSAGE, MQTT_CONNECT, MQTT_DISCONNECT } from "./stateActions";
|
||||
import { Store } from "redux";
|
||||
import Config from "./config";
|
||||
import R from "ramda";
|
||||
|
||||
export default function connectMqtt(url: string, store: Store<*,*>) {
|
||||
const client = mqtt.connect(url);
|
||||
client.on("connect", () => {
|
||||
store.dispatch({
|
||||
type: MQTT_CONNECT, mqtt: client
|
||||
});
|
||||
R.forEachObjIndexed(v =>
|
||||
client.subscribe(v.state), Config.topics);
|
||||
});
|
||||
client.on("message", (topic, message) => {
|
||||
store.dispatch({
|
||||
type: "mqtt_message", message: message, topic: topic
|
||||
});
|
||||
});
|
||||
}
|
||||
5
src/stateActions.js
Normal file
5
src/stateActions.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// @flow
|
||||
|
||||
export const MQTT_DISCONNECT = "DISCONNECT";
|
||||
export const MQTT_CONNECT = "CONNECT";
|
||||
export const MQTT_MESSAGE = "MESSAGE";
|
||||
Loading…
Add table
Add a link
Reference in a new issue