Commit e1e85b3c authored by alain's avatar alain 🐙
Browse files

add search box for all pois on map

parent c63f9fba
...@@ -24,7 +24,8 @@ export const textsNL = { ...@@ -24,7 +24,8 @@ export const textsNL = {
timeSelectionHint: "↖ tijdselectie", timeSelectionHint: "↖ tijdselectie",
chartHeaderAddition: "(uurwaarden)", chartHeaderAddition: "(uurwaarden)",
downloadCsv: "download csv", downloadCsv: "download csv",
downloadInfo: "" downloadInfo: "",
search: "Zoeken"
} }
export const textsEN = { export const textsEN = {
...@@ -53,4 +54,5 @@ export const textsEN = { ...@@ -53,4 +54,5 @@ export const textsEN = {
chartHeaderAddition: "(hourly values)", chartHeaderAddition: "(hourly values)",
downloadCsv: "download csv", downloadCsv: "download csv",
downloadInfo: "", downloadInfo: "",
search: "Search"
} }
\ No newline at end of file
import React from 'react'
const IconSearch = () => {
return (
<svg className="icon icon-ui icon-search" viewBox="0 0 34 34">
<path d="M11,23l4.18-4.15" fill="none" stroke="#000" strokeWidth="2"/>
<circle cx="18.5" cy="15.5" r="4.5" fill="none" stroke="#000" strokeMiterlimit="10" strokeWidth="1.38"/>
</svg>
)
}
export default IconSearch
\ No newline at end of file
...@@ -6,7 +6,7 @@ class MapAttributions extends React.Component { ...@@ -6,7 +6,7 @@ class MapAttributions extends React.Component {
<div className="mapboxgl-ctrl-bottom-right"> <div className="mapboxgl-ctrl-bottom-right">
<div className="mapboxgl-ctrl mapboxgl-ctrl-attrib"> <div className="mapboxgl-ctrl mapboxgl-ctrl-attrib">
<div className="mapboxgl-ctrl-attrib-inner"> <div className="mapboxgl-ctrl-attrib-inner">
<span>v. 28/02/2020</span> <span>v. 04/03/2020</span>
<span><a href="https://waag.org" target="_blank" rel="noopener noreferrer">waag</a></span> <span><a href="https://waag.org" target="_blank" rel="noopener noreferrer">waag</a></span>
<span>© <a href="http://www.openstreetmap.org/about/" target="_blank" rel="noopener noreferrer">OpenStreetMap</a></span> <span>© <a href="http://www.openstreetmap.org/about/" target="_blank" rel="noopener noreferrer">OpenStreetMap</a></span>
</div> </div>
......
...@@ -77,14 +77,12 @@ class MapLocations extends React.Component { ...@@ -77,14 +77,12 @@ class MapLocations extends React.Component {
return ( return (
<div className='mapboxgl-ctrl-top-left'> <div id="map-locations" className={(open ? "select open" : "select")}>
<div id="map-locations" className={(open ? "select open" : "select")}> <IconArrowDown />
<IconArrowDown /> <div className="value-current" onClick={() => { this.setState({ open: !open }) }}>{current}</div>
<div className="value-current" onClick={() => { this.setState({ open: !open }) }}>{current}</div> <ul className="value-list">
<ul className="value-list"> {listOptions}
{listOptions} </ul>
</ul>
</div>
</div> </div>
) )
} }
......
import React from 'react'
import IconSearch from 'Icons/IconSearch'
import { texts } from "../../../config/texts"
class MapSearch extends React.Component {
constructor(props){
super()
this.state = {
search: texts.search,
current: -1,
pois: []
}
this.handleKeydown = this.handleKeydown.bind(this)
this.handleOnFocus = this.handleOnFocus.bind(this)
this.handleOnBlur = this.handleOnBlur.bind(this)
this.handleOnChange = this.handleOnChange.bind(this)
this.searchOptionsRef = React.createRef();
}
static getDerivedStateFromProps(props, state) {
const deckLayers = props.deckLayers
var uniquePois = {}
Object.keys(deckLayers).forEach(parameter => {
Object.keys(deckLayers[parameter]).forEach(source => {
if(deckLayers[parameter][source].data) {
deckLayers[parameter][source].data.forEach(poi => {
uniquePois[poi.name] = poi.coordinates
})
}
})
})
const pois = Object.keys(uniquePois).map(poi => {
return {
name: poi,
coordinates: uniquePois[poi]
}
})
if(pois.length !== state.pois.length) {
return {
pois: pois,
poisFiltered: pois
}
}
return false
}
setSearch(value) {
this.setState({
search: value
})
}
handleKeydown(e){
const { current, poisFiltered } = this.state
const searchOptions = this.searchOptionsRef.current
// key arrow up
if(e.keyCode === 38 && current > -1) {
e.preventDefault()
this.setState({
current: current-1
})
if(current < poisFiltered.length-1) {
searchOptions.scrollBy(0, -searchOptions.children[current].clientHeight)
}
}
// key arrow down
if(e.keyCode === 40 && current < poisFiltered.length-1) {
e.preventDefault()
this.setState({
current: current+1
})
if(current > 1) {
searchOptions.scrollBy(0, searchOptions.children[current].clientHeight)
}
}
// key enter
if(e.keyCode === 13 && current > -1) {
e.preventDefault()
this.props.changeViewportCenter(e, poisFiltered[current].coordinates, 17)
e.target.blur()
}
// key escape
if(e.keyCode === 27) {
e.target.blur()
}
}
handleOnFocus(e) {
if(this.state.search === texts.search) {
this.setSearch('')
} else {
e.target.setSelectionRange(0, e.target.value.length)
}
document.addEventListener("keydown", this.handleKeydown, false)
}
handleOnBlur() {
this.setState({
current: -1
})
setTimeout(() => {
if(this.state.search === '') {
this.setSearch(texts.search)
}
}, 200)
document.removeEventListener("keydown", this.handleKeydown, false)
}
handleOnChange(e) {
const search = e.target.value
const poisFiltered = this.state.pois.filter(poi => {
return poi.name.toLowerCase().includes(search.toLowerCase())
})
this.setState({
current: -1,
search,
poisFiltered
})
this.searchOptionsRef.current.scrollTo(0, 0)
}
render() {
const { changeViewportCenter } = this.props
const { search, current, pois, poisFiltered } = this.state
if (pois.length === 0) return null
return (
<div id="search-form">
<IconSearch />
<input
value={ search }
onFocus={ e => this.handleOnFocus(e) }
onBlur={ this.handleOnBlur }
onChange={ e => this.handleOnChange(e) }
/>
<div id="search-options" ref={this.searchOptionsRef}>{
poisFiltered.map((poi , i) => {
const className = (current === i ? 'current' : '')
return <div key={poi.name} className={className} onClick={(e)=>{
changeViewportCenter(e, poi.coordinates, 17)
this.setState({
search: texts.search,
poisFiltered: pois
})
}
}>{poi.name}</div>
})
}</div>
</div>
)
}
}
export default MapSearch
\ No newline at end of file
...@@ -51,7 +51,7 @@ class Panel extends React.Component { ...@@ -51,7 +51,7 @@ class Panel extends React.Component {
<IconArrowDown /> <IconArrowDown />
</h3> </h3>
<div className="panel-content"> <div className="panel-content">
{ ReactHtmlParser(content.body) } { typeof content.body === 'string' || content.body instanceof String ? ReactHtmlParser(content.body) : content.body }
</div> </div>
</div> </div>
: :
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
.panel { .panel {
margin: 10px; margin: 10px;
padding: 4px 10px; padding: 4px 10px;
width: 260px; width: 270px;
background-color: #FFF; background-color: #FFF;
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.15); box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.15);
cursor: default; cursor: default;
...@@ -95,4 +95,73 @@ ...@@ -95,4 +95,73 @@
justify-content: space-between; justify-content: space-between;
font-size: 13px; font-size: 13px;
//text-transform: lowercase; //text-transform: lowercase;
}
// panel search
#search-form {
pointer-events: auto;
position: relative;
height: 34px;
margin: 10px;
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.15);
.icon-search {
position: absolute;
width: 34px;
height: 34px;
top: 0px;
right: 0px;
z-index: 5;
pointer-events: none;
}
input {
position: absolute;
width: 100%;
line-height: 16px;
//height: 1.75rem;
padding: 9px;
font-size: 14px;
border: none;
z-index: 4;
}
#search-options {
opacity: 0;
visibility: hidden;
position: absolute;
left: 0;
top: 34px;
width: 100%;
max-height: 12rem;
overflow-y: auto;
background-color: #fff;
line-height: 1.5;
font-size: 0.75rem;
z-index: 3;
transition: all 150ms ease;
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.15);
border-top: 1px solid #ddd;
&::-webkit-scrollbar { width: 5px; }
&::-webkit-scrollbar-thumb { background-color: rgba(0,0,0,0.1); }
&::-webkit-scrollbar-track { background-color: #eee; }
div {
padding: 9px;
cursor: pointer;
&:hover,
&.current {
background-color: rgba(0,0,0,0.05);
}
}
}
input:focus + #search-options {
visibility: visible;
opacity: 1;
}
} }
\ No newline at end of file
...@@ -79,7 +79,7 @@ $color-input: #FFF; ...@@ -79,7 +79,7 @@ $color-input: #FFF;
//position: absolute; //position: absolute;
top: 100%; top: 100%;
left: 0; left: 0;
width: 260px; width: 270px;
min-width: 100%;; min-width: 100%;;
max-height: 0; max-height: 0;
overflow: hidden; overflow: hidden;
......
...@@ -24,6 +24,7 @@ import DeckLayers from "./DeckLayers/DeckLayers" ...@@ -24,6 +24,7 @@ import DeckLayers from "./DeckLayers/DeckLayers"
import MapControls from "./Map/MapControls.js" import MapControls from "./Map/MapControls.js"
import MapLocations from "./Map/MapLocations.js" import MapLocations from "./Map/MapLocations.js"
import MapSearch from 'Map/MapSearch.js'
import MapAttributions from "./Map/MapAttributions.js" import MapAttributions from "./Map/MapAttributions.js"
import MapLoading from "./Map/MapLoading" import MapLoading from "./Map/MapLoading"
...@@ -59,6 +60,7 @@ class App extends React.Component { ...@@ -59,6 +60,7 @@ class App extends React.Component {
this.changeViewport = this.changeViewport.bind(this) this.changeViewport = this.changeViewport.bind(this)
this.changeViewportBounds = this.changeViewportBounds.bind(this) this.changeViewportBounds = this.changeViewportBounds.bind(this)
this.changeViewportCenter = this.changeViewportCenter.bind(this)
this.onViewportChange = this.onViewportChange.bind(this) this.onViewportChange = this.onViewportChange.bind(this)
this.toggleLayer = this.toggleLayer.bind(this) this.toggleLayer = this.toggleLayer.bind(this)
this.reload = this.reload.bind(this) this.reload = this.reload.bind(this)
...@@ -147,6 +149,25 @@ class App extends React.Component { ...@@ -147,6 +149,25 @@ class App extends React.Component {
}) })
} }
changeViewportCenter(event, center, zoom) {
if(event) event.stopPropagation()
const { viewport } = this.state
const change = {
longitude: center[0],
latitude: center[1],
zoom,
transitionDuration: 500,
transitionInterpolator: new FlyToInterpolator()
}
this.setState({
viewport: {...viewport, ...change },
selectedCenter: center,
})
}
changeViewportBounds(event, bounds) { changeViewportBounds(event, bounds) {
if(event) event.stopPropagation() if(event) event.stopPropagation()
...@@ -229,8 +250,6 @@ class App extends React.Component { ...@@ -229,8 +250,6 @@ class App extends React.Component {
const { viewport, layers, selectedCenter, deckLayers, loading, loadingError } = this.state const { viewport, layers, selectedCenter, deckLayers, loading, loadingError } = this.state
const { pitchMax, zoomMax, zoomMin } = mapControlSettings const { pitchMax, zoomMax, zoomMin } = mapControlSettings
//console.log(viewport)
return ( return (
<div id="app"> <div id="app">
<MapGL { ...viewport } width="100%" height="100%" maxPitch={pitchMax} maxZoom={zoomMax} minZoom={zoomMin} onViewportChange={ this.onViewportChange } mapStyle={mapStyle} onTransitionStart={() => { this.setTransitionState(true) } } onTransitionEnd={() => { this.setTransitionState(false) } }> <MapGL { ...viewport } width="100%" height="100%" maxPitch={pitchMax} maxZoom={zoomMax} minZoom={zoomMin} onViewportChange={ this.onViewportChange } mapStyle={mapStyle} onTransitionStart={() => { this.setTransitionState(true) } } onTransitionEnd={() => { this.setTransitionState(false) } }>
...@@ -246,7 +265,10 @@ class App extends React.Component { ...@@ -246,7 +265,10 @@ class App extends React.Component {
<PanelLayers layers={layers} forceUpdate={layers.join()} dataGroups={dataGroups} toggleLayer={this.toggleLayer} viewportHeight={viewport.height} /> <PanelLayers layers={layers} forceUpdate={layers.join()} dataGroups={dataGroups} toggleLayer={this.toggleLayer} viewportHeight={viewport.height} />
</div> </div>
{ mapLocations && <MapLocations mapLocations={mapLocations} changeViewportBounds={this.changeViewportBounds} selectedCenter={selectedCenter} /> } <div className='mapboxgl-ctrl-top-left'>
{ mapLocations && <MapLocations mapLocations={mapLocations} changeViewportBounds={this.changeViewportBounds} selectedCenter={selectedCenter} /> }
<MapSearch deckLayers={deckLayers} changeViewportCenter={this.changeViewportCenter} />
</div>
{ this.renderModal() } { this.renderModal() }
</div> </div>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment