Add search

This commit is contained in:
uwap 2018-10-20 08:21:40 +02:00
parent 68be5fd1c3
commit c9ec79442b
6 changed files with 1286 additions and 1391 deletions

View file

@ -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": {

View file

@ -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,13 +58,15 @@ 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}
/>; />;
} }
@ -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",

View file

@ -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 =

View file

@ -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>

View file

@ -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`;

2543
yarn.lock

File diff suppressed because it is too large Load diff