import React from 'react' import Filters from './Filters' import Listings from './Listings' import ListingDetails from './ListingDetails' import {pacSelectFirst} from '../helpers/googleMaps' import {loadProperties, loadSeen, loadListing} from '../lib/api' import {handleMessage} from '../lib/handlers' import Router from '../lib/router' class Main extends React.Component { constructor (props) { super(props) const state = { listingDetails: false, listings: new Map(), imageIndex: 0, page: 0, sort: 'relevance', filters: { rooms: {}, category: {} }, mobileView: 'MAP', contact: { message: '', name: '', email: '', phone: '', valid: true } } if (props.initialState) { props.initialState.sort = props.initialState.sort || state.sort state.filters.rooms = props.initialState.rooms state.filters.category = props.initialState.category state.sort = props.initialState.sort || state.sort state.listingId = props.initialState.listingId state.bounds = props.initialState.bounds state.zoom = props.initialState.zoom if (state.listingId) { state.listingDetails = true } state.filters.minSize = props.initialState.minSize state.filters.maxSize = props.initialState.maxSize state.filters.minPrice = props.initialState.minPrice state.filters.maxPrice = props.initialState.maxPrice } this.state = state this.router = new Router(this, props.initialState) } dispatch ({type, action = {}}) { handleMessage({type, action}, this) } componentDidMount () { const uluru = {lat: 43.845031, lng: 18.4019262} const opts = { //zoom: 13, //center: uluru, streetViewControl: false, mapTypeControl: false } if (!this.state.bounds) { opts.zoom = 13 opts.center = uluru } const map = new google.maps.Map(this.refs.map, opts) window.gmap = map //const marker = new google.maps.Marker({ //position: uluru, //map: map //}); var control = document.createElement('div') control.classList.add('filters-btn-toggle') control.innerHTML = '' //control.style = "top: 200px;" // TODO: enable this //control['style'] = 'top: 200px;' var input = document.getElementById('gmaps-places-input') pacSelectFirst(input) input.addEventListener('focus', (e) => { e.target.value = ''; }); var options = { componentRestrictions: {country: 'BA'} } const regularIdle = () => { this.dispatch({ type: 'UPDATE_ROUTE', action: { params: { bounds: map.getBounds().toUrlValue(), zoom: map.getZoom() } } }) this.dispatch({type: 'MAP_IDLE'}) } // Check if initial bounds are passed-in google.maps.event.addListenerOnce(map, 'idle', () => { if (this.state.bounds) { const [lat1, lng1, lat2, lng2] = this.state.bounds.split(',') const sw = new google.maps.LatLng({ lat: parseFloat(lat1), lng: parseFloat(lng1) }) const ne = new google.maps.LatLng({ lat: parseFloat(lat2), lng: parseFloat(lng2) }) const initialBounds = new google.maps.LatLngBounds(sw, ne) //map.fitBounds(initialBounds); const originalMaxZoom = map.maxZoom const originalMinZoom = map.minZoom map.setOptions({ maxZoom: parseInt(this.state.zoom), minZoom: parseInt(this.state.zoom) }) map.fitBounds(initialBounds) map.setOptions({maxZoom: originalMaxZoom, minZoom: originalMinZoom}) } }) map.addListener('idle', regularIdle) var searchBox = new google.maps.places.Autocomplete(input, options) searchBox.addListener('place_changed', () => { var place = searchBox.getPlace() if (!place.geometry) { return } if (place.geometry.viewport) { map.fitBounds(place.geometry.viewport) } else { map.setCenter(place.geometry.location) map.setZoom(18) } document.activeElement.blur(); this.dispatch({type: 'SEARCH_PLACE_CHANGED'}) }) control.addEventListener('click', e => { this.setState({ mapClicked: true }) }) control.index = 1 map.controls[google.maps.ControlPosition.TOP_RIGHT].push(control) this.map = map // TODO: if state contains listingId reload if (this.state.listingId) { loadListing(this.state.listingId).then(l => l.text()).then(l => { this.dispatch({ type: 'VIEW_LISTING_DETAILS', action: { id: this.state.listingId, listing: JSON.parse(l) } }) }) } } removeAllMarkers () { if (this.markers) { this.markers.forEach(m => m.marker.setMap(null)) } } onCloseClick (e) { if (this.state.mapClicked) { setTimeout( () => { google.maps.event.trigger(this.map, 'resize') }, 100 ) } this.setState({ mapClicked: false }) } findMarker (id) { if (!this.markers) { return null } const index = this.markers.findIndex(m => m.id === id) return this.markers[index] } isSeen (id) { const seen = loadSeen() return seen.findIndex(s => s === id) !== -1 } loadPins () { const map = this.map const { rooms, minSize, maxSize, minPrice, maxPrice, category } = this.state.filters const bounds = map.getBounds() const properties = loadProperties({ bounds: bounds.toUrlValue(), rooms, minSize, maxSize, minPrice, maxPrice, category, page: this.state.page, pins: true }) const markerExists = id => { return this.findMarker(id) != null } properties .then(p => { return { body: p.text(), totalCount: p.headers.get('X-Total-Count') } }) .then(({body, totalCount}) => { body.then(p => { const data = JSON.parse(p) const listingExists = id => { return data.findIndex(l => l._id === id) !== -1 } const newMarkers = [] if (this.markers) { this.markers.forEach(m => { if (!listingExists(m.id)) { m.marker.setMap(null) } else { newMarkers.push(m) } }) } for (const [index, prop] of data.entries()) { const myLatLng = {lat: prop.loc[0], lng: prop.loc[1]} if (!markerExists(prop._id)) { const marker = new google.maps.Marker({ position: myLatLng, map: map, //title : prop.title, icon: this.isSeen(prop._id) ? this.visitedMarkerIcon() : this.defaultMarkerIcon(), id: prop._id }) marker.addListener('mouseover', () => { if (marker.id !== this.state.listingId) { if (this.isSeen(marker.id)) { marker.setIcon(this.visitedHoveredMarkerIcon()) } else { marker.setIcon(this.hoveredMarkerIcon()) } } }) marker.addListener('mouseout', () => { if (marker.id !== this.state.listingId) { if (this.isSeen(marker.id)) { marker.setIcon(this.visitedMarkerIcon()) } else { marker.setIcon(this.defaultMarkerIcon()) } } }) marker.addListener('click', () => { // Maybe move out and call when popping state if (this.state.listingId) { const prevSelected = this.findMarker(this.state.listingId) if (prevSelected) { prevSelected.marker.setIcon(this.visitedMarkerIcon()) } } marker.setIcon(this.selectedMarkerIcon()) loadListing(prop._id).then(l => l.text()).then(l => { this.dispatch({ type: 'UPDATE_ROUTE', action: { toDispatch: { type: 'VIEW_LISTING_DETAILS', action: { id: prop._id, listing: JSON.parse(l) } }, params: { listingId: prop._id } } }) //this.dispatch({type: 'UPDATE_ROUTE', action: {type: 'VIEW_LISTING_DETAILS', action: { //id: prop._id, //listing: JSON.parse(l) //}}}); this.dispatch({ type: 'VIEW_LISTING_DETAILS', action: { id: prop._id, listing: JSON.parse(l) } }) }) }) newMarkers.push({ marker, id: prop._id }) } } this.dispatch({ type: 'PINS_LOADED', action: { newMarkers } }) }) }) } /* * Refreshes search */ refreshListings (more = false) { // TODO: move somewhere else if (!more && this.state.listingId) { loadListing(this.state.listingId).then(l => l.text()).then(l => { this.dispatch({ type: 'VIEW_LISTING_DETAILS', action: { id: this.state.listingId, listing: JSON.parse(l) } }) }) } if (!more) { this.loadPins() } const map = this.map const { rooms, minSize, maxSize, minPrice, maxPrice, category } = this.state.filters const bounds = map.getBounds() const properties = loadProperties({ bounds: bounds.toUrlValue(), rooms, minSize, maxSize, minPrice, maxPrice, category, page: this.state.page, sort: this.state.sort }) properties .then(p => { return { body: p.text(), totalCount: p.headers.get('X-Total-Count') } }) .then(({body, totalCount}) => { body.then(p => { const data = JSON.parse(p) this.dispatch({ type: 'LISTINGS_LOADED', action: { listings: data, more, totalCount } }) }) }) } /* * Get default marker icon */ defaultMarkerIcon () { const sf = 0.5 const width = 48 const height = 64 const icon = { url: 'static/images/pins_sprite.png', size: new google.maps.Size(width * sf, height * sf), scaledSize: new google.maps.Size(730 * sf, 102 * sf), origin: new google.maps.Point(0, 36 * sf) } return icon } /* * Get visited marker icon */ visitedMarkerIcon () { const sf = 0.5 const width = 48 const height = 64 const icon = { url: 'static/images/pins_sprite.png', size: new google.maps.Size(width * sf, height * sf), scaledSize: new google.maps.Size(730 * sf, 102 * sf), origin: new google.maps.Point(152 * sf, 36 * sf) } return icon } /* * Visited hovered marker icon */ visitedHoveredMarkerIcon () { const sf = 0.5 const width = 61 const height = 82 const icon = { url: 'static/images/pins_sprite.png', size: new google.maps.Size(width * sf, height * sf), scaledSize: new google.maps.Size(730 * sf, 102 * sf), origin: new google.maps.Point(480 * sf, 18 * sf) } return icon } /* * Hovered marker icon */ hoveredMarkerIcon () { const sf = 0.5 const width = 61 const height = 82 const icon = { url: 'static/images/pins_sprite.png', size: new google.maps.Size(width * sf, height * sf), scaledSize: new google.maps.Size(730 * sf, 102 * sf), origin: new google.maps.Point(303 * sf, 18 * sf) } return icon } /* * Selected marker icon */ selectedMarkerIcon () { const sf = 0.5 const width = 73 const height = 100 const icon = { url: 'static/images/pins_sprite.png', size: new google.maps.Size(width * sf, height * sf), scaledSize: new google.maps.Size(730 * sf, 102 * sf), origin: new google.maps.Point(655 * sf, 1 * sf) } return icon } renderRightContent () { const children = [] if (this.state.listingDetails) { const listing = this.state.listing //this.state.listings.get(this.state.listingId); children.push( ) } else { children.push( ) children.push( ) } const content = (
{children}
) return content } onMobileListViewClick (e) { e.preventDefault() this.dispatch({ type: 'MOBILE_LIST_VIEW' }) } onMobileMapViewClick (e) { e.preventDefault() this.dispatch({ type: 'MOBILE_MAP_VIEW' }) } render () { const leftStyle = {} const rightStyle = {} const listingDetails = true let leftClass = 'left-base' let rightClass = 'right-base' if (this.state.listingId || this.state.mapClicked) { leftClass = 'left-hidden' rightClass = 'right-shown' } return (
{this.state.mobileView === 'LIST' && !this.state.listingDetails &&
}
) } } export default Main