Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions react-map/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
var path = require('path');
var express = require('express');
var app = express();
var morgan = require('morgan');
var env = process.env.NODE_ENV || 'development';

module.exports = app;

app.set('env', process.env.NODE_ENV || 'development');
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.static(path.join(__dirname, 'dist')));
app.set('view engine', 'jade');

app.get('/', function(req, res, next) {
res.render('index');
});
8 changes: 8 additions & 0 deletions react-map/bin/get_data
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env node

var app = require(__dirname + '/../app');
var request = require('request');
var fs = require('fs');

request('http://reactjs.meetup.com/newest/json/New+reactjs+Groups').pipe(fs.createWriteStream('./public/groups.json'));

66 changes: 66 additions & 0 deletions react-map/client/react/app.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
'use strict';

import React from 'react/addons';
import Header from './components/header';
import MapContainer from './components/map';
import CitiesContainer from './components/sidebar';
import {MapContainerStyle, CitiesContainerStyle} from './styles/styles';

class App extends React.Component {
constructor(props) {
super(props);
this.state = {
locations: [],
currentLocation: {
lat: 48.86,
lng: 2.34,
id: 779
}
};
}

componentDidMount() {
$.ajax({
url: this.props.url,
dataType: 'json',
cache: false,
success: function(res) {
this.setState({ locations: res });
}.bind(this),
error: function(xhr, status, err) {
console.error(status, err.toString());
}.bind(this)
});
}

_handleClick(locationInfo) {
return this.setState({ currentLocation: locationInfo });
}

render() {
let coordsCentre = [this.state.currentLocation.lat, this.state.currentLocation.lng];

return (
<div>
<Header />
<div>
<div style={MapContainerStyle}>
<MapContainer
center={coordsCentre}
currentLocation={this.state.currentLocation}
onChildClick={this._handleClick.bind(this)}
locations={this.state.locations}/>
</div>
<div style={CitiesContainerStyle}>
<CitiesContainer
onChildClick={this._handleClick.bind(this)}
currentLocation={this.state.currentLocation}
locations={this.state.locations} />
</div>
</div>
</div>
)
}
}

React.render(<App url='/groups.json'/>, document.getElementById('main'));
15 changes: 15 additions & 0 deletions react-map/client/react/components/header.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use strict';

import React from 'react/addons';

class Header extends React.Component {
render() {
return (
<div className='header'>
<h1>React.JS Meetup</h1><div id='logo'></div>
</div>
)
}
}

export default Header;
47 changes: 47 additions & 0 deletions react-map/client/react/components/location.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
'use strict';

import React from 'react/addons';
import {PropTypes} from 'react/addons';
import PureComponent from 'react-pure-render/component';
import {LocationContainerStyle} from './../styles/styles';

class LocationContainer extends PureComponent {
constructor(props) {
super(props);
this.state = { 'hover': false };
}

_handleMouseEnter() {
this.setState({ 'hover': true });
}

_handleMouseLeave() {
this.setState({ 'hover': false });
}

render() {
let locationData;
let imgUrl = this.props.img;
let id = this.props.id;
let pinClass = this.state.hover ? 'selected ' : '';

LocationContainerStyle.backgroundImage = `url(${imgUrl})`;
LocationContainerStyle.border = this.props.selectedClass ? '2px solid #000' : '2px solid #f44336';

if (this.props.selectedClass) {
pinClass += 'selected';
}

return (
<div
className={pinClass}
style={LocationContainerStyle}
onMouseEnter={this._handleMouseEnter.bind(this)}
onMouseLeave={this._handleMouseLeave.bind(this)} />
);
}
}

LocationContainer.defaultProps = {};

export default LocationContainer;
55 changes: 55 additions & 0 deletions react-map/client/react/components/map.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
'use strict';

import React from 'react/addons';
import GoogleMap from 'google-map-react';
import LocationContainer from './location.jsx';
import PureComponent from 'react-pure-render/component';
let coordsCentre = [50.938043, 10.337157];

class MapContainer extends PureComponent {
constructor(props) {
super(props);
}

_renderLocations() {
let locationImg;
let selectedClass;
let locationsContainers = [];
let locations = this.props.locations;

for (let location of locations) {
locationImg = location.photo_urls.thumb === undefined ?
'/img/meetup-icon.png' :
location.photo_urls.thumb;

selectedClass = this.props.currentLocation.id === location.number ? 'selected' : '';

locationsContainers.push(
<LocationContainer
selectedClass={selectedClass}
currentLocationId={this.props.currentLocation.id}
key={locations.indexOf(location)}
lat={location.lat}
lng={location.lon}
id={location.number}
img={locationImg} />
)
}
return locationsContainers;
}

render() {
let locationsContainers = this._renderLocations();
let currentLocation = this.props.currentLocation;

return (
<GoogleMap
center={this.props.center}
zoom={0}>
{locationsContainers}
</GoogleMap>
);
}
}

export default MapContainer;
74 changes: 74 additions & 0 deletions react-map/client/react/components/sidebar.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
'use strict';

import React from 'react/addons';
import { List, ListItem, Avatar } from 'material-ui';
import {cityStyle, membersStyle} from './../styles/styles';

let mui = require('material-ui');
let ThemeManager = new mui.Styles.ThemeManager();
let injectTapEventPlugin = require('react-tap-event-plugin');

injectTapEventPlugin();
window.React = React;

class CitiesContainer extends React.Component {
constructor(props) {
super(props);
}

getChildContext() {
return { muiTheme: ThemeManager.getCurrentTheme() };
}

_handleClick(locationData) {
this.props.onChildClick(locationData);
}

_renderCities() {
let locationImg;
let locationData;
let citiesContainers = [];
let locations = this.props.locations;
let currentLocation = this.props.currentLocation;

for (let location of locations) {
locationImg = location.photo_urls.thumb === undefined ?
'/img/meetup-icon.png' :
location.photo_urls.thumb;

locationData = {
lat: location.lat,
lng: location.lon,
id: location.number
};

citiesContainers.push(
<ListItem
onClick={this._handleClick.bind(this, locationData)}
leftAvatar={<Avatar src={locationImg} />}
secondaryText={
<p>
<span style={cityStyle}>{location.city}</span>
<span style={membersStyle}>&nbsp; {location.member_count} members</span>
</p>
}
key={locations.indexOf(location)}>{location.name}&nbsp;</ListItem>
)
}
return citiesContainers;
}

render() {
let cityListItems = this._renderCities();

return (
<List>
{cityListItems}
</List>
);
}
}

CitiesContainer.childContextTypes = { muiTheme: React.PropTypes.object };

export default CitiesContainer;
54 changes: 54 additions & 0 deletions react-map/client/react/styles/styles.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const K_WIDTH = 40;
const K_HEIGHT = 40;

let Colors = require('material-ui/lib/styles/colors');

export const LocationContainerStyle = {
position: 'absolute',
width: K_WIDTH,
height: K_HEIGHT,
left: -K_WIDTH / 2,
top: -K_HEIGHT / 2,

borderRadius: '50%',
textAlign: 'center',
color: '#3f51b5',
fontSize: 16,
fontWeight: 'bold',
padding: 4,
backgroundSize: '100%',
backgroundColor: 'white'
};

export const MapContainerStyle = {
zIndex: -1,
position: 'absolute',
width: '70%',
right: 0,
height: 700,
paddingTop: 30,
borderBottom: '3px solid #f44336'
};

export const CitiesContainerStyle = {
zIndex: -1,
position: 'absolute',
width: '30%',
left: 0,
height: 700,
paddingTop: 30,
borderBottom: '3px solid #f44336',
overflow: 'scroll'
}

export const cityStyle = {
color: Colors.darkBlack,
textTransform: 'uppercase',
fontWeight: '500',
fontSize: '13px'
};
export const membersStyle = {
fontStyle: 'italic',
fontSize: '12px'
};

Loading