Files
old-kivi/web/src/components/Main.js
Edin Dazdarevic 7c40035f6f Mobile adjustments
2017-04-14 02:00:38 +02:00

635 lines
16 KiB
JavaScript

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 = '<button>Filteri</button>'
//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(
<ListingDetails
contactFormOpen={this.state.contactFormOpen}
contact={this.state.contact}
listing={listing}
imageIndex={this.state.imageIndex}
dispatch={this.dispatch.bind(this)}
descriptionExpanded={this.state.descriptionExpanded}
/>
)
} else {
children.push(
<Filters
filters={this.state.filters}
dispatch={this.dispatch.bind(this)}
onClose={this.onCloseClick.bind(this)}
/>
)
children.push(
<Listings
sort={this.state.sort}
totalCount={this.state.totalCount}
loadingMore={this.state.loadingMore}
listings={this.state.listings}
dispatch={this.dispatch.bind(this)}
/>
)
}
const content = (
<div className="right-content">
{children}
</div>
)
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 (
<div id="container">
<div id="header">
<a className="hamburger-menu">K</a>
<span className="title">KIVI</span>
<input
id="gmaps-places-input"
placeholder="Unesite adresu, naselje ili grad"
className="where-to"
type="text"
/>
<div className="view-types">
<a onClick={this.onMobileListViewClick.bind(this)}className="view-type-left">
<i className="btn-select-map fa fa-list" />
</a>
<a onClick={this.onMobileMapViewClick.bind(this)} className="view-type-right">
<i className="view-type-map-icon fa fa-map-marker" />
</a>
</div>
</div>
<div id="right" style={rightStyle} className={rightClass}>
{this.renderRightContent()}
</div>
<div id="left" style={leftStyle} className={leftClass}>
{this.state.mobileView === 'LIST' && !this.state.listingDetails &&
<div className="map-list-view">
<Listings
sort={this.state.sort}
totalCount={this.state.totalCount}
loadingMore={this.state.loadingMore}
listings={this.state.listings}
dispatch={this.dispatch.bind(this)}
/>
</div>}
<div id="map" ref="map" className={this.state.mobileView !== 'MAP' && "hide"} />
</div>
</div>
)
}
}
export default Main