Add search
This commit is contained in:
parent
68be5fd1c3
commit
c9ec79442b
6 changed files with 1286 additions and 1391 deletions
|
|
@ -21,7 +21,6 @@
|
||||||
"react": "^16.0.0",
|
"react": "^16.0.0",
|
||||||
"react-dom": "^16.0.0",
|
"react-dom": "^16.0.0",
|
||||||
"react-leaflet": "^2.0.0",
|
"react-leaflet": "^2.0.0",
|
||||||
"react-tap-event-plugin": "^3.0.0",
|
|
||||||
"redux": "^3.7.2"
|
"redux": "^3.7.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ export type AppState = {
|
||||||
mqttState: State,
|
mqttState: State,
|
||||||
mqttSend: (topic: string, value: Buffer) => void,
|
mqttSend: (topic: string, value: Buffer) => void,
|
||||||
mqttConnected: boolean,
|
mqttConnected: boolean,
|
||||||
|
search: string,
|
||||||
error: ?string
|
error: ?string
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -57,14 +58,16 @@ class App extends React.PureComponent<AppProps & Classes, AppState> {
|
||||||
(x) => this.topics[x].state.name)
|
(x) => this.topics[x].state.name)
|
||||||
}),
|
}),
|
||||||
mqttConnected: false,
|
mqttConnected: false,
|
||||||
|
search: "",
|
||||||
error: null
|
error: null
|
||||||
};
|
};
|
||||||
this.controlMap =
|
this.controlMap = (search: string) =>
|
||||||
<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}
|
onChangeControl={this.changeControl}
|
||||||
/>;
|
search={search}
|
||||||
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
get topics(): Topics {
|
get topics(): Topics {
|
||||||
|
|
@ -144,7 +147,8 @@ class App extends React.PureComponent<AppProps & Classes, AppState> {
|
||||||
changeState: this.changeState
|
changeState: this.changeState
|
||||||
}}>
|
}}>
|
||||||
<TopBar title={`${this.props.config.space.name} Map`}
|
<TopBar title={`${this.props.config.space.name} Map`}
|
||||||
connected={this.state.mqttConnected} />
|
connected={this.state.mqttConnected}
|
||||||
|
onSearch={(s) => this.setState({ search: s })} />
|
||||||
<SideBar open={this.state.drawerOpened}
|
<SideBar open={this.state.drawerOpened}
|
||||||
control={this.state.selectedControl}
|
control={this.state.selectedControl}
|
||||||
onCloseRequest={this.closeDrawer}
|
onCloseRequest={this.closeDrawer}
|
||||||
|
|
@ -154,7 +158,7 @@ class App extends React.PureComponent<AppProps & Classes, AppState> {
|
||||||
{this.state.selectedControl == null
|
{this.state.selectedControl == null
|
||||||
|| <UiItemList controls={this.state.selectedControl.ui} />}
|
|| <UiItemList controls={this.state.selectedControl.ui} />}
|
||||||
</SideBar>
|
</SideBar>
|
||||||
{this.controlMap}
|
{this.controlMap(this.state.search)}
|
||||||
<Snackbar
|
<Snackbar
|
||||||
anchorOrigin={{
|
anchorOrigin={{
|
||||||
vertical: "bottom",
|
vertical: "bottom",
|
||||||
|
|
|
||||||
|
|
@ -3,6 +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 filter from "lodash/filter";
|
||||||
import MqttContext from "mqtt/context";
|
import MqttContext from "mqtt/context";
|
||||||
import type { Controls, Control } from "config/flowtypes";
|
import type { Controls, Control } from "config/flowtypes";
|
||||||
|
|
||||||
|
|
@ -16,6 +17,7 @@ export type ControlMapProps = {
|
||||||
zoom: number,
|
zoom: number,
|
||||||
layers: Array<Layer>,
|
layers: Array<Layer>,
|
||||||
controls: Controls,
|
controls: Controls,
|
||||||
|
search: string,
|
||||||
onChangeControl: (control: Control) => void
|
onChangeControl: (control: Control) => void
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -56,8 +58,42 @@ const renderMarker = (props: ControlMapProps) =>
|
||||||
</MqttContext.Consumer>
|
</MqttContext.Consumer>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const safeIncludes = (o: {type?: string, text?: string, topic?: string},
|
||||||
|
s: string) => {
|
||||||
|
if (o.type != null) {
|
||||||
|
if (o.type.toLowerCase().includes(s)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (o.text != null) {
|
||||||
|
if (o.text.toLowerCase().includes(s)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (o.topic != null) {
|
||||||
|
if (o.topic.toLowerCase().includes(s)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isVisible = (props: ControlMapProps) => (c: UIControl) => {
|
||||||
|
if (safeIncludes(c, props.search.toLowerCase())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (c.ui != null) {
|
||||||
|
for (let k in c.ui) {
|
||||||
|
if (safeIncludes(c.ui[k], props.search.toLowerCase())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
const renderMarkers = (props: ControlMapProps) =>
|
const renderMarkers = (props: ControlMapProps) =>
|
||||||
map(props.controls, renderMarker(props));
|
map(filter(props.controls, isVisible(props)), renderMarker(props));
|
||||||
|
|
||||||
const renderLayer = (layer: Layer) => {
|
const renderLayer = (layer: Layer) => {
|
||||||
const LayersControlType =
|
const LayersControlType =
|
||||||
|
|
|
||||||
|
|
@ -5,25 +5,92 @@ import AppBar from "@material-ui/core/AppBar";
|
||||||
import Toolbar from "@material-ui/core/Toolbar";
|
import Toolbar from "@material-ui/core/Toolbar";
|
||||||
import Typography from "@material-ui/core/Typography";
|
import Typography from "@material-ui/core/Typography";
|
||||||
import CircularProgress from "@material-ui/core/CircularProgress";
|
import CircularProgress from "@material-ui/core/CircularProgress";
|
||||||
|
import InputBase from "@material-ui/core/InputBase";
|
||||||
|
import { fade } from "@material-ui/core/styles/colorManipulator";
|
||||||
|
import { withStyles } from "@material-ui/core/styles";
|
||||||
|
|
||||||
export type TopBarProps = {
|
export type TopBarProps = {
|
||||||
title: string,
|
title: string,
|
||||||
connected: boolean
|
connected: boolean,
|
||||||
|
onSearch: string => void
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SearchBarProps = {
|
||||||
|
onSearch: string => void
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderConnectionIndicator = (connected: boolean) => {
|
const renderConnectionIndicator = (connected: boolean) => {
|
||||||
if (connected) {
|
if (connected) {
|
||||||
return (<i style={{fontSize: 48}} className="mdi mdi-map"></i>);
|
return (<i style={{fontSize: 32}} className="mdi mdi-map"></i>);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<CircularProgress size={48} style={{color: "rgba(0, 0, 0, 0.54)"}} />
|
<CircularProgress size={32} style={{color: "rgba(0, 0, 0, 0.54)"}} />
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const searchStyles = (theme) => ({
|
||||||
|
search: {
|
||||||
|
position: "relative",
|
||||||
|
borderRadius: theme.shape.borderRadius,
|
||||||
|
backgroundColor: fade(theme.palette.common.white, 0.15),
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: fade(theme.palette.common.white, 0.25)
|
||||||
|
},
|
||||||
|
marginRight: theme.spacing.unit * 2,
|
||||||
|
marginLeft: 0,
|
||||||
|
width: "100%",
|
||||||
|
[theme.breakpoints.up("sm")]: {
|
||||||
|
marginLeft: theme.spacing.unit * 3,
|
||||||
|
width: "auto"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
searchIcon: {
|
||||||
|
width: theme.spacing.unit * 6,
|
||||||
|
height: "100%",
|
||||||
|
position: "absolute",
|
||||||
|
pointerEvents: "none",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
fontSize: "24px"
|
||||||
|
},
|
||||||
|
inputRoot: {
|
||||||
|
color: "inherit",
|
||||||
|
width: "100%"
|
||||||
|
},
|
||||||
|
inputInput: {
|
||||||
|
paddingTop: theme.spacing.unit,
|
||||||
|
paddingRight: theme.spacing.unit,
|
||||||
|
paddingBottom: theme.spacing.unit,
|
||||||
|
paddingLeft: theme.spacing.unit * 6,
|
||||||
|
transition: theme.transitions.create("width"),
|
||||||
|
width: "100%",
|
||||||
|
[theme.breakpoints.up("md")]: {
|
||||||
|
width: 200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const RawSearch = (props: SearchBarProps & Classes) => (
|
||||||
|
<div className={props.classes.search}>
|
||||||
|
<i className={`mdi mdi-magnify ${props.classes.searchIcon}`}></i>
|
||||||
|
<InputBase placeholder="Search…" type="search"
|
||||||
|
onChange={(e) => props.onSearch(e.target.value)}
|
||||||
|
classes={{
|
||||||
|
root: props.classes.inputRoot,
|
||||||
|
input: props.classes.inputInput
|
||||||
|
}} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const Search = withStyles(searchStyles)(RawSearch);
|
||||||
|
|
||||||
const TopBar = (props: TopBarProps) => (
|
const TopBar = (props: TopBarProps) => (
|
||||||
<AppBar position="static">
|
<AppBar position="static">
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
{renderConnectionIndicator(props.connected)}
|
{renderConnectionIndicator(props.connected)}
|
||||||
|
<Search onSearch={props.onSearch} />
|
||||||
|
<span style={{flex: 1}}></span>
|
||||||
<Typography variant="title">{props.title}</Typography>
|
<Typography variant="title">{props.title}</Typography>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</AppBar>
|
</AppBar>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import injectTapEventPlugin from "react-tap-event-plugin";
|
|
||||||
|
|
||||||
import App from "components/App";
|
import App from "components/App";
|
||||||
|
|
||||||
|
|
@ -11,7 +10,6 @@ import "../css/styles.css";
|
||||||
import type { Config } from "config/flowtypes";
|
import type { Config } from "config/flowtypes";
|
||||||
|
|
||||||
const config: Config = window.config;
|
const config: Config = window.config;
|
||||||
injectTapEventPlugin();
|
|
||||||
|
|
||||||
document.title = `${config.space.name} Map`;
|
document.title = `${config.space.name} Map`;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue