Commit 9142ec18 authored by Mattias J Duffy's avatar Mattias J Duffy
Browse files

Merge branch 'development' into 'master'

Version 2.2.0

Closes #48, #45, #17, #39, #44, #26, and #37

See merge request !8
parents 3d7f91d2 1d51fc46
Pipeline #2872 passed with stage
in 3 minutes and 6 seconds
......@@ -20,6 +20,7 @@
"plugin:react/recommended"
],
"rules": {
"no-unused-vars": "warn",
"no-console": "off",
"no-case-declarations": "off",
"no-template-curly-in-string": "warn",
......@@ -43,7 +44,11 @@
"error",
4,
{
"SwitchCase": 1
"SwitchCase": 1,
"ignoredNodes": [
"JSXAttribute",
"JSXSpreadAttribute"
]
}
],
"jsx-quotes": "error",
......@@ -123,6 +128,7 @@
"react/no-typos": "warn",
"react/jsx-indent": "error",
"react/jsx-pascal-case": "error",
"react/jsx-wrap-multilines": "warn"
"react/jsx-wrap-multilines": "warn",
"react/no-deprecated": "off"
}
}
\ No newline at end of file
image: node:6.5.0
image: node:8.11.2
stages:
- build
......@@ -7,11 +7,10 @@ stages:
build:
stage: build
variables:
API_GET_FACILITIES: "https://api.srct.gmu.edu/whatsopen/v2/facilities/"
API_GET_FACILITIES: "'https://api.srct.gmu.edu/whatsopen/v2/facilities/'"
script:
- npm install -g yarn
- yarn install
- CI=false REACT_APP_API_GET_FACILITIES=$API_GET_FACILITIES npm run build
- yarn
- CI=false API_GET_FACILITIES=$API_GET_FACILITIES npm run build
artifacts:
paths:
- build
......@@ -19,11 +18,10 @@ build:
build_shopmason:
stage: build
variables:
API_GET_FACILITIES: "https://api.srct.gmu.edu/whatsopen/v2/facilities/?facility_classifier=shopmason"
API_GET_FACILITIES: "'https://api.srct.gmu.edu/whatsopen/v2/facilities/?facility_classifier=shopmason'"
script:
- npm install -g yarn
- yarn install
- CI=false REACT_APP_API_GET_FACILITIES=$API_GET_FACILITIES npm run build
- yarn
- CI=false API_GET_FACILITIES=$API_GET_FACILITIES npm run build
artifacts:
paths:
- build
......@@ -36,7 +34,7 @@ deploy_staging:
name: staging
url: https://whatsopen.gmu.io
only:
- 2.1-dev
- development
deploy_production:
stage: deploy
......
# Changelog
## [2.2.0] - 2018-09-05
### Added
- Route for Apple app site association
- Map location dropdown
- Map support for all campuses
- Close button for the map dialog
- Custom Webpack
### Removed
- Create React App
### Changed
- README updated to not be the React default
### Fixed
- The clear button now works properly on mobile
- The search bar is focused when the search button is pressed on mobile
- The favorite icon now shows on mobile
## [2.1.4] - 2018-08-28
### Added
......@@ -68,4 +92,5 @@
[2.1.1]: https://git.gmu.edu/srct/whats-open-web/compare/v2.1-Midnight-Cherry...v2.1.1
[2.1.2]: https://git.gmu.edu/srct/whats-open-web/compare/v2.1.1...v2.1.2
[2.1.3]: https://git.gmu.edu/srct/whats-open-web/compare/v2.1.2...v2.1.3
[2.1.4]: https://git.gmu.edu/srct/whats-open-web/compare/v2.1.3...v2.1.4
\ No newline at end of file
[2.1.4]: https://git.gmu.edu/srct/whats-open-web/compare/v2.1.3...v2.1.4
[2.2.0]: https://git.gmu.edu/srct/whats-open-web/compare/v2.1.4...v2.2.0
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
......@@ -5,48 +5,75 @@
"private": true,
"dependencies": {
"array-sort": "^0.1.4",
"autosuggest-highlight": "^3.1.0",
"classnames": "^2.2.5",
"fuzzysearch": "^1.0.3",
"history": "^4.6.3",
"immutability-helper": "^2.3.0",
"mapbox": "^1.0.0-beta9",
"mapbox-gl": "^0.40.1",
"material-ui": "^1.0.0-beta.25",
"material-ui-icons": "^1.0.0-alpha.19",
"node-sass-chokidar": "^0.0.3",
"npm-run-all": "^4.1.1",
"material-ui": "1.0.0-beta.25",
"material-ui-icons": "1.0.0-alpha.19",
"phone-formatter": "^0.0.2",
"promise": "7.1.1",
"prop-types": "^15.5.10",
"react": "^15.6.1",
"react-autosuggest": "^9.3.2",
"react-classnames": "^0.1.2",
"react-dom": "^15.6.1",
"react-mapbox-gl": "^2.5.2",
"react-notification-system": "^0.2.16",
"react-redux": "^5.0.5",
"react-router-dom": "^4.1.2",
"react-router-redux": "^5.0.0-alpha.9",
"react-scripts": "1.0.10",
"redux": "^3.7.2",
"redux-thunk": "^2.2.0",
"typeface-roboto": "0.0.50"
},
"devDependencies": {
"babel-eslint": "^7.2.3",
"eslint-plugin-react": "^7.5.1"
"autoprefixer": "7.1.1",
"babel": "^6.23.0",
"babel-cli": "^6.26.0",
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.3",
"babel-loader": "^7.1.4",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.24.1",
"clean-webpack-plugin": "^0.1.19",
"css-loader": "^0.28.4",
"eslint": "4.19.1",
"eslint-loader": "^2.0.0",
"eslint-plugin-react": "^7.8.2",
"file-loader": "^1.1.11",
"html-webpack-plugin": "^3.2.0",
"mini-css-extract-plugin": "^0.4.0",
"node-sass": "^4.9.0",
"optimize-css-assets-webpack-plugin": "^4.0.2",
"postcss-loader": "2.0.6",
"sass-loader": "^7.0.2",
"style-loader": "^0.18.2",
"sw-precache-webpack-plugin": "0.11.3",
"uglifyjs-webpack-plugin": "^1.2.6",
"webpack": "^4.4.1",
"webpack-cli": "^2.1.5",
"webpack-dev-server": "^3.1.4"
},
"proxy": "http://localhost:3001",
"scripts": {
"build-css": "node-sass-chokidar src/styles/ -o src/styles/build",
"watch-css": "node-sass-chokidar src/styles/ -o src/styles/build --watch --recursive",
"start-js": "react-scripts start",
"start": "npm-run-all -p build-css watch-css start-js",
"build-js": "react-scripts build",
"build": "npm-run-all build-css build-js",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject",
"storybook": "start-storybook -p 9009 -s public",
"build-storybook": "build-storybook -s public"
"build": "webpack --config webpack/webpack.config.prod.js",
"start": "webpack-dev-server --config webpack/webpack.config.dev.js --open"
},
"babel": {
"presets": [
[
"env",
{
"modules": false
}
],
"react"
],
"plugins": [
"transform-class-properties"
]
},
"eslintConfig": {
"extends": "react"
}
}
{
"webcredentials": {
"apps": [
"K5MMVK2UFR.edu.gmu.srct.whatsopen"
]
},
"applinks": {
"apps": [],
"details": [
{
"appID": "K5MMVK2UFR.edu.gmu.srct.whatsopen",
"paths": [
"/"
]
}
]
}
}
\ No newline at end of file
......@@ -28,8 +28,8 @@
<meta name="twitter:title" content="What's Open">
<meta name="twitter:description" content="Facility hours for George Mason University">
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.png">
<link rel="manifest" href="./manifest.json">
<link rel="shortcut icon" href="./favicon.png">
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.40.1/mapbox-gl.css' rel='stylesheet' />
</head>
......
import {GET_ALERTS, GET_FACILITIES, SET_ALERTS, SET_FACILITIES, SORT_FACILITY_CARDS, VIEW_ALERT} from './action-types';
const API_GET_FACILITIES = process.env.REACT_APP_API_GET_FACILITIES ? process.env.REACT_APP_API_GET_FACILITIES :
const API_GET_FACILITIES = process.env.API_GET_FACILITIES ? process.env.API_GET_FACILITIES :
'https://api.srct.gmu.edu/whatsopen/v2/facilities/';
const API_GET_ALERTS = 'https://api.srct.gmu.edu/whatsopen/v2/alerts/?ordering=urgency_tag';
export const getFacilities = () => (dispatch) => {
......
......@@ -29,7 +29,7 @@ const Alert = ({alert}) => {
return (
<span className={'alert-message'}>
{alert.message.substring(0, links.index)}
<a href={links[0]} className={'alert-link'} target="_blank" rel="noopener">{links[0]}</a>
<a href={links[0]} className={'alert-link'} target="_blank" rel="noopener noreferrer">{links[0]}</a>
{alert.message.substring(links.index + links[0].length)}
</span>
);
......
import React from 'react';
import ReactMapboxGl, {Marker} from 'react-mapbox-gl';
import {withStyles} from 'material-ui/styles';
import {getMaxBounds} from '../utils/mapboxUtils';
import MapDialog from './MapDialog';
import ReactMapboxGl, {Marker, Popup} from 'react-mapbox-gl';
import {MenuItem} from 'material-ui/Menu';
import Select from 'material-ui/Select';
import {FormControl} from 'material-ui/Form';
import {getMaxBounds, getCenterOfCampusRegion} from '../utils/mapboxUtils';
import mapboxgl from 'mapbox-gl';
import {Typography} from 'material-ui';
import {removeBrackets} from '../utils/nameUtils';
const mapboxToken = 'pk.eyJ1IjoibWR1ZmZ5OCIsImEiOiJjaXk2a2lxODQwMDdyMnZzYTdyb3M4ZTloIn0.mSocl7zUnZBO6-CV9cvmnA';
const Map = ReactMapboxGl({
accessToken: mapboxToken,
interactive: false,
attributionControl: false
});
const Mark = {
backgroundColor: '#e74c3c',
borderRadius: '50%',
......@@ -20,104 +18,186 @@ const Mark = {
border: '3px solid #EAA29B'
};
class FacilitiesMap extends React.Component {
constructor(props) {
super(props);
const maxBounds = getMaxBounds(props.campusRegion);
// const southWestBounds = [-77.321649, 38.823919]; //Coordinates for the south-west bound
// const northEastBounds = [-77.295213, 38.835720]; //Coordinates for the north-east bound
const southWestBounds = maxBounds[0]; //Coordinates for the south-west bound
const northEastBounds = maxBounds[1]; //Coordinates for the north-east bound
const {facility, interactive = true} = this.props;
const campusRegion = facility && facility.facility_location ? facility.facility_location.campus_region : 'fairfax';
this.Map = ReactMapboxGl({
accessToken: mapboxToken,
interactive: interactive,
attributionControl: false
});
const facilityLocationExists = facility && facility.facility_location && facility.facility_location.campus_region === campusRegion;
/**
* facilityLocations is an array of the type:
* {
* location: {}
* facilities: [{}, {}, ...]
* }
*/
this.state = {
positionReady: false,
position: {
longitude: 0,
latitude: 0
},
mappedRoute: false,
fitBounds: [southWestBounds, northEastBounds],
maxBounds: maxBounds,
maxBounds: getMaxBounds(campusRegion),
campusRegion: campusRegion,
center: facilityLocationExists ? facility.facility_location.coordinate_location.coordinates : getCenterOfCampusRegion(campusRegion),
zoom: facilityLocationExists ? [17] : [0],
fitBoundsOptions: {},
mapDialogOpen: false
facilityLocations: [],
selectedLocation: null
};
}
handleRequestClose = () => {
componentWillReceiveProps(nextProps) {
const {facility, facilities} = nextProps;
const campusRegion = facility && facility.facility_location ? facility.facility_location.campus_region : 'fairfax';
this.changeRegion(campusRegion, facility);
if (this.state.facilityLocations.length === 0) {
this.generateLocationArray(facilities);
}
}
changeRegion = (campusRegion, facility) => {
if (!facility) {
facility = this.props.facility;
}
const facilityLocationExists = facility && facility.facility_location && facility.facility_location.campus_region === campusRegion;
this.setState({
mapDialogOpen: false
maxBounds: getMaxBounds(campusRegion),
campusRegion: campusRegion,
center: facilityLocationExists ? facility.facility_location.coordinate_location.coordinates : getCenterOfCampusRegion(campusRegion),
zoom: facilityLocationExists ? [17] : [0]
});
};
render() {
const {facilities, facility, classes} = this.props;
const {fitBounds, maxBounds, fitBoundsOptions, mapDialogOpen} = this.state;
let center, zoom;
try {
center = facility.facility_location.coordinate_location.coordinates;
zoom = [17];
} catch (e) {
center = [(maxBounds[0][0] + maxBounds[1][0]) / 2, (maxBounds[0][1] + maxBounds[1][1]) / 2];
zoom = [17];
generateLocationArray = (facilities) => {
const locations = [];
facilities.forEach((facility) => {
const location = locations.find((loc) => loc.location.id === facility.facility_location.id);
if (location) {
location.facilities.push(facility);
} else {
locations.push({
location: facility.facility_location,
facilities: [facility]
});
}
});
this.setState({
facilityLocations: locations
});
}
selectLocation = (location) => {
const {interactive = true} = this.props;
const oldSelectedLocation = this.state.selectedLocation;
const oldZoom = this.state.zoom;
if (!interactive) {
return;
}
this.setState({
selectedLocation: oldSelectedLocation !== location ? location : null,
center: location && location.location.coordinate_location.coordinates,
zoom: oldSelectedLocation !== location ? [17] : oldZoom
});
}
render() {
const {interactive = true} = this.props;
const {maxBounds, fitBoundsOptions, facilityLocations, selectedLocation, center, zoom} = this.state;
return (
<div className={classes.mapContainer}>
<Map
animationOptions={{
animate: false
}}
onClick={() => {
this.setState({
mapDialogOpen: true
});
}}
style="mapbox://styles/mduffy8/cjbcdxi3v73hp2spiyhxbkjde"
movingMethod={'easeTo'}
containerStyle={{
height: '200px',
width: '380px',
margin: '10px',
borderRadius: '3px',
cursor: 'pointer'
}}
center={center}
fitBounds={fitBounds}
fitBoundsOptions={fitBoundsOptions}
zoom={zoom}
maxBounds={maxBounds}>
{(facilities.length > 0) && facilities.map((item) => {
return (
<Marker
key={item.slug}
coordinates={item.facility_location.coordinate_location.coordinates}
anchor="bottom">
<div style={Mark} />
</Marker>
);
})}
</Map>
<MapDialog
open={mapDialogOpen}
facilities={facilities}
maxBounds={maxBounds}
zoom={zoom}
center={center}
onClose={this.handleRequestClose}
/>
</div>
<this.Map
onStyleLoad={(map) => {
if (interactive) {
map.addControl(new mapboxgl.GeolocateControl({
positionOptions: {
enableHighAccuracy: true
},
trackUserLocation: true
}));
}
}}
animationOptions={{
animate: true,
duration: 1500
}}
style="mapbox://styles/mduffy8/cjbcdxi3v73hp2spiyhxbkjde"
movingMethod="easeTo"
containerStyle={{
height: '100%',
width: '100%',
borderRadius: '5px',
cursor: 'pointer'
}}
center={center}
fitBounds={maxBounds}
fitBoundsOptions={fitBoundsOptions}
zoom={zoom}
maxBounds={maxBounds}>
{interactive &&
(
<FormControl className={'facilities-map-campus-select'}>
<Select
disableUnderline
value={this.state.campusRegion}
onChange={(e) => this.changeRegion(e.target.value)}>
<MenuItem value={'fairfax'}>Fairfax</MenuItem>
<MenuItem value={'arlington'}>Arlington</MenuItem>
<MenuItem value={'prince william'}>SciTech</MenuItem>
<MenuItem value={'front royal'}>Front Royal</MenuItem>
</Select>
</FormControl>
)}
{(facilityLocations.length > 0) && facilityLocations.map((item) => {
const location = item.location;
return (
<Marker
key={location.id}
coordinates={location.coordinate_location.coordinates}
anchor="bottom"
onClick={() => this.selectLocation(item)}>
<div style={Mark} />
</Marker>
);
})}
{selectedLocation && (
<Popup coordinates={selectedLocation.location.coordinate_location.coordinates} anchor="top">
<div>
<Typography type="subheading" align={'center'}>
{selectedLocation.location.building}
</Typography>
</div>
<div>
<ul className={'facilities-map-popup-list'}>
{selectedLocation.facilities.map((facility) => {
return (
<li key={facility.slug}>
<Typography type="caption">{removeBrackets(facility.facility_name)}</Typography>
</li>
);
})}
</ul>
</div>
</Popup>
)}
</this.Map>
);
}
}
const styleSheet = {
mapContainer: {
transition: '250ms ease-in-out',
width: '100%'
}
};
export default withStyles(styleSheet)(FacilitiesMap);
\ No newline at end of file
export default FacilitiesMap;
\ No newline at end of file
......@@ -10,7 +10,6 @@ import MapDialog from './MapDialog';
import CloseIcon from 'material-ui-icons/Close';
import IconButton from 'material-ui/IconButton';
import LocationOnIcon from 'material-ui-icons/LocationOn';
// import {getMaxBounds} from '../utils/mapboxUtils';
import {removeBrackets} from '../utils/nameUtils';
class FacilityDialog extends React.Component {
......@@ -19,13 +18,11 @@ class FacilityDialog extends React.Component {
super(props);
this.state = {
isMapOpen: false,
maxBounds: props.maxBounds,
isMapOpen: false
};
}
toggleMap = (e) => {
e.stopPropagation();
toggleMap = () => {
this.setState({
isMapOpen: !this.state.isMapOpen
});
......@@ -33,16 +30,8 @@ class FacilityDialog extends React.Component {
render() {
const {facility, facilities, isOpen, onClose} = this.props;
const {isMapOpen, maxBounds} = this.state;
const {isMapOpen} = this.state;
let mapCenter, mapZoom;
try {
mapCenter = facility.facility_location.coordinate_location.coordinates;
mapZoom = [17];
} catch (e) {
mapCenter = [(maxBounds[0][0] + maxBounds[1][0]) / 2, (maxBounds[0][1] + maxBounds[1][1]) / 2];
mapZoom = [13];
}
return (
<Dialog classes={{
......@@ -91,9 +80,8 @@ class FacilityDialog extends React.Component {