From adac11fe7b39268103f2179aff0d7180072fd8a6 Mon Sep 17 00:00:00 2001 From: Daniel Silva Date: Wed, 27 May 2015 16:22:12 +0100 Subject: [PATCH 01/38] Contribute to #53. Add dropdown system Improve copy to clipboard action --- app/assets/scripts/components/results_item.js | 22 ++++- .../scripts/components/shared/dropdown.js | 87 +++++++++++++++++++ .../scripts/components/shared/zc_button.js | 57 ++++++++++++ .../scripts/components/shared/zc_input.js | 57 ------------ 4 files changed, 164 insertions(+), 59 deletions(-) create mode 100644 app/assets/scripts/components/shared/dropdown.js create mode 100644 app/assets/scripts/components/shared/zc_button.js delete mode 100644 app/assets/scripts/components/shared/zc_input.js diff --git a/app/assets/scripts/components/results_item.js b/app/assets/scripts/components/results_item.js index ae012904..18ffb3c1 100644 --- a/app/assets/scripts/components/results_item.js +++ b/app/assets/scripts/components/results_item.js @@ -1,7 +1,8 @@ 'use strict'; var React = require('react/addons'); var actions = require('../actions/actions'); -var ZcInput = require('./shared/zc_input'); +var ZcButton = require('./shared/zc_button'); +var Dropdown = require('./shared/dropdown'); var utils = require('../utils/utils'); @@ -19,6 +20,10 @@ var ResultsItem = React.createClass({ actions.nextResult(); }, + onCopy: function(e) { + return this.getDOMNode().querySelector('[data-hook="copy:data"]').value; + }, + render: function() { var d = this.props.data; var pagination = this.props.pagination; @@ -28,7 +33,20 @@ var ResultsItem = React.createClass({ var tmsOptions = null; if (d.properties.tms) { - tmsOptions = (); + tmsOptions = ( +
+ + + + +
+ ); } var blurImage = { diff --git a/app/assets/scripts/components/shared/dropdown.js b/app/assets/scripts/components/shared/dropdown.js new file mode 100644 index 00000000..2a4af7a8 --- /dev/null +++ b/app/assets/scripts/components/shared/dropdown.js @@ -0,0 +1,87 @@ +'use strict'; +var React = require('react'); +var Reflux = require('reflux'); +var $ = require('jquery'); +var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup; + +var dropdownActions = Reflux.createActions({ + 'closeOthers': {} +}); + +var activeDropdowns = 0; + +var Dropdown = React.createClass({ + mixins: [Reflux.listenTo(dropdownActions.closeOthers, "onCloseOthers")], + + onCloseOthers: function($exception) { + if (this.getDOMNode() != $exception) { + this.setState({ open: false }); + } + }, + + bodyListener: function(e) { + var $clickedDropdown = $(e.target).parents('[data-hook="dropdown"]'); + dropdownActions.closeOthers($clickedDropdown[0]); + }, + + getDefaultProps: function() { + return { + element: 'div', + className: '', + + triggerTitle: '', + triggerClassName: '', + triggerText: '', + } + }, + + getInitialState: function() { + return { + open: false, + } + }, + + // Lifecycle method. + // Called once as soon as the component has a DOM representation. + componentDidMount: function() { + // With a cross Dropdown variable we ensure that only one event is setup. + if (++activeDropdowns === 1) { + // Namespace the event so it's easy to remove. + $(document).bind('click.dropdown', this.bodyListener); + } + }, + + // Lifecycle method. + // Called once as soon as the component has a DOM representation. + componentWillUnmount: function() { + if (--activeDropdowns === 0) { + $(document).unbind('click.dropdown'); + } + }, + + closeDropdown: function(e) { + e.preventDefault(); + this.setState({ open: !this.state.open }); + }, + + render: function() { + var klasses = ['drop']; + if (this.state.open) { + klasses.push('open'); + } + if (this.props.className) { + klasses.push(this.props.className); + } + + return ( + + {this.props.triggerText} +
+ {this.props.children} +
+
+ ); + } +}); + +module.exports = Dropdown; \ No newline at end of file diff --git a/app/assets/scripts/components/shared/zc_button.js b/app/assets/scripts/components/shared/zc_button.js new file mode 100644 index 00000000..7e76dda3 --- /dev/null +++ b/app/assets/scripts/components/shared/zc_button.js @@ -0,0 +1,57 @@ +'use strict'; +var React = require('react'); +var ZeroClipboard = require('zeroclipboard'); + +var ZcButton = React.createClass({ + propTypes: { + onCopy: React.PropTypes.func.isRequired, + }, + + getDefaultProps: function() { + return { + title: '', + className: '', + text: '', + } + }, + + // Lifecycle method. + // Called once as soon as the component has a DOM representation. + componentDidMount: function() { + ZeroClipboard.config({ + swfPath: "/ZeroClipboard.swf", + hoverClass: "zc-hover", + activeClass: "zc-active", + }); + + var _this = this; + var el = this.getDOMNode(); + var client = new ZeroClipboard(el); + client.on( "ready", function( readyEvent ) { + console.log( "ZeroClipboard SWF is ready!" ); + + L.DomUtil.removeClass(el, 'disabled'); + + client.on( 'copy', function(event) { + var toCopy = _this.props.onCopy(event); + if (toCopy === false) { + return; + } + event.clipboardData.setData('text/plain', toCopy); + }); + + }); + }, + + onCopyClick: function(e) { + e.preventDefault(); + }, + + render: function() { + return ( + {this.props.text} + ); + } +}) + +module.exports = ZcButton; \ No newline at end of file diff --git a/app/assets/scripts/components/shared/zc_input.js b/app/assets/scripts/components/shared/zc_input.js deleted file mode 100644 index 5e16e85f..00000000 --- a/app/assets/scripts/components/shared/zc_input.js +++ /dev/null @@ -1,57 +0,0 @@ -'use strict'; -var React = require('react'); -var ZeroClipboard = require('zeroclipboard'); - -var ZcInput = React.createClass({ - - // Lifecycle method. - // Called once as soon as the component has a DOM representation. - componentDidMount: function() { - ZeroClipboard.config({ - swfPath: "/ZeroClipboard.swf", - hoverClass: "zc-hover", - activeClass: "zc-active", - }); - - var client = new ZeroClipboard( this.getDOMNode().querySelector('[data-hook="copy:trigger"]') ); - client.on( "ready", function( readyEvent ) { - console.log( "ZeroClipboard SWF is ready!" ); - - client.elements().forEach(function(el) { - L.DomUtil.removeClass(el, 'disabled'); - el.addEventListener('mouseenter', function(event) { - event.target.setAttribute('data-title', 'Copy URL to clipboard'); - }, false); - }); - - client.on( 'copy', function(event) { - event.clipboardData.setData('text/plain', document.querySelector('[data-hook="copy:data"]').value); - event.target.setAttribute('data-title', 'Copied!'); - }); - - }); - }, - - render: function() { - return ( -
- - - {/* - - Options -
- -
-
- */} -
- ); - } -}) - -module.exports = ZcInput; \ No newline at end of file From e4847fc62aecdb592e8011465f3bf1a9cde4bb2e Mon Sep 17 00:00:00 2001 From: Ricardo Mestre Date: Mon, 8 Jun 2015 13:39:33 +0100 Subject: [PATCH 02/38] Fix a:hover glitch on info modal --- app/assets/styles/_base.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/styles/_base.scss b/app/assets/styles/_base.scss index 0153b9e8..a45aa5fe 100644 --- a/app/assets/styles/_base.scss +++ b/app/assets/styles/_base.scss @@ -198,6 +198,7 @@ a:active{ @include columns(3); @include column-gap(2rem); margin-bottom: -0.75rem; + -webkit-backface-visibility: hidden; h2 { @include heading(1.25rem); // 20 margin: 0 0 1rem 0; From 11d29320e57f13cc6b92ccded75ead02dc3da808 Mon Sep 17 00:00:00 2001 From: Daniel Silva Date: Tue, 9 Jun 2015 14:08:17 +0100 Subject: [PATCH 03/38] Contribute to #68. Add map position and selected square to url --- app/assets/scripts/components/map.js | 91 ++++++++++++++++++- .../components/modals/welcome_modal.js | 2 +- app/assets/scripts/components/results_list.js | 20 +++- app/assets/scripts/routes.js | 9 +- 4 files changed, 116 insertions(+), 6 deletions(-) diff --git a/app/assets/scripts/components/map.js b/app/assets/scripts/components/map.js index 1bfb987a..23eac423 100644 --- a/app/assets/scripts/components/map.js +++ b/app/assets/scripts/components/map.js @@ -2,6 +2,7 @@ require('mapbox.js'); var React = require('react/addons'); var Reflux = require('reflux'); +var Router = require('react-router'); var overlaps = require('turf-overlaps'); // Not working. Using cdn. (turf.intersect was throwing a weird error) //var turf = require('turf'); @@ -30,6 +31,9 @@ var Map = React.createClass({ Reflux.listenTo(actions.goToLatest, "onGoToLatest"), Reflux.listenTo(actions.geocoderResult, "onGeocoderResult"), + + Router.Navigation, + Router.State ], map: null, @@ -50,6 +54,10 @@ var Map = React.createClass({ // square that contains it. Check updateGrid() selectIntersecting: null, + // If there's a selected square in the path, we store it and then select the + // correct one when the grid is updating. + routerSelectedSquare: null, + getInitialState: function() { return { loading: true @@ -98,7 +106,12 @@ var Map = React.createClass({ // Coordinates must be inverted for panTo. this.map.panTo([sqrFeature.properties.centroid[1], sqrFeature.properties.centroid[0]]); - this.updateGrid(); + // Set the correct path. + var mapLocation = this.mapViewToString(); + var square = mapStore.getSelectedSquareCenter(); + this.replaceWith('results', { map: mapLocation, square: (square[1] + ',' + square[0]) }); + + //this.updateGrid(); }, // Actions listener. @@ -107,6 +120,10 @@ var Map = React.createClass({ this.map.removeLayer(this.overImageLayer); } + // Set the correct path. + var mapLocation = this.mapViewToString(); + this.replaceWith('map', { map: mapLocation }); + actions.resultsChange([]); this.updateGrid(); }, @@ -281,6 +298,19 @@ var Map = React.createClass({ L.DomUtil.addClass(l._path, 'gs'); var featureCenter = null; + // If there is a selected square in the path, select the correct one. + if (_this.routerSelectedSquare) { + featureCenter = turf.centroid(l.feature); + if (_this.routerSelectedSquare[0] == featureCenter.geometry.coordinates[1] && + _this.routerSelectedSquare[1] == featureCenter.geometry.coordinates[0]) { + // Done with selecting. + _this.routerSelectedSquare = null; + // Trigger action. + actions.mapSquareSelected(l.feature); + return; + } + } + // Select the square that intersects the stored image. if (_this.selectIntersecting) { featureCenter = turf.centroid(l.feature); @@ -336,13 +366,29 @@ var Map = React.createClass({ console.log('componentDidMount MapBoxMap'); var _this = this; var view = [60.177, 25.148]; + var zoom = 6; + + // Map position from path. + var routerMap = this.getParams().map; + if (routerMap) { + routerMap = this.stringToMapView(routerMap); + view = [routerMap.lat, routerMap.lng]; + zoom = routerMap.zoom; + } + + // Check if there's a selected square in the path. + var routerSquare = this.getParams().square; + if (routerSquare) { + this.routerSelectedSquare = routerSquare.split(','); + view = [this.routerSelectedSquare[0], this.routerSelectedSquare[1]]; + } this.map = L.mapbox.map(this.getDOMNode().querySelector('#map'), 'devseed.m9i692do', { zoomControl: false, minZoom : 4, //maxZoom : 18, maxBounds: L.latLngBounds([-90, -180], [90, 180]) - }).setView(view, 6); + }).setView(view, zoom); // Custom zoom control. var zoom = new dsZoom({ @@ -387,8 +433,21 @@ var Map = React.createClass({ // Footprint layer. this.overFootprintLayer = L.geoJson(null, { style: L.mapbox.simplestyle.style }).addTo(this.map); + // Map move listener. this.map.on('moveend', function() { + + // Compute new map location for the path + var mapLocation = _this.mapViewToString(); + // Preserve selected square if any. + var routerSquare = _this.getParams().square; + if (routerSquare) { + _this.replaceWith('results', {map: mapLocation, square: routerSquare }); + } + else { + _this.replaceWith('map', {map: mapLocation }); + } + _this.setState({loading: true}); actions.mapMove(_this.map); _this.updateFauxGrid(); @@ -417,6 +476,34 @@ var Map = React.createClass({ ); }, + /** + * Converts the map view (coords + zoom) to use on the path. + * + * @return string + */ + mapViewToString: function() { + var center = this.map.getCenter(); + var zoom = this.map.getZoom(); + return center.lat + ',' + center.lng + ',' + zoom; + }, + + /** + * Converts a path string like 60.359564131824214,4.010009765624999,6 + * to a readable object + * + * @param String + * string to convert + * @return object + */ + stringToMapView: function(string) { + var data = string.split(','); + return { + lat: data[0], + lng: data[1], + zoom: data[2], + } + }, + /** * Creates a equidistant pixel line grid for the given bbox. * The grid drawing will start form the closest cellSize multiple thus diff --git a/app/assets/scripts/components/modals/welcome_modal.js b/app/assets/scripts/components/modals/welcome_modal.js index 45a49566..82cc75dc 100644 --- a/app/assets/scripts/components/modals/welcome_modal.js +++ b/app/assets/scripts/components/modals/welcome_modal.js @@ -78,7 +78,7 @@ var WelcomeModal = React.createClass({ header={this.getHeader()} body={this.getBody()} footer={this.getFooter()} - revealed={true} /> + revealed={false} /> ); } }); diff --git a/app/assets/scripts/components/results_list.js b/app/assets/scripts/components/results_list.js index 3b89d2b9..1124115d 100644 --- a/app/assets/scripts/components/results_list.js +++ b/app/assets/scripts/components/results_list.js @@ -1,11 +1,25 @@ 'use strict'; var React = require('react/addons'); +var Router = require('react-router'); var actions = require('../actions/actions'); var utils = require('../utils/utils'); +var mapStore = require('../stores/map_store'); var ResultsListItem = React.createClass({ + + mixins: [ + Router.Navigation, + Router.State + ], + onClick: function(e) { e.preventDefault(); + + var params = this.getParams(); + params.item_id = this.props.data._id + //this.replaceWith('item', params); + + console.log(this.props.data); actions.resultItemSelect(this.props.data); }, onOver: function(e) { @@ -49,6 +63,10 @@ var ResultsListItem = React.createClass({ var ResultsList = React.createClass({ render: function() { + var square = mapStore.getSelectedSquareCenter(); + var north = Math.round(square[1] * 100000) / 100000; + var east = Math.round(square[0] * 100000) / 100000; + var numRes = this.props.results.length; var results = this.props.results.map(function(o) { return (); @@ -56,7 +74,7 @@ var ResultsList = React.createClass({ return (
-

Selection

+

{'N ' + north + ', E ' + east}

{numRes} results

diff --git a/app/assets/scripts/routes.js b/app/assets/scripts/routes.js index 6d785762..ef48338a 100644 --- a/app/assets/scripts/routes.js +++ b/app/assets/scripts/routes.js @@ -15,8 +15,13 @@ var About = require('./components/about'); var Home = require('./components/home'); var routes = ( - - + + + + + + + ); From 60d5c79f3e99780e3f1cbc9fcbd0726c632f6ac9 Mon Sep 17 00:00:00 2001 From: Daniel Silva Date: Wed, 10 Jun 2015 10:10:39 +0100 Subject: [PATCH 04/38] Fix #68. Shareable url --- app/assets/scripts/components/map.js | 29 ++++++++++----- app/assets/scripts/components/results_list.js | 12 ------ app/assets/scripts/components/results_pane.js | 37 ++++++++++++++++++- app/assets/scripts/routes.js | 15 ++++++++ 4 files changed, 71 insertions(+), 22 deletions(-) diff --git a/app/assets/scripts/components/map.js b/app/assets/scripts/components/map.js index 23eac423..19d578fd 100644 --- a/app/assets/scripts/components/map.js +++ b/app/assets/scripts/components/map.js @@ -107,9 +107,15 @@ var Map = React.createClass({ this.map.panTo([sqrFeature.properties.centroid[1], sqrFeature.properties.centroid[0]]); // Set the correct path. - var mapLocation = this.mapViewToString(); - var square = mapStore.getSelectedSquareCenter(); - this.replaceWith('results', { map: mapLocation, square: (square[1] + ',' + square[0]) }); + var params = this.getParams(); + var route = params.item_id ? 'item' : 'results'; + + var selectedSquare = mapStore.getSelectedSquareCenter(); + + params.map = this.mapViewToString(); + params.square = selectedSquare[1] + ',' + selectedSquare[0]; + + this.replaceWith(route, params); //this.updateGrid(); }, @@ -439,14 +445,19 @@ var Map = React.createClass({ // Compute new map location for the path var mapLocation = _this.mapViewToString(); - // Preserve selected square if any. - var routerSquare = _this.getParams().square; - if (routerSquare) { - _this.replaceWith('results', {map: mapLocation, square: routerSquare }); + // Preserve other params if any. + var params = _this.getParams(); + params.map = mapLocation; + + // Check what's the route to use. + var route = 'map'; + if (params.item_id) { + route = 'item'; } - else { - _this.replaceWith('map', {map: mapLocation }); + else if (params.square) { + route = 'results'; } + _this.replaceWith(route, params); _this.setState({loading: true}); actions.mapMove(_this.map); diff --git a/app/assets/scripts/components/results_list.js b/app/assets/scripts/components/results_list.js index 1124115d..f3bfe220 100644 --- a/app/assets/scripts/components/results_list.js +++ b/app/assets/scripts/components/results_list.js @@ -1,25 +1,13 @@ 'use strict'; var React = require('react/addons'); -var Router = require('react-router'); var actions = require('../actions/actions'); var utils = require('../utils/utils'); var mapStore = require('../stores/map_store'); var ResultsListItem = React.createClass({ - mixins: [ - Router.Navigation, - Router.State - ], - onClick: function(e) { e.preventDefault(); - - var params = this.getParams(); - params.item_id = this.props.data._id - //this.replaceWith('item', params); - - console.log(this.props.data); actions.resultItemSelect(this.props.data); }, onOver: function(e) { diff --git a/app/assets/scripts/components/results_pane.js b/app/assets/scripts/components/results_pane.js index ed3347db..da166d06 100644 --- a/app/assets/scripts/components/results_pane.js +++ b/app/assets/scripts/components/results_pane.js @@ -1,6 +1,7 @@ 'use strict'; var React = require('react/addons'); var Reflux = require('reflux'); +var Router = require('react-router'); var ResultsList = require('./results_list'); var ResultsItem = require('./results_item'); var resultsStore = require('../stores/results_store'); @@ -8,7 +9,16 @@ var mapStore = require('../stores/map_store'); var actions = require('../actions/actions'); var ResultsPane = React.createClass({ - mixins: [Reflux.listenTo(resultsStore, "onResults")], + mixins: [ + Reflux.listenTo(resultsStore, "onResults"), + Router.Navigation, + Router.State + ], + + // We only want to load the item from the id in the path the first time the + // "page" is loaded. + // More on how the router works can be found in routes.js + loadFromRouter: true, onResults: function(data) { this.setState({ @@ -16,6 +26,14 @@ var ResultsPane = React.createClass({ selectedItem: data.selectedItem, selectedItemIndex: data.selectedItemIndex }); + + var params = this.getParams(); + var route = 'results'; + if (data.selectedItem) { + route = 'item'; + params.item_id = data.selectedItem._id + } + this.replaceWith(route, params); }, getInitialState: function() { @@ -31,6 +49,23 @@ var ResultsPane = React.createClass({ actions.mapSquareUnselected(); }, + componentWillUpdate: function(nextProps, nextState) { + var params = this.getParams(); + // if: + // - didn't load already + // - there's an id in the url + // - there are results + if (this.loadFromRouter && params.item_id && nextState.results.length) { + this.loadFromRouter = false; + // Search for the item to trigger the action. + for (var i in nextState.results) { + if (nextState.results[i]._id == params.item_id) { + actions.resultItemSelect(nextState.results[i]); + } + } + } + }, + render: function() { var resultsPane = null; diff --git a/app/assets/scripts/routes.js b/app/assets/scripts/routes.js index ef48338a..45610c43 100644 --- a/app/assets/scripts/routes.js +++ b/app/assets/scripts/routes.js @@ -14,6 +14,21 @@ var App = require('./components/app'); var About = require('./components/about'); var Home = require('./components/home'); +// Brief explanation on how the router works: +// The routing system was not built in from the beginning so adding one +// at this point would require quite a substantial refactor. +// A lighter and easier solution was adopted instead. +// The app still works by retaining the information in memory (i.e. storing the +// item the user clicked instead of getting it from the id in the url), but +// everytime there's a change the path gets replaces with the correct values. +// Replacing the path means that there's no history, so no going back. +// The url is shareable and the system will check for values in the url on load. +// It will only work on the first load. If a value is manually changed in the +// url, a simple "enter" won't make a difference. A hard refresh is necessary. + +// Bottom line is that the router works in reverse. Instead of updating the page +// with info from it, it gets updated with info from the actions on the page. + var routes = ( From de5f6e36f9d8a657e249652e9374dfd3930cb485 Mon Sep 17 00:00:00 2001 From: Daniel Silva Date: Mon, 15 Jun 2015 11:03:42 +0100 Subject: [PATCH 05/38] Fix #77. Use smaller grid size. Also move configuration to config.js --- app/assets/scripts/components/map.js | 28 +++++++++++++++----------- app/assets/scripts/config.js | 21 +++++++++++++++++++ app/assets/scripts/stores/map_store.js | 3 ++- 3 files changed, 39 insertions(+), 13 deletions(-) create mode 100644 app/assets/scripts/config.js diff --git a/app/assets/scripts/components/map.js b/app/assets/scripts/components/map.js index 19d578fd..065520c8 100644 --- a/app/assets/scripts/components/map.js +++ b/app/assets/scripts/components/map.js @@ -11,6 +11,7 @@ var mapStore = require('../stores/map_store'); var resultsStore = require('../stores/results_store'); var utils = require('../utils/utils'); var dsZoom = require('../utils/ds_zoom'); +var config = require('../config.js'); L.mapbox.accessToken = 'pk.eyJ1IjoiZGV2c2VlZCIsImEiOiJnUi1mbkVvIn0.018aLhX0Mb0tdtaT2QNe2Q'; @@ -158,7 +159,7 @@ var Map = React.createClass({ latest.properties.centroid = latestCenter; this.selectIntersecting = latest; // Move the map - this.map.setView([latestCenter.geometry.coordinates[1], latestCenter.geometry.coordinates[0]], 8); + this.map.setView([latestCenter.geometry.coordinates[1], latestCenter.geometry.coordinates[0]], config.map.initialZoom); } }, @@ -171,12 +172,13 @@ var Map = React.createClass({ }, // Redraws the line grid. - // This is a pixel grid with 200px squares at zoom level 8. + // This is a pixel grid with config.map.grid.pxSize squares + // at zoom level config.map.grid.atZoom. updateFauxGrid: function() { var bounds = this.map.getBounds(); var extent = [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()]; - var grid = this.linePixelGrid(extent, 200, 8); + var grid = this.linePixelGrid(extent, config.map.grid.pxSize, config.map.grid.atZoom); this.fauxLineGridLayer.clearLayers().addData(grid); this.fauxLineGridLayer.eachLayer(function(l) { @@ -207,20 +209,21 @@ var Map = React.createClass({ }, // Updates the colored grid. - // This is a pixel grid with 200px squares at zoom level 8. + // This is a pixel grid with config.map.grid.pxSize squares + // at zoom level config.map.grid.atZoom. // It is separated from the line grid to allow independent styling of // the stroke/content. updateGrid: function() { var _this = this; this.gridLayer.clearLayers(); - // Do not draw below zoom level 6 - if (this.map.getZoom() < 6) { return; } + // Do not draw below zoom level config.map.interactiveGridZoomLimit + if (this.map.getZoom() < config.map.interactiveGridZoomLimit) { return; } var bounds = this.map.getBounds(); var extent = [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()]; // Square grid to color. - var squareGrid = this.squarePixelGrid(extent, 200, 8); + var squareGrid = this.squarePixelGrid(extent, config.map.grid.pxSize, config.map.grid.atZoom); /* // How this works: @@ -371,8 +374,8 @@ var Map = React.createClass({ componentDidMount: function() { console.log('componentDidMount MapBoxMap'); var _this = this; - var view = [60.177, 25.148]; - var zoom = 6; + var view = config.map.initialView; + var zoom = config.map.initialZoom; // Map position from path. var routerMap = this.getParams().map; @@ -389,10 +392,10 @@ var Map = React.createClass({ view = [this.routerSelectedSquare[0], this.routerSelectedSquare[1]]; } - this.map = L.mapbox.map(this.getDOMNode().querySelector('#map'), 'devseed.m9i692do', { + this.map = L.mapbox.map(this.getDOMNode().querySelector('#map'), config.map.baseLayer, { zoomControl: false, - minZoom : 4, - //maxZoom : 18, + minZoom : config.map.minZoom, + maxZoom : config.map.maxZoom, maxBounds: L.latLngBounds([-90, -180], [90, 180]) }).setView(view, zoom); @@ -442,6 +445,7 @@ var Map = React.createClass({ // Map move listener. this.map.on('moveend', function() { + console.log(_this.map.getZoom()); // Compute new map location for the path var mapLocation = _this.mapViewToString(); diff --git a/app/assets/scripts/config.js b/app/assets/scripts/config.js new file mode 100644 index 00000000..929dbe60 --- /dev/null +++ b/app/assets/scripts/config.js @@ -0,0 +1,21 @@ +'use strict'; + +module.exports = { + map: { + baseLayer: 'devseed.m9i692do', + + initialZoom: 8, + minZoom: 4, + maxZoom: undefined, + + intialView: [60.177, 25.148], + + // Zoom below which the interactive grid ceases to exist. + interactiveGridZoomLimit: 8, + + grid: { + pxSize: 48, + atZoom: 8 + } + } +}; \ No newline at end of file diff --git a/app/assets/scripts/stores/map_store.js b/app/assets/scripts/stores/map_store.js index e5557864..4ce1f4d9 100644 --- a/app/assets/scripts/stores/map_store.js +++ b/app/assets/scripts/stores/map_store.js @@ -4,6 +4,7 @@ var $ = require('jquery'); var actions = require('../actions/actions'); var overlaps = require('turf-overlaps'); var utils = require('../utils/utils'); +var config = require('../config.js'); module.exports = Reflux.createStore({ @@ -37,7 +38,7 @@ module.exports = Reflux.createStore({ onMapMove: function(map) { var _this = this; - if (map.getZoom() < 6) { + if (map.getZoom() < config.map.interactiveGridZoomLimit) { this.trigger([]); return; } From e4c0562cc5775896717d70f48e9db20269a20171 Mon Sep 17 00:00:00 2001 From: Daniel Silva Date: Mon, 15 Jun 2015 11:24:51 +0100 Subject: [PATCH 06/38] Fix router error. --- app/assets/scripts/components/results_pane.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/assets/scripts/components/results_pane.js b/app/assets/scripts/components/results_pane.js index da166d06..8e6a5fbc 100644 --- a/app/assets/scripts/components/results_pane.js +++ b/app/assets/scripts/components/results_pane.js @@ -27,6 +27,11 @@ var ResultsPane = React.createClass({ selectedItemIndex: data.selectedItemIndex }); + // No square selected. Do not update route. + if (!mapStore.isSelectedSquare()) { + return; + } + var params = this.getParams(); var route = 'results'; if (data.selectedItem) { From da6299565f2ae9a3fa01bf236b0336f10be0227f Mon Sep 17 00:00:00 2001 From: Daniel Silva Date: Mon, 15 Jun 2015 12:54:58 +0100 Subject: [PATCH 07/38] Fix config property name --- app/assets/scripts/config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/scripts/config.js b/app/assets/scripts/config.js index 929dbe60..f7691bfc 100644 --- a/app/assets/scripts/config.js +++ b/app/assets/scripts/config.js @@ -8,7 +8,7 @@ module.exports = { minZoom: 4, maxZoom: undefined, - intialView: [60.177, 25.148], + initialView: [60.177, 25.148], // Zoom below which the interactive grid ceases to exist. interactiveGridZoomLimit: 8, From 03ec26b4672d934056631c1fa86adbb68c172c11 Mon Sep 17 00:00:00 2001 From: Daniel Silva Date: Mon, 15 Jun 2015 12:56:20 +0100 Subject: [PATCH 08/38] Fix #24. Show result count over grid --- app/assets/scripts/components/map.js | 18 +++++++++++++++--- app/assets/styles/_map.scss | 22 ++++++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/app/assets/scripts/components/map.js b/app/assets/scripts/components/map.js index 065520c8..0f307d13 100644 --- a/app/assets/scripts/components/map.js +++ b/app/assets/scripts/components/map.js @@ -305,11 +305,11 @@ var Map = React.createClass({ this.gridLayer.addData(squareGrid); this.gridLayer.eachLayer(function(l) { L.DomUtil.addClass(l._path, 'gs'); - var featureCenter = null; + var featureCenter = turf.centroid(l.feature); + l.feature.properties.featureCenter = featureCenter; // If there is a selected square in the path, select the correct one. if (_this.routerSelectedSquare) { - featureCenter = turf.centroid(l.feature); if (_this.routerSelectedSquare[0] == featureCenter.geometry.coordinates[1] && _this.routerSelectedSquare[1] == featureCenter.geometry.coordinates[0]) { // Done with selecting. @@ -322,7 +322,6 @@ var Map = React.createClass({ // Select the square that intersects the stored image. if (_this.selectIntersecting) { - featureCenter = turf.centroid(l.feature); var latestFeature = utils.getPolygonFeature(_this.selectIntersecting.coordinates); if (turf.inside(featureCenter, latestFeature) || turf.inside(_this.selectIntersecting.properties.centroid, l.feature) || overlaps(latestFeature, l.feature)) { @@ -364,6 +363,15 @@ var Map = React.createClass({ L.DomUtil.addClass(l._path, 'gs-density-low'); } + var p = L.popup({ + autoPan: false, + closeButton: false, + offset: L.point(0, 10), + className: 'gs-tooltip-count' + }).setContent(intersectCount.toString()); + + l.bindPopup(p); + }); this.gridLayer.bringToBack(); return this; @@ -432,11 +440,15 @@ var Map = React.createClass({ this.gridLayer.on('mouseover', function(e) { if (!mapStore.isSelectedSquare() && e.layer.feature.properties.intersectCount > 0) { L.DomUtil.addClass(e.layer._path, 'gs-highlight'); + // Open popup on square center. + var sqrCenter = e.layer.feature.properties.featureCenter.geometry.coordinates; + e.layer.openPopup([sqrCenter[1], sqrCenter[0]]); } }); // On mouseout remove gs-highlight. this.gridLayer.on('mouseout', function(e) { L.DomUtil.removeClass(e.layer._path, 'gs-highlight'); + e.layer.closePopup(); }); // Footprint layer. diff --git a/app/assets/styles/_map.scss b/app/assets/styles/_map.scss index 259836f1..64967fc0 100644 --- a/app/assets/styles/_map.scss +++ b/app/assets/styles/_map.scss @@ -63,4 +63,26 @@ .gs-highlight { stroke: rgba($base-color, 0.64); stroke-width: 1; +} + +.gs-tooltip-count { + + & > * { + pointer-events: none; + } + .leaflet-popup-content-wrapper { + padding: 0; + background: none; + box-shadow: none; + } + .leaflet-popup-content { + padding: 0; + text-align: center; + color: rgba($base-color, 0.64); + font-size: 0.825rem; + font-weight: $base-font-bold; + } + .leaflet-popup-tip-container { + display: none; + } } \ No newline at end of file From 7647fdadd89c170d0352b31480baa11aef849301 Mon Sep 17 00:00:00 2001 From: Daniel Silva Date: Mon, 15 Jun 2015 18:05:18 +0100 Subject: [PATCH 09/38] Fix #78. Add minimap --- app/assets/scripts/actions/actions.js | 3 + app/assets/scripts/components/home.js | 2 + app/assets/scripts/components/map.js | 12 ++- app/assets/scripts/components/minimap.js | 105 +++++++++++++++++++++++ app/assets/scripts/config.js | 2 +- app/assets/styles/_map.scss | 10 +++ 6 files changed, 130 insertions(+), 4 deletions(-) create mode 100644 app/assets/scripts/components/minimap.js diff --git a/app/assets/scripts/actions/actions.js b/app/assets/scripts/actions/actions.js index 06bf6b27..8c7821c4 100644 --- a/app/assets/scripts/actions/actions.js +++ b/app/assets/scripts/actions/actions.js @@ -29,4 +29,7 @@ module.exports = Reflux.createActions({ 'goToLatest': {}, 'geocoderResult': {}, + + + 'miniMapClick': {}, }); \ No newline at end of file diff --git a/app/assets/scripts/components/home.js b/app/assets/scripts/components/home.js index d5783351..0d281c21 100644 --- a/app/assets/scripts/components/home.js +++ b/app/assets/scripts/components/home.js @@ -1,6 +1,7 @@ 'use strict'; var React = require('react/addons'); var MapBoxMap = require('./map'); +var MiniMap = require('./minimap'); var ResultsPane = require('./results_pane'); var Home = React.createClass({ @@ -8,6 +9,7 @@ var Home = React.createClass({ return (
+
); diff --git a/app/assets/scripts/components/map.js b/app/assets/scripts/components/map.js index 0f307d13..42b0a44d 100644 --- a/app/assets/scripts/components/map.js +++ b/app/assets/scripts/components/map.js @@ -30,6 +30,8 @@ var Map = React.createClass({ Reflux.listenTo(actions.resultItemView, "onResultItemView"), Reflux.listenTo(actions.resultListView, "onResultListView"), + Reflux.listenTo(actions.miniMapClick, "onMiniMapClick"), + Reflux.listenTo(actions.goToLatest, "onGoToLatest"), Reflux.listenTo(actions.geocoderResult, "onGeocoderResult"), @@ -73,6 +75,12 @@ var Map = React.createClass({ }); }, + // Actions listener. + onMiniMapClick: function(latlng) { + // Remove footprint highlight. + this.map.setView(latlng); + }, + // Actions listener. onResultItemSelect: function() { // Remove footprint highlight. @@ -457,9 +465,7 @@ var Map = React.createClass({ // Map move listener. this.map.on('moveend', function() { - console.log(_this.map.getZoom()); - - // Compute new map location for the path + // Compute new map location for the path . var mapLocation = _this.mapViewToString(); // Preserve other params if any. var params = _this.getParams(); diff --git a/app/assets/scripts/components/minimap.js b/app/assets/scripts/components/minimap.js new file mode 100644 index 00000000..df1bedda --- /dev/null +++ b/app/assets/scripts/components/minimap.js @@ -0,0 +1,105 @@ +'use strict'; +require('mapbox.js'); +var React = require('react/addons'); +var Reflux = require('reflux'); +// Not working. Using cdn. (turf.intersect was throwing a weird error) +//var turf = require('turf'); +var actions = require('../actions/actions'); +var mapStore = require('../stores/map_store'); +var config = require('../config.js'); + +var MiniMap = React.createClass({ + mixins: [ + Reflux.listenTo(actions.mapMove, "onMapMove"), + Reflux.listenTo(actions.mapSquareSelected, "onMapSquareSelected"), + Reflux.listenTo(actions.mapSquareUnselected, "onMapSquareUnselected") + ], + + map: null, + + viewfinder: null, + targetLines: null, + + // Actions listener. + onMapMove: function(mainmap) { + var b = mainmap.getBounds(); + + this.viewfinder.setLatLngs([ + b.getNorthEast(), + b.getNorthWest(), + b.getSouthWest(), + b.getSouthEast() + ]).addTo(this.map); + }, + + onMapSquareSelected: function(sqrFeature) { + var center = sqrFeature.properties.centroid; + + this.targetLines.setLatLngs([ + [ + [-90, center[0]], + [90, center[0]] + ], + [ + [center[1], -220], + [center[1], 220] + ] + ]); + }, + + onMapSquareUnselected: function() { + this.targetLines.clearLayers(); + }, + + // Lifecycle method. + // Called once as soon as the component has a DOM representation. + componentDidMount: function() { + console.log('componentDidMount MiniMap'); + var _this = this; + + this.map = L.mapbox.map(this.getDOMNode(), config.map.baseLayer, { + center: [0, 0], + + zoomControl: false, + attributionControl: false, + dragging: false, + touchZoom: false, + scrollWheelZoom: false, + doubleClickZoom: false, + boxZoom: false, + + maxBounds: L.latLngBounds([-90, -180], [90, 180]) + }).fitBounds(L.latLngBounds([-90, -180], [90, 180])); + + this.viewfinder = L.polygon([], { + clickable: false, + color: '#439ab4', + weight: 2 + }).addTo(this.map); + + + this.targetLines = L.multiPolyline([], { + clickable: false, + color: '#439ab4', + weight: 2 + }).addTo(this.map); + + console.log(this.targetLines); + this.map.on('click', function(e) { + actions.miniMapClick(e.latlng); + }); + }, + + // Lifecycle method. + // Called when the component gets updated. + componentDidUpdate: function(/*prevProps, prevState*/) { + console.log('componentDidUpdate'); + }, + + render: function() { + return (
); + }, + +}); + +module.exports = MiniMap; \ No newline at end of file diff --git a/app/assets/scripts/config.js b/app/assets/scripts/config.js index f7691bfc..c1ce8616 100644 --- a/app/assets/scripts/config.js +++ b/app/assets/scripts/config.js @@ -5,7 +5,7 @@ module.exports = { baseLayer: 'devseed.m9i692do', initialZoom: 8, - minZoom: 4, + minZoom: 8, maxZoom: undefined, initialView: [60.177, 25.148], diff --git a/app/assets/styles/_map.scss b/app/assets/styles/_map.scss index 64967fc0..6de05c82 100644 --- a/app/assets/styles/_map.scss +++ b/app/assets/styles/_map.scss @@ -85,4 +85,14 @@ .leaflet-popup-tip-container { display: none; } +} + +#minimap { + position: absolute; + left: 1rem; + bottom: 1rem; + width: 16rem; + height: 10rem; + + @extend %press-box-skin; } \ No newline at end of file From eb7dea6f70c3703d7ca8758698d18d514bbf7f01 Mon Sep 17 00:00:00 2001 From: Daniel Silva Date: Tue, 16 Jun 2015 11:36:30 +0100 Subject: [PATCH 10/38] Show welcome modal again --- app/assets/scripts/components/app.js | 6 ------ app/assets/scripts/components/modals/welcome_modal.js | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/app/assets/scripts/components/app.js b/app/assets/scripts/components/app.js index d3a981fd..73e8ab5c 100644 --- a/app/assets/scripts/components/app.js +++ b/app/assets/scripts/components/app.js @@ -7,12 +7,6 @@ var WelcomeModal = require('./modals/welcome_modal'); var Header = require('./header'); var App = React.createClass({ - - aboutClickHandler: function(e) { - e.preventDefault(); - actions.openModal('about'); - }, - render: function() { return (
diff --git a/app/assets/scripts/components/modals/welcome_modal.js b/app/assets/scripts/components/modals/welcome_modal.js index 82cc75dc..45a49566 100644 --- a/app/assets/scripts/components/modals/welcome_modal.js +++ b/app/assets/scripts/components/modals/welcome_modal.js @@ -78,7 +78,7 @@ var WelcomeModal = React.createClass({ header={this.getHeader()} body={this.getBody()} footer={this.getFooter()} - revealed={false} /> + revealed={true} /> ); } }); From 053a487b5f9009e9a3c078711d49679ac9d4f95d Mon Sep 17 00:00:00 2001 From: Daniel Silva Date: Tue, 16 Jun 2015 11:40:01 +0100 Subject: [PATCH 11/38] Fix #12. Add keyboard shortcuts --- app/assets/scripts/components/header.js | 19 ++++++++++++++++++- app/assets/scripts/components/map.js | 3 +++ .../scripts/components/modals/base_modal.js | 8 ++++++++ app/assets/scripts/components/results_item.js | 18 ++++++++++++++++++ app/assets/scripts/components/results_pane.js | 13 ++++++++++++- package.json | 1 + 6 files changed, 60 insertions(+), 2 deletions(-) diff --git a/app/assets/scripts/components/header.js b/app/assets/scripts/components/header.js index 42e7fb1f..825587d4 100644 --- a/app/assets/scripts/components/header.js +++ b/app/assets/scripts/components/header.js @@ -1,9 +1,26 @@ 'use strict'; var React = require('react/addons'); -var actions = require('../actions/actions'); +var Keys = require('react-keybinding'); var actions = require('../actions/actions'); var Header = React.createClass({ + mixins: [ + Keys + ], + + keybindings: { + 'i': function() { + actions.openModal('info'); + }, + 's': function() { + var geocoder = this.getDOMNode().querySelector('[data-hook="geocoder"]'); + geocoder.focus(); + // Prevent the 's' from being typed in the search box. + setTimeout(function() { + geocoder.value = ''; + }, 1); + } + }, aboutClickHandler: function(e) { e.preventDefault(); diff --git a/app/assets/scripts/components/map.js b/app/assets/scripts/components/map.js index 42b0a44d..4fbad924 100644 --- a/app/assets/scripts/components/map.js +++ b/app/assets/scripts/components/map.js @@ -431,6 +431,9 @@ var Map = React.createClass({ this.gridLayer = L.geoJson(null, { style: L.mapbox.simplestyle.style }).addTo(this.map); // On click select the square. this.gridLayer.on('click', function(e) { + // Ensure that the popup doesn't open. + e.layer.closePopup(); + // No previous square selected. if (mapStore.isSelectedSquare()) { // Unselect. diff --git a/app/assets/scripts/components/modals/base_modal.js b/app/assets/scripts/components/modals/base_modal.js index 552ffb50..8aac837c 100644 --- a/app/assets/scripts/components/modals/base_modal.js +++ b/app/assets/scripts/components/modals/base_modal.js @@ -3,6 +3,7 @@ var React = require('react/addons'); var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup; var Reflux = require('reflux'); +var Keys = require('react-keybinding'); var actions = require('../../actions/actions'); /** @@ -21,8 +22,15 @@ var actions = require('../../actions/actions'); var BModal = React.createClass({ mixins: [ Reflux.listenTo(actions.openModal, 'onOpenModal'), + Keys ], + keybindings: { + 'esc': function() { + this.setState({ revealed: false }); + } + }, + onOpenModal: function(which) { if (which == this.props.type) { this.setState({ revealed: true }); diff --git a/app/assets/scripts/components/results_item.js b/app/assets/scripts/components/results_item.js index bc1362dd..f7b93418 100644 --- a/app/assets/scripts/components/results_item.js +++ b/app/assets/scripts/components/results_item.js @@ -1,5 +1,6 @@ 'use strict'; var React = require('react/addons'); +var Keys = require('react-keybinding'); var actions = require('../actions/actions'); var ZcButton = require('./shared/zc_button'); var Dropdown = require('./shared/dropdown'); @@ -7,6 +8,23 @@ var utils = require('../utils/utils'); var ResultsItem = React.createClass({ + mixins: [ + Keys + ], + + keybindings: { + 'arrow-left': function() { + if (this.props.pagination.current > 1) { + actions.prevResult(); + } + }, + 'arrow-right': function() { + if (this.props.pagination.current < this.props.pagination.total) { + actions.nextResult(); + } + } + }, + prevResult: function(e) { e.preventDefault(); actions.prevResult(); diff --git a/app/assets/scripts/components/results_pane.js b/app/assets/scripts/components/results_pane.js index 8e6a5fbc..5b1ee598 100644 --- a/app/assets/scripts/components/results_pane.js +++ b/app/assets/scripts/components/results_pane.js @@ -2,6 +2,7 @@ var React = require('react/addons'); var Reflux = require('reflux'); var Router = require('react-router'); +var Keys = require('react-keybinding'); var ResultsList = require('./results_list'); var ResultsItem = require('./results_item'); var resultsStore = require('../stores/results_store'); @@ -12,9 +13,19 @@ var ResultsPane = React.createClass({ mixins: [ Reflux.listenTo(resultsStore, "onResults"), Router.Navigation, - Router.State + Router.State, + Keys ], + keybindings: { + 'esc': function() { + if (this.state.results.length === 0) { + return; + } + actions.mapSquareUnselected(); + } + }, + // We only want to load the item from the id in the path the first time the // "page" is loaded. // More on how the router works can be found in routes.js diff --git a/package.json b/package.json index 3a364fbf..f6f46d75 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "lodash": "^3.8.0", "mapbox.js": "^2.1.9", "react": "^0.13.3", + "react-keybinding": "^2.0.0", "react-router": "^0.13.3", "reflux": "^0.2.7", "turf": "^2.0.2", From ac3dd2f845d311dff923420bd272d12f854f2910 Mon Sep 17 00:00:00 2001 From: smit1678 Date: Tue, 23 Jun 2015 12:48:15 -0400 Subject: [PATCH 12/38] adding pointer cursor over minimap --- app/assets/styles/_map.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/styles/_map.scss b/app/assets/styles/_map.scss index 6de05c82..7b7bcf7e 100644 --- a/app/assets/styles/_map.scss +++ b/app/assets/styles/_map.scss @@ -93,6 +93,7 @@ bottom: 1rem; width: 16rem; height: 10rem; + cursor: pointer; @extend %press-box-skin; } \ No newline at end of file From 7f188026dd854fae651f1bea8a67e940e88e56ab Mon Sep 17 00:00:00 2001 From: smit1678 Date: Thu, 25 Jun 2015 16:01:57 -0400 Subject: [PATCH 13/38] change result type name --- app/assets/scripts/components/results_item.js | 2 +- app/assets/scripts/components/results_list.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/scripts/components/results_item.js b/app/assets/scripts/components/results_item.js index f7b93418..941de6a3 100644 --- a/app/assets/scripts/components/results_item.js +++ b/app/assets/scripts/components/results_item.js @@ -89,7 +89,7 @@ var ResultsItem = React.createClass({
Type
-
{d.properties.tms ? 'Multiscene TMS' : 'Single Scene'}
+
{d.properties.tms ? 'Image + Tiled Map' : 'Image'}
Date
{d.acquisition_start.slice(0,10)}
Resolution
diff --git a/app/assets/scripts/components/results_list.js b/app/assets/scripts/components/results_list.js index f3bfe220..79d2be75 100644 --- a/app/assets/scripts/components/results_list.js +++ b/app/assets/scripts/components/results_list.js @@ -35,7 +35,7 @@ var ResultsListItem = React.createClass({
Type
-
{d.properties.tms ? 'Multiscene TMS' : 'Single Scene'}
+
{d.properties.tms ? 'Image + Tiled Map' : 'Image'}
Date
{d.acquisition_start.slice(0,10)}
Res
From aba7c9381d25c12be935f41936c88f4e17789aee Mon Sep 17 00:00:00 2001 From: Anand Thakker Date: Thu, 25 Jun 2015 18:29:49 -0400 Subject: [PATCH 14/38] Add .eslintrc --- .eslintrc | 3 +++ package.json | 5 +++++ 2 files changed, 8 insertions(+) create mode 100644 .eslintrc diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 00000000..86a12be1 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,3 @@ +{ + "extends": ["semistandard", "standard-react"] +} diff --git a/package.json b/package.json index f6f46d75..d2bb4628 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,11 @@ "devDependencies": { "browser-sync": "^2.5.3", "browserify": "^6.2.0", + "eslint": "^0.23.0", + "eslint-config-semistandard": "^4.0.0", + "eslint-config-standard": "^3.3.0", + "eslint-config-standard-react": "^1.0.0", + "eslint-plugin-react": "^2.5.2", "glob": "^4.0.6", "gulp": "^3.8.11", "gulp-clean": "^0.3.1", From 5252e9ca8e271c6d9db4f75ad03aa6c79c903ed5 Mon Sep 17 00:00:00 2001 From: Anand Thakker Date: Thu, 25 Jun 2015 18:36:02 -0400 Subject: [PATCH 15/38] revert eslint config --- .eslintrc | 3 --- package.json | 4 ---- 2 files changed, 7 deletions(-) delete mode 100644 .eslintrc diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 86a12be1..00000000 --- a/.eslintrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": ["semistandard", "standard-react"] -} diff --git a/package.json b/package.json index d2bb4628..5e820268 100644 --- a/package.json +++ b/package.json @@ -11,10 +11,6 @@ "devDependencies": { "browser-sync": "^2.5.3", "browserify": "^6.2.0", - "eslint": "^0.23.0", - "eslint-config-semistandard": "^4.0.0", - "eslint-config-standard": "^3.3.0", - "eslint-config-standard-react": "^1.0.0", "eslint-plugin-react": "^2.5.2", "glob": "^4.0.6", "gulp": "^3.8.11", From 6e349152f946b7842072c1c92242d1568f3d666e Mon Sep 17 00:00:00 2001 From: Ricardo Mestre Date: Mon, 29 Jun 2015 15:51:45 +0100 Subject: [PATCH 16/38] Structure and style filters dropdown --- app/assets/scripts/components/filters.js | 24 ++++++++++++++++++++++++ app/assets/scripts/components/header.js | 8 ++------ app/assets/styles/_overlays.scss | 21 ++++++++++++++++++--- 3 files changed, 44 insertions(+), 9 deletions(-) create mode 100644 app/assets/scripts/components/filters.js diff --git a/app/assets/scripts/components/filters.js b/app/assets/scripts/components/filters.js new file mode 100644 index 00000000..0bf053f9 --- /dev/null +++ b/app/assets/scripts/components/filters.js @@ -0,0 +1,24 @@ +'use strict'; +var React = require('react/addons'); +var Dropdown = require('./shared/dropdown'); + +var Filters = module.exports = React.createClass({ + render: function() { + return ( + +
+
Time
+
All
+
Last week
+
Last month
+
Last year
+
Resolution
+
All
+
Low
+
Medium
+
High
+
+
+ ); + } +}); diff --git a/app/assets/scripts/components/header.js b/app/assets/scripts/components/header.js index 825587d4..99283155 100644 --- a/app/assets/scripts/components/header.js +++ b/app/assets/scripts/components/header.js @@ -2,6 +2,7 @@ var React = require('react/addons'); var Keys = require('react-keybinding'); var actions = require('../actions/actions'); +var Filters = require('./filters'); var Header = React.createClass({ mixins: [ @@ -52,12 +53,7 @@ var Header = React.createClass({
    -
  • - Settings -
    -

    Settings go here.

    -
    -
  • +
diff --git a/app/assets/styles/_overlays.scss b/app/assets/styles/_overlays.scss index 555e038c..45a8cb09 100644 --- a/app/assets/styles/_overlays.scss +++ b/app/assets/styles/_overlays.scss @@ -107,8 +107,23 @@ } } -.drop-menu > li > a, -.drop-menu > li > a:visited { +.drop-menu .drop-menu-sectitle { + display: block; + padding: 0.375rem 1rem; + text-transform: uppercase; + font-size: 0.875rem; + line-height: 1.25rem; + color: tint($base-font-color, 32%); +} + +.drop-menu .drop-menu-sectitle:not(:first-child) { + box-shadow: inset 0 1px 0 0 $brdr-rgba; + margin-top: 0.5rem; + padding-top: 0.75rem; +} + +.drop-menu > * > a, +.drop-menu > * > a:visited { position: relative; display: block; padding: 0.375rem 1rem; @@ -122,7 +137,7 @@ } } -.drop-menu > li { +.drop-menu > * { &.has-icon-bef a, &.has-icon-aft a { &:before, From 0e92d887755f15d0c7672a7aa309d7a840192166 Mon Sep 17 00:00:00 2001 From: Ricardo Mestre Date: Mon, 29 Jun 2015 16:58:07 +0100 Subject: [PATCH 17/38] Make repo link clickable. Fix #87 --- app/assets/styles/_base.scss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/styles/_base.scss b/app/assets/styles/_base.scss index a45aa5fe..48576494 100644 --- a/app/assets/styles/_base.scss +++ b/app/assets/styles/_base.scss @@ -198,7 +198,9 @@ a:active{ @include columns(3); @include column-gap(2rem); margin-bottom: -0.75rem; - -webkit-backface-visibility: hidden; + p, a { + -webkit-backface-visibility:hidden; + } h2 { @include heading(1.25rem); // 20 margin: 0 0 1rem 0; From d9366c0ff95a9cab15e2d8316932cafeb390d71a Mon Sep 17 00:00:00 2001 From: smit1678 Date: Mon, 29 Jun 2015 14:23:09 -0400 Subject: [PATCH 18/38] add filesize utility, add sensor, provider, and filesize metadata --- app/assets/scripts/components/results_item.js | 6 ++++++ app/assets/scripts/utils/utils.js | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/app/assets/scripts/components/results_item.js b/app/assets/scripts/components/results_item.js index f7b93418..1fc72087 100644 --- a/app/assets/scripts/components/results_item.js +++ b/app/assets/scripts/components/results_item.js @@ -94,8 +94,14 @@ var ResultsItem = React.createClass({
{d.acquisition_start.slice(0,10)}
Resolution
{utils.gsdToUnit(d.gsd)}
+
Image Size
+
{utils.file_sizeToUnit(d.file_size)}
Platform
{d.platform}
+
Sensor
+
{d.properties.sensor ? d.properties.sensor : 'not available'}
+
Provider
+
{d.provider}
diff --git a/app/assets/scripts/utils/utils.js b/app/assets/scripts/utils/utils.js index 26ce286d..3c084eb7 100644 --- a/app/assets/scripts/utils/utils.js +++ b/app/assets/scripts/utils/utils.js @@ -51,3 +51,22 @@ module.exports.gsdToUnit = function(gsd) { return Math.round(gsd) + ' ' + unit; }; + +/** + * Converts filesize into MB or GB + * @param float file_size in bytes + * @return string + */ + +module.exports.file_sizeToUnit = function(file_size) { + var unit = 'GB'; + // Check size to convert appropriate units to display more nicely + if (file_size < 1000000000) { + unit = 'MB'; + file_size /= 1000000; + } else { + file_size /= 1000000000; + } + + return Math.round(file_size * 10) / 10 + ' ' + unit; +}; From 201754ca68c395d4a3858d107f1bf40e5ff60adc Mon Sep 17 00:00:00 2001 From: Anand Thakker Date: Mon, 29 Jun 2015 18:32:39 -0400 Subject: [PATCH 19/38] Generalize search paramters in map_store --- app/assets/scripts/actions/actions.js | 3 +- app/assets/scripts/stores/map_store.js | 44 +++++++++++++++++++++----- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/app/assets/scripts/actions/actions.js b/app/assets/scripts/actions/actions.js index 8c7821c4..48738fe2 100644 --- a/app/assets/scripts/actions/actions.js +++ b/app/assets/scripts/actions/actions.js @@ -6,6 +6,7 @@ module.exports = Reflux.createActions({ 'mapMove': {}, 'mapSquareSelected': {}, 'mapSquareUnselected': {}, + 'setSearchParameter': {}, 'latestImageryLoaded': {}, @@ -32,4 +33,4 @@ module.exports = Reflux.createActions({ 'miniMapClick': {}, -}); \ No newline at end of file +}); diff --git a/app/assets/scripts/stores/map_store.js b/app/assets/scripts/stores/map_store.js index 4ce1f4d9..310c4965 100644 --- a/app/assets/scripts/stores/map_store.js +++ b/app/assets/scripts/stores/map_store.js @@ -1,5 +1,7 @@ 'use strict'; +var qs = require('querystring'); var Reflux = require('reflux'); +var _ = require('lodash'); var $ = require('jquery'); var actions = require('../actions/actions'); var overlaps = require('turf-overlaps'); @@ -9,6 +11,7 @@ var config = require('../config.js'); module.exports = Reflux.createStore({ storage: { + searchParameters: { limit: 4000 }, results: [], sqrSelected: null, latestImagery: null @@ -20,6 +23,7 @@ module.exports = Reflux.createStore({ this.listenTo(actions.mapMove, this.onMapMove); this.listenTo(actions.mapSquareSelected, this.onMapSquareSelected); this.listenTo(actions.mapSquareUnselected, this.onMapSquareUnselected); + this.listenTo(actions.setSearchParameter, this.onSetSearchParameter); this.queryLatestImagery(); }, @@ -36,8 +40,6 @@ module.exports = Reflux.createStore({ // Actions listener. onMapMove: function(map) { - var _this = this; - if (map.getZoom() < config.map.interactiveGridZoomLimit) { this.trigger([]); return; @@ -45,11 +47,37 @@ module.exports = Reflux.createStore({ var bbox = map.getBounds().toBBoxString(); // ?bbox=[lon_min],[lat_min],[lon_max],[lat_max] - $.get('http://oam-catalog.herokuapp.com/meta?limit=400&bbox=' + bbox) - .success(function(data) { - _this.storage.results = data.results; - _this.trigger(_this.storage.results); - }); + this.onSetSearchParameter({bbox: bbox}); + }, + + /** + * Update the current search parameters with the key-value pairs in the + * given `params` object. In keeping with React's setState style, this is + * an *additive* change, except that any existing search parameter whose + * value in `params` is `null` is removed. + * + * After the paramters are update, hit the API and broadcast results. + */ + onSetSearchParameter: function(params) { + var _this = this; + + // update stored search params + _.assign(this.storage.searchParameters, params); + for (key in this.storage.params) { + if (this.storage.params[key] === null) { + delete this.storage.params[key]; + } + } + + // hit API and broadcast result + if (this.storage.searchParameters.bbox) { + var params = qs.stringify(this.storage.searchParameters); + $.get('http://oam-catalog.herokuapp.com/meta?' + params) + .success(function(data) { + _this.storage.results = data.results; + _this.trigger(_this.storage.results); + }); + } }, // Actions listener. @@ -145,4 +173,4 @@ module.exports = Reflux.createStore({ return intersected; }, -}); \ No newline at end of file +}); From e3515cf953659d41fff091f9f15cc5a0715f9108 Mon Sep 17 00:00:00 2001 From: Anand Thakker Date: Mon, 29 Jun 2015 22:17:30 -0400 Subject: [PATCH 20/38] Wire up search filter store --- app/assets/scripts/actions/actions.js | 13 +++- app/assets/scripts/components/filters.js | 45 +++++++++--- app/assets/scripts/stores/map_store.js | 71 ++++++++++--------- .../scripts/stores/search_query_store.js | 53 ++++++++++++++ 4 files changed, 141 insertions(+), 41 deletions(-) create mode 100644 app/assets/scripts/stores/search_query_store.js diff --git a/app/assets/scripts/actions/actions.js b/app/assets/scripts/actions/actions.js index 48738fe2..ca5f04eb 100644 --- a/app/assets/scripts/actions/actions.js +++ b/app/assets/scripts/actions/actions.js @@ -6,12 +6,23 @@ module.exports = Reflux.createActions({ 'mapMove': {}, 'mapSquareSelected': {}, 'mapSquareUnselected': {}, - 'setSearchParameter': {}, 'latestImageryLoaded': {}, 'resultsChange': {}, + // Filter actios + 'setDateFilter': { + shouldEmit: function (val) { + return [ 'all', 'week', 'month', 'year' ].indexOf(val) >= 0; + } + }, + 'setResolutionFilter': { + shouldEmit: function (val) { + return [ 'all', 'low', 'medium', 'high' ].indexOf(val) >= 0; + } + }, + // Results pane related actions. 'resultItemSelect': {}, 'resultItemView': {}, diff --git a/app/assets/scripts/components/filters.js b/app/assets/scripts/components/filters.js index 0bf053f9..7b761d1d 100644 --- a/app/assets/scripts/components/filters.js +++ b/app/assets/scripts/components/filters.js @@ -1,22 +1,51 @@ 'use strict'; var React = require('react/addons'); +var Reflux = require('reflux'); var Dropdown = require('./shared/dropdown'); +var actions = require('../actions/actions'); +var searchQuery = require('../stores/search_query_store'); var Filters = module.exports = React.createClass({ + mixins: [ + Reflux.connect(searchQuery) // calls this.setState() with results of searchQuery store + ], + render: function() { + var dates = [ + {key: 'all', title: 'All'}, + {key: 'week', title: 'Last week'}, + {key: 'month', title: 'Last week'}, + {key: 'year', title: 'Last year'} + ].map(function (d) { + var klass = this.state.date === d.key ? 'active' : ''; + var click = actions.setDateFilter.bind(actions, d.key); + return ( +
+ {d.title} +
); + }.bind(this)); + + var resolutions = [ + {key: 'all', title: 'All'}, + {key: 'low', title: 'Low'}, + {key: 'medium', title: 'Medium'}, + {key: 'high', title: 'High'} + ].map(function (d) { + var klass = this.state.resolution === d.key ? 'active' : ''; + var click = actions.setResolutionFilter.bind(actions, d.key); + return ( +
+ {d.title} +
); + }.bind(this)); + return (
Time
-
All
-
Last week
-
Last month
-
Last year
+ {dates}
Resolution
-
All
-
Low
-
Medium
-
High
+ {resolutions}
); diff --git a/app/assets/scripts/stores/map_store.js b/app/assets/scripts/stores/map_store.js index 310c4965..9b48e4ca 100644 --- a/app/assets/scripts/stores/map_store.js +++ b/app/assets/scripts/stores/map_store.js @@ -4,9 +4,9 @@ var Reflux = require('reflux'); var _ = require('lodash'); var $ = require('jquery'); var actions = require('../actions/actions'); +var searchQuery = require('./search_query_store') var overlaps = require('turf-overlaps'); var utils = require('../utils/utils'); -var config = require('../config.js'); module.exports = Reflux.createStore({ @@ -20,10 +20,9 @@ module.exports = Reflux.createStore({ // Called on creation. // Setup listeners. init: function() { - this.listenTo(actions.mapMove, this.onMapMove); this.listenTo(actions.mapSquareSelected, this.onMapSquareSelected); this.listenTo(actions.mapSquareUnselected, this.onMapSquareUnselected); - this.listenTo(actions.setSearchParameter, this.onSetSearchParameter); + this.listenTo(searchQuery, this.onSearchQuery); this.queryLatestImagery(); }, @@ -38,45 +37,53 @@ module.exports = Reflux.createStore({ }); }, - // Actions listener. - onMapMove: function(map) { - if (map.getZoom() < config.map.interactiveGridZoomLimit) { - this.trigger([]); - return; - } - - var bbox = map.getBounds().toBBoxString(); - // ?bbox=[lon_min],[lat_min],[lon_max],[lat_max] - this.onSetSearchParameter({bbox: bbox}); - }, - /** - * Update the current search parameters with the key-value pairs in the - * given `params` object. In keeping with React's setState style, this is - * an *additive* change, except that any existing search parameter whose - * value in `params` is `null` is removed. - * - * After the paramters are update, hit the API and broadcast results. + * Translate the application-based search parameters into terms that the + * API understands, then hit the API and broadcast the result. */ - onSetSearchParameter: function(params) { + onSearchQuery: function (parameters) { + console.log('onSearchQuery', parameters); var _this = this; + // hit API and broadcast result + if (parameters.bbox) { + var resolutionFilter = { + 'all': {}, + 'low': {gsd_from: 5}, // 5 + + 'medium': {gsd_from: 1, gsd_to: 5}, // 1 - 5 + 'high': {gsd_to: 1} // 1 + }[parameters.resolution]; + + var d = new Date(); + if (parameters.date === 'week') { + d.setDate(d.getDate() - 7); + } else if (parameters.date === 'month') { + d.setMonth(d.getMonth() - 1); + } else if (parameters.date === 'year') { + d.setFullYear(d.getFullYear() - 1); + } - // update stored search params - _.assign(this.storage.searchParameters, params); - for (key in this.storage.params) { - if (this.storage.params[key] === null) { - delete this.storage.params[key]; + var dateFilter = parameters.date === 'all' ? {} : { + acquisition_from: [ + d.getFullYear(), + d.getMonth() + 1, + d.getDate() + ].join('-') } - } - // hit API and broadcast result - if (this.storage.searchParameters.bbox) { - var params = qs.stringify(this.storage.searchParameters); - $.get('http://oam-catalog.herokuapp.com/meta?' + params) + var params = _.assign({ + limit: 4000, + bbox: parameters.bbox, + }, resolutionFilter, dateFilter); + + console.log('search:', params); + + $.get('http://oam-catalog.herokuapp.com/meta?' + qs.stringify(params)) .success(function(data) { _this.storage.results = data.results; _this.trigger(_this.storage.results); }); + } else { + _this.trigger([]); } }, diff --git a/app/assets/scripts/stores/search_query_store.js b/app/assets/scripts/stores/search_query_store.js new file mode 100644 index 00000000..735cba75 --- /dev/null +++ b/app/assets/scripts/stores/search_query_store.js @@ -0,0 +1,53 @@ +var Reflux = require('reflux'); +var actions = require('../actions/actions'); +var config = require('../config.js'); +var _ = require('lodash'); + +/** + * Models the "search parameters" from the point of view of the application. + * NOT responsible for undersatning the API -- that's done by map_store, which + * consumes this one. + */ +module.exports = Reflux.createStore({ + _parameters: { + date: 'all', + resolution: 'all' + }, + + init: function () { + this.listenTo(actions.mapMove, this.onMapMove); + this.listenTo(actions.setDateFilter, this.onSetDateFilter); + this.listenTo(actions.setResolutionFilter, this.onSetResolutionFilter); + }, + + onMapMove: function(map) { + if (map.getZoom() < config.map.interactiveGridZoomLimit) { + this._setParameter({bbox: null}); + return; + } + + var bbox = map.getBounds().toBBoxString(); + // ?bbox=[lon_min],[lat_min],[lon_max],[lat_max] + this._setParameter({bbox: bbox}); + }, + + onSetDateFilter: function(period) { + this._setParameter({date: period}); + }, + + onSetResolutionFilter: function(resolutionLevel) { + this._setParameter({resolution: resolutionLevel}); + }, + + _setParameter: function(params) { + // update stored search params + _.assign(this._parameters, params); + for (var key in this._parameters) { + if (this._parameters[key] === null) { + delete this._parameters[key]; + } + } + + this.trigger(this._parameters) + } +}) From 22317296055104e125ec4ab24fcf03d0f921b3e5 Mon Sep 17 00:00:00 2001 From: Ricardo Mestre Date: Tue, 30 Jun 2015 11:34:06 +0100 Subject: [PATCH 21/38] Restyle tile popup count --- app/assets/styles/_map.scss | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/app/assets/styles/_map.scss b/app/assets/styles/_map.scss index 7b7bcf7e..f81d3af5 100644 --- a/app/assets/styles/_map.scss +++ b/app/assets/styles/_map.scss @@ -66,7 +66,6 @@ } .gs-tooltip-count { - & > * { pointer-events: none; } @@ -76,11 +75,17 @@ box-shadow: none; } .leaflet-popup-content { - padding: 0; + @extend .antialiased; text-align: center; - color: rgba($base-color, 0.64); - font-size: 0.825rem; font-weight: $base-font-bold; + background: rgba($base-color, 0.48); + display: block; + color: #fff; + border-radius: $global-radius; + padding: 0 0.5rem; + font-size: 0.875rem; + line-height: 1.25rem; + width: auto !important; } .leaflet-popup-tip-container { display: none; From 659eb10e16c89eef01dc82f870eab4b3e7c48b89 Mon Sep 17 00:00:00 2001 From: Ricardo Mestre Date: Tue, 30 Jun 2015 11:38:54 +0100 Subject: [PATCH 22/38] Restyle minimap crosshair --- app/assets/scripts/components/minimap.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/scripts/components/minimap.js b/app/assets/scripts/components/minimap.js index df1bedda..0b8baf09 100644 --- a/app/assets/scripts/components/minimap.js +++ b/app/assets/scripts/components/minimap.js @@ -73,15 +73,15 @@ var MiniMap = React.createClass({ this.viewfinder = L.polygon([], { clickable: false, - color: '#439ab4', - weight: 2 + color: '#1f3b45', + weight: 0.5 }).addTo(this.map); this.targetLines = L.multiPolyline([], { clickable: false, - color: '#439ab4', - weight: 2 + color: '#1f3b45', + weight: 0.5 }).addTo(this.map); console.log(this.targetLines); From f26678d916135fdbc6277ab9161ab8ca2749784a Mon Sep 17 00:00:00 2001 From: Anand Thakker Date: Tue, 30 Jun 2015 07:28:08 -0400 Subject: [PATCH 23/38] Keep query parameters across URL updates --- app/assets/scripts/components/map.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/scripts/components/map.js b/app/assets/scripts/components/map.js index 4fbad924..5ef5e835 100644 --- a/app/assets/scripts/components/map.js +++ b/app/assets/scripts/components/map.js @@ -124,7 +124,7 @@ var Map = React.createClass({ params.map = this.mapViewToString(); params.square = selectedSquare[1] + ',' + selectedSquare[0]; - this.replaceWith(route, params); + this.replaceWith(route, params, this.getQuery()); //this.updateGrid(); }, @@ -137,7 +137,7 @@ var Map = React.createClass({ // Set the correct path. var mapLocation = this.mapViewToString(); - this.replaceWith('map', { map: mapLocation }); + this.replaceWith('map', { map: mapLocation }, this.getQuery()); actions.resultsChange([]); this.updateGrid(); @@ -482,7 +482,7 @@ var Map = React.createClass({ else if (params.square) { route = 'results'; } - _this.replaceWith(route, params); + _this.replaceWith(route, params, _this.getQuery()); _this.setState({loading: true}); actions.mapMove(_this.map); @@ -661,4 +661,4 @@ var Map = React.createClass({ }); -module.exports = Map; \ No newline at end of file +module.exports = Map; From bf76e4c290af5aba8a90f3d21f8c6e06df144f3b Mon Sep 17 00:00:00 2001 From: Anand Thakker Date: Tue, 30 Jun 2015 07:46:42 -0400 Subject: [PATCH 24/38] Persist search filters in URL --- app/assets/scripts/components/app.js | 20 +++++++++++++++ app/assets/scripts/components/filters.js | 32 +++++++++++++++++++++--- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/app/assets/scripts/components/app.js b/app/assets/scripts/components/app.js index 73e8ab5c..ffb6df0f 100644 --- a/app/assets/scripts/components/app.js +++ b/app/assets/scripts/components/app.js @@ -5,8 +5,28 @@ var RouteHandler = Router.RouteHandler; var InfoModal = require('./modals/info_modal'); var WelcomeModal = require('./modals/welcome_modal'); var Header = require('./header'); +var actions = require('../actions/actions'); var App = React.createClass({ + mixins: [ Router.State ], + + componentDidMount () { + // Pull the search filter state from the URL. Why is this here instead + // of in the Filters component? Because we want to ensure that we set + // these filter parameters BEFORE the map component loads, since that is + // where the map move action will get fired, triggering the first API load. + // + // TODO: this is really a stopgap until we integrate the router more + // fully. (See routes.js for more.) + var params = this.getQuery(); + if (params.date) { + actions.setDateFilter(params.date); + } + if (params.resolution) { + actions.setResolutionFilter(params.resolution); + } + }, + render: function() { return (
diff --git a/app/assets/scripts/components/filters.js b/app/assets/scripts/components/filters.js index 7b761d1d..b4161ca0 100644 --- a/app/assets/scripts/components/filters.js +++ b/app/assets/scripts/components/filters.js @@ -1,24 +1,48 @@ 'use strict'; var React = require('react/addons'); var Reflux = require('reflux'); +var Router = require('react-router'); var Dropdown = require('./shared/dropdown'); var actions = require('../actions/actions'); var searchQuery = require('../stores/search_query_store'); var Filters = module.exports = React.createClass({ mixins: [ - Reflux.connect(searchQuery) // calls this.setState() with results of searchQuery store + Reflux.connect(searchQuery), // calls this.setState() with results of searchQuery store + Router.Navigation, + Router.State ], + setDate: function (d) { + actions.setDateFilter(d.key); + this._updateUrl('date', d.key); + }, + + setResolution: function (d) { + actions.setResolutionFilter(d.key); + this._updateUrl('resolution', d.key) + }, + + _updateUrl: function (prop, value) { + var query = this.getQuery(); + if (value === 'all') { + delete query[prop] + } else { + query[prop] = value; + } + var routes = this.getRoutes(); + this.replaceWith(routes[routes.length - 1].name, this.getParams(), query); + }, + render: function() { var dates = [ {key: 'all', title: 'All'}, {key: 'week', title: 'Last week'}, - {key: 'month', title: 'Last week'}, + {key: 'month', title: 'Last month'}, {key: 'year', title: 'Last year'} ].map(function (d) { var klass = this.state.date === d.key ? 'active' : ''; - var click = actions.setDateFilter.bind(actions, d.key); + var click = this.setDate.bind(this, d); return (
{d.title} @@ -32,7 +56,7 @@ var Filters = module.exports = React.createClass({ {key: 'high', title: 'High'} ].map(function (d) { var klass = this.state.resolution === d.key ? 'active' : ''; - var click = actions.setResolutionFilter.bind(actions, d.key); + var click = this.setResolution.bind(this, d); return (
{d.title} From dfc08b33e5cf5779102dbaf055d325810a2107ae Mon Sep 17 00:00:00 2001 From: Anand Thakker Date: Tue, 30 Jun 2015 07:51:01 -0400 Subject: [PATCH 25/38] Trigger loading indicator on search filter change --- app/assets/scripts/components/map.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/assets/scripts/components/map.js b/app/assets/scripts/components/map.js index 5ef5e835..18572f96 100644 --- a/app/assets/scripts/components/map.js +++ b/app/assets/scripts/components/map.js @@ -9,6 +9,7 @@ var overlaps = require('turf-overlaps'); var actions = require('../actions/actions'); var mapStore = require('../stores/map_store'); var resultsStore = require('../stores/results_store'); +var searchQueryStore = require('../stores/search_query_store'); var utils = require('../utils/utils'); var dsZoom = require('../utils/ds_zoom'); var config = require('../config.js'); @@ -22,6 +23,7 @@ var Map = React.createClass({ // removing the listener when the component is unmounted. mixins: [ Reflux.listenTo(mapStore, "onMapData"), + Reflux.listenTo(searchQueryStore, "onSearchQueryChanged"), Reflux.listenTo(actions.mapSquareSelected, "onMapSquareSelected"), Reflux.listenTo(actions.mapSquareUnselected, "onMapSquareUnselected"), Reflux.listenTo(actions.resultOver, "onResultOver"), @@ -75,6 +77,10 @@ var Map = React.createClass({ }); }, + onSearchQueryChanged: function() { + this.setState({ loading: true }); + }, + // Actions listener. onMiniMapClick: function(latlng) { // Remove footprint highlight. @@ -484,7 +490,6 @@ var Map = React.createClass({ } _this.replaceWith(route, params, _this.getQuery()); - _this.setState({loading: true}); actions.mapMove(_this.map); _this.updateFauxGrid(); }); From 149839441fbea94f5e2efd492001b7e35881eca2 Mon Sep 17 00:00:00 2001 From: Ricardo Mestre Date: Tue, 30 Jun 2015 15:10:45 +0100 Subject: [PATCH 26/38] Update tms options icons --- app/assets/scripts/components/results_item.js | 4 ++-- app/assets/styles/_icons.scss | 12 +++++++++++- app/assets/styles/_overlays.scss | 2 ++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/app/assets/scripts/components/results_item.js b/app/assets/scripts/components/results_item.js index f7b93418..700053ba 100644 --- a/app/assets/scripts/components/results_item.js +++ b/app/assets/scripts/components/results_item.js @@ -56,8 +56,8 @@ var ResultsItem = React.createClass({
    -
  • Open with iD editor
  • -
  • Open with JOSM
  • +
  • Open with iD editor
  • +
  • Open with JOSM
  • diff --git a/app/assets/styles/_icons.scss b/app/assets/styles/_icons.scss index 508d6e0a..2520af00 100644 --- a/app/assets/styles/_icons.scss +++ b/app/assets/styles/_icons.scss @@ -6,7 +6,7 @@ @font-face { font-family: 'oam-ib-ui-icons'; - src: url(data:application/x-font-ttf;charset=utf-8;base64,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) format('truetype'); + src: url(data:application/x-font-ttf;charset=utf-8;base64,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) format('truetype'); font-weight: normal; font-style: normal; } @@ -25,6 +25,16 @@ -moz-osx-font-smoothing: grayscale; } +.icon-id-editor { + @extend .icon; + content: "\e637"; +} + +.icon-josm { + @extend .icon; + content: "\e638"; +} + .icon-history { @extend .icon; content: "\e636"; diff --git a/app/assets/styles/_overlays.scss b/app/assets/styles/_overlays.scss index 555e038c..4a3128ad 100644 --- a/app/assets/styles/_overlays.scss +++ b/app/assets/styles/_overlays.scss @@ -150,6 +150,8 @@ &.facebook a:before { @extend .icon-facebook; } &.google-plus a:before { @extend .icon-google-plus; } &.clipboard a:before { @extend .icon-clipboard; } + &.id-editor a:before { @extend .icon-id-editor; } + &.josm a:before { @extend .icon-josm; } } .drop-menu > .active > a, From 56fa910622ea4669c37cb8c9b327f657f855fe26 Mon Sep 17 00:00:00 2001 From: Anand Thakker Date: Tue, 30 Jun 2015 11:10:52 -0400 Subject: [PATCH 27/38] Fix code style --- app/assets/scripts/components/app.js | 2 +- app/assets/scripts/components/filters.js | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/assets/scripts/components/app.js b/app/assets/scripts/components/app.js index ffb6df0f..3e4a7b07 100644 --- a/app/assets/scripts/components/app.js +++ b/app/assets/scripts/components/app.js @@ -10,7 +10,7 @@ var actions = require('../actions/actions'); var App = React.createClass({ mixins: [ Router.State ], - componentDidMount () { + componentDidMount: function () { // Pull the search filter state from the URL. Why is this here instead // of in the Filters component? Because we want to ensure that we set // these filter parameters BEFORE the map component loads, since that is diff --git a/app/assets/scripts/components/filters.js b/app/assets/scripts/components/filters.js index b4161ca0..6106e361 100644 --- a/app/assets/scripts/components/filters.js +++ b/app/assets/scripts/components/filters.js @@ -8,11 +8,15 @@ var searchQuery = require('../stores/search_query_store'); var Filters = module.exports = React.createClass({ mixins: [ - Reflux.connect(searchQuery), // calls this.setState() with results of searchQuery store + Reflux.listenTo(searchQuery, 'onSearchQuery'), Router.Navigation, Router.State ], + onSearchQuery: function (data) { + this.setState(data); + }, + setDate: function (d) { actions.setDateFilter(d.key); this._updateUrl('date', d.key); From 9a233ad83075dbffe00b307a685d405158cccc8a Mon Sep 17 00:00:00 2001 From: smit1678 Date: Tue, 30 Jun 2015 11:14:10 -0400 Subject: [PATCH 28/38] add map layer name --- app/assets/scripts/components/results_item.js | 2 +- app/assets/scripts/components/results_list.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/scripts/components/results_item.js b/app/assets/scripts/components/results_item.js index 941de6a3..42d6d2aa 100644 --- a/app/assets/scripts/components/results_item.js +++ b/app/assets/scripts/components/results_item.js @@ -89,7 +89,7 @@ var ResultsItem = React.createClass({
Type
-
{d.properties.tms ? 'Image + Tiled Map' : 'Image'}
+
{d.properties.tms ? 'Image + Map Layer' : 'Image'}
Date
{d.acquisition_start.slice(0,10)}
Resolution
diff --git a/app/assets/scripts/components/results_list.js b/app/assets/scripts/components/results_list.js index 79d2be75..297fa6a8 100644 --- a/app/assets/scripts/components/results_list.js +++ b/app/assets/scripts/components/results_list.js @@ -35,7 +35,7 @@ var ResultsListItem = React.createClass({
Type
-
{d.properties.tms ? 'Image + Tiled Map' : 'Image'}
+
{d.properties.tms ? 'Image + Map Layer' : 'Image'}
Date
{d.acquisition_start.slice(0,10)}
Res
From 20570270bacae789920ee5d088df4b1754532a31 Mon Sep 17 00:00:00 2001 From: Anand Thakker Date: Tue, 30 Jun 2015 11:36:34 -0400 Subject: [PATCH 29/38] fix initial state error --- app/assets/scripts/components/filters.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/assets/scripts/components/filters.js b/app/assets/scripts/components/filters.js index 6106e361..44b6e40a 100644 --- a/app/assets/scripts/components/filters.js +++ b/app/assets/scripts/components/filters.js @@ -13,6 +13,13 @@ var Filters = module.exports = React.createClass({ Router.State ], + getInitialState: function () { + return { + date: 'all', + resolution: 'all' + } + }, + onSearchQuery: function (data) { this.setState(data); }, From 325a0ad2ac0a6b5befcc9e1c86b9d0168aa6248b Mon Sep 17 00:00:00 2001 From: Anand Thakker Date: Tue, 30 Jun 2015 11:36:52 -0400 Subject: [PATCH 30/38] Unselect grid if it no longer has data on filter change. --- app/assets/scripts/components/map.js | 10 ++++++++++ app/assets/scripts/components/results_pane.js | 4 ++-- app/assets/scripts/stores/results_store.js | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app/assets/scripts/components/map.js b/app/assets/scripts/components/map.js index 18572f96..303d71be 100644 --- a/app/assets/scripts/components/map.js +++ b/app/assets/scripts/components/map.js @@ -75,6 +75,16 @@ var Map = React.createClass({ mapData: data, loading: false }); + + var sqrFeature = mapStore.getSelectedSquare(); + if (sqrFeature !== null) { + var intersected = mapStore.getResultsIntersect(sqrFeature); + if (intersected.length > 0) { + actions.resultsChange(intersected); + } else { + actions.mapSquareUnselected(); + } + } }, onSearchQueryChanged: function() { diff --git a/app/assets/scripts/components/results_pane.js b/app/assets/scripts/components/results_pane.js index 5b1ee598..0098558d 100644 --- a/app/assets/scripts/components/results_pane.js +++ b/app/assets/scripts/components/results_pane.js @@ -49,7 +49,7 @@ var ResultsPane = React.createClass({ route = 'item'; params.item_id = data.selectedItem._id } - this.replaceWith(route, params); + this.replaceWith(route, params, this.getQuery()); }, getInitialState: function() { @@ -114,4 +114,4 @@ var ResultsPane = React.createClass({ } }) -module.exports = ResultsPane; \ No newline at end of file +module.exports = ResultsPane; diff --git a/app/assets/scripts/stores/results_store.js b/app/assets/scripts/stores/results_store.js index dd22d54c..040999f7 100644 --- a/app/assets/scripts/stores/results_store.js +++ b/app/assets/scripts/stores/results_store.js @@ -67,4 +67,4 @@ module.exports = Reflux.createStore({ this.trigger(this.storage); }, -}); \ No newline at end of file +}); From cc2e3f98494dee955c6cc5ddc5649199e3303327 Mon Sep 17 00:00:00 2001 From: Anand Thakker Date: Mon, 29 Jun 2015 13:46:18 -0400 Subject: [PATCH 31/38] Add Open in JOSM and Open in iD functionality --- app/assets/scripts/components/results_item.js | 53 +++++++++++++++++-- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/app/assets/scripts/components/results_item.js b/app/assets/scripts/components/results_item.js index 700053ba..294bc1c6 100644 --- a/app/assets/scripts/components/results_item.js +++ b/app/assets/scripts/components/results_item.js @@ -1,6 +1,9 @@ 'use strict'; +var qs = require('querystring'); +var $ = require('jquery'); var React = require('react/addons'); var Keys = require('react-keybinding'); +var Router = require('react-router'); var actions = require('../actions/actions'); var ZcButton = require('./shared/zc_button'); var Dropdown = require('./shared/dropdown'); @@ -9,7 +12,8 @@ var utils = require('../utils/utils'); var ResultsItem = React.createClass({ mixins: [ - Keys + Keys, + Router.State ], keybindings: { @@ -42,6 +46,35 @@ var ResultsItem = React.createClass({ return this.getDOMNode().querySelector('[data-hook="copy:data"]').value; }, + onOpenJosm: function(d) { + var source = 'OpenAerialMap - ' + d.provider + ' - ' + d.uuid; + // Reference: + // http://josm.openstreetmap.de/wiki/Help/Preferences/RemoteControl#load_and_zoom + $.get('http://127.0.0.1:8111/load_and_zoom?' + qs.stringify({ + left: d.bbox[0], + right: d.bbox[2], + bottom: d.bbox[1], + top: d.bbox[3], + source: source + })) + .success(function (data) { + // Reference: + // http://josm.openstreetmap.de/wiki/Help/Preferences/RemoteControl#imagery + // Note: `url` needs to be the last parameter. + $.get('http://127.0.0.1:8111/imagery?' + qs.stringify({ + type: 'tms', + title: source + }) + '&url=' + d.properties.tms) + .success(function () { + // all good! + // TODO: feedback to user. + }); + }) + .fail(function () { + // TODO: message to user, indicating there was an error using JSOM RemoteControl + }); + }, + render: function() { var d = this.props.data; var pagination = this.props.pagination; @@ -51,13 +84,25 @@ var ResultsItem = React.createClass({ var tmsOptions = null; if (d.properties.tms) { + // Generate the iD URL: + // grab centroid of the footprint + var centroid = turf.centroid(d.geojson).geometry.coordinates; + // cheat by using current zoom level + var zoom = this.getParams().map.split(',')[2] + var idUrl = 'http://www.openstreetmap.org/edit' + + '#map=' + [zoom, centroid[1], centroid[0]].join('/') + + '?' + qs.stringify({ + editor: 'id', + background: 'custom:' + d.properties.tms + }); + tmsOptions = (
    -
  • Open with iD editor
  • -
  • Open with JOSM
  • +
  • Open with iD editor
  • +
  • Open with JOSM
  • @@ -111,4 +156,4 @@ var ResultsItem = React.createClass({ } }) -module.exports = ResultsItem; \ No newline at end of file +module.exports = ResultsItem; From 2b2886d80b47ddbb83328bd0dbb183c82e0ad98c Mon Sep 17 00:00:00 2001 From: Anand Thakker Date: Mon, 29 Jun 2015 14:41:32 -0400 Subject: [PATCH 32/38] Add feedback for JOSM actions. --- app/assets/scripts/components/app.js | 2 + .../components/modals/message_modal.js | 52 +++++++++++++++++++ app/assets/scripts/components/results_item.js | 15 ++++-- 3 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 app/assets/scripts/components/modals/message_modal.js diff --git a/app/assets/scripts/components/app.js b/app/assets/scripts/components/app.js index 73e8ab5c..3f1a61b6 100644 --- a/app/assets/scripts/components/app.js +++ b/app/assets/scripts/components/app.js @@ -4,6 +4,7 @@ var Router = require('react-router'); var RouteHandler = Router.RouteHandler; var InfoModal = require('./modals/info_modal'); var WelcomeModal = require('./modals/welcome_modal'); +var MessageModal = require('./modals/message_modal'); var Header = require('./header'); var App = React.createClass({ @@ -16,6 +17,7 @@ var App = React.createClass({ +
); } diff --git a/app/assets/scripts/components/modals/message_modal.js b/app/assets/scripts/components/modals/message_modal.js new file mode 100644 index 00000000..3e1e76d2 --- /dev/null +++ b/app/assets/scripts/components/modals/message_modal.js @@ -0,0 +1,52 @@ +var React = require('react/addons'); +var Reflux = require('reflux'); +var BModal = require('./base_modal'); +var actions = require('../../actions/actions'); + +var MessageModal = React.createClass({ + mixins: [ + Reflux.listenTo(actions.openModal, 'onOpenModal') + ], + + getInitialState: function() { + return { + title: 'Message', + message: '' + } + }, + + onOpenModal: function (which, data) { + if (which === 'message' && data) { + this.setState(data); + } + }, + + getHeader: function () { + return (

{this.state.title}

); + }, + + getBody: function() { + return ( +
+ {this.state.message} +
+ ); + }, + + getFooter: function () { + return false; + }, + + render: function () { + return ( + + ); + } +}); + +module.exports = MessageModal; diff --git a/app/assets/scripts/components/results_item.js b/app/assets/scripts/components/results_item.js index 294bc1c6..d81d6ffb 100644 --- a/app/assets/scripts/components/results_item.js +++ b/app/assets/scripts/components/results_item.js @@ -8,6 +8,7 @@ var actions = require('../actions/actions'); var ZcButton = require('./shared/zc_button'); var Dropdown = require('./shared/dropdown'); var utils = require('../utils/utils'); +var actions = require('../actions/actions'); var ResultsItem = React.createClass({ @@ -47,6 +48,7 @@ var ResultsItem = React.createClass({ }, onOpenJosm: function(d) { + var self = this; var source = 'OpenAerialMap - ' + d.provider + ' - ' + d.uuid; // Reference: // http://josm.openstreetmap.de/wiki/Help/Preferences/RemoteControl#load_and_zoom @@ -67,11 +69,18 @@ var ResultsItem = React.createClass({ }) + '&url=' + d.properties.tms) .success(function () { // all good! - // TODO: feedback to user. + actions.openModal('message', { + title: 'Success', + message: 'This scene has been loaded into JOSM.' + }); }); }) - .fail(function () { - // TODO: message to user, indicating there was an error using JSOM RemoteControl + .fail(function (err) { + console.error(err); + actions.openModal('message', { + title: 'Error', + message:

Could not connect to JOSM via Remote Control. Is JOSM configured to allow remote control?

+ }); }); }, From 4f5491c22825e2e11cae5f071012de775c3e99a3 Mon Sep 17 00:00:00 2001 From: smit1678 Date: Tue, 30 Jun 2015 12:13:47 -0400 Subject: [PATCH 33/38] reorder metadata --- app/assets/scripts/components/results_item.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/scripts/components/results_item.js b/app/assets/scripts/components/results_item.js index 9dea3499..022125a3 100644 --- a/app/assets/scripts/components/results_item.js +++ b/app/assets/scripts/components/results_item.js @@ -142,12 +142,12 @@ var ResultsItem = React.createClass({ Download
-
Type
-
{d.properties.tms ? 'Image + Map Layer' : 'Image'}
Date
{d.acquisition_start.slice(0,10)}
Resolution
{utils.gsdToUnit(d.gsd)}
+
Type
+
{d.properties.tms ? 'Image + Map Layer' : 'Image'}
Image Size
{utils.file_sizeToUnit(d.file_size)}
Platform
From d0f5e4cc2907789f258ed565aa887f580b8565c6 Mon Sep 17 00:00:00 2001 From: smit1678 Date: Tue, 30 Jun 2015 12:58:06 -0400 Subject: [PATCH 34/38] switch to pretty-bytes --- app/assets/scripts/components/results_item.js | 3 ++- app/assets/scripts/utils/utils.js | 19 ------------------- package.json | 1 + 3 files changed, 3 insertions(+), 20 deletions(-) diff --git a/app/assets/scripts/components/results_item.js b/app/assets/scripts/components/results_item.js index 022125a3..7c450231 100644 --- a/app/assets/scripts/components/results_item.js +++ b/app/assets/scripts/components/results_item.js @@ -9,6 +9,7 @@ var ZcButton = require('./shared/zc_button'); var Dropdown = require('./shared/dropdown'); var utils = require('../utils/utils'); var actions = require('../actions/actions'); +var prettyBytes = require('pretty-bytes'); var ResultsItem = React.createClass({ @@ -149,7 +150,7 @@ var ResultsItem = React.createClass({
Type
{d.properties.tms ? 'Image + Map Layer' : 'Image'}
Image Size
-
{utils.file_sizeToUnit(d.file_size)}
+
{prettyBytes(d.file_size)}
Platform
{d.platform}
Sensor
diff --git a/app/assets/scripts/utils/utils.js b/app/assets/scripts/utils/utils.js index 3c084eb7..26ce286d 100644 --- a/app/assets/scripts/utils/utils.js +++ b/app/assets/scripts/utils/utils.js @@ -51,22 +51,3 @@ module.exports.gsdToUnit = function(gsd) { return Math.round(gsd) + ' ' + unit; }; - -/** - * Converts filesize into MB or GB - * @param float file_size in bytes - * @return string - */ - -module.exports.file_sizeToUnit = function(file_size) { - var unit = 'GB'; - // Check size to convert appropriate units to display more nicely - if (file_size < 1000000000) { - unit = 'MB'; - file_size /= 1000000; - } else { - file_size /= 1000000000; - } - - return Math.round(file_size * 10) / 10 + ' ' + unit; -}; diff --git a/package.json b/package.json index 5e820268..abe8c8c8 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "jquery": "^2.1.4", "lodash": "^3.8.0", "mapbox.js": "^2.1.9", + "pretty-bytes": "^2.0.1", "react": "^0.13.3", "react-keybinding": "^2.0.0", "react-router": "^0.13.3", From 5384d72ca8037922db588dd18b5e9330f4165d77 Mon Sep 17 00:00:00 2001 From: Anand Thakker Date: Tue, 30 Jun 2015 15:16:58 -0400 Subject: [PATCH 35/38] Add filter for data type (map layer vs image only) Closes #80 --- app/assets/scripts/actions/actions.js | 5 +++ app/assets/scripts/components/app.js | 3 ++ app/assets/scripts/components/filters.js | 40 +++++++++++-------- app/assets/scripts/stores/map_store.js | 4 +- .../scripts/stores/search_query_store.js | 5 +++ 5 files changed, 40 insertions(+), 17 deletions(-) diff --git a/app/assets/scripts/actions/actions.js b/app/assets/scripts/actions/actions.js index ca5f04eb..d752b673 100644 --- a/app/assets/scripts/actions/actions.js +++ b/app/assets/scripts/actions/actions.js @@ -22,6 +22,11 @@ module.exports = Reflux.createActions({ return [ 'all', 'low', 'medium', 'high' ].indexOf(val) >= 0; } }, + 'setDataTypeFilter': { + shouldEmit: function (val) { + return [ 'all', 'service' ].indexOf(val) >= 0; + } + }, // Results pane related actions. 'resultItemSelect': {}, diff --git a/app/assets/scripts/components/app.js b/app/assets/scripts/components/app.js index 2bfda252..4907424c 100644 --- a/app/assets/scripts/components/app.js +++ b/app/assets/scripts/components/app.js @@ -26,6 +26,9 @@ var App = React.createClass({ if (params.resolution) { actions.setResolutionFilter(params.resolution); } + if (params.type) { + actions.setDataTypeFilter(params.type); + } }, render: function() { diff --git a/app/assets/scripts/components/filters.js b/app/assets/scripts/components/filters.js index 44b6e40a..07a58a14 100644 --- a/app/assets/scripts/components/filters.js +++ b/app/assets/scripts/components/filters.js @@ -34,6 +34,11 @@ var Filters = module.exports = React.createClass({ this._updateUrl('resolution', d.key) }, + setDataType: function (d) { + actions.setDataTypeFilter(d.key); + this._updateUrl('type', d.key); + }, + _updateUrl: function (prop, value) { var query = this.getQuery(); if (value === 'all') { @@ -45,34 +50,35 @@ var Filters = module.exports = React.createClass({ this.replaceWith(routes[routes.length - 1].name, this.getParams(), query); }, + render: function() { + function filterItem (property, clickHandler, d) { + var klass = this.state[property] === d.key ? 'active' : ''; + var click = clickHandler.bind(this, d); + return ( +
+ {d.title} +
); + } + var dates = [ {key: 'all', title: 'All'}, {key: 'week', title: 'Last week'}, {key: 'month', title: 'Last month'}, {key: 'year', title: 'Last year'} - ].map(function (d) { - var klass = this.state.date === d.key ? 'active' : ''; - var click = this.setDate.bind(this, d); - return ( -
- {d.title} -
); - }.bind(this)); + ].map(filterItem.bind(this, 'date', this.setDate)); var resolutions = [ {key: 'all', title: 'All'}, {key: 'low', title: 'Low'}, {key: 'medium', title: 'Medium'}, {key: 'high', title: 'High'} - ].map(function (d) { - var klass = this.state.resolution === d.key ? 'active' : ''; - var click = this.setResolution.bind(this, d); - return ( -
- {d.title} -
); - }.bind(this)); + ].map(filterItem.bind(this, 'resolution', this.setResolution)); + + var dataTypes = [ + {key: 'all', title: 'All Images'}, + {key: 'service', title: 'Image + Map Layer'} + ].map(filterItem.bind(this, 'dataType', this.setDataType)); return ( @@ -81,6 +87,8 @@ var Filters = module.exports = React.createClass({ {dates}
Resolution
{resolutions} +
Data Type
+ {dataTypes}
); diff --git a/app/assets/scripts/stores/map_store.js b/app/assets/scripts/stores/map_store.js index 9b48e4ca..f2e18d41 100644 --- a/app/assets/scripts/stores/map_store.js +++ b/app/assets/scripts/stores/map_store.js @@ -70,10 +70,12 @@ module.exports = Reflux.createStore({ ].join('-') } + var typeFilter = parameters.dataType === 'all' ? {} : { has_tiled: true }; + var params = _.assign({ limit: 4000, bbox: parameters.bbox, - }, resolutionFilter, dateFilter); + }, resolutionFilter, dateFilter, typeFilter); console.log('search:', params); diff --git a/app/assets/scripts/stores/search_query_store.js b/app/assets/scripts/stores/search_query_store.js index 735cba75..a6ea66b2 100644 --- a/app/assets/scripts/stores/search_query_store.js +++ b/app/assets/scripts/stores/search_query_store.js @@ -18,6 +18,7 @@ module.exports = Reflux.createStore({ this.listenTo(actions.mapMove, this.onMapMove); this.listenTo(actions.setDateFilter, this.onSetDateFilter); this.listenTo(actions.setResolutionFilter, this.onSetResolutionFilter); + this.listenTo(actions.setDataTypeFilter, this.onSetDataTypeFilter); }, onMapMove: function(map) { @@ -39,6 +40,10 @@ module.exports = Reflux.createStore({ this._setParameter({resolution: resolutionLevel}); }, + onSetDataTypeFilter: function(type) { + this._setParameter({dataType: type}); + }, + _setParameter: function(params) { // update stored search params _.assign(this._parameters, params); From 08db89f9d9b43f7e3961ffac0ff211811f941af3 Mon Sep 17 00:00:00 2001 From: Anand Thakker Date: Tue, 30 Jun 2015 16:43:25 -0400 Subject: [PATCH 36/38] Debounce map movement handler --- app/assets/scripts/components/map.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/assets/scripts/components/map.js b/app/assets/scripts/components/map.js index 303d71be..eac7dc58 100644 --- a/app/assets/scripts/components/map.js +++ b/app/assets/scripts/components/map.js @@ -3,6 +3,7 @@ require('mapbox.js'); var React = require('react/addons'); var Reflux = require('reflux'); var Router = require('react-router'); +var _ = require('lodash'); var overlaps = require('turf-overlaps'); // Not working. Using cdn. (turf.intersect was throwing a weird error) //var turf = require('turf'); @@ -483,7 +484,7 @@ var Map = React.createClass({ // Map move listener. - this.map.on('moveend', function() { + var onMoveEnd = _.debounce(function() { // Compute new map location for the path . var mapLocation = _this.mapViewToString(); // Preserve other params if any. @@ -502,7 +503,9 @@ var Map = React.createClass({ actions.mapMove(_this.map); _this.updateFauxGrid(); - }); + }, 300) + this.map.on('moveend', onMoveEnd); + this.map.on('movestart', onMoveEnd.cancel); // Create fauxGrid. this.updateFauxGrid(); From 14e0c0dcc5903ec2df806946a3740ddcfa2e205f Mon Sep 17 00:00:00 2001 From: Anand Thakker Date: Wed, 1 Jul 2015 07:35:27 -0400 Subject: [PATCH 37/38] Set initial state of data type filter. Fixes #98 --- app/assets/scripts/components/filters.js | 3 ++- app/assets/scripts/stores/search_query_store.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/assets/scripts/components/filters.js b/app/assets/scripts/components/filters.js index 07a58a14..0644e41b 100644 --- a/app/assets/scripts/components/filters.js +++ b/app/assets/scripts/components/filters.js @@ -16,7 +16,8 @@ var Filters = module.exports = React.createClass({ getInitialState: function () { return { date: 'all', - resolution: 'all' + resolution: 'all', + dataType: 'all' } }, diff --git a/app/assets/scripts/stores/search_query_store.js b/app/assets/scripts/stores/search_query_store.js index a6ea66b2..574362d4 100644 --- a/app/assets/scripts/stores/search_query_store.js +++ b/app/assets/scripts/stores/search_query_store.js @@ -11,7 +11,8 @@ var _ = require('lodash'); module.exports = Reflux.createStore({ _parameters: { date: 'all', - resolution: 'all' + resolution: 'all', + dataType: 'all' }, init: function () { From f802818be7850193cfecf53862c603f794981490 Mon Sep 17 00:00:00 2001 From: smit1678 Date: Mon, 6 Jul 2015 18:19:28 -0400 Subject: [PATCH 38/38] add hot mapbox account --- app/assets/scripts/components/map.js | 2 +- app/assets/scripts/config.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/scripts/components/map.js b/app/assets/scripts/components/map.js index eac7dc58..39aac0fe 100644 --- a/app/assets/scripts/components/map.js +++ b/app/assets/scripts/components/map.js @@ -15,7 +15,7 @@ var utils = require('../utils/utils'); var dsZoom = require('../utils/ds_zoom'); var config = require('../config.js'); -L.mapbox.accessToken = 'pk.eyJ1IjoiZGV2c2VlZCIsImEiOiJnUi1mbkVvIn0.018aLhX0Mb0tdtaT2QNe2Q'; +L.mapbox.accessToken = 'pk.eyJ1IjoiaG90IiwiYSI6IjU3MjE1YTYxZGM2YmUwMDIxOTg2OGZmNWU0NzRlYTQ0In0.MhK7SIwO00rhs3yMudBfIw'; var Map = React.createClass({ // Connect to the store "mapStore". Whenever the store calls "this.trigger()" diff --git a/app/assets/scripts/config.js b/app/assets/scripts/config.js index c1ce8616..3fa5c82e 100644 --- a/app/assets/scripts/config.js +++ b/app/assets/scripts/config.js @@ -2,7 +2,7 @@ module.exports = { map: { - baseLayer: 'devseed.m9i692do', + baseLayer: 'hot.ml5mgnm7', initialZoom: 8, minZoom: 8,