Use React Context API

This commit is contained in:
uwap 2018-07-22 04:28:20 +02:00
parent 3b78a4d8ad
commit df237ef336
7 changed files with 90 additions and 72 deletions

View file

@ -21,6 +21,7 @@ import ControlMap from "components/ControlMap";
import TopBar from "components/TopBar"; import TopBar from "components/TopBar";
import UiItemList from "components/UiItemList"; import UiItemList from "components/UiItemList";
import MqttContext from "mqtt/context";
import connectMqtt from "../connectMqtt"; import connectMqtt from "../connectMqtt";
export type AppProps = { export type AppProps = {
@ -119,7 +120,6 @@ class App extends React.PureComponent<AppProps & Classes, AppState> {
const val = const val =
transformValue == null ? value : transformValue(Buffer.from(value)); transformValue == null ? value : transformValue(Buffer.from(value));
this.state.mqttSend(rawTopic, Buffer.from(val)); this.state.mqttSend(rawTopic, Buffer.from(val));
throw new Error("test");
} catch (err) { } catch (err) {
this.setState({ error: err.toString() }); this.setState({ error: err.toString() });
} }
@ -127,9 +127,12 @@ class App extends React.PureComponent<AppProps & Classes, AppState> {
render() { render() {
return ( return (
<div> <MqttContext.Provider value={{
state: this.state.mqttState,
changeState: this.changeState.bind(this)
}}>
<MuiThemeProvider theme={this.theme}> <MuiThemeProvider theme={this.theme}>
<div> <React.Fragment>
<TopBar title={`${this.props.config.space.name} Map`} <TopBar title={`${this.props.config.space.name} Map`}
connected={this.state.mqttConnected} /> connected={this.state.mqttConnected} />
<SideBar open={this.state.drawerOpened} <SideBar open={this.state.drawerOpened}
@ -139,18 +142,14 @@ class App extends React.PureComponent<AppProps & Classes, AppState> {
this.state.selectedControl.icon(this.state.mqttState)} this.state.selectedControl.icon(this.state.mqttState)}
> >
{this.state.selectedControl == null {this.state.selectedControl == null
|| <UiItemList state={this.state.mqttState} || <UiItemList controls={this.state.selectedControl.ui} />}
controls={this.state.selectedControl.ui}
onChangeState={this.changeState.bind(this)}
/>}
</SideBar> </SideBar>
</div> </React.Fragment>
</MuiThemeProvider> </MuiThemeProvider>
<ControlMap width={1000} height={700} zoom={0} <ControlMap width={1000} height={700} zoom={0}
layers={this.props.config.layers} layers={this.props.config.layers}
controls={this.props.config.controls} controls={this.props.config.controls}
onChangeControl={this.changeControl.bind(this)} onChangeControl={this.changeControl.bind(this)}
state={this.state.mqttState}
/> />
<Snackbar <Snackbar
anchorOrigin={{ anchorOrigin={{
@ -176,7 +175,7 @@ class App extends React.PureComponent<AppProps & Classes, AppState> {
<i className="mdi mdi-close" /> <i className="mdi mdi-close" />
</IconButton> </IconButton>
} /> } />
</div> </MqttContext.Provider>
); );
} }
} }

View file

@ -3,7 +3,7 @@ import React from "react";
import { Map, ImageOverlay, Marker, LayersControl } from "react-leaflet"; import { Map, ImageOverlay, Marker, LayersControl } from "react-leaflet";
import { CRS, point, divIcon } from "leaflet"; import { CRS, point, divIcon } from "leaflet";
import map from "lodash/map"; import map from "lodash/map";
import MqttContext from "mqtt/context";
import type { Controls, Control } from "config/flowtypes"; import type { Controls, Control } from "config/flowtypes";
export type Point = [number, number]; export type Point = [number, number];
@ -16,8 +16,7 @@ export type ControlMapProps = {
zoom: number, zoom: number,
layers: Array<Layer>, layers: Array<Layer>,
controls: Controls, controls: Controls,
onChangeControl: (control: Control) => void, onChangeControl: (control: Control) => void
state: State
}; };
export default class ControlMap extends React.PureComponent<ControlMapProps> { export default class ControlMap extends React.PureComponent<ControlMapProps> {
@ -48,32 +47,36 @@ export default class ControlMap extends React.PureComponent<ControlMapProps> {
return map(this.props.controls, this.renderMarker.bind(this)); return map(this.props.controls, this.renderMarker.bind(this));
} }
createLeafletIcon(control: Control) { createLeafletIcon(control: Control, state: State) {
const icon = control.icon(this.props.state); const icon = control.icon(state);
const iconClass = `${icon} mdi-36px`; const iconClass = `${icon} mdi-36px`;
return divIcon({ return divIcon({
iconSize: point(36, 36), iconSize: point(36, 36),
iconAnchor: point(18, 18), iconAnchor: point(18, 18),
html: `<i class="${iconClass}" html: `<i class="${iconClass}"
style="line-height: 1; color: ${this.iconColor(control)}"></i>` style="line-height: 1; color: ${this.iconColor(control, state)}"></i>`
}); });
} }
iconColor(control: Control): string { iconColor(control: Control, state: State): string {
if (control.iconColor != null) { if (control.iconColor != null) {
return control.iconColor(this.props.state); return control.iconColor(state);
} }
return "#000"; return "#000";
} }
renderMarker(control: Control, key: string) { renderMarker(control: Control, key: string) {
return ( return (
<Marker position={convertPoint(control.position)} <MqttContext.Consumer>
key={key} {({ state }) => (
icon={this.createLeafletIcon(control)} <Marker position={convertPoint(control.position)}
onClick={() => this.props.onChangeControl(control)} key={key}
> icon={this.createLeafletIcon(control, state)}
</Marker> onClick={() => this.props.onChangeControl(control)}
>
</Marker>
)}
</MqttContext.Consumer>
); );
} }

View file

@ -8,7 +8,7 @@ import IconButton from "@material-ui/core/IconButton";
import AppBar from "@material-ui/core/AppBar"; import AppBar from "@material-ui/core/AppBar";
import Toolbar from "@material-ui/core/Toolbar"; import Toolbar from "@material-ui/core/Toolbar";
import List from "@material-ui/core/List"; import List from "@material-ui/core/List";
import { renderIcon } from "config/icon"; import { renderRawIcon } from "config/icon";
import type { RawIcon } from "config/icon"; import type { RawIcon } from "config/icon";
import type { Control } from "config/flowtypes"; import type { Control } from "config/flowtypes";
@ -57,7 +57,7 @@ class SideBar extends React.PureComponent<Props, SideBarState> {
<AppBar position="static"> <AppBar position="static">
<Toolbar> <Toolbar>
<span>{this.props.icon == null <span>{this.props.icon == null
|| renderIcon(this.props.icon, "mdi-36px")}</span> || renderRawIcon(this.props.icon, "mdi-36px")}</span>
<Typography variant="title" className={this.props.classes.flex}> <Typography variant="title" className={this.props.classes.flex}>
{this.props.control == null ? "" : this.props.control.name} {this.props.control == null ? "" : this.props.control.name}
</Typography> </Typography>

View file

@ -3,7 +3,7 @@ import React from "react";
import keys from "lodash/keys"; import keys from "lodash/keys";
import map from "lodash/map"; import map from "lodash/map";
import debounce from "lodash/debounce"; import debounce from "lodash/debounce";
import { renderIcon } from "config/icon"; import { renderRawIcon } from "config/icon";
import ListItemSecondaryAction from "@material-ui/core/ListItemSecondaryAction"; import ListItemSecondaryAction from "@material-ui/core/ListItemSecondaryAction";
import ListItemText from "@material-ui/core/ListItemText"; import ListItemText from "@material-ui/core/ListItemText";
import ListSubheader from "@material-ui/core/ListSubheader"; import ListSubheader from "@material-ui/core/ListSubheader";
@ -218,7 +218,7 @@ export class Link extends UiItem<UILink> {
disabled={!this.isEnabled()} disabled={!this.isEnabled()}
> >
{this.props.item.icon == null ? "" {this.props.item.icon == null ? ""
: renderIcon(this.props.item.icon(this.props.state), "mdi-24px")} : renderRawIcon(this.props.item.icon(this.props.state), "mdi-24px")}
{this.props.item.text} {this.props.item.text}
</Button> </Button>
); );

View file

@ -2,17 +2,17 @@
import React from "react"; import React from "react";
import ListItem from "@material-ui/core/ListItem"; import ListItem from "@material-ui/core/ListItem";
import ListItemIcon from "@material-ui/core/ListItemIcon"; import ListItemIcon from "@material-ui/core/ListItemIcon";
import { renderIcon } from "config/icon"; import { renderRawIcon } from "config/icon";
import type { ControlUI } from "config/flowtypes"; import type { ControlUI } from "config/flowtypes";
import { Toggle, DropDown, Link, import { Toggle, DropDown, Link,
Section, Text, Progress, Slider } from "./UiItem"; Section, Text, Progress, Slider } from "./UiItem";
import MqttContext from "mqtt/context";
import type { MqttContextValue } from "mqtt/context";
export type UiItemListProps = { export type UiItemListProps = {
controls: Array<ControlUI>, controls: Array<ControlUI>
state: State,
onChangeState: (topic: string, nextState: string) => void
}; };
export default class UiItemList extends React.PureComponent<UiItemListProps> { export default class UiItemList extends React.PureComponent<UiItemListProps> {
@ -27,66 +27,60 @@ export default class UiItemList extends React.PureComponent<UiItemListProps> {
"A control is missing the \"type\" parameter" "A control is missing the \"type\" parameter"
); );
} }
if (control.type === "section") {
return this.renderControl(control, key.toString());
}
return ( return (
<ListItem key={key}> <ListItem key={key}>
<React.Fragment> <MqttContext.Consumer>
{control.icon == null || control.type === "link" || {this.renderListItem(control, key)}
<ListItemIcon key={`${key.toString()}-liicon`}> </MqttContext.Consumer>
{renderIcon(control.icon(this.props.state), "mdi-24px")}
</ListItemIcon>}
{this.renderControl(control, key.toString())}
</React.Fragment>
</ListItem> </ListItem>
); );
}); });
} }
renderControl(control: ControlUI, key: string) { renderListItem(control: ControlUI, key: number) {
return (mqtt: MqttContextValue) => {
const node = this.renderControl(control, key.toString(), mqtt);
if (control.icon == null || control.type === "link"
|| control.type === "section") {
return node;
} else {
const listIconNode = (
<ListItemIcon key={`${key.toString()}-liicon`}>
{renderRawIcon(control.icon(mqtt.state), "mdi-24px")}
</ListItemIcon>
);
return [listIconNode, node];
}
};
}
renderControl(control: ControlUI, key: string, mqtt: MqttContextValue) {
const props = {
state: mqtt.state,
onChangeState: mqtt.changeState,
key: `${key}-licontrol`
};
switch (control.type) { switch (control.type) {
case "toggle": { case "toggle": {
return <Toggle item={control} return <Toggle item={control} {...props} />;
state={this.props.state}
onChangeState={this.props.onChangeState}
key={`${key}-licontrol`} />;
} }
case "dropDown": { case "dropDown": {
return <DropDown item={control} return <DropDown item={control} {...props} />;
state={this.props.state}
onChangeState={this.props.onChangeState}
key={`${key}-licontrol`} />;
} }
case "section": { case "section": {
return <Section item={control} return <Section item={control} {...props} />;
state={this.props.state}
onChangeState={this.props.onChangeState}
key={`${key}-licontrol`} />;
} }
case "link": { case "link": {
return <Link item={control} return <Link item={control} {...props} />;
state={this.props.state}
onChangeState={this.props.onChangeState}
key={`${key}-licontrol`} />;
} }
case "slider": { case "slider": {
return <Slider item={control} return <Slider item={control} {...props} />;
state={this.props.state}
onChangeState={this.props.onChangeState}
key={`${key}-licontrol`} />;
} }
case "text": { case "text": {
return <Text item={control} return <Text item={control} {...props} />;
state={this.props.state}
onChangeState={this.props.onChangeState}
key={`${key}-licontrol`} />;
} }
case "progress": { case "progress": {
return <Progress item={control} return <Progress item={control} {...props} />;
state={this.props.state}
onChangeState={this.props.onChangeState}
key={`${key}-licontrol`} />;
} }
default: { default: {
throw new Error( throw new Error(

View file

@ -1,5 +1,6 @@
// @flow // @flow
import * as React from "react"; import * as React from "react";
import ReactContext from "mqtt/context";
export opaque type RawIcon: string = string; export opaque type RawIcon: string = string;
@ -39,7 +40,16 @@ export const mdiBattery = (topic: string) => (state: State) => {
} }
}; };
export const renderIcon = export const renderRawIcon =
(icon: RawIcon, extraClass?: string): React.Node => { (icon: RawIcon, extraClass?: string): React.Node => {
return <i className={`${extraClass || ""} ${icon}`}></i>; return <i className={`${extraClass || ""} ${icon}`}></i>;
}; };
export const renderIcon =
(icon: Icon, extraClass?: string): React.Node => {
return (
<ReactContext.Consumer>
{({state}) => renderRawIcon(icon(state), extraClass)}
</ReactContext.Consumer>
);
};

12
src/mqtt/context.js Normal file
View file

@ -0,0 +1,12 @@
// @flow
import React from "react";
export type MqttContextValue = {
state: State,
changeState: (topic: string, value: string) => void
};
export default React.createContext({
state: {},
changeState: (_topic, _val) => {}
});