diff --git a/Makefile b/Makefile
index 9c7e33783..125957615 100644
--- a/Makefile
+++ b/Makefile
@@ -17,6 +17,7 @@ all: \
js/lib/d3.keybinding.js \
js/lib/d3.one.js \
js/lib/d3.size.js \
+ js/lib/d3.trigger.js \
js/lib/d3.typeahead.js \
js/lib/jxon.js \
js/lib/lodash.js \
diff --git a/css/app.css b/css/app.css
index 8e4c019f3..e7f5d9466 100644
--- a/css/app.css
+++ b/css/app.css
@@ -291,6 +291,25 @@ button.delete:hover {
button.save {
background-color: #6bc641;
+ width: 120px;
+ position: relative;
+}
+
+button.save.has-count {
+ padding-right:10px;
+}
+
+button.save.has-count .count {
+ display: block;
+ position: absolute;
+ font-size:small;
+ top: 0;
+ right: 0;
+ background: #fff;
+ height: 37px;
+ border-radius: 0 3px 3px 0;
+ padding: 0 10px;
+ line-height: 40px;
}
button.save:hover {
@@ -452,7 +471,7 @@ button.Browse .label {
position:relative;
}
-.inspector-wrap a.permalink {
+.inspector-inner.head a {
text-decoration:none;
margin-right: 10px;
display: inline-block
@@ -489,7 +508,7 @@ button.Browse .label {
.tag-row input {
width: 50%;
- border-right: 0;
+ border-left: 0;
}
.tag-row input.key {
@@ -500,9 +519,8 @@ button.Browse .label {
border-top: 1px solid #ccc;
}
-.tag-row input.value {
- position: relative;
- border-right: 1px solid #ccc;
+.tag-row input.key {
+ border-left: 1px solid #ccc;
}
.input-wrap::after {
@@ -531,8 +549,17 @@ button.Browse .label {
border-top: 1px solid #ccc;
}
-.tag-row-empty button {
- display: none;
+.inspector-inner .add-tag-row {
+ width: 100%;
+ padding-right: 70px;
+}
+
+.inspector-inner .add-tag {
+ width: 50%;
+ height: 30px;
+ font-size: 100%;
+ border: 1px solid #ccc;
+ border-top: 0;
}
/* Map Controls */
@@ -572,6 +599,30 @@ button.Browse .label {
top:210px;
}
+.layerswitcher-control .adjustments {
+ padding:5px;
+ opacity:0.2;
+}
+
+.layerswitcher-control .adjustments:hover {
+ opacity:1;
+}
+
+.layerswitcher-control .adjustments .reset {
+ height:20px;
+ font-size:10px;
+ font-weight:normal;
+ padding:0 5px;
+}
+
+.layerswitcher-control .nudge {
+ height:20px;
+ width:20px;
+ font-size:10px;
+ margin-right:2px;
+ font-weight:normal;
+}
+
.opacity-options-wrapper {
padding: 10px 10px 0 10px;
}
@@ -630,6 +681,10 @@ button.Browse .label {
margin: 4px;
}
+.geolocate-control {
+ top:260px;
+}
+
/* Map
------------------------------------------------------- */
@@ -709,8 +764,8 @@ div.typeahead {
box-shadow: 0 5px 10px 0 rgba(0,0,0,.2);
margin-top: -1px;
background: white;
- max-height: 120px;
- overflow: auto;
+ max-height: 180px;
+ overflow: hidden;
border: 1px solid #ccc;
}
@@ -722,6 +777,7 @@ div.typeahead a {
border-top:1px solid #ccc;
background-color: #fff;
padding:1px 4px;
+ white-space: nowrap;
}
div.typeahead a:hover,
@@ -801,6 +857,14 @@ div.typeahead a:first-child {
padding:5px 10px;
}
+.changeset-list li span.count {
+ font-size:10px;
+ color:#555;
+}
+
+.changeset-list li span.count:before { content: '('; }
+.changeset-list li span.count:after { content: ')'; }
+
.changeset-list li:first-child { border-top: 0;}
.commit-modal .changeset-comment {
@@ -823,6 +887,7 @@ div.typeahead a:first-child {
left:70px;
width:250px;
height:50px;
+ padding:10px;
background:#fff;
font-size: 20px;
font-weight: bold;
diff --git a/css/map.css b/css/map.css
index c8f8c676b..c093db4e4 100644
--- a/css/map.css
+++ b/css/map.css
@@ -10,30 +10,51 @@ g.point circle {
fill:#fff;
}
-g.point.hover circle,
-g.point.selected circle {
- fill:#ffff00;
- stroke-width:4;
- stroke:#fff
+g.point.hover circle.stroke,
+g.point.selected circle.stroke {
+ fill:#333;
+ -webkit-transform:scale(1.2, 1.2);
+ -moz-transform:scale(1.2, 1.2);
+ transform:scale(1.2, 1.2);
}
/* interactive elements */
-circle.vertex {
+g.vertex circle.fill {
fill:white;
- stroke:#333;
fill-opacity:1;
- stroke-width:2;
- stroke-opacity: 1;
}
-circle.vertex.shared {
+circle.stroke,
+circle.fill {
+ -webkit-transition: -webkit-transform 50ms linear;
+ transition: transform 50ms linear;
+ -moz-transition: stroke 50ms linear;
+ -webkit-transform:scale(1, 1);
+ -moz-transform:scale(1, 1);
+ transform:scale(1, 1);
+}
+
+g.vertex circle.stroke {
+ fill:#333;
+}
+
+g.vertex.shared circle {
fill:#aff;
}
-circle.vertex.hover {
+g.vertex.hover circle.fill {
+ -webkit-transform:scale(2, 2);
+ -moz-transform:scale(2, 2);
+ transform:scale(2, 2);
}
-circle.vertex.selected {
+g.vertex.hover circle.stroke {
+ -webkit-transform:scale(1.8, 1.8);
+ -moz-transform:scale(1.8, 1.8);
+ transform:scale(1.8, 1.8);
+}
+
+g.vertex circle.selected {
fill: #ffff00;
}
@@ -60,12 +81,12 @@ path.casing {
path.casing.hover {
stroke:#FF0F0F !important;
- opacity:0.8;
+ stroke-opacity:0.8;
}
path.casing.selected {
stroke:#E96666 !important;
- opacity:1 !important;
+ stroke-opacity:1 !important;
stroke-width:10 !important;
}
@@ -134,7 +155,7 @@ path.stroke.highway-residential {
path.casing.highway-residential {
stroke:#E8E8E8;
stroke-width:10;
- opacity:0.4;
+ stroke-opacity:0.4;
}
path.stroke.highway-unclassified,
@@ -179,12 +200,12 @@ path.stroke.highway-motorway, path.stroke.highway-motorway_link {
path.casing.highway-motorway, path.casing.highway-motorway_link {
stroke:#809BC0;
stroke-width:9;
- opacity:0.4;
+ stroke-opacity:0.4;
}
path.stroke.highway-trunk, path.stroke.highway-trunk_link {
stroke-width:7;
- opacity:0.4;
+ stroke-opacity:0.4;
stroke:#7FC97F;
}
path.casing.highway-trunk, path.casing.highway-trunk_link {
@@ -198,7 +219,7 @@ path.stroke.highway-primary, path.stroke.highway-primary_link {
}
path.casing.highway-primary, path.casing.highway-primary_link {
stroke:#FF6363;
- opacity:0.4;
+ stroke-opacity:0.4;
stroke-width:12;
}
@@ -207,7 +228,7 @@ path.stroke.highway-secondary, path.stroke.highway-secondary_link {
stroke-width:4;
}
path.casing.highway-secondary, path.casing.highway-secondary_link {
- opacity:0.4;
+ stroke-opacity:0.4;
stroke:#FDBF6F;
stroke-width:11;
}
diff --git a/index.html b/index.html
index 676463cfc..b0153fd9a 100644
--- a/index.html
+++ b/index.html
@@ -20,6 +20,7 @@
+
@@ -39,6 +40,7 @@
+
diff --git a/js/id/connection.js b/js/id/connection.js
index 9ccbb5255..a3cd28fd9 100644
--- a/js/id/connection.js
+++ b/js/id/connection.js
@@ -14,17 +14,19 @@ iD.Connection = function() {
}
function bboxFromAPI(box, tile, callback) {
- loadFromURL(bboxUrl(box), function(err, parsed) {
+ function done(err, parsed) {
loadedTiles[tile.toString()] = true;
callback(err, parsed);
- });
+ }
+ loadFromURL(bboxUrl(box), done);
}
function loadFromURL(url, callback) {
+ function done(dom) {
+ return callback(null, parse(dom));
+ }
inflight.push(d3.xml(url).get()
- .on('load', function(dom) {
- return callback(null, parse(dom));
- }));
+ .on('load', done));
}
function getNodes(obj) {
@@ -67,9 +69,7 @@ iD.Connection = function() {
tags: getTags(obj)
};
for (var i = 0, l = obj.attributes.length; i < l; i++) {
- var n = obj.attributes[i].nodeName;
- var v = obj.attributes[i].nodeValue;
- o[n] = v;
+ o[obj.attributes[i].nodeName] = obj.attributes[i].nodeValue;
}
if (o.lon && o.lat) {
o.loc = [parseFloat(o.lon), parseFloat(o.lat)];
@@ -129,13 +129,14 @@ iD.Connection = function() {
};
function userDetails(callback) {
- oauth.xhr({ method: 'GET', path: '/api/0.6/user/details' }, function(err, user_details) {
+ function done(err, user_details) {
var u = user_details.getElementsByTagName('user')[0];
callback(connection.user({
display_name: u.attributes.display_name.nodeValue,
id: u.attributes.id.nodeValue
}).user());
- });
+ }
+ oauth.xhr({ method: 'GET', path: '/api/0.6/user/details' }, done);
}
function tileAlreadyLoaded(c) { return !loadedTiles[c.toString()]; }
@@ -143,9 +144,10 @@ iD.Connection = function() {
function abortRequest(i) { i.abort(); }
function loadTile(e) {
- bboxFromAPI(e.box, e.tile, function(err, g) {
+ function done(err, g) {
event.load(err, g);
- });
+ }
+ bboxFromAPI(e.box, e.tile, done);
}
function loadTiles(projection) {
@@ -211,10 +213,11 @@ iD.Connection = function() {
};
connection.authenticate = function(callback) {
- return oauth.authenticate(function(err, res) {
+ function done(err, res) {
event.auth();
if (callback) callback(err, res);
- });
+ }
+ return oauth.authenticate(done);
};
connection.bboxFromAPI = bboxFromAPI;
diff --git a/js/id/graph/entity.js b/js/id/graph/entity.js
index c94dcb2fe..58e454805 100644
--- a/js/id/graph/entity.js
+++ b/js/id/graph/entity.js
@@ -84,6 +84,28 @@ iD.Entity.prototype = {
key != 'odbl' &&
key.indexOf('tiger:') !== 0;
});
+ },
+
+ friendlyName: function() {
+ // Generate a string such as 'river' or 'Fred's House' for an entity.
+ if (!this.tags || !Object.keys(this.tags).length) { return ''; }
+
+ var mainkeys = ['highway','amenity','railway','waterway','natural'],
+ n = [];
+
+ if (this.tags.name) n.push(this.tags.name);
+ if (this.tags.ref) n.push(this.tags.ref);
+
+ if (!n.length) {
+ for (var k in this.tags) {
+ if (mainkeys.indexOf(k) !== -1) {
+ n.push(this.tags[k]);
+ break;
+ }
+ }
+ }
+
+ return n.length === 0 ? 'unknown' : n.join('; ');
}
};
diff --git a/js/id/graph/node.js b/js/id/graph/node.js
index 3a29cd8e6..9500991fa 100644
--- a/js/id/graph/node.js
+++ b/js/id/graph/node.js
@@ -3,5 +3,9 @@ iD.Node = iD.Entity.extend({
extent: function() {
return [this.loc, this.loc];
+ },
+
+ geometry: function() {
+ return this._poi ? 'point' : 'vertex';
}
});
diff --git a/js/id/graph/way.js b/js/id/graph/way.js
index a62afb8e1..77858d29a 100644
--- a/js/id/graph/way.js
+++ b/js/id/graph/way.js
@@ -36,5 +36,9 @@ iD.Way = iD.Entity.extend({
this.tags.area !== 'no' &&
!this.tags.highway &&
!this.tags.barrier);
+ },
+
+ geometry: function() {
+ return this.isArea() ? 'area' : 'line';
}
});
diff --git a/js/id/id.js b/js/id/id.js
index 914567cf2..3aa6802f3 100644
--- a/js/id/id.js
+++ b/js/id/id.js
@@ -107,10 +107,11 @@ window.iD = function(container) {
.append('div')
.attr('class', 'hello');
- bar.append('button')
+ var save_button = bar.append('button')
.attr('class', 'save action wide')
.html("Save")
.attr('title', 'Save changes to OpenStreetMap, making them visible to other users')
+ .property('disabled', true)
.call(bootstrap.tooltip()
.placement('bottom'))
.on('click', function() {
@@ -121,6 +122,17 @@ window.iD = function(container) {
l.remove();
history.reset();
map.flush().redraw();
+ var modal = iD.modal();
+ modal.select('.content')
+ .classed('success-modal', true)
+ .datum({
+ id: changeset_id,
+ comment: e.comment
+ })
+ .call(iD.success()
+ .on('cancel', function() {
+ modal.remove();
+ }));
});
}
var changes = history.changes();
@@ -146,6 +158,24 @@ window.iD = function(container) {
}
});
+ save_button.append('span')
+ .attr('class', 'count');
+
+ history.on('change.save-button', function() {
+ var changes = history.changes(),
+ num_changes = d3.sum(d3.values(changes).map(function(c) {
+ return c.length;
+ }));
+
+ save_button.property('disabled', num_changes === 0);
+
+ save_button
+ .classed('has-count', num_changes > 0);
+
+ save_button.select('span.count')
+ .text(num_changes);
+ });
+
bar.append('div')
.attr('class', 'messages');
@@ -160,6 +190,21 @@ window.iD = function(container) {
return d[0] + ' icon';
});
+ function geolocateSuccess(position) {
+ map.center([position.coords.longitude, position.coords.latitude]);
+ }
+ function geolocateError() { }
+ if (navigator.geolocation) {
+ container.append('div')
+ .attr('class', 'geolocate-control map-control')
+ .append('button')
+ .attr('class', 'narrow')
+ .text('G')
+ .on('click', function() {
+ navigator.geolocation.getCurrentPosition(geolocateSuccess, geolocateError);
+ });
+ }
+
var gc = container.append('div').attr('class', 'geocode-control map-control')
.call(iD.geocoder().map(map));
diff --git a/js/id/modes/add_line.js b/js/id/modes/add_line.js
index 0c3ef27aa..24a53a115 100644
--- a/js/id/modes/add_line.js
+++ b/js/id/modes/add_line.js
@@ -13,7 +13,6 @@ iD.modes.AddLine = function() {
controller = mode.controller;
map.dblclickEnable(false)
- .hoverEnable(false)
.hoverEnable(false)
.hint('Click on the map to start drawing an road, path, or route.');
diff --git a/js/id/modes/add_point.js b/js/id/modes/add_point.js
index 677830b32..06eefd00b 100644
--- a/js/id/modes/add_point.js
+++ b/js/id/modes/add_point.js
@@ -30,7 +30,7 @@ iD.modes.AddPoint = function() {
};
mode.exit = function() {
- map.hoverEnable(true);
+ mode.map.hoverEnable(true);
mode.map.hint(false);
mode.map.surface.on('click.addpoint', null);
mode.map.keybinding().on('⎋.addpoint', null);
diff --git a/js/id/modes/select.js b/js/id/modes/select.js
index fe658eaf0..8defb17ba 100644
--- a/js/id/modes/select.js
+++ b/js/id/modes/select.js
@@ -66,14 +66,35 @@ iD.modes.Select = function (entity) {
mode.controller.exit();
});
- surface.on('click.select', function () {
+ function click() {
var datum = d3.select(d3.event.target).datum();
if (datum instanceof iD.Entity) {
mode.controller.enter(iD.modes.Select(datum));
} else {
mode.controller.enter(iD.modes.Browse());
}
- });
+ }
+
+ function dblclick() {
+ var datum = d3.select(d3.event.target).datum();
+ if (datum instanceof iD.Entity &&
+ (datum.geometry() === 'area' || datum.geometry() === 'line')) {
+ var choice = iD.util.geo.chooseIndex(datum,
+ d3.mouse(mode.map.surface.node()), mode.map),
+ node = iD.Node({ loc: choice.loc });
+
+ mode.history.perform(
+ iD.actions.AddNode(node),
+ iD.actions.AddWayNode(datum.id, node.id, choice.index),
+ 'added a point to a road');
+
+ d3.event.preventDefault();
+ d3.event.stopPropagation();
+ }
+ }
+
+ surface.on('click.select', click)
+ .on('dblclick.browse', dblclick);
mode.map.keybinding().on('⌫.select', function(e) {
remove();
diff --git a/js/id/oauth.js b/js/id/oauth.js
index 5e29517e0..c310004e5 100644
--- a/js/id/oauth.js
+++ b/js/id/oauth.js
@@ -66,6 +66,10 @@ iD.OAuth = function() {
var l = iD.loading('contacting openstreetmap...');
+ // it would make more sense to have this code within the callback
+ // to oauth.xhr below. however, it needs to be directly within a
+ // browser event handler in order to open a popup without it being
+ // blocked.
var w = 600, h = 550,
settings = [
['width', w], ['height', h],
diff --git a/js/id/renderer/background.js b/js/id/renderer/background.js
index 54803980f..184f98401 100644
--- a/js/id/renderer/background.js
+++ b/js/id/renderer/background.js
@@ -2,6 +2,7 @@ iD.Background = function() {
var tile = d3.geo.tile(),
projection,
cache = {},
+ offset = [0, 0],
transformProp = iD.util.prefixCSSProperty('Transform'),
source = d3.functor('');
@@ -27,6 +28,10 @@ iD.Background = function() {
return tiles;
}
+ function tileSize(d, z) {
+ return Math.ceil(256 * Math.pow(2, z - d[2])) / 256;
+ }
+
// derive the tiles onscreen, remove those offscreen and position tiles
// correctly for the currentstate of `projection`
function background() {
@@ -82,21 +87,29 @@ iD.Background = function() {
.on('error', error)
.on('load', load);
- function tileSize(d) {
- return Math.ceil(256 * Math.pow(2, z - d[2])) / 256;
- }
-
image.style(transformProp, function(d) {
var _ts = 256 * Math.pow(2, z - d[2]);
- var scale = tileSize(d);
+ var scale = tileSize(d, z);
return 'translate(' +
- Math.round((d[0] * _ts) - tile_origin[0]) + 'px,' +
- Math.round((d[1] * _ts) - tile_origin[1]) + 'px) scale(' + scale + ',' + scale + ')';
+ (Math.round((d[0] * _ts) - tile_origin[0]) + offset[0]) + 'px,' +
+ (Math.round((d[1] * _ts) - tile_origin[1]) + offset[1]) + 'px) scale(' + scale + ',' + scale + ')';
});
if (Object.keys(cache).length > 100) cache = {};
}
+ background.offset = function(_) {
+ if (!arguments.length) return offset;
+ offset = _;
+ return background;
+ };
+
+ background.nudge = function(_) {
+ offset[0] += _[0];
+ offset[1] += _[1];
+ return background;
+ };
+
background.projection = function(_) {
if (!arguments.length) return projection;
projection = _;
diff --git a/js/id/renderer/background_source.js b/js/id/renderer/background_source.js
index d0408557c..cfed23294 100644
--- a/js/id/renderer/background_source.js
+++ b/js/id/renderer/background_source.js
@@ -45,3 +45,7 @@ iD.BackgroundSource.Tiger2012 = iD.BackgroundSource.template(
iD.BackgroundSource.OSM = iD.BackgroundSource.template(
'http://{t}.tile.openstreetmap.org/{z}/{x}/{y}.png',
['a', 'b', 'c'], [0, 18]);
+
+iD.BackgroundSource.MapBox = iD.BackgroundSource.template(
+ 'http://{t}.tiles.mapbox.com/v3/openstreetmap.map-4wvf9l0l/{z}/{x}/{y}.jpg70',
+ ['a', 'b', 'c'], [0, 16]);
diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js
index 66727cd06..4cc7de184 100644
--- a/js/id/renderer/map.js
+++ b/js/id/renderer/map.js
@@ -84,9 +84,9 @@ iD.Map = function() {
filter = d3.functor(true);
} else {
var only = {};
- difference.forEach(function (id) {
+ difference.forEach(function buildDifference(id) {
only[id] = graph.fetch(id);
- graph.parentWays(id).forEach(function (parent) {
+ graph.parentWays(id).forEach(function buildOnly(parent) {
only[parent.id] = graph.fetch(parent.id);
});
});
@@ -94,7 +94,6 @@ iD.Map = function() {
filter = function(d) { return d.accuracy ? d.way in only : d.id in only; };
}
-
if (all.length > 10000) return editOff();
else editOn();
@@ -112,9 +111,10 @@ iD.Map = function() {
}
}
var parentStructure = graph.parentStructure(ways);
- var wayAccuracyHandles = ways.reduce(function(mem, w) {
- return mem.concat(accuracyHandles(w));
- }, []);
+ var wayAccuracyHandles = [];
+ for (i = 0; i < ways.length; i++) {
+ accuracyHandles(ways[i], wayAccuracyHandles);
+ }
drawVertices(vertices, parentStructure, filter);
drawAccuracyHandles(wayAccuracyHandles, filter);
drawCasings(lines, filter);
@@ -123,8 +123,8 @@ iD.Map = function() {
drawPoints(points, filter);
}
- function accuracyHandles(way) {
- var handles = [];
+ // updates handles by reference
+ function accuracyHandles(way, handles) {
for (var i = 0; i < way.nodes.length - 1; i++) {
if (iD.util.geo.dist(way.nodes[i].loc, way.nodes[i + 1].loc) > 0.0001) {
handles.push({
@@ -135,32 +135,36 @@ iD.Map = function() {
});
}
}
- return handles;
+ }
+
+ function pointTransform(entity) {
+ return 'translate(' + iD.util.geo.roundCoords(projection(entity.loc)) + ')';
}
function drawVertices(vertices, parentStructure, filter) {
function shared(d) { return parentStructure[d.id] > 1; }
- var circles = g.hit.selectAll('circle.vertex')
+ var circles = g.hit.selectAll('g.vertex')
.filter(filter)
.data(vertices, key);
circles.exit().remove();
- circles.enter().insert('circle', ':first-child')
+ var cg = circles.enter()
+ .insert('g', ':first-child')
.attr('class', 'node vertex');
- circles.attr('transform', function(entity) {
- var p = projection(entity.loc);
- return 'translate(' + [~~p[0], ~~p[1]] +
- ')';
- })
+ cg.append('circle')
+ .attr('class', 'stroke')
+ .attr('r', 6);
+
+ cg.append('circle')
+ .attr('class', 'fill')
+ .attr('r', 4);
+
+ circles.attr('transform', pointTransform)
.classed('shared', shared)
.classed('hover', classHover);
-
- circles.transition().duration(50).attr('r', function(d) {
- return d.id === hover ? 8: 4;
- });
}
function drawAccuracyHandles(waynodes, filter) {
@@ -170,10 +174,7 @@ iD.Map = function() {
handles.exit().remove();
handles.enter().append('circle')
.attr({ r: 3, 'class': 'accuracy-handle' });
- handles.attr('transform', function(entity) {
- var p = projection(entity.loc);
- return 'translate(' + [~~p[0], ~~p[1]] + ')';
- });
+ handles.attr('transform', pointTransform);
}
function editOff() {
@@ -209,20 +210,30 @@ iD.Map = function() {
}
function drawPoints(points, filter) {
+
var groups = g.hit.selectAll('g.point')
.filter(filter)
.data(points, key);
+
groups.exit().remove();
+
var group = groups.enter().append('g')
.attr('class', 'node point');
+
group.append('circle')
- .attr({ r: 10, cx: 8, cy: 8 });
+ .attr('class', 'stroke')
+ .attr({ r: 10 });
+
+ group.append('circle')
+ .attr('class', 'fill')
+ .attr({ r: 10 });
+
group.append('image')
- .attr({ width: 16, height: 16 });
- groups.attr('transform', function(d) {
- var pt = projection(d.loc);
- return 'translate(' + [~~pt[0], ~~pt[1]] + ') translate(-8, -8)';
- });
+ .attr({ width: 16, height: 16 })
+ .attr('transform', 'translate(-8, -8)');
+
+ groups.attr('transform', pointTransform);
+
groups.classed('hover', classHover);
groups.select('image').attr('xlink:href', iD.Style.pointImage);
}
@@ -299,11 +310,11 @@ iD.Map = function() {
if (fast) {
if (!translateStart) translateStart = d3.event.translate.slice();
var a = d3.event.translate,
- b = translateStart;
- tilegroup.style(transformProp,
- 'translate(' + ~~(a[0] - b[0]) + 'px,' + ~~(a[1] - b[1]) + 'px)');
- surface.style(transformProp,
- 'translate(' + ~~(a[0] - b[0]) + 'px,' + ~~(a[1] - b[1]) + 'px)');
+ b = translateStart,
+ translate = 'translate(' + ~~(a[0] - b[0]) + 'px,' +
+ ~~(a[1] - b[1]) + 'px)';
+ tilegroup.style(transformProp, translate);
+ surface.style(transformProp, translate);
} else {
redraw();
translateStart = null;
diff --git a/js/id/taginfo.js b/js/id/taginfo.js
index b8c77d168..051339d27 100644
--- a/js/id/taginfo.js
+++ b/js/id/taginfo.js
@@ -5,7 +5,7 @@ iD.taginfo = function() {
taginfo.keys = function(parameters, callback) {
d3.json(endpoint + 'db/keys?' +
iD.util.qsString(_.extend({
- rp: 20,
+ rp: 6,
sortname: 'count_all',
sortorder: 'desc',
page: 1
diff --git a/js/id/ui/commit.js b/js/id/ui/commit.js
index b9320c0d7..e24cbc0dc 100644
--- a/js/id/ui/commit.js
+++ b/js/id/ui/commit.js
@@ -14,51 +14,77 @@ iD.commit = function() {
.attr('class', 'changeset-comment')
.attr('placeholder', 'Brief Description of your contributions');
- var buttonwrap = commit.append('div')
- .attr('class', 'buttons');
+ var buttonwrap = commit.append('div')
+ .attr('class', 'buttons');
- var savebutton = buttonwrap.append('button')
- .attr('class', 'action wide')
- .on('click.save', function() {
- event.save({
- comment: d3.select('textarea.changeset-comment').node().value
- });
- });
- savebutton.append('span').attr('class','icon save icon-pre-text');
- savebutton.append('span').attr('class','label').text('Save');
- var cancelbutton = buttonwrap.append('button')
- .attr('class', 'cancel wide')
- .on('click.cancel', function() {
- event.cancel();
- });
- cancelbutton.append('span').attr('class','icon close icon-pre-text');
- cancelbutton.append('span').attr('class','label').text('Cancel');
+ var savebutton = buttonwrap.append('button')
+ .attr('class', 'action wide')
+ .on('click.save', function() {
+ event.save({
+ comment: d3.select('textarea.changeset-comment').node().value
+ });
+ });
+ savebutton.append('span').attr('class','icon save icon-pre-text');
+ savebutton.append('span').attr('class','label').text('Save');
+
+ var cancelbutton = buttonwrap.append('button')
+ .attr('class', 'cancel wide')
+ .on('click.cancel', function() {
+ event.cancel();
+ });
+ cancelbutton.append('span').attr('class','icon close icon-pre-text');
+ cancelbutton.append('span').attr('class','label').text('Cancel');
+
+ function changesLength(d) { return changes[d].length; }
var section = body.selectAll('div.commit-section')
- .data(['modified', 'deleted', 'created'].filter(function(d) {
- return changes[d].length;
- }))
+ .data(['modified', 'deleted', 'created'].filter(changesLength))
.enter()
.append('div').attr('class', 'commit-section modal-section fillL2');
section.append('h3').text(String)
.append('small')
.attr('class', 'count')
- .text(function(d) { return changes[d].length; });
+ .text(changesLength);
+
+ function zipSame(d) {
+ var c = [], n = -1;
+ for (var i = 0; i < d.length; i++) {
+ var desc = {
+ name: d[i].friendlyName(),
+ type: d[i].type,
+ count: 1,
+ tagText: iD.util.tagText(d[i])
+ };
+ if (c[n] &&
+ c[n].name == desc.name &&
+ c[n].tagText == desc.tagText) {
+ c[n].count++;
+ } else {
+ c[++n] = desc;
+ }
+ }
+ return c;
+ }
var li = section.append('ul')
.attr('class','changeset-list')
.selectAll('li')
- .data(function(d) { return changes[d]; })
+ .data(function(d) { return zipSame(changes[d]); })
.enter()
.append('li');
- li.append('strong').text(function(d) { return d.type + ' '; });
+ li.append('strong').text(function(d) {
+ return (d.count > 1) ? d.type + 's ' : d.type + ' ';
+ });
li.append('span')
- .text(function(d) {
- return iD.util.friendlyName(d);
- })
- .attr('title', iD.util.tagText);
+ .text(function(d) { return d.name; })
+ .attr('title', function(d) { return d.tagText; });
+
+ li.filter(function(d) { return d.count > 1; })
+ .append('span')
+ .attr('class', 'count')
+ .text(function(d) { return d.count; });
}
return d3.rebind(commit, event, 'on');
diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js
index f9d133f6d..db9377d94 100644
--- a/js/id/ui/inspector.js
+++ b/js/id/ui/inspector.js
@@ -2,220 +2,237 @@ iD.Inspector = function() {
var event = d3.dispatch('changeTags', 'changeWayDirection',
'update', 'remove', 'close', 'splitWay'),
taginfo = iD.taginfo(),
- inspectorwrap;
+ tagList;
+
+ function inspector(selection) {
+ var entity = selection.datum();
+
+ selection.html("").append('button')
+ .attr('class', 'narrow close')
+ .html("")
+ .on('click', function() {
+ event.close(entity);
+ });
+
+ selection.append('div')
+ .attr('class', 'head inspector-inner')
+ .call(drawHead);
+
+ var inspectorbody = selection.append('div')
+ .attr('class', 'inspector-body');
+
+ var inspectorwrap = inspectorbody.append('div')
+ .attr('class', 'inspector-inner tag-wrap fillL2');
+
+ inspectorwrap.append('h4')
+ .text('Edit tags');
+
+ tagList = inspectorwrap.append('ul');
+
+ inspectorwrap.append('div').attr('class', 'add-tag-row').append('button')
+ .attr('class', 'add-tag')
+ .text('+ Add New Tag')
+ .on('click', function() {
+ addTag();
+ tagList.selectAll('li:last-child input.key').node().focus();
+ });
+
+ var formsel = drawTags(entity.tags);
+
+ inspectorbody.append('div')
+ .attr('class', 'inspector-buttons')
+ .call(drawButtons);
+
+ var inHeight = inspectorbody.node().offsetHeight;
+
+ inspectorbody.style('display', 'none')
+ .style('margin-top', (-inHeight) + 'px');
+
+ var inspectortoggle = selection.append('button')
+ .attr('class', 'inspector-toggle action')
+ .on('click', function() {
+ inspectortoggle.style('display', 'none');
+ inspectorbody
+ .style('display', 'block')
+ .transition()
+ .style('margin-top', '0px');
+ });
+
+ formsel.selectAll('input').node().focus();
+
+ inspectortoggle.append('span')
+ .text('Details')
+ .attr('class','label');
+ }
+
+ function drawHead(selection) {
+ var entity = selection.datum();
- function drawhead(selection) {
- function osmLink(d) {
- return 'http://www.openstreetmap.org/browse/' + d.type + '/' + d.osmId();
- }
- function emitChangeDirection(d) { event.changeWayDirection(d); }
- function emitSplitWay(d) { event.splitWay(d); }
- selection.html('');
var h2 = selection.append('h2');
- h2.append('span').attr('class', function(d) {
- var icons = { way: 'line', node: 'point' };
- return 'icon big icon-pre-text big-' + icons[d.type];
- });
- h2.append('span').text(iD.util.friendlyName(selection.datum()));
+
+ h2.append('span')
+ .attr('class', 'icon big icon-pre-text big-' + entity.geometry());
+
+ h2.append('span')
+ .text(entity.friendlyName());
+
selection.append('a')
- .attr('class', 'permalink')
- .attr('href', osmLink)
+ .attr('href', 'http://www.openstreetmap.org/browse/' + entity.type + '/' + entity.osmId())
.text('View on OSM');
- if (selection.datum().type === 'way') {
+
+ if (entity.type === 'way') {
selection.append('a')
- .attr('class', 'permalink')
.attr('href', '#')
.text('Reverse Direction')
- .on('click', emitChangeDirection);
+ .on('click', function() { event.changeWayDirection(entity); });
}
- if (selection.datum().type === 'node' && !selection.datum()._poi) {
+
+ if (entity.geometry() === 'vertex') {
selection.append('a')
- .attr('class', 'permalink')
.attr('href', '#')
.text('Split Way')
- .on('click', emitSplitWay);
+ .on('click', function() { event.splitWay(entity); });
}
}
- function inspector(selection) {
- selection.each(function(entity) {
+ function drawButtons(selection) {
+ selection.append('button')
+ .attr('class', 'apply wide action')
+ .html("Apply")
+ .on('click', apply);
- function draw(tags) {
+ selection.append('button')
+ .attr('class', 'delete wide action')
+ .html("Delete")
+ .on('click', function(entity) { event.remove(entity); });
+ }
- function emptyTag(d) {
- return d.key === '';
- }
+ function drawTags(tags) {
+ tags = d3.entries(tags);
- function pushMore() {
- if (d3.event.keyCode === 9) {
- draw(inspector.tags());
- }
- }
+ if (!tags.length) {
+ tags = [{key: '', value: ''}];
+ }
- function bindTypeahead() {
- var row = d3.select(this),
- key = row.selectAll('.key'),
- value = row.selectAll('.value');
+ var li = tagList.selectAll('li')
+ .data(tags, function(d) { return d.key; });
- key.call(d3.typeahead()
- .data(function(_, callback) {
- taginfo.keys({query: key.property('value')}, function(err, data) {
- callback(data.data.map(function (d) {
- return {value: d.key};
- }));
- });
- }));
+ li.exit().remove();
- value.call(d3.typeahead()
- .data(function(_, callback) {
- taginfo.values({key: key.property('value'), query: value.property('value')}, function(err, data) {
- callback(data.data.map(function (d) {
- return {value: d.value, title: d.description};
- }));
- });
- }));
- }
+ var row = li.enter().append('li')
+ .attr('class', 'tag-row');
- tags = d3.entries(tags);
- tags.push({ key: '', value: ''});
+ var inputs = row.append('div')
+ .attr('class', 'input-wrap');
- var li = inspectorwrap.selectAll('li')
- .data(tags, function(d) { return d.key; });
+ inputs.append('input')
+ .property('type', 'text')
+ .attr('class', 'key')
+ .property('value', function(d) { return d.key; })
+ .on('change', function(d) { d.key = this.value; });
- li.exit().remove();
+ inputs.append('input')
+ .property('type', 'text')
+ .attr('class', 'value')
+ .property('value', function(d) { return d.value; })
+ .on('change', function(d) { d.value = this.value; })
+ .on('keydown.push-more', pushMore);
- var row = li.enter().append('li').attr('class','tag-row');
- var inputs = row.append('div').attr('class','input-wrap');
+ inputs.each(bindTypeahead);
- li.classed('tag-row-empty', emptyTag);
+ var removeBtn = row.append('button')
+ .attr('tabindex', -1)
+ .attr('class','remove minor')
+ .on('click', removeTag);
- inputs.append('input')
- .property('type', 'text')
- .attr('class', 'key')
- .property('value', function(d) { return d.key; });
+ removeBtn.append('span')
+ .attr('class', 'icon remove');
- inputs.append('input')
- .property('type', 'text')
- .attr('class', 'value')
- .property('value', function(d) { return d.value; })
- .on('keydown.push-more', pushMore);
-
- inputs.each(bindTypeahead);
-
- var removeBtn = row.append('button')
- .attr('tabindex', -1)
- .attr('class','remove minor')
- .on('click', removeTag);
-
- removeBtn.append('span').attr('class', 'icon remove');
-
- var helpBtn = row.append('button')
- .attr('tabindex', -1)
- .attr('class', 'tag-help minor')
- .append('a')
- .attr('tabindex', -1)
- .attr('target', '_blank')
- .on('click', function(d) {
- taginfo.docs(d, function(err, docs) {
- var en = _.find(docs, function(d) {
- return d.lang == 'en';
- });
- if (en) {
- var types = [];
- if (en.on_area) types.push('area');
- if (en.on_node) types.push('point');
- if (en.on_way) types.push('line');
- en.types = types;
- var mod = iD.modal();
- mod.select('.content')
- .datum(en)
- .call(iD.tagReference);
- }
- });
- d3.event.preventDefault();
- })
- .attr('href', function(d) {
- return 'http://taginfo.openstreetmap.org/keys/' + d.key;
+ var helpBtn = row.append('button')
+ .attr('tabindex', -1)
+ .attr('class', 'tag-help minor')
+ .append('a')
+ .attr('tabindex', -1)
+ .attr('target', '_blank')
+ .on('click', function(d) {
+ taginfo.docs(d, function(err, docs) {
+ var en = _.find(docs, function(d) {
+ return d.lang == 'en';
});
-
- helpBtn.append('span').attr('class', 'icon inspect');
-
- return li;
- }
-
- function removeTag(d) {
- var tags = inspector.tags();
- delete tags[d.key];
- draw(tags);
- }
-
- function apply(entity) {
- event.changeTags(entity, inspector.tags());
- event.close(entity);
- }
-
- function drawbuttons(selection) {
- selection.append('button')
- .attr('class', 'apply wide action')
- .html("Apply")
- .on('click', apply);
- selection.append('button')
- .attr('class', 'delete wide action')
- .html("Delete")
- .on('click', function(entity) { event.remove(entity); });
- }
-
- selection.html("").append('button')
- .attr('class', 'narrow close')
- .html("")
- .on('click', function() {
- event.close(entity);
+ if (en) {
+ var types = [];
+ if (en.on_area) types.push('area');
+ if (en.on_node) types.push('point');
+ if (en.on_way) types.push('line');
+ en.types = types;
+ var mod = iD.modal();
+ mod.select('.content')
+ .datum(en)
+ .call(iD.tagReference);
+ }
+ });
+ d3.event.preventDefault();
+ })
+ .attr('href', function(d) {
+ return 'http://taginfo.openstreetmap.org/keys/' + d.key;
});
- selection.append('div')
- .attr('class', 'head inspector-inner')
- .call(drawhead);
+ helpBtn.append('span')
+ .attr('class', 'icon inspect');
- var inspectorbody = selection.append('div')
- .attr('class', 'inspector-body');
+ return li;
+ }
- inspectorwrap = inspectorbody
- .append('ul').attr('class', 'inspector-inner tag-wrap fillL2');
+ function pushMore() {
+ if (d3.event.keyCode === 9 && tagList.selectAll('li:last-child input.value').node() === this) {
+ addTag();
+ }
+ }
- inspectorwrap.append('h4').text('Edit tags');
+ function bindTypeahead() {
+ var row = d3.select(this),
+ key = row.selectAll('.key'),
+ value = row.selectAll('.value');
-
- var formsel = draw(entity.tags);
-
- inspectorbody.append('div')
- .attr('class', 'inspector-buttons').call(drawbuttons);
-
- var inHeight = inspectorbody.node().offsetHeight;
-
- inspectorbody.style('display', 'none')
- .style('margin-top', (-inHeight) + 'px');
-
- var inspectortoggle = selection.append('button')
- .attr('class', 'inspector-toggle action')
- .on('click', function() {
- inspectortoggle.style('display', 'none');
- inspectorbody
- .style('display', 'block')
- .transition()
- .style('margin-top', '0px');
+ key.call(d3.typeahead()
+ .data(function(_, callback) {
+ taginfo.keys({query: key.property('value')}, function(err, data) {
+ callback(data.data.map(function (d) {
+ return {value: d.key};
+ }));
});
+ }));
- formsel.select('input').node().focus();
+ value.call(d3.typeahead()
+ .data(function(_, callback) {
+ taginfo.values({key: key.property('value'), query: value.property('value')}, function(err, data) {
+ callback(data.data.map(function (d) {
+ return {value: d.value, title: d.description};
+ }));
+ });
+ }));
+ }
- inspectortoggle.append('span')
- .text('Details')
- .attr('class','label');
- });
+ function addTag() {
+ var tags = inspector.tags();
+ tags[''] = '';
+ drawTags(tags);
+ }
+
+ function removeTag(d) {
+ var tags = inspector.tags();
+ delete tags[d.key];
+ drawTags(tags);
+ }
+
+ function apply(entity) {
+ event.changeTags(entity, inspector.tags());
+ event.close(entity);
}
inspector.tags = function () {
var tags = {};
- inspectorwrap.selectAll('li').each(function() {
+ tagList.selectAll('li').each(function() {
var row = d3.select(this),
key = row.selectAll('.key').property('value'),
value = row.selectAll('.value').property('value');
diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js
index f79ab169c..94d4956d9 100644
--- a/js/id/ui/layerswitcher.js
+++ b/js/id/ui/layerswitcher.js
@@ -12,6 +12,10 @@ iD.layerswitcher = function(map) {
name: 'OSM',
source: iD.BackgroundSource.OSM,
description: 'The default OpenStreetMap layer.'
+ }, {
+ name: 'MapBox',
+ source: iD.BackgroundSource.MapBox,
+ description: 'Satellite and Aerial Imagery'
}, {
name: 'Custom',
source: iD.BackgroundSource.Custom,
@@ -121,6 +125,36 @@ iD.layerswitcher = function(map) {
.insert('span')
.attr('class','icon toggle');
+ var adjustments = content
+ .append('div')
+ .attr('class', 'adjustments');
+
+ var directions = [
+ ['←', [-1, 0]],
+ ['↑', [0, -1]],
+ ['→', [1, 0]],
+ ['↓', [0, 1]]];
+
+ function nudge(d) {
+ map.background.nudge(d[1]);
+ map.redraw();
+ }
+
+ adjustments.selectAll('button')
+ .data(directions).enter()
+ .append('button')
+ .attr('class', 'nudge')
+ .text(function(d) { return d[0]; })
+ .on('click', nudge);
+
+ adjustments.append('button')
+ .text('reset')
+ .attr('class', 'reset')
+ .on('click', function() {
+ map.background.offset([0, 0]);
+ map.redraw();
+ });
+
selection.call(clickoutside);
selectLayer(map.background.source());
diff --git a/js/id/ui/notice.js b/js/id/ui/notice.js
index 05f3c85b6..5467e3745 100644
--- a/js/id/ui/notice.js
+++ b/js/id/ui/notice.js
@@ -3,7 +3,7 @@ iD.notice = function(selection) {
notice = {};
notice.message = function(_) {
- selection.attr('class','inner');
+ selection.attr('class', 'notice inner');
if (!arguments.length) return _;
if (!message && _) {
selection
diff --git a/js/id/ui/success.js b/js/id/ui/success.js
new file mode 100644
index 000000000..f81be1028
--- /dev/null
+++ b/js/id/ui/success.js
@@ -0,0 +1,43 @@
+iD.success = function() {
+ var event = d3.dispatch('cancel', 'save');
+
+ function success(selection) {
+ var changeset = selection.datum(),
+ header = selection.append('div').attr('class', 'header modal-section'),
+ body = selection.append('div').attr('class', 'body');
+
+ var section = body.append('div').attr('class','modal-section');
+
+ header.append('h2').text('You Just Edited OpenStreetMap!');
+ header.append('p').text('You just improved the world\'s best free map');
+
+ var m = '';
+ if (changeset.comment) {
+ m = '"' + changeset.comment.substring(0, 20) + '" ';
+ }
+
+ var message = 'Edited OpenStreetMap! ' + m +
+ 'http://osm.org/browse/changeset/' + changeset.id;
+
+ section.append('a')
+ .attr('href', function(d) {
+ return 'https://twitter.com/intent/tweet?source=webclient&text=' +
+ encodeURIComponent(message);
+ })
+ .text('Tweet: ' + message);
+
+ var buttonwrap = section.append('div')
+ .attr('class', 'buttons');
+
+ var okbutton = buttonwrap.append('button')
+ .attr('class', 'action wide')
+ .on('click.save', function() {
+ event.cancel();
+ });
+
+ okbutton.append('span').attr('class','icon apply icon-pre-text');
+ okbutton.append('span').attr('class','label').text('OK');
+ }
+
+ return d3.rebind(success, event, 'on');
+};
diff --git a/js/id/util.js b/js/id/util.js
index f9c515ae5..91bbbb1c1 100644
--- a/js/id/util.js
+++ b/js/id/util.js
@@ -6,28 +6,6 @@ iD.util.trueObj = function(arr) {
return o;
};
-iD.util.friendlyName = function(entity) {
- // Generate a string such as 'river' or 'Fred's House' for an entity.
- if (!entity.tags || !Object.keys(entity.tags).length) { return ''; }
-
- var mainkeys = ['highway','amenity','railway','waterway','natural'],
- n = [];
-
- if (entity.tags.name) n.push(entity.tags.name);
- if (entity.tags.ref) n.push(entity.tags.ref);
-
- if (!n.length) {
- for (var k in entity.tags) {
- if (mainkeys.indexOf(k) !== -1) {
- n.push(entity.tags[k]);
- break;
- }
- }
- }
-
- return n.length === 0 ? 'unknown' : n.join('; ');
-};
-
iD.util.codeWindow = function(content) {
top.win = window.open('','contentWindow',
'width=350,height=350,menubar=0' +
diff --git a/js/lib/d3.trigger.js b/js/lib/d3.trigger.js
new file mode 100644
index 000000000..59b7cd5a5
--- /dev/null
+++ b/js/lib/d3.trigger.js
@@ -0,0 +1,7 @@
+d3.selection.prototype.trigger = function (type) {
+ this.each(function() {
+ var evt = document.createEvent('HTMLEvents');
+ evt.initEvent(type, true, true);
+ this.dispatchEvent(evt);
+ });
+};
diff --git a/js/lib/d3.typeahead.js b/js/lib/d3.typeahead.js
index a93da8877..554b89a60 100644
--- a/js/lib/d3.typeahead.js
+++ b/js/lib/d3.typeahead.js
@@ -14,35 +14,47 @@ d3.typeahead = function() {
top: rect.bottom + 'px'
});
selection
- .on('keyup.typeahead', update);
+ .on('keyup.typeahead', key);
hidden = false;
}
function hide() {
- window.setTimeout(function() {
- container.remove();
- idx = 0;
- hidden = true;
- }, 500);
+ container.remove();
+ idx = 0;
+ hidden = true;
+ }
+
+ function slowHide() {
+ window.setTimeout(hide, 150);
}
selection
.on('focus.typeahead', setup)
- .on('blur.typeahead', hide);
+ .on('blur.typeahead', slowHide);
- function update() {
- if (hidden) setup();
-
- if (d3.event.keyCode === 40) idx++;
- if (d3.event.keyCode === 38) idx--;
- if (d3.event.keyCode === 13) {
- selection.property('value', container.select('a.selected').datum().value);
- hide();
- }
+ function key() {
+ if (d3.event.keyCode === 40) {
+ idx++;
+ return highlight();
+ } else if (d3.event.keyCode === 38) {
+ idx--;
+ return highlight();
+ } else if (d3.event.keyCode === 13) {
+ select(container.select('a.selected').datum());
+ hide();
+ } else {
+ update();
+ }
+ }
+ function highlight() {
container
.selectAll('a')
.classed('selected', function(d, i) { return i == idx; });
+ }
+
+ function update() {
+ if (hidden) setup();
data(selection, function(data) {
container.style('display', function() {
@@ -57,11 +69,21 @@ d3.typeahead = function() {
.append('a')
.text(function(d) { return d.value; })
.attr('title', function(d) { return d.title; })
- .on('click', function(d) { selection.property('value', d.value); });
+ .on('click', select);
options.exit().remove();
+
+ options
+ .classed('selected', function(d, i) { return i == idx; });
});
}
+
+ function select(d) {
+ selection
+ .property('value', d.value)
+ .trigger('change');
+ }
+
};
typeahead.data = function(_) {
diff --git a/test/index.html b/test/index.html
index 3d0a995c3..f45262f73 100644
--- a/test/index.html
+++ b/test/index.html
@@ -22,6 +22,7 @@
+
diff --git a/test/spec/graph/entity.js b/test/spec/graph/entity.js
index 6dd5cafa0..e5741104f 100644
--- a/test/spec/graph/entity.js
+++ b/test/spec/graph/entity.js
@@ -130,4 +130,18 @@ describe('iD.Entity', function () {
expect(iD.Entity({tags: {'tiger:source': 'blah', 'tiger:foo': 'bar'}}).hasInterestingTags()).to.equal(false);
});
});
+
+ describe("#friendlyName", function () {
+ it("returns the name", function () {
+ expect(iD.Entity({ tags: { name: 'hi' }}).friendlyName()).to.equal('hi');
+ });
+
+ it("returns a highway tag value", function () {
+ expect(iD.Entity({ tags: { highway: 'Route 5' }}).friendlyName()).to.equal('Route 5');
+ });
+
+ it("prefers the name to a highway tag value", function () {
+ expect(iD.Entity({ tags: { name: 'hi', highway: 'Route 5' }}).friendlyName()).to.equal('hi');
+ });
+ });
});
diff --git a/test/spec/graph/node.js b/test/spec/graph/node.js
index c7ce0c406..929946b04 100644
--- a/test/spec/graph/node.js
+++ b/test/spec/graph/node.js
@@ -36,4 +36,14 @@ describe('iD.Node', function () {
expect(iD.Node({loc: [0, 0]}).intersects([[100, 90], [180, -90]])).to.equal(false);
});
});
+
+ describe("#geometry", function () {
+ it("returns 'vertex' if the node is not a point", function () {
+ expect(iD.Node().geometry()).to.equal('vertex');
+ });
+
+ it("returns 'point' if the node is a point", function () {
+ expect(iD.Node({_poi: true}).geometry()).to.equal('point');
+ });
+ });
});
diff --git a/test/spec/graph/way.js b/test/spec/graph/way.js
index 4f64fac67..3aa8045fb 100644
--- a/test/spec/graph/way.js
+++ b/test/spec/graph/way.js
@@ -110,4 +110,14 @@ describe('iD.Way', function() {
expect(iD.Way({tags: { highway: 'residential' }, nodes: ['n1', 'n1']}).isArea()).to.equal(false);
});
});
+
+ describe("#geometry", function() {
+ it("returns 'line' when the way is not an area", function () {
+ expect(iD.Way().geometry()).to.equal('line');
+ });
+
+ it("returns 'area' when the way is an area", function () {
+ expect(iD.Way({tags: { area: 'yes' }}).geometry()).to.equal('area');
+ });
+ });
});
diff --git a/test/spec/modes/add_point.js b/test/spec/modes/add_point.js
index c78370d42..26cfcd013 100644
--- a/test/spec/modes/add_point.js
+++ b/test/spec/modes/add_point.js
@@ -8,6 +8,8 @@ describe("iD.modes.AddPoint", function () {
controller = iD.Controller(map, history);
container.call(map);
+ container.append('div')
+ .attr('class', 'inspector-wrap');
mode = iD.modes.AddPoint();
controller.enter(mode);
diff --git a/test/spec/taginfo.js b/test/spec/taginfo.js
index ae1855484..0c9fec93f 100644
--- a/test/spec/taginfo.js
+++ b/test/spec/taginfo.js
@@ -26,7 +26,7 @@ describe("iD.taginfo", function() {
server.respond();
expect(query(server.requests[0].url)).to.eql(
- {query: "amen", page: "1", rp: "20", sortname: "count_all", sortorder: "desc"});
+ {query: "amen", page: "1", rp: "6", sortname: "count_all", sortorder: "desc"});
expect(callback).to.have.been.calledWith(null,
{"data":[{"count_all":5190337,"key":"amenity"}]});
});
diff --git a/test/spec/ui/inspector.js b/test/spec/ui/inspector.js
index 5c6760354..6d4175ef3 100644
--- a/test/spec/ui/inspector.js
+++ b/test/spec/ui/inspector.js
@@ -1,15 +1,20 @@
describe("iD.Inspector", function () {
var inspector, element,
tags = {highway: 'residential'},
- entity = iD.Entity({type: 'node', id: "n12345", tags: tags});
+ entity;
- beforeEach(function () {
+ function render() {
inspector = iD.Inspector();
element = d3.select('body')
.append('div')
.attr('id', 'inspector-wrap')
.datum(entity)
.call(inspector);
+ }
+
+ beforeEach(function () {
+ entity = iD.Entity({type: 'node', id: "n12345", tags: tags});
+ render();
});
afterEach(function () {
@@ -22,9 +27,9 @@ describe("iD.Inspector", function () {
});
it("returns updated tags when input values have changed", function () {
- element.selectAll(".tag-row-empty input.key").property('value', 'k');
- element.selectAll(".tag-row-empty input.value").property('value', 'v');
- expect(inspector.tags()).to.eql({highway: 'residential', k: 'v'});
+ element.selectAll("input.key").property('value', 'k');
+ element.selectAll("input.value").property('value', 'v');
+ expect(inspector.tags()).to.eql({k: 'v'});
});
});
@@ -33,17 +38,22 @@ describe("iD.Inspector", function () {
expect(element.selectAll("input[value=residential]")).not.to.be.empty;
});
- it("creates one trailing pair of empty input elements", function () {
+ it("creates a pair of empty input elements if the entity has no tags", function () {
+ element.remove();
+ entity = entity.update({tags: {}});
+ render();
+ expect(element.selectAll("input.value").property('value')).to.be.empty;
+ expect(element.selectAll("input.key").property('value')).to.be.empty;
+ });
+
+ it("adds tags when clicking the add button", function () {
+ element.selectAll("button.add-tag").trigger('click');
expect(element.selectAll("input")[0][2].value).to.be.empty;
expect(element.selectAll("input")[0][3].value).to.be.empty;
});
- it("sets the 'tag-row-empty' class on the placeholder row", function () {
- expect(element.selectAll(".tag-row:last-child").classed('tag-row-empty')).to.be.true;
- });
-
it("removes tags when clicking the remove button", function () {
- happen.click(element.selectAll("button.remove").node());
+ element.selectAll("button.remove").trigger('click');
expect(inspector.tags()).to.eql({});
});
@@ -51,7 +61,7 @@ describe("iD.Inspector", function () {
var spy = sinon.spy();
inspector.on('close', spy);
- happen.click(element.select('.close').node());
+ element.select('.close').trigger('click');
expect(spy).to.have.been.calledWith(entity);
});
@@ -60,7 +70,7 @@ describe("iD.Inspector", function () {
var spy = sinon.spy();
inspector.on('changeTags', spy);
- happen.click(element.select('.apply').node());
+ element.select('.apply').trigger('click');
expect(spy).to.have.been.calledWith(entity, tags);
});
@@ -69,7 +79,7 @@ describe("iD.Inspector", function () {
var spy = sinon.spy();
inspector.on('remove', spy);
- happen.click(element.select('.delete').node());
+ element.select('.delete').trigger('click');
expect(spy).to.have.been.calledWith(entity);
});
diff --git a/test/spec/util.js b/test/spec/util.js
index 3b9b547a0..e6663ade2 100644
--- a/test/spec/util.js
+++ b/test/spec/util.js
@@ -24,12 +24,6 @@ describe('Util', function() {
expect(iD.util.qsString({})).to.eql('');
});
- it('#friendlyName', function() {
- expect(iD.util.friendlyName({ tags: { name: 'hi' }})).to.equal('hi');
- expect(iD.util.friendlyName({ tags: { highway: 'Route 5' }})).to.equal('Route 5');
- expect(iD.util.friendlyName({ tags: { name: 'hi', highway: 'Route 5' }})).to.equal('hi');
- });
-
describe('geo', function() {
describe('#roundCoords', function() {
expect(iD.util.geo.roundCoords([0.1, 1])).to.eql([0, 1]);