diff --git a/css/app.css b/css/app.css index 62b5afaea..cf466dada 100644 --- a/css/app.css +++ b/css/app.css @@ -756,6 +756,7 @@ a:hover .icon.out-link { background-position: -500px -14px;} width:100%; } +.geocode-item, .feature-list-item { width: 100%; position: relative; @@ -1949,48 +1950,6 @@ img.wiki-image { display:inline-block; } -/* Geocoder */ - -.geocode-control { - position: relative; -} - -.geocode-control form { - position: absolute; - top: 0; - padding: 5px; -} - -.geocode-control input { - width: 100%; -} - -.geocode-control div.map-overlay { - position: absolute; - top: 40px; - left: -260px; - border-top: 1px solid #CCC; - z-index: 100; - max-height: 240px; - overflow-y: auto; - padding: 0; -} - -.geocode-control div.map-overlay span { - display: inline-block; - border-bottom: 1px solid #CCC; - padding: 5px 10px; - width: 100%; -} -.geocode-control div.map-overlay span.not-found { - line-height: 28px; - width: 100%; -} - -.geocode-control a:focus { - text-decoration: underline; -} - /* Geolocator */ .geolocate-control { diff --git a/data/core.yaml b/data/core.yaml index 377d3f806..a530e3f57 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -164,9 +164,8 @@ en: list: "Edits by {users}" truncated_list: "Edits by {users} and {count} others" geocoder: - title: Find a place - placeholder: Find a place - no_results: "Couldn't locate a place named '{name}'" + search: Continue search on server... + no_results: No results found geolocate: title: Show My Location inspector: diff --git a/dist/locales/en.json b/dist/locales/en.json index aa0587a9b..5c2f41949 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -205,9 +205,8 @@ "truncated_list": "Edits by {users} and {count} others" }, "geocoder": { - "title": "Find a place", - "placeholder": "Find a place", - "no_results": "Couldn't locate a place named '{name}'" + "search": "Continue search on server...", + "no_results": "No results found" }, "geolocate": { "title": "Show My Location" diff --git a/index.html b/index.html index de2f1f51a..6fc21254d 100644 --- a/index.html +++ b/index.html @@ -75,7 +75,6 @@ - diff --git a/js/id/behavior/hash.js b/js/id/behavior/hash.js index 8004287f4..7fb24eeb4 100644 --- a/js/id/behavior/hash.js +++ b/js/id/behavior/hash.js @@ -37,33 +37,6 @@ iD.behavior.Hash = function(context) { } } - // the hash can declare that the map should select a feature, but it can - // do so before any features are loaded. thus wait for the feature to - // be loaded and then select - function willselect(id, hasMap) { - if (!hasMap) { - context.connection().loadEntity(id, function(error, entity) { - if (entity) { - context.map().zoomTo(entity); - } - }); - } - - context.map().on('drawn.hash', function() { - if (!context.hasEntity(id)) return; - selectoff(); - context.enter(iD.modes.Select(context, [id])); - }); - - context.on('enter.hash', function() { - if (context.mode().id !== 'browse') selectoff(); - }); - } - - function selectoff() { - context.map().on('drawn.hash', null); - } - function hash() { context.map() .on('move.hash', move); @@ -73,7 +46,7 @@ iD.behavior.Hash = function(context) { if (location.hash) { var q = iD.util.stringQs(location.hash.substring(1)); - if (q.id) willselect(q.id, q.map); + if (q.id) context.loadEntity(q.id, !q.map); hashchange(); if (q.map) hash.hadHash = true; } diff --git a/js/id/id.js b/js/id/id.js index 851257329..301ef2dbb 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -119,6 +119,30 @@ window.iD = function () { } }; + context.loadEntity = function(id, zoomTo) { + if (zoomTo !== false) { + connection.loadEntity(id, function(error, entity) { + if (entity) { + map.zoomTo(entity); + } + }); + } + + map.on('drawn.loadEntity', function() { + if (!context.hasEntity(id)) return; + map.on('drawn.loadEntity', null); + context.on('enter.loadEntity', null); + context.enter(iD.modes.Select(context, [id])); + }); + + context.on('enter.loadEntity', function() { + if (mode.id !== 'browse') { + map.on('drawn.loadEntity', null); + context.on('enter.loadEntity', null); + } + }); + }; + context.editable = function() { return map.editable() && mode && mode.id !== 'save'; }; diff --git a/js/id/ui.js b/js/id/ui.js index a908fa3ee..b647e7fc2 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -75,12 +75,6 @@ iD.ui = function(context) { .attr('class', 'map-control background-control') .call(iD.ui.Background(context)); - if (!context.embed()) { - controls.append('div') - .attr('class', 'map-control geocode-control') - .call(iD.ui.Geocoder(context)); - } - controls.append('div') .attr('class', 'map-control help-control') .call(iD.ui.Help(context)); diff --git a/js/id/ui/feature_list.js b/js/id/ui/feature_list.js index d25376171..b4c22c2f4 100644 --- a/js/id/ui/feature_list.js +++ b/js/id/ui/feature_list.js @@ -1,4 +1,6 @@ iD.ui.FeatureList = function(context) { + var geocodeResults; + function featureList(selection) { var header = selection.append('div') .attr('class', 'header fillL cf'); @@ -14,6 +16,7 @@ iD.ui.FeatureList = function(context) { } function inputevent() { + geocodeResults = undefined; drawList(); } @@ -61,9 +64,10 @@ iD.ui.FeatureList = function(context) { var name = iD.util.displayName(entity) || ''; if (name.toLowerCase().indexOf(q) >= 0) { result.push({ + id: entity.id, entity: entity, geometry: context.geometry(entity.id), - preset: context.presets().match(entity, graph), + type: context.presets().match(entity, graph).name(), name: name }); } @@ -78,22 +82,56 @@ iD.ui.FeatureList = function(context) { addEntity(visible[i].__data__); } + (geocodeResults || []).forEach(function(d) { + result.push({ + id: iD.Entity.id.fromOSM(d.osm_type, d.osm_id), + geometry: d.osm_type === 'relation' ? 'relation' : d.osm_type === 'way' ? 'line' : 'point', + type: (d.type.charAt(0).toUpperCase() + d.type.slice(1)).replace('_', ' '), + name: d.display_name, + extent: new iD.geo.Extent( + [parseFloat(d.boundingbox[3]), parseFloat(d.boundingbox[0])], + [parseFloat(d.boundingbox[2]), parseFloat(d.boundingbox[1])]) + }) + }); + return result; } function drawList() { - list.classed('filtered', search.property('value').length); + var value = search.property('value'); + + list.classed('filtered', value.length); + + var geocodeButton = list.selectAll('.geocode-item') + .data([0]) + .enter().append('button') + .attr('class', 'geocode-item') + .on('click', geocode); + + var label = geocodeButton.append('div') + .attr('class', 'label'); + + label.append('span') + .attr('class', 'entity-name'); + + var noResults = geocodeResults && geocodeResults.length === 0; + + list.selectAll('.geocode-item') + .style('display', noResults || (value && geocodeResults === undefined) ? 'block' : 'none') + .property('disabled', noResults) + .selectAll('.entity-name') + .text(noResults ? t('geocoder.no_results') : t('geocoder.search')); var items = list.selectAll('.feature-list-item') - .data(features(), function(d) { return d.entity.id; }); + .data(features(), function(d) { return d.id; }); - var enter = items.enter().append('button') + var enter = items.enter().insert('button', '.geocode-item') .attr('class', 'feature-list-item') - .on('mouseover', function(d) { mouseover(d.entity); }) - .on('mouseout', function(d) { mouseout(); }) - .on('click', function(d) { click(d.entity); }); + .on('mouseover', mouseover) + .on('mouseout', mouseout) + .on('click', click); - var label = enter.append('div') + label = enter.append('div') .attr('class', 'label'); label.append('span') @@ -101,7 +139,7 @@ iD.ui.FeatureList = function(context) { label.append('span') .attr('class', 'entity-type') - .text(function(d) { return d.preset.name(); }); + .text(function(d) { return d.type; }); label.append('span') .attr('class', 'entity-name') @@ -117,8 +155,8 @@ iD.ui.FeatureList = function(context) { .remove(); } - function mouseover(entity) { - context.surface().selectAll(iD.util.entityOrMemberSelector([entity.id], context.graph())) + function mouseover(d) { + context.surface().selectAll(iD.util.entityOrMemberSelector([d.id], context.graph())) .classed('hover', true); } @@ -127,8 +165,20 @@ iD.ui.FeatureList = function(context) { .classed('hover', false); } - function click(entity) { - context.enter(iD.modes.Select(context, [entity.id])); + function click(d) { + if (d.entity) { + context.enter(iD.modes.Select(context, [d.entity.id])); + } else { + context.loadEntity(d.id); + } + } + + function geocode() { + var searchVal = encodeURIComponent(search.property('value')); + d3.json('http://nominatim.openstreetmap.org/search/' + searchVal + '?limit=10&format=json', function(err, resp) { + geocodeResults = resp || []; + drawList(); + }); } } diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js deleted file mode 100644 index d837f111e..000000000 --- a/js/id/ui/geocoder.js +++ /dev/null @@ -1,171 +0,0 @@ -iD.ui.Geocoder = function(context) { - - var key = 'f'; - - function resultExtent(bounds) { - return new iD.geo.Extent( - [parseFloat(bounds[3]), parseFloat(bounds[0])], - [parseFloat(bounds[2]), parseFloat(bounds[1])]); - } - - function truncate(d) { - if (d.display_name.length > 80) { - return d.display_name.substr(0, 80) + '…'; - } else { - return d.display_name; - } - } - - function geocoder(selection) { - - var shown = false; - - function keydown() { - if (d3.event.keyCode !== 13) return; - d3.event.preventDefault(); - var searchVal = this.value; - inputNode.classed('loading', true); - d3.json('http://nominatim.openstreetmap.org/search/' + - encodeURIComponent(searchVal) + '?limit=10&format=json', function(err, resp) { - inputNode.classed('loading', false); - if (err) return hide(); - if (!resp.length) { - resultsList.html('') - .call(iD.ui.Toggle(true)) - .append('span') - .attr('class', 'not-found') - .text(t('geocoder.no_results', { name: searchVal })); - } else if (resp.length > 1) { - var spans = resultsList.html('').selectAll('span') - .data(resp, function(d) { return d.place_id; }); - - spans.enter() - .append('span') - .text(function(d) { - return d.type.charAt(0).toUpperCase() + d.type.slice(1) + ': '; - }) - .append('a') - .attr('tabindex', 1) - .text(truncate) - .on('click', clickResult) - .on('keydown', function(d) { - // support tabbing to and accepting this - // entry - if (d3.event.keyCode == 13) clickResult(d); - }); - spans.exit().remove(); - resultsList.call(iD.ui.Toggle(true)); - } else { - hide(); - applyBounds(resultExtent(resp[0].boundingbox)); - selectId(resp[0].osm_type, resp[0].osm_id); - } - }); - } - - function clickResult(d) { - selectId(d.osm_type, d.osm_id); - applyBounds(resultExtent(d.boundingbox)); - } - - function applyBounds(extent) { - var map = context.map(); - map.extent(extent); - if (map.zoom() > 19) map.zoom(19); - } - - function selectId(type, id) { - id = type[0] + id; - - if (context.hasEntity(id)) { - context.enter(iD.modes.Select(context, [id])); - } else { - context.map().on('drawn.geocoder', function() { - if (!context.hasEntity(id)) return; - context.map().on('drawn.geocoder', null); - context.enter(iD.modes.Select(context, [id])); - }); - } - } - - var tooltip = bootstrap.tooltip() - .placement('right') - .html(true) - .title(iD.ui.tooltipHtml(t('geocoder.title'), key)); - - var gcForm = selection.append('form'); - - var inputNode = gcForm.attr('class', 'fillL map-overlay content hide') - .append('input') - .attr({ type: 'text', placeholder: t('geocoder.placeholder') }) - .attr('tabindex', 1) - .on('keydown', keydown); - - var resultsList = selection.append('div') - .attr('class', 'fillL map-overlay hide'); - - var keybinding = d3.keybinding('geocoder'); - - function hide() { setVisible(false); } - function toggle() { - if (d3.event) d3.event.preventDefault(); - tooltip.hide(button); - setVisible(!button.classed('active')); - } - - function setVisible(show) { - if (show !== shown) { - button.classed('active', show); - shown = show; - - if (!show && !resultsList.classed('hide')) { - resultsList.call(iD.ui.Toggle(show)); - // remove results so that they lose focus. if the user has - // tabbed into the list, then they will have focus still, - // even if they're hidden. - resultsList.selectAll('span').remove(); - } - - if (show) { - selection.on('mousedown.geocoder-inside', function() { - return d3.event.stopPropagation(); - }); - gcForm.style('display', 'block') - .style('left', '0px') - .transition() - .duration(200) - .style('left', '-260px'); - inputNode.node().focus(); - } else { - selection.on('mousedown.geocoder-inside', null); - gcForm.style('display', 'block') - .style('left', '-260px') - .transition() - .duration(200) - .style('left', '0px') - .each('end', function() { - d3.select(this).style('display', 'none'); - }); - inputNode.node().blur(); - } - } - } - var button = selection.append('button') - .attr('tabindex', -1) - .on('click', toggle) - .call(tooltip); - - button.append('span') - .attr('class', 'icon geocode'); - - keybinding.on(key, toggle); - - d3.select(document) - .call(keybinding); - - context.surface().on('mousedown.geocoder-outside', hide); - context.container().on('mousedown.b.geocoder-outside', hide); - - } - return geocoder; -}; diff --git a/test/index.html b/test/index.html index ef21d8ba2..5f7be8643 100644 --- a/test/index.html +++ b/test/index.html @@ -75,7 +75,6 @@ - @@ -245,7 +244,6 @@ - diff --git a/test/index_packaged.html b/test/index_packaged.html index 309bf84dc..793cad825 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -75,7 +75,6 @@ - diff --git a/test/spec/ui/geocoder.js b/test/spec/ui/geocoder.js deleted file mode 100644 index 91e107ee2..000000000 --- a/test/spec/ui/geocoder.js +++ /dev/null @@ -1,6 +0,0 @@ -describe("iD.ui.Geocoder", function () { - it('can be instantiated', function () { - var geocoder = iD.ui.Geocoder(); - expect(geocoder).to.be.ok; - }); -});