From 7e68e8e114020da318e33537cce9d57d1698cfeb Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 31 Jan 2013 15:50:00 -0500 Subject: [PATCH 001/332] Add iD.Context This is a facade interface that ties together a bunch of different internal objects and will make it easier to write tests for behaviors, modes, and operations. --- Makefile | 1 + index.html | 3 +- js/id/behavior/add_way.js | 18 ++++---- js/id/behavior/drag_midpoint.js | 15 +++--- js/id/behavior/drag_node.js | 22 ++++----- js/id/behavior/draw.js | 24 +++++----- js/id/behavior/draw_way.js | 69 ++++++++++++++-------------- js/id/behavior/hash.js | 29 ++++++------ js/id/behavior/select.js | 8 ++-- js/id/context.js | 44 ++++++++++++++++++ js/id/controller.js | 6 +-- js/id/id.js | 39 ++++++++++------ js/id/modes/add_area.js | 38 +++++++--------- js/id/modes/add_line.js | 42 ++++++++--------- js/id/modes/add_point.js | 25 ++++------ js/id/modes/browse.js | 22 ++++----- js/id/modes/draw_area.js | 12 ++--- js/id/modes/draw_line.js | 12 ++--- js/id/modes/move_way.js | 47 +++++++++---------- js/id/modes/select.js | 81 ++++++++++++++++----------------- js/id/operations/circularize.js | 20 +++----- js/id/operations/delete.js | 15 +++--- js/id/operations/move.js | 10 ++-- js/id/operations/reverse.js | 11 ++--- js/id/operations/split.js | 12 ++--- js/id/operations/unjoin.js | 12 ++--- js/id/ui/save.js | 29 +++--------- test/index.html | 11 +++-- test/index_packaged.html | 10 ++-- test/spec/behavior/hash.js | 70 ++++++++++++++-------------- test/spec/modes/add_point.js | 33 ++++++-------- test/spec/ui/confirm.js | 3 ++ test/spec/ui/modal.js | 1 + 33 files changed, 384 insertions(+), 410 deletions(-) create mode 100644 js/id/context.js diff --git a/Makefile b/Makefile index 1db4f2efb..598bce278 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,7 @@ all: \ js/lib/sha.js \ js/id/start.js \ js/id/id.js \ + js/id/context.js \ js/id/connection.js \ js/id/oauth.js \ js/id/services/*.js \ diff --git a/index.html b/index.html index c8ddd7562..306575804 100644 --- a/index.html +++ b/index.html @@ -122,9 +122,10 @@ + + - diff --git a/js/id/behavior/add_way.js b/js/id/behavior/add_way.js index 89ba7f1cd..207c88566 100644 --- a/js/id/behavior/add_way.js +++ b/js/id/behavior/add_way.js @@ -1,8 +1,6 @@ -iD.behavior.AddWay = function(mode) { - var map = mode.map, - controller = mode.controller, - event = d3.dispatch('start', 'startFromWay', 'startFromNode', 'startFromMidpoint'), - draw = iD.behavior.Draw(map); +iD.behavior.AddWay = function(context) { + var event = d3.dispatch('start', 'startFromWay', 'startFromNode', 'startFromMidpoint'), + draw = iD.behavior.Draw(context); var addWay = function(surface) { draw.on('click', event.start) @@ -12,7 +10,8 @@ iD.behavior.AddWay = function(mode) { .on('cancel', addWay.cancel) .on('finish', addWay.cancel); - map.fastEnable(false) + context.map() + .fastEnable(false) .minzoom(16) .dblclickEnable(false); @@ -20,19 +19,20 @@ iD.behavior.AddWay = function(mode) { }; addWay.off = function(surface) { - map.fastEnable(true) + context.map() + .fastEnable(true) .minzoom(0) .tail(false); window.setTimeout(function() { - map.dblclickEnable(true); + context.map().dblclickEnable(true); }, 1000); surface.call(draw.off); }; addWay.cancel = function() { - controller.enter(iD.modes.Browse()); + context.enter(iD.modes.Browse(context)); }; return d3.rebind(addWay, event, 'on'); diff --git a/js/id/behavior/drag_midpoint.js b/js/id/behavior/drag_midpoint.js index 6041946cd..42099963c 100644 --- a/js/id/behavior/drag_midpoint.js +++ b/js/id/behavior/drag_midpoint.js @@ -1,16 +1,13 @@ -iD.behavior.DragMidpoint = function(mode) { - var history = mode.history, - projection = mode.map.projection; - +iD.behavior.DragMidpoint = function(context) { var behavior = iD.behavior.drag() .delegate(".midpoint") .origin(function(d) { - return projection(d.loc); + return context.projection(d.loc); }) .on('start', function(d) { var node = iD.Node(); - history.perform(iD.actions.AddMidpoint(d, node)); + context.perform(iD.actions.AddMidpoint(d, node)); var vertex = d3.selectAll('.vertex') .filter(function(data) { return data.id === node.id; }); @@ -19,11 +16,11 @@ iD.behavior.DragMidpoint = function(mode) { }) .on('move', function(d) { d3.event.sourceEvent.stopPropagation(); - history.replace( - iD.actions.MoveNode(d.id, projection.invert(d3.event.point))); + context.replace( + iD.actions.MoveNode(d.id, context.projection.invert(d3.event.point))); }) .on('end', function() { - history.replace( + context.replace( iD.actions.Noop(), t('operations.add.annotation.vertex')); }); diff --git a/js/id/behavior/drag_node.js b/js/id/behavior/drag_node.js index b9846617d..b62162e94 100644 --- a/js/id/behavior/drag_node.js +++ b/js/id/behavior/drag_node.js @@ -1,8 +1,6 @@ -iD.behavior.DragNode = function(mode) { - var history = mode.history, - size = mode.map.size(), - nudgeInterval, - projection = mode.map.projection; +iD.behavior.DragNode = function(context) { + var size = context.map().size(), + nudgeInterval; function edge(point) { var pad = [30, 100, 30, 100]; @@ -16,7 +14,7 @@ iD.behavior.DragNode = function(mode) { function startNudge(nudge) { if (nudgeInterval) window.clearInterval(nudgeInterval); nudgeInterval = window.setInterval(function() { - mode.map.pan(nudge).redraw(); + context.map().pan(nudge).redraw(); }, 50); } @@ -26,16 +24,16 @@ iD.behavior.DragNode = function(mode) { } function annotation(entity) { - return t('operations.move.annotation.' + entity.geometry(mode.history.graph())); + return t('operations.move.annotation.' + entity.geometry(context.graph())); } return iD.behavior.drag() .delegate(".node") .origin(function(entity) { - return projection(entity.loc); + return context.projection(entity.loc); }) .on('start', function() { - history.perform( + context.perform( iD.actions.Noop()); }) .on('move', function(entity) { @@ -45,13 +43,13 @@ iD.behavior.DragNode = function(mode) { if (nudge) startNudge(nudge); else stopNudge(); - history.replace( - iD.actions.MoveNode(entity.id, projection.invert(d3.event.point)), + context.replace( + iD.actions.MoveNode(entity.id, context.projection.invert(d3.event.point)), annotation(entity)); }) .on('end', function(entity) { stopNudge(); - history.replace( + context.replace( iD.actions.Noop(), annotation(entity)); }); diff --git a/js/id/behavior/draw.js b/js/id/behavior/draw.js index bce4470b6..8405ac341 100644 --- a/js/id/behavior/draw.js +++ b/js/id/behavior/draw.js @@ -1,7 +1,8 @@ -iD.behavior.Draw = function(map) { +iD.behavior.Draw = function(context) { var event = d3.dispatch('move', 'click', 'clickWay', 'clickNode', 'clickMidpoint', 'undo', 'cancel', 'finish'), keybinding = d3.keybinding('draw'), - down, surface, hover; + hover = iD.behavior.Hover(), + down; function datum() { if (d3.event.altKey) { @@ -28,7 +29,7 @@ iD.behavior.Draw = function(map) { function click() { var d = datum(); if (d.type === 'way') { - var choice = iD.geo.chooseIndex(d, d3.mouse(map.surface.node()), map); + var choice = iD.geo.chooseIndex(d, d3.mouse(context.surface().node()), context.map()); event.clickWay(d, choice.loc, choice.index); } else if (d.type === 'node') { @@ -38,19 +39,19 @@ iD.behavior.Draw = function(map) { event.clickMidpoint(d); } else { - event.click(map.mouseCoordinates()); + event.click(context.map().mouseCoordinates()); } } function keydown() { if (d3.event.keyCode === d3.keybinding.modifierCodes.alt) { - surface.call(hover.off); + context.uninstall(hover); } } function keyup() { if (d3.event.keyCode === d3.keybinding.modifierCodes.alt) { - surface.call(hover); + context.install(hover); } } @@ -70,8 +71,7 @@ iD.behavior.Draw = function(map) { } function draw(selection) { - surface = selection; - hover = iD.behavior.Hover(); + context.install(hover); keybinding .on('⌫', backspace) @@ -83,8 +83,7 @@ iD.behavior.Draw = function(map) { .on('mousedown.draw', mousedown) .on('mouseup.draw', mouseup) .on('mousemove.draw', mousemove) - .on('click.draw', click) - .call(hover); + .on('click.draw', click); d3.select(document) .call(keybinding) @@ -95,12 +94,13 @@ iD.behavior.Draw = function(map) { } draw.off = function(selection) { + context.uninstall(hover); + selection .on('mousedown.draw', null) .on('mouseup.draw', null) .on('mousemove.draw', null) - .on('click.draw', null) - .call(hover.off); + .on('click.draw', null); d3.select(document) .call(keybinding.off) diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 1cc62b755..28a8fbb38 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -1,35 +1,32 @@ -iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { - var map = mode.map, - history = mode.history, - controller = mode.controller, - way = history.graph().entity(wayId), +iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { + var way = context.entity(wayId), finished = false, annotation = t((way.isDegenerate() ? 'operations.start.annotation.' : - 'operations.continue.annotation.') + way.geometry(history.graph())), - draw = iD.behavior.Draw(map); + 'operations.continue.annotation.') + context.geometry(wayId)), + draw = iD.behavior.Draw(context); - var node = iD.Node({loc: map.mouseCoordinates()}), + var node = iD.Node({loc: context.map().mouseCoordinates()}), nodeId = node.id; - history[way.isDegenerate() ? 'replace' : 'perform']( + context[way.isDegenerate() ? 'replace' : 'perform']( iD.actions.AddEntity(node), iD.actions.AddVertex(wayId, node.id, index)); function move(datum) { - var loc = map.mouseCoordinates(); + var loc = context.map().mouseCoordinates(); if (datum.type === 'node' || datum.type === 'midpoint') { loc = datum.loc; } else if (datum.type === 'way') { - loc = iD.geo.chooseIndex(datum, d3.mouse(map.surface.node()), map).loc; + loc = iD.geo.chooseIndex(datum, d3.mouse(context.surface().node()), context.map()).loc; } - history.replace(iD.actions.MoveNode(nodeId, loc)); + context.replace(iD.actions.MoveNode(nodeId, loc)); } function undone() { - controller.enter(iD.modes.Browse()); + context.enter(iD.modes.Browse(context)); } var drawWay = function(surface) { @@ -38,11 +35,12 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { .on('clickWay', drawWay.addWay) .on('clickNode', drawWay.addNode) .on('clickMidpoint', drawWay.addMidpoint) - .on('undo', history.undo) + .on('undo', context.undo) .on('cancel', drawWay.cancel) .on('finish', drawWay.finish); - map.fastEnable(false) + context.map() + .fastEnable(false) .minzoom(16) .dblclickEnable(false); @@ -51,26 +49,29 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { .filter(function (d) { return d.id === wayId || d.id === nodeId; }) .classed('active', true); - history.on('undone.draw', undone); + context.history() + .on('undone.draw', undone); }; drawWay.off = function(surface) { if (!finished) - history.pop(); + context.pop(); - map.fastEnable(true) + context.map() + .fastEnable(true) .minzoom(0) .tail(false); window.setTimeout(function() { - map.dblclickEnable(true); + context.map().dblclickEnable(true); }, 1000); surface.call(draw.off) .selectAll('.way, .node') .classed('active', false); - history.on('undone.draw', null); + context.history() + .on('undone.draw', null); }; function ReplaceTemporaryNode(newNode) { @@ -85,74 +86,74 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { drawWay.add = function(loc) { var newNode = iD.Node({loc: loc}); - history.replace( + context.replace( iD.actions.AddEntity(newNode), ReplaceTemporaryNode(newNode), annotation); finished = true; - controller.enter(mode); + context.enter(mode); }; // Connect the way to an existing way. drawWay.addWay = function(way, loc, wayIndex) { var newNode = iD.Node({loc: loc}); - history.perform( + context.perform( iD.actions.AddEntity(newNode), iD.actions.AddVertex(way.id, newNode.id, wayIndex), ReplaceTemporaryNode(newNode), annotation); finished = true; - controller.enter(mode); + context.enter(mode); }; // Connect the way to an existing node and continue drawing. drawWay.addNode = function(node) { - history.perform( + context.perform( ReplaceTemporaryNode(node), annotation); finished = true; - controller.enter(mode); + context.enter(mode); }; // Add a midpoint, connect the way to it, and continue drawing. drawWay.addMidpoint = function(midpoint) { var node = iD.Node(); - history.perform( + context.perform( iD.actions.AddMidpoint(midpoint, node), ReplaceTemporaryNode(node), annotation); finished = true; - controller.enter(mode); + context.enter(mode); }; // Finish the draw operation, removing the temporary node. If the way has enough // nodes to be valid, it's selected. Otherwise, return to browse mode. drawWay.finish = function() { - history.pop(); + context.pop(); finished = true; - var way = history.graph().entity(wayId); + var way = context.entity(wayId); if (way) { - controller.enter(iD.modes.Select([way.id], true)); + context.enter(iD.modes.Select(context, [way.id], true)); } else { - controller.enter(iD.modes.Browse()); + context.enter(iD.modes.Browse(context)); } }; // Cancel the draw operation and return to browse, deleting everything drawn. drawWay.cancel = function() { - history.perform( + context.perform( d3.functor(baseGraph), t('operations.cancel_draw.annotation')); finished = true; - controller.enter(iD.modes.Browse()); + context.enter(iD.modes.Browse(context)); }; return d3.rebind(drawWay, event, 'on'); diff --git a/js/id/behavior/hash.js b/js/id/behavior/hash.js index 65e748e28..871b73a35 100644 --- a/js/id/behavior/hash.js +++ b/js/id/behavior/hash.js @@ -1,4 +1,4 @@ -iD.behavior.Hash = function(controller, map) { +iD.behavior.Hash = function(context) { var s0 = null, // cached location.hash lat = 90 - 1e-8; // allowable latitude range @@ -27,13 +27,13 @@ iD.behavior.Hash = function(controller, map) { }; var move = _.throttle(function() { - var s1 = formatter(map); + var s1 = formatter(context.map()); if (s0 !== s1) location.replace(s0 = s1); // don't recenter the map! }, 100); function hashchange() { if (location.hash === s0) return; // ignore spurious hashchange events - if (parser(map, (s0 = location.hash).substring(1))) { + if (parser(context.map(), (s0 = location.hash).substring(1))) { move(); // replace bogus hash } } @@ -42,24 +42,24 @@ iD.behavior.Hash = function(controller, map) { // do so before any features are loaded. thus wait for the feature to // be loaded and then select function willselect(id) { - map.on('drawn.hash', function() { - var entity = map.history().graph().entity(id); - if (entity === undefined) return; - else selectoff(); - controller.enter(iD.modes.Select([entity.id])); - map.on('drawn.hash', null); + context.map().on('drawn.hash', function() { + if (!context.entity(id)) return; + selectoff(); + context.enter(iD.modes.Select([id])); }); - controller.on('enter.hash', function() { - if (controller.mode.id !== 'browse') selectoff(); + + context.controller().on('enter.hash', function() { + if (context.mode().id !== 'browse') selectoff(); }); } function selectoff() { - map.on('drawn.hash', null); + context.map().on('drawn.hash', null); } function hash() { - map.on('move.hash', move); + context.map() + .on('move.hash', move); d3.select(window) .on('hashchange.hash', hashchange); @@ -75,7 +75,8 @@ iD.behavior.Hash = function(controller, map) { } hash.off = function() { - map.on('move.hash', null); + context.map() + .on('move.hash', null); d3.select(window) .on('hashchange.hash', null); diff --git a/js/id/behavior/select.js b/js/id/behavior/select.js index cee2cc940..e078fdbf1 100644 --- a/js/id/behavior/select.js +++ b/js/id/behavior/select.js @@ -1,12 +1,10 @@ -iD.behavior.Select = function(mode) { - var controller = mode.controller; - +iD.behavior.Select = function(context) { function click() { var datum = d3.select(d3.event.target).datum(); if (datum instanceof iD.Entity) { - controller.enter(iD.modes.Select([datum.id])); + context.enter(iD.modes.Select(context, [datum.id])); } else { - controller.enter(iD.modes.Browse()); + context.enter(iD.modes.Browse(context)); } } diff --git a/js/id/context.js b/js/id/context.js new file mode 100644 index 000000000..60f878b32 --- /dev/null +++ b/js/id/context.js @@ -0,0 +1,44 @@ +iD.Context = function() { + var history = iD.History(), + connection = iD.Connection(), + controller = iD.Controller(), + container, + map = iD.Map().connection(connection).history(history); + + var context = {}; + + context.container = function (_) { + if (!arguments.length) return container; + container = _; + return context; + }; + + context.connection = function () { return connection; }; + + context.history = function () { return history; }; + context.graph = history.graph; + context.perform = history.perform; + context.replace = history.replace; + context.pop = history.pop; + context.undo = history.undo; + context.redo = history.undo; + context.changes = history.changes; + + context.entity = function (id) { return history.graph().entity(id); }; + context.geometry = function (id) { return context.entity(id).geometry(history.graph()); }; + + context.controller = function () { return controller; }; + context.enter = controller.enter; + context.mode = function () { return controller.mode; }; + + context.install = function (behavior) { context.surface().call(behavior); }; + context.uninstall = function (behavior) { context.surface().call(behavior.off); }; + + context.map = function () { return map; }; + context.background = function () { return map.background; }; + context.surface = function () { return map.surface; }; + context.projection = map.projection; + context.tail = map.tail; + + return context; +}; diff --git a/js/id/controller.js b/js/id/controller.js index 6c4e0fd1e..af1e7c388 100644 --- a/js/id/controller.js +++ b/js/id/controller.js @@ -1,14 +1,10 @@ // A controller holds a single action at a time and calls `.enter` and `.exit` // to bind and unbind actions. -iD.Controller = function(map, history) { +iD.Controller = function() { var event = d3.dispatch('enter', 'exit'); var controller = { mode: null }; controller.enter = function(mode) { - mode.controller = controller; - mode.history = history; - mode.map = map; - if (controller.mode) { controller.mode.exit(); event.exit(controller.mode); diff --git a/js/id/id.js b/js/id/id.js index 3e8aabcc3..3c2d0c482 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -2,17 +2,22 @@ window.iD = function(container) { // the reported, displayed version of iD. var version = '0.0.0-alpha1'; - var connection = iD.Connection() - .version(version), - history = iD.History(), - map = iD.Map() - .connection(connection) - .history(history), - controller = iD.Controller(map, history); + var context = iD.Context(); - map.background.source(iD.BackgroundSource.Bing); + var connection = context.connection(), + history = context.history(), + map = context.map(), + controller = context.controller(); + + context.connection() + .version(version); + + context.background() + .source(iD.BackgroundSource.Bing); function editor(container) { + context.container(container); + if (!iD.supported()) { container.html('This editor is supported in Firefox, Chrome, Safari, Opera, ' + 'and Internet Explorer 9 and above. Please upgrade your browser ' + @@ -39,8 +44,14 @@ window.iD = function(container) { var buttons_joined = limiter.append('div') .attr('class', 'button-wrap joined col4'); + var modes = [ + iD.modes.Browse(context), + iD.modes.AddPoint(context), + iD.modes.AddLine(context), + iD.modes.AddArea(context)]; + var buttons = buttons_joined.selectAll('button.add-button') - .data([iD.modes.Browse(), iD.modes.AddPoint(), iD.modes.AddLine(), iD.modes.AddArea()]) + .data(modes) .enter().append('button') .attr('tabindex', -1) .attr('class', function (mode) { return mode.title + ' add-button col3'; }) @@ -57,7 +68,7 @@ window.iD = function(container) { } else { buttons.attr('disabled', 'disabled'); notice.message(true); - controller.enter(iD.modes.Browse()); + controller.enter(iD.modes.Browse(context)); } } @@ -106,7 +117,7 @@ window.iD = function(container) { var save_button = limiter.append('div').attr('class','button-wrap col1').append('button') .attr('class', 'save col12') - .call(iD.ui.save().map(map).controller(controller)); + .call(iD.ui.save(context)); var zoom = container.append('div') .attr('class', 'zoombuttons map-control') @@ -227,14 +238,14 @@ window.iD = function(container) { .on('⌃+⇧+Z', function() { history.redo(); }) .on('⌫', function() { d3.event.preventDefault(); }); - [iD.modes.Browse(), iD.modes.AddPoint(), iD.modes.AddLine(), iD.modes.AddArea()].forEach(function(m) { + modes.forEach(function(m) { keybinding.on(m.key, function() { if (map.editable()) controller.enter(m); }); }); d3.select(document) .call(keybinding); - var hash = iD.behavior.Hash(controller, map); + var hash = iD.behavior.Hash(context); hash(); @@ -246,7 +257,7 @@ window.iD = function(container) { .on('logout.editor', connection.logout) .on('login.editor', connection.authenticate)); - controller.enter(iD.modes.Browse()); + controller.enter(iD.modes.Browse(context)); if (!localStorage.sawSplash) { iD.ui.splash(); diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js index bda4e0daa..ae2e008ee 100644 --- a/js/id/modes/add_area.js +++ b/js/id/modes/add_area.js @@ -1,4 +1,4 @@ -iD.modes.AddArea = function() { +iD.modes.AddArea = function(context) { var mode = { id: 'add-area', button: 'area', @@ -11,77 +11,73 @@ iD.modes.AddArea = function() { defaultTags = {area: 'yes'}; mode.enter = function() { - var map = mode.map, - history = mode.history, - controller = mode.controller; - function start(loc) { - var graph = history.graph(), + var graph = context.graph(), node = iD.Node({loc: loc}), way = iD.Way({tags: defaultTags}); - history.perform( + context.perform( iD.actions.AddEntity(node), iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id), iD.actions.AddVertex(way.id, node.id)); - controller.enter(iD.modes.DrawArea(way.id, graph)); + context.enter(iD.modes.DrawArea(context, way.id, graph)); } function startFromWay(other, loc, index) { - var graph = history.graph(), + var graph = context.graph(), node = iD.Node({loc: loc}), way = iD.Way({tags: defaultTags}); - history.perform( + context.perform( iD.actions.AddEntity(node), iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id), iD.actions.AddVertex(way.id, node.id), iD.actions.AddVertex(other.id, node.id, index)); - controller.enter(iD.modes.DrawArea(way.id, graph)); + context.enter(iD.modes.DrawArea(context, way.id, graph)); } function startFromNode(node) { - var graph = history.graph(), + var graph = context.graph(), way = iD.Way({tags: defaultTags}); - history.perform( + context.perform( iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id), iD.actions.AddVertex(way.id, node.id)); - controller.enter(iD.modes.DrawArea(way.id, graph)); + context.enter(iD.modes.DrawArea(context, way.id, graph)); } function startFromMidpoint(midpoint) { - var graph = history.graph(), + var graph = context.graph(), node = iD.Node(), way = iD.Way({tags: defaultTags}); - history.perform( + context.perform( iD.actions.AddMidpoint(midpoint, node), iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id), iD.actions.AddVertex(way.id, node.id)); - controller.enter(iD.modes.DrawArea(way.id, graph)); + context.enter(iD.modes.DrawArea(context, way.id, graph)); } - behavior = iD.behavior.AddWay(mode) + behavior = iD.behavior.AddWay(context) .on('start', start) .on('startFromWay', startFromWay) .on('startFromNode', startFromNode) .on('startFromMidpoint', startFromMidpoint); - mode.map.surface.call(behavior); - mode.map.tail(t('modes.add_area.tail')); + context.install(behavior); + context.tail(t('modes.add_area.tail')); }; mode.exit = function() { - mode.map.surface.call(behavior.off); + context.uninstall(behavior); }; return mode; diff --git a/js/id/modes/add_line.js b/js/id/modes/add_line.js index 92e9a64d7..79c5f3031 100644 --- a/js/id/modes/add_line.js +++ b/js/id/modes/add_line.js @@ -1,4 +1,4 @@ -iD.modes.AddLine = function() { +iD.modes.AddLine = function(context) { var mode = { id: 'add-line', button: 'line', @@ -11,84 +11,80 @@ iD.modes.AddLine = function() { defaultTags = {highway: 'residential'}; mode.enter = function() { - var map = mode.map, - history = mode.history, - controller = mode.controller; - function start(loc) { - var graph = history.graph(), + var graph = context.graph(), node = iD.Node({loc: loc}), way = iD.Way({tags: defaultTags}); - history.perform( + context.perform( iD.actions.AddEntity(node), iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id)); - controller.enter(iD.modes.DrawLine(way.id, 'forward', graph)); + context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); } function startFromWay(other, loc, index) { - var graph = history.graph(), + var graph = context.graph(), node = iD.Node({loc: loc}), way = iD.Way({tags: defaultTags}); - history.perform( + context.perform( iD.actions.AddEntity(node), iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id), iD.actions.AddVertex(other.id, node.id, index)); - controller.enter(iD.modes.DrawLine(way.id, 'forward', graph)); + context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); } function startFromNode(node) { - var graph = history.graph(), + var graph = context.graph(), parent = graph.parentWays(node)[0], isLine = parent && parent.geometry(graph) === 'line'; if (isLine && parent.first() === node.id) { - controller.enter(iD.modes.DrawLine(parent.id, 'backward', graph)); + context.enter(iD.modes.DrawLine(context, parent.id, 'backward', graph)); } else if (isLine && parent.last() === node.id) { - controller.enter(iD.modes.DrawLine(parent.id, 'forward', graph)); + context.enter(iD.modes.DrawLine(context, parent.id, 'forward', graph)); } else { var way = iD.Way({tags: defaultTags}); - history.perform( + context.perform( iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id)); - controller.enter(iD.modes.DrawLine(way.id, 'forward', graph)); + context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); } } function startFromMidpoint(midpoint) { - var graph = history.graph(), + var graph = context.graph(), node = iD.Node(), way = iD.Way({tags: defaultTags}); - history.perform( + context.perform( iD.actions.AddMidpoint(midpoint, node), iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id)); - controller.enter(iD.modes.DrawLine(way.id, 'forward', graph)); + context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); } - behavior = iD.behavior.AddWay(mode) + behavior = iD.behavior.AddWay(context) .on('start', start) .on('startFromWay', startFromWay) .on('startFromNode', startFromNode) .on('startFromMidpoint', startFromMidpoint); - mode.map.surface.call(behavior); - mode.map.tail(t('modes.add_line.tail')); + context.install(behavior); + context.tail(t('modes.add_line.tail')); }; mode.exit = function() { - mode.map.surface.call(behavior.off); + context.uninstall(behavior); }; return mode; diff --git a/js/id/modes/add_point.js b/js/id/modes/add_point.js index 6dd524bb5..c3dc7f075 100644 --- a/js/id/modes/add_point.js +++ b/js/id/modes/add_point.js @@ -1,4 +1,4 @@ -iD.modes.AddPoint = function() { +iD.modes.AddPoint = function(context) { var mode = { id: 'add-point', title: t('modes.add_point.title'), @@ -9,18 +9,14 @@ iD.modes.AddPoint = function() { var behavior; mode.enter = function() { - var map = mode.map, - history = mode.history, - controller = mode.controller; - function add(loc) { var node = iD.Node({loc: loc}); - history.perform( + context.perform( iD.actions.AddEntity(node), t('operations.add.annotation.point')); - controller.enter(iD.modes.Select([node.id], true)); + context.enter(iD.modes.Select(context, [node.id], true)); } function addWay(way, loc, index) { @@ -32,10 +28,10 @@ iD.modes.AddPoint = function() { } function cancel() { - controller.enter(iD.modes.Browse()); + context.enter(iD.modes.Browse(context)); } - behavior = iD.behavior.Draw(map) + behavior = iD.behavior.Draw(context) .on('click', add) .on('clickWay', addWay) .on('clickNode', addNode) @@ -43,16 +39,13 @@ iD.modes.AddPoint = function() { .on('cancel', cancel) .on('finish', cancel); - mode.map.surface.call(behavior); - mode.map.tail(t('modes.add_point.tail')); + context.install(behavior); + context.tail(t('modes.add_point.tail')); }; mode.exit = function() { - var map = mode.map, - surface = map.surface; - - map.tail(false); - behavior.off(surface); + context.tail(false); + context.uninstall(behavior); }; return mode; diff --git a/js/id/modes/browse.js b/js/id/modes/browse.js index 7652576ea..33add1262 100644 --- a/js/id/modes/browse.js +++ b/js/id/modes/browse.js @@ -1,4 +1,4 @@ -iD.modes.Browse = function() { +iD.modes.Browse = function(context) { var mode = { button: 'browse', id: 'browse', @@ -7,27 +7,21 @@ iD.modes.Browse = function() { key: t('modes.browse.key') }; - var behaviors; + var behaviors = [ + iD.behavior.Hover(), + iD.behavior.Select(context), + iD.behavior.DragNode(context), + iD.behavior.DragMidpoint(context)]; mode.enter = function() { - var surface = mode.map.surface; - - behaviors = [ - iD.behavior.Hover(), - iD.behavior.Select(mode), - iD.behavior.DragNode(mode), - iD.behavior.DragMidpoint(mode)]; - behaviors.forEach(function(behavior) { - behavior(surface); + context.install(behavior); }); }; mode.exit = function() { - var surface = mode.map.surface; - behaviors.forEach(function(behavior) { - behavior.off(surface); + context.uninstall(behavior); }); }; diff --git a/js/id/modes/draw_area.js b/js/id/modes/draw_area.js index 9801a092c..aee2f153c 100644 --- a/js/id/modes/draw_area.js +++ b/js/id/modes/draw_area.js @@ -1,4 +1,4 @@ -iD.modes.DrawArea = function(wayId, baseGraph) { +iD.modes.DrawArea = function(context, wayId, baseGraph) { var mode = { button: 'area', id: 'draw-area' @@ -7,11 +7,11 @@ iD.modes.DrawArea = function(wayId, baseGraph) { var behavior; mode.enter = function() { - var way = mode.history.graph().entity(wayId), + var way = context.entity(wayId), headId = way.nodes[way.nodes.length - 2], tailId = way.first(); - behavior = iD.behavior.DrawWay(wayId, -1, mode, baseGraph); + behavior = iD.behavior.DrawWay(context, wayId, -1, mode, baseGraph); var addNode = behavior.addNode; @@ -23,12 +23,12 @@ iD.modes.DrawArea = function(wayId, baseGraph) { } }; - mode.map.surface.call(behavior); - mode.map.tail(t('modes.draw_area.tail')); + context.install(behavior); + context.tail(t('modes.draw_area.tail')); }; mode.exit = function() { - mode.map.surface.call(behavior.off); + context.uninstall(behavior); }; return mode; diff --git a/js/id/modes/draw_line.js b/js/id/modes/draw_line.js index 5ae8c2703..9fd07c937 100644 --- a/js/id/modes/draw_line.js +++ b/js/id/modes/draw_line.js @@ -1,4 +1,4 @@ -iD.modes.DrawLine = function(wayId, direction, baseGraph) { +iD.modes.DrawLine = function(context, wayId, direction, baseGraph) { var mode = { button: 'line', id: 'draw-line' @@ -7,11 +7,11 @@ iD.modes.DrawLine = function(wayId, direction, baseGraph) { var behavior; mode.enter = function() { - var way = mode.history.graph().entity(wayId), + var way = context.entity(wayId), index = (direction === 'forward') ? undefined : 0, headId = (direction === 'forward') ? way.last() : way.first(); - behavior = iD.behavior.DrawWay(wayId, index, mode, baseGraph); + behavior = iD.behavior.DrawWay(context, wayId, index, mode, baseGraph); var addNode = behavior.addNode; @@ -23,12 +23,12 @@ iD.modes.DrawLine = function(wayId, direction, baseGraph) { } }; - mode.map.surface.call(behavior); - mode.map.tail(t('modes.draw_line.tail')); + context.install(behavior); + context.tail(t('modes.draw_line.tail')); }; mode.exit = function() { - mode.map.surface.call(behavior.off); + context.uninstall(behavior); }; return mode; diff --git a/js/id/modes/move_way.js b/js/id/modes/move_way.js index 5c262e259..30c473406 100644 --- a/js/id/modes/move_way.js +++ b/js/id/modes/move_way.js @@ -1,4 +1,4 @@ -iD.modes.MoveWay = function(wayId) { +iD.modes.MoveWay = function(context, wayId) { var mode = { id: 'move-way' }; @@ -6,55 +6,53 @@ iD.modes.MoveWay = function(wayId) { var keybinding = d3.keybinding('move-way'); mode.enter = function() { - var map = mode.map, - history = mode.history, - graph = history.graph(), - selection = map.surface, - controller = mode.controller, - projection = map.projection, - way = graph.entity(wayId), - origin = d3.mouse(selection.node()), - annotation = t('operations.move.annotation.' + way.geometry(graph)); + var origin = point(), + annotation = t('operations.move.annotation.' + context.geometry(wayId)); // If intiated via keyboard if (!origin[0] && !origin[1]) origin = null; - history.perform( + context.perform( iD.actions.Noop(), annotation); + function point() { + return d3.mouse(context.surface().node()); + } + function move() { - var p = d3.mouse(selection.node()), + var p = point(), delta = origin ? [p[0] - origin[0], p[1] - origin[1]] : [0, 0]; origin = p; - history.replace( - iD.actions.MoveWay(wayId, delta, projection), + context.replace( + iD.actions.MoveWay(wayId, delta, context.projection), annotation); } function finish() { d3.event.stopPropagation(); - controller.enter(iD.modes.Select([way.id], true)); + context.enter(iD.modes.Select(context, [wayId], true)); } function cancel() { - history.pop(); - controller.enter(iD.modes.Select([way.id], true)); + context.pop(); + context.enter(iD.modes.Select(context, [wayId], true)); } function undone() { - controller.enter(iD.modes.Browse()); + context.enter(iD.modes.Browse(context)); } - selection + context.selection() .on('mousemove.move-way', move) .on('click.move-way', finish); - history.on('undone.move-way', undone); + context.history() + .on('undone.move-way', undone); keybinding .on('⎋', cancel) @@ -65,15 +63,12 @@ iD.modes.MoveWay = function(wayId) { }; mode.exit = function() { - var map = mode.map, - history = mode.history, - selection = map.surface; - - selection + context.selection() .on('mousemove.move-way', null) .on('click.move-way', null); - history.on('undone.move-way', null); + context.history() + .on('undone.move-way', null); keybinding.off(); }; diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 8d29450d6..df445862d 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -1,4 +1,4 @@ -iD.modes.Select = function(selection, initial) { +iD.modes.Select = function(context, selection, initial) { var mode = { id: 'select', button: 'browse' @@ -6,12 +6,16 @@ iD.modes.Select = function(selection, initial) { var inspector = iD.ui.inspector().initial(!!initial), keybinding = d3.keybinding('select'), - behaviors, + behaviors = [ + iD.behavior.Hover(), + iD.behavior.Select(context), + iD.behavior.DragNode(context), + iD.behavior.DragMidpoint(context)], radialMenu; function changeTags(d, tags) { if (!_.isEqual(singular().tags, tags)) { - mode.history.perform( + context.perform( iD.actions.ChangeTags(d.id, tags), t('operations.change_tags.annotation')); } @@ -19,7 +23,7 @@ iD.modes.Select = function(selection, initial) { function singular() { if (selection.length === 1) { - return mode.map.history().graph().entity(selection[0]); + return context.entity(selection[0]); } } @@ -28,24 +32,14 @@ iD.modes.Select = function(selection, initial) { }; mode.enter = function() { - var map = mode.map, - history = map.history(), - graph = history.graph(), - surface = map.surface, - entity = singular(); - - behaviors = [ - iD.behavior.Hover(), - iD.behavior.Select(mode), - iD.behavior.DragNode(mode), - iD.behavior.DragMidpoint(mode)]; + var entity = singular(); behaviors.forEach(function(behavior) { - behavior(surface); + context.install(behavior); }); var operations = d3.values(iD.operations) - .map(function (o) { return o(selection, mode); }) + .map(function (o) { return o(selection, context); }) .filter(function (o) { return o.available(); }); operations.forEach(function(operation) { @@ -62,9 +56,10 @@ iD.modes.Select = function(selection, initial) { }), true)); if (entity) { - inspector.graph(graph); + inspector.graph(context.graph()); - d3.select('.inspector-wrap') + context.container() + .select('.inspector-wrap') .style('display', 'block') .style('opacity', 1) .datum(entity) @@ -73,38 +68,38 @@ iD.modes.Select = function(selection, initial) { if (d3.event) { // Pan the map if the clicked feature intersects with the position // of the inspector - var inspector_size = d3.select('.inspector-wrap').size(), - map_size = mode.map.size(), + var inspector_size = context.container().select('.inspector-wrap').size(), + map_size = context.map().size(), offset = 50, shift_left = d3.event.x - map_size[0] + inspector_size[0] + offset, center = (map_size[0] / 2) + shift_left + offset; if (shift_left > 0 && inspector_size[1] > d3.event.y) { - mode.map.centerEase(mode.map.projection.invert([center, map_size[1]/2])); + context.map().centerEase(context.projection.invert([center, map_size[1]/2])); } } inspector .on('changeTags', changeTags) - .on('close', function() { mode.controller.enter(iD.modes.Browse()); }); + .on('close', function() { context.enter(iD.modes.Browse(context)); }); - history.on('change.select', function() { + context.history().on('change.select', function() { // Exit mode if selected entity gets undone var oldEntity = entity, - newEntity = history.graph().entity(selection[0]); + newEntity = context.entity(selection[0]); if (!newEntity) { - mode.controller.enter(iD.modes.Browse()); + context.enter(iD.modes.Browse(context)); } else if (!_.isEqual(oldEntity.tags, newEntity.tags)) { inspector.tags(newEntity.tags); } - surface.call(radialMenu.close); + context.surface().call(radialMenu.close); }); } - map.on('move.select', function() { - surface.call(radialMenu.close); + context.map().on('move.select', function() { + context.surface().call(radialMenu.close); }); function dblclick() { @@ -113,10 +108,10 @@ iD.modes.Select = function(selection, initial) { if (datum instanceof iD.Way && !target.classed('fill')) { var choice = iD.geo.chooseIndex(datum, - d3.mouse(mode.map.surface.node()), mode.map), + d3.mouse(context.surface().node()), context.map()), node = iD.Node({ loc: choice.loc }); - history.perform( + context.perform( iD.actions.AddEntity(node), iD.actions.AddVertex(datum.id, node.id, choice.index), t('operations.add.annotation.vertex')); @@ -129,7 +124,8 @@ iD.modes.Select = function(selection, initial) { d3.select(document) .call(keybinding); - surface.on('dblclick.select', dblclick) + context.surface() + .on('dblclick.select', dblclick) .selectAll("*") .filter(function (d) { return d && selection.indexOf(d.id) >= 0; }) .classed('selected', true); @@ -137,25 +133,23 @@ iD.modes.Select = function(selection, initial) { radialMenu = iD.ui.RadialMenu(operations); if (d3.event && !initial) { - var loc = map.mouseCoordinates(); + var loc = context.map().mouseCoordinates(); if (entity && entity.type === 'node') { loc = entity.loc; } - surface.call(radialMenu, map.projection(loc)); + context.surface().call(radialMenu, context.projection(loc)); } }; mode.exit = function () { - var surface = mode.map.surface, - history = mode.history; - if (singular()) { changeTags(singular(), inspector.tags()); } - d3.select('.inspector-wrap') + context.container() + .select('.inspector-wrap') .style('display', 'none') .html(''); @@ -164,7 +158,7 @@ iD.modes.Select = function(selection, initial) { d3.selectAll('div.typeahead').remove(); behaviors.forEach(function(behavior) { - behavior.off(surface); + context.uninstall(behavior); }); var q = iD.util.stringQs(location.hash.substring(1)); @@ -172,13 +166,14 @@ iD.modes.Select = function(selection, initial) { keybinding.off(); - history.on('change.select', null); + context.history() + .on('change.select', null); - surface.on('dblclick.select', null) + context.surface() + .call(radialMenu.close) + .on('dblclick.select', null) .selectAll(".selected") .classed('selected', false); - - surface.call(radialMenu.close); }; return mode; diff --git a/js/id/operations/circularize.js b/js/id/operations/circularize.js index 4b0f27bf9..419a6e9eb 100644 --- a/js/id/operations/circularize.js +++ b/js/id/operations/circularize.js @@ -1,25 +1,19 @@ -iD.operations.Circularize = function(selection, mode) { +iD.operations.Circularize = function(selection, context) { var entityId = selection[0], - history = mode.map.history(), - action = iD.actions.Circularize(entityId, mode.map); + action = iD.actions.Circularize(entityId, context.map()); var operation = function() { - var graph = history.graph(), - entity = graph.entity(entityId), - annotation = t('operations.circularize.annotation.' + entity.geometry(graph)); - - history.perform(action, annotation); + var annotation = t('operations.circularize.annotation.' + context.geometry(entityId)); + context.perform(action, annotation); }; operation.available = function() { - var graph = history.graph(), - entity = graph.entity(entityId); - return selection.length === 1 && entity.type === 'way'; + return selection.length === 1 && + context.entity(entityId).type === 'way'; }; operation.enabled = function() { - var graph = history.graph(); - return action.enabled(graph); + return action.enabled(context.graph()); }; operation.id = "circularize"; diff --git a/js/id/operations/delete.js b/js/id/operations/delete.js index 12df22a09..d631a2ed7 100644 --- a/js/id/operations/delete.js +++ b/js/id/operations/delete.js @@ -1,21 +1,18 @@ -iD.operations.Delete = function(selection, mode) { - var entityId = selection[0], - history = mode.map.history(); +iD.operations.Delete = function(selection, context) { + var entityId = selection[0]; var operation = function() { - var graph = history.graph(), - entity = graph.entity(entityId), + var entity = context.entity(entityId), action = {way: iD.actions.DeleteWay, node: iD.actions.DeleteNode}[entity.type], - annotation = t('operations.delete.annotation.' + entity.geometry(graph)); + annotation = t('operations.delete.annotation.' + context.geometry(entityId)); - history.perform( + context.perform( action(entityId), annotation); }; operation.available = function() { - var graph = history.graph(), - entity = graph.entity(entityId); + var entity = context.entity(entityId); return selection.length === 1 && (entity.type === 'way' || entity.type === 'node'); }; diff --git a/js/id/operations/move.js b/js/id/operations/move.js index 10405cc53..d9da39f64 100644 --- a/js/id/operations/move.js +++ b/js/id/operations/move.js @@ -1,15 +1,13 @@ -iD.operations.Move = function(selection, mode) { - var entityId = selection[0], - history = mode.map.history(); +iD.operations.Move = function(selection, context) { + var entityId = selection[0]; var operation = function() { - mode.controller.enter(iD.modes.MoveWay(entityId)); + context.enter(iD.modes.MoveWay(context, entityId)); }; operation.available = function() { - var graph = history.graph(); return selection.length === 1 && - graph.entity(entityId).type === 'way'; + context.entity(entityId).type === 'way'; }; operation.enabled = function() { diff --git a/js/id/operations/reverse.js b/js/id/operations/reverse.js index 78b8789dd..77bacc64e 100644 --- a/js/id/operations/reverse.js +++ b/js/id/operations/reverse.js @@ -1,18 +1,15 @@ -iD.operations.Reverse = function(selection, mode) { - var entityId = selection[0], - history = mode.map.history(); +iD.operations.Reverse = function(selection, context) { + var entityId = selection[0]; var operation = function() { - history.perform( + context.perform( iD.actions.ReverseWay(entityId), t('operations.reverse.annotation')); }; operation.available = function() { - var graph = history.graph(), - entity = graph.entity(entityId); return selection.length === 1 && - entity.geometry(graph) === 'line'; + context.geometry(entityId) === 'line'; }; operation.enabled = function() { diff --git a/js/id/operations/split.js b/js/id/operations/split.js index 4274fc41b..0d7ff6ae4 100644 --- a/js/id/operations/split.js +++ b/js/id/operations/split.js @@ -1,22 +1,18 @@ -iD.operations.Split = function(selection, mode) { +iD.operations.Split = function(selection, context) { var entityId = selection[0], - history = mode.map.history(), action = iD.actions.SplitWay(entityId); var operation = function() { - history.perform(action, t('operations.split.annotation')); + context.perform(action, t('operations.split.annotation')); }; operation.available = function() { - var graph = history.graph(), - entity = graph.entity(entityId); return selection.length === 1 && - entity.geometry(graph) === 'vertex'; + context.geometry(entityId) === 'vertex'; }; operation.enabled = function() { - var graph = history.graph(); - return action.enabled(graph); + return action.enabled(context.graph()); }; operation.id = "split"; diff --git a/js/id/operations/unjoin.js b/js/id/operations/unjoin.js index 2a40ec067..d4fcc1426 100644 --- a/js/id/operations/unjoin.js +++ b/js/id/operations/unjoin.js @@ -1,22 +1,18 @@ -iD.operations.Unjoin = function(selection, mode) { +iD.operations.Unjoin = function(selection, context) { var entityId = selection[0], - history = mode.map.history(), action = iD.actions.UnjoinNode(entityId); var operation = function() { - history.perform(action, 'Unjoined lines.'); + context.perform(action, 'Unjoined lines.'); }; operation.available = function() { - var graph = history.graph(), - entity = graph.entity(entityId); return selection.length === 1 && - entity.geometry(graph) === 'vertex'; + context.geometry(entityId) === 'vertex'; }; operation.enabled = function() { - var graph = history.graph(); - return action.enabled(graph); + return action.enabled(context.graph()); }; operation.id = "unjoin"; diff --git a/js/id/ui/save.js b/js/id/ui/save.js index b8085e21a..6098519da 100644 --- a/js/id/ui/save.js +++ b/js/id/ui/save.js @@ -1,11 +1,8 @@ -iD.ui.save = function() { - - var map, controller; - - function save(selection) { - - var history = map.history(), - connection = map.connection(), +iD.ui.save = function(context) { + return function (selection) { + var map = context.map(), + history = context.history(), + connection = context.connection(), tooltip = bootstrap.tooltip() .placement('bottom'); @@ -60,7 +57,7 @@ iD.ui.save = function() { .on('fix', function(d) { map.extent(d.entity.extent(map.history().graph())); if (map.zoom() > 19) map.zoom(19); - controller.enter(iD.modes.Select([d.entity.id])); + context.enter(iD.modes.Select(context, [d.entity.id])); modal.remove(); }) .on('save', commit)); @@ -88,19 +85,5 @@ iD.ui.save = function() { selection.call(tooltip.hide); } }); - } - - save.map = function(_) { - if (!arguments.length) return map; - map = _; - return save; }; - - save.controller = function(_) { - if (!arguments.length) return controller; - controller = _; - return save; - }; - - return save; }; diff --git a/test/index.html b/test/index.html index b85eb1b56..b56645ee7 100644 --- a/test/index.html +++ b/test/index.html @@ -119,6 +119,7 @@ + @@ -150,9 +151,6 @@ - - - @@ -162,8 +160,6 @@ - - @@ -189,6 +185,11 @@ + + + + + diff --git a/test/index_packaged.html b/test/index_packaged.html index 60979e236..11f8368f4 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -44,9 +44,6 @@ - - - @@ -56,8 +53,6 @@ - - @@ -83,6 +78,11 @@ + + + + + diff --git a/test/spec/behavior/hash.js b/test/spec/behavior/hash.js index 7616bf536..5aceb53fc 100644 --- a/test/spec/behavior/hash.js +++ b/test/spec/behavior/hash.js @@ -1,19 +1,18 @@ describe("iD.behavior.Hash", function () { - var hash, map, controller; + mocha.globals('__onhashchange.hash'); + + var hash, context; beforeEach(function () { - map = { - on: function () { return map; }, - zoom: function () { return arguments.length ? map : 0; }, - center: function () { return arguments.length ? map : [0, 0]; }, - centerZoom: function () { return arguments.length ? map : [0, 0]; } - }; + context = iD.Context(); - controller = { - on: function () { return controller; } - }; + // Neuter connection + context.connection().loadTiles = function () {}; - hash = iD.behavior.Hash(controller, map); + hash = iD.behavior.Hash(context); + + d3.select(document.createElement('div')) + .call(context.map()); }); afterEach(function () { @@ -22,44 +21,41 @@ describe("iD.behavior.Hash", function () { it("sets hadHash if location.hash is present", function () { location.hash = "map=20.00/38.87952/-77.02405"; + hash(); + expect(hash.hadHash).to.be.true; }); it("centerZooms map to requested level", function () { location.hash = "map=20.00/38.87952/-77.02405"; - sinon.spy(map, 'centerZoom'); + hash(); - expect(map.centerZoom).to.have.been.calledWith([-77.02405,38.87952], 20.0); + + expect(context.map().center()[0]).to.be.closeTo(-77.02405, 0.1); + expect(context.map().center()[1]).to.be.closeTo(38.87952, 0.1); + expect(context.map().zoom()).to.equal(20.0); }); - describe("on window hashchange events", function () { - beforeEach(function () { - hash(); + it("centerZooms map at requested coordinates on hash change", function (done) { + hash(); + + d3.select(window).one('hashchange', function () { + expect(context.map().center()[0]).to.be.closeTo(-77.02405, 0.1); + expect(context.map().center()[1]).to.be.closeTo(38.87952, 0.1); + expect(context.map().zoom()).to.equal(20.0); + done(); }); - function onhashchange(fn) { - d3.select(window).one("hashchange", fn); - } - - it("centerZooms map at requested coordinates", function (done) { - onhashchange(function () { - expect(map.centerZoom).to.have.been.calledWith([-77.02405,38.87952], 20.0); - done(); - }); - - sinon.spy(map, 'centerZoom'); - location.hash = "#map=20.00/38.87952/-77.02405"; - }); + location.hash = "#map=20.00/38.87952/-77.02405"; }); - describe("on map move events", function () { - it("stores the current zoom and coordinates in location.hash", function () { - sinon.stub(map, 'on') - .withArgs("move.hash", sinon.match.instanceOf(Function)) - .yields(); - hash(); - expect(location.hash).to.equal("#map=0.00/0/0"); - }); + it("stores the current zoom and coordinates in location.hash on map move events", function () { + hash(); + + context.map().center([38.9, -77.0]); + context.map().zoom(2.0); + + expect(location.hash).to.equal("#map=2.00/-77.0/38.9"); }); }); diff --git a/test/spec/modes/add_point.js b/test/spec/modes/add_point.js index a284cf682..5c544c0c5 100644 --- a/test/spec/modes/add_point.js +++ b/test/spec/modes/add_point.js @@ -1,41 +1,36 @@ describe("iD.modes.AddPoint", function () { - var container, map, history, controller, mode; + var context; beforeEach(function () { - container = d3.select('body').append('div'); - history = iD.History(); - map = iD.Map().history(history); - controller = iD.Controller(map, history); + var container = d3.select(document.createElement('div')); - container.call(map); - container.append('div') + context = iD.Context() + .container(container); + + container.call(context.map()) + .append('div') .attr('class', 'inspector-wrap'); - mode = iD.modes.AddPoint(); - controller.enter(mode); - }); - - afterEach(function() { - container.remove(); + context.enter(iD.modes.AddPoint(context)); }); describe("clicking the map", function () { it("adds a node", function () { - happen.click(map.surface.node(), {}); - expect(history.changes().created).to.have.length(1); + happen.click(context.surface().node(), {}); + expect(context.changes().created).to.have.length(1); }); it("selects the node", function () { - happen.click(map.surface.node(), {}); - expect(controller.mode.id).to.equal('select'); - expect(controller.mode.selection()).to.eql([history.changes().created[0].id]); + happen.click(context.surface().node(), {}); + expect(context.mode().id).to.equal('select'); + expect(context.mode().selection()).to.eql([context.changes().created[0].id]); }); }); describe("pressing ⎋", function () { it("exits to browse mode", function () { happen.keydown(document, {keyCode: 27}); - expect(controller.mode.id).to.equal('browse'); + expect(context.mode().id).to.equal('browse'); }); }); }); diff --git a/test/spec/ui/confirm.js b/test/spec/ui/confirm.js index a9380e1cc..428a8aa6c 100644 --- a/test/spec/ui/confirm.js +++ b/test/spec/ui/confirm.js @@ -2,10 +2,13 @@ describe("iD.ui.confirm", function () { it('can be instantiated', function () { var confirm = iD.ui.confirm(); expect(confirm).to.be.ok; + happen.keydown(document, {keyCode: 27}); // dismiss }); + it('can be dismissed', function () { var confirm = iD.ui.confirm(); happen.click(confirm.select('button').node()); expect(confirm.node().parentNode).to.be.null; + happen.keydown(document, {keyCode: 27}); // dismiss }); }); diff --git a/test/spec/ui/modal.js b/test/spec/ui/modal.js index 32a42878a..d7f818d80 100644 --- a/test/spec/ui/modal.js +++ b/test/spec/ui/modal.js @@ -4,5 +4,6 @@ describe("iD.ui.modal", function () { .select('.content') .text('foo'); expect(modal).to.be.ok; + happen.keydown(document, {keyCode: 27}); // dismiss }); }); From 579d6325636938443f34837fc05dd856db65539f Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 31 Jan 2013 17:19:01 -0500 Subject: [PATCH 002/332] Hoist functions up a scope --- js/id/modes/add_area.js | 120 +++++++++++++++++------------------ js/id/modes/add_line.js | 134 +++++++++++++++++++-------------------- js/id/modes/add_point.js | 62 +++++++++--------- 3 files changed, 155 insertions(+), 161 deletions(-) diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js index ae2e008ee..0a9496bb6 100644 --- a/js/id/modes/add_area.js +++ b/js/id/modes/add_area.js @@ -7,71 +7,69 @@ iD.modes.AddArea = function(context) { key: t('modes.add_area.key') }; - var behavior, - defaultTags = {area: 'yes'}; - - mode.enter = function() { - function start(loc) { - var graph = context.graph(), - node = iD.Node({loc: loc}), - way = iD.Way({tags: defaultTags}); - - context.perform( - iD.actions.AddEntity(node), - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id), - iD.actions.AddVertex(way.id, node.id)); - - context.enter(iD.modes.DrawArea(context, way.id, graph)); - } - - function startFromWay(other, loc, index) { - var graph = context.graph(), - node = iD.Node({loc: loc}), - way = iD.Way({tags: defaultTags}); - - context.perform( - iD.actions.AddEntity(node), - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id), - iD.actions.AddVertex(way.id, node.id), - iD.actions.AddVertex(other.id, node.id, index)); - - context.enter(iD.modes.DrawArea(context, way.id, graph)); - } - - function startFromNode(node) { - var graph = context.graph(), - way = iD.Way({tags: defaultTags}); - - context.perform( - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id), - iD.actions.AddVertex(way.id, node.id)); - - context.enter(iD.modes.DrawArea(context, way.id, graph)); - } - - function startFromMidpoint(midpoint) { - var graph = context.graph(), - node = iD.Node(), - way = iD.Way({tags: defaultTags}); - - context.perform( - iD.actions.AddMidpoint(midpoint, node), - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id), - iD.actions.AddVertex(way.id, node.id)); - - context.enter(iD.modes.DrawArea(context, way.id, graph)); - } - - behavior = iD.behavior.AddWay(context) + var behavior = iD.behavior.AddWay(context) .on('start', start) .on('startFromWay', startFromWay) .on('startFromNode', startFromNode) - .on('startFromMidpoint', startFromMidpoint); + .on('startFromMidpoint', startFromMidpoint), + defaultTags = {area: 'yes'}; + function start(loc) { + var graph = context.graph(), + node = iD.Node({loc: loc}), + way = iD.Way({tags: defaultTags}); + + context.perform( + iD.actions.AddEntity(node), + iD.actions.AddEntity(way), + iD.actions.AddVertex(way.id, node.id), + iD.actions.AddVertex(way.id, node.id)); + + context.enter(iD.modes.DrawArea(context, way.id, graph)); + } + + function startFromWay(other, loc, index) { + var graph = context.graph(), + node = iD.Node({loc: loc}), + way = iD.Way({tags: defaultTags}); + + context.perform( + iD.actions.AddEntity(node), + iD.actions.AddEntity(way), + iD.actions.AddVertex(way.id, node.id), + iD.actions.AddVertex(way.id, node.id), + iD.actions.AddVertex(other.id, node.id, index)); + + context.enter(iD.modes.DrawArea(context, way.id, graph)); + } + + function startFromNode(node) { + var graph = context.graph(), + way = iD.Way({tags: defaultTags}); + + context.perform( + iD.actions.AddEntity(way), + iD.actions.AddVertex(way.id, node.id), + iD.actions.AddVertex(way.id, node.id)); + + context.enter(iD.modes.DrawArea(context, way.id, graph)); + } + + function startFromMidpoint(midpoint) { + var graph = context.graph(), + node = iD.Node(), + way = iD.Way({tags: defaultTags}); + + context.perform( + iD.actions.AddMidpoint(midpoint, node), + iD.actions.AddEntity(way), + iD.actions.AddVertex(way.id, node.id), + iD.actions.AddVertex(way.id, node.id)); + + context.enter(iD.modes.DrawArea(context, way.id, graph)); + } + + mode.enter = function() { context.install(behavior); context.tail(t('modes.add_area.tail')); }; diff --git a/js/id/modes/add_line.js b/js/id/modes/add_line.js index 79c5f3031..7845e6e6f 100644 --- a/js/id/modes/add_line.js +++ b/js/id/modes/add_line.js @@ -7,78 +7,76 @@ iD.modes.AddLine = function(context) { key: t('modes.add_line.key') }; - var behavior, - defaultTags = {highway: 'residential'}; - - mode.enter = function() { - function start(loc) { - var graph = context.graph(), - node = iD.Node({loc: loc}), - way = iD.Way({tags: defaultTags}); - - context.perform( - iD.actions.AddEntity(node), - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id)); - - context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); - } - - function startFromWay(other, loc, index) { - var graph = context.graph(), - node = iD.Node({loc: loc}), - way = iD.Way({tags: defaultTags}); - - context.perform( - iD.actions.AddEntity(node), - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id), - iD.actions.AddVertex(other.id, node.id, index)); - - context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); - } - - function startFromNode(node) { - var graph = context.graph(), - parent = graph.parentWays(node)[0], - isLine = parent && parent.geometry(graph) === 'line'; - - if (isLine && parent.first() === node.id) { - context.enter(iD.modes.DrawLine(context, parent.id, 'backward', graph)); - - } else if (isLine && parent.last() === node.id) { - context.enter(iD.modes.DrawLine(context, parent.id, 'forward', graph)); - - } else { - var way = iD.Way({tags: defaultTags}); - - context.perform( - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id)); - - context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); - } - } - - function startFromMidpoint(midpoint) { - var graph = context.graph(), - node = iD.Node(), - way = iD.Way({tags: defaultTags}); - - context.perform( - iD.actions.AddMidpoint(midpoint, node), - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id)); - - context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); - } - - behavior = iD.behavior.AddWay(context) + var behavior = iD.behavior.AddWay(context) .on('start', start) .on('startFromWay', startFromWay) .on('startFromNode', startFromNode) - .on('startFromMidpoint', startFromMidpoint); + .on('startFromMidpoint', startFromMidpoint), + defaultTags = {highway: 'residential'}; + function start(loc) { + var graph = context.graph(), + node = iD.Node({loc: loc}), + way = iD.Way({tags: defaultTags}); + + context.perform( + iD.actions.AddEntity(node), + iD.actions.AddEntity(way), + iD.actions.AddVertex(way.id, node.id)); + + context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); + } + + function startFromWay(other, loc, index) { + var graph = context.graph(), + node = iD.Node({loc: loc}), + way = iD.Way({tags: defaultTags}); + + context.perform( + iD.actions.AddEntity(node), + iD.actions.AddEntity(way), + iD.actions.AddVertex(way.id, node.id), + iD.actions.AddVertex(other.id, node.id, index)); + + context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); + } + + function startFromNode(node) { + var graph = context.graph(), + parent = graph.parentWays(node)[0], + isLine = parent && parent.geometry(graph) === 'line'; + + if (isLine && parent.first() === node.id) { + context.enter(iD.modes.DrawLine(context, parent.id, 'backward', graph)); + + } else if (isLine && parent.last() === node.id) { + context.enter(iD.modes.DrawLine(context, parent.id, 'forward', graph)); + + } else { + var way = iD.Way({tags: defaultTags}); + + context.perform( + iD.actions.AddEntity(way), + iD.actions.AddVertex(way.id, node.id)); + + context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); + } + } + + function startFromMidpoint(midpoint) { + var graph = context.graph(), + node = iD.Node(), + way = iD.Way({tags: defaultTags}); + + context.perform( + iD.actions.AddMidpoint(midpoint, node), + iD.actions.AddEntity(way), + iD.actions.AddVertex(way.id, node.id)); + + context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); + } + + mode.enter = function() { context.install(behavior); context.tail(t('modes.add_line.tail')); }; diff --git a/js/id/modes/add_point.js b/js/id/modes/add_point.js index c3dc7f075..5a24dad25 100644 --- a/js/id/modes/add_point.js +++ b/js/id/modes/add_point.js @@ -6,46 +6,44 @@ iD.modes.AddPoint = function(context) { key: t('modes.add_point.key') }; - var behavior; + var behavior = iD.behavior.Draw(context) + .on('click', add) + .on('clickWay', addWay) + .on('clickNode', addNode) + .on('clickMidpoint', addNode) + .on('cancel', cancel) + .on('finish', cancel); + + function add(loc) { + var node = iD.Node({loc: loc}); + + context.perform( + iD.actions.AddEntity(node), + t('operations.add.annotation.point')); + + context.enter(iD.modes.Select(context, [node.id], true)); + } + + function addWay(way, loc, index) { + add(loc); + } + + function addNode(node) { + add(node.loc); + } + + function cancel() { + context.enter(iD.modes.Browse(context)); + } mode.enter = function() { - function add(loc) { - var node = iD.Node({loc: loc}); - - context.perform( - iD.actions.AddEntity(node), - t('operations.add.annotation.point')); - - context.enter(iD.modes.Select(context, [node.id], true)); - } - - function addWay(way, loc, index) { - add(loc); - } - - function addNode(node) { - add(node.loc); - } - - function cancel() { - context.enter(iD.modes.Browse(context)); - } - - behavior = iD.behavior.Draw(context) - .on('click', add) - .on('clickWay', addWay) - .on('clickNode', addNode) - .on('clickMidpoint', addNode) - .on('cancel', cancel) - .on('finish', cancel); - context.install(behavior); context.tail(t('modes.add_point.tail')); }; mode.exit = function() { - context.tail(false); context.uninstall(behavior); + context.tail(false); }; return mode; From 000ceb6467d7c79e22522a2575b413bd8d09b8f3 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 11:36:53 -0500 Subject: [PATCH 003/332] iD.Context -> iD All the UI setup code moved to iD.ui. --- Makefile | 1 - index.html | 9 +- index_packaged.html | 14 +- js/id/context.js | 44 ----- js/id/id.js | 323 +++++------------------------------ js/id/ui.js | 257 +++++++++++++++++++++++++++- test/index.html | 1 - test/spec/behavior/hash.js | 2 +- test/spec/modes/add_point.js | 2 +- 9 files changed, 319 insertions(+), 334 deletions(-) delete mode 100644 js/id/context.js diff --git a/Makefile b/Makefile index 598bce278..1db4f2efb 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,6 @@ all: \ js/lib/sha.js \ js/id/start.js \ js/id/id.js \ - js/id/context.js \ js/id/connection.js \ js/id/oauth.js \ js/id/services/*.js \ diff --git a/index.html b/index.html index 306575804..ab6070fbc 100644 --- a/index.html +++ b/index.html @@ -123,7 +123,6 @@ - @@ -135,9 +134,13 @@ locale.current = 'en'; d3.json('keys.json', function(err, keys) { var id = iD(); - id.connection().keys(keys) + + id.connection() + .keys(keys) .url('http://api06.dev.openstreetmap.org'); - d3.select("#iD").call(id); + + d3.select("#iD") + .call(id.ui()) }); diff --git a/index_packaged.html b/index_packaged.html index c48a21873..a961a5202 100644 --- a/index_packaged.html +++ b/index_packaged.html @@ -16,8 +16,16 @@
diff --git a/js/id/context.js b/js/id/context.js deleted file mode 100644 index 60f878b32..000000000 --- a/js/id/context.js +++ /dev/null @@ -1,44 +0,0 @@ -iD.Context = function() { - var history = iD.History(), - connection = iD.Connection(), - controller = iD.Controller(), - container, - map = iD.Map().connection(connection).history(history); - - var context = {}; - - context.container = function (_) { - if (!arguments.length) return container; - container = _; - return context; - }; - - context.connection = function () { return connection; }; - - context.history = function () { return history; }; - context.graph = history.graph; - context.perform = history.perform; - context.replace = history.replace; - context.pop = history.pop; - context.undo = history.undo; - context.redo = history.undo; - context.changes = history.changes; - - context.entity = function (id) { return history.graph().entity(id); }; - context.geometry = function (id) { return context.entity(id).geometry(history.graph()); }; - - context.controller = function () { return controller; }; - context.enter = controller.enter; - context.mode = function () { return controller.mode; }; - - context.install = function (behavior) { context.surface().call(behavior); }; - context.uninstall = function (behavior) { context.surface().call(behavior.off); }; - - context.map = function () { return map; }; - context.background = function () { return map.background; }; - context.surface = function () { return map.surface; }; - context.projection = map.projection; - context.tail = map.tail; - - return context; -}; diff --git a/js/id/id.js b/js/id/id.js index 3c2d0c482..42a8cfb11 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -1,291 +1,56 @@ -window.iD = function(container) { - // the reported, displayed version of iD. - var version = '0.0.0-alpha1'; +window.iD = function () { + var context = {}, + history = iD.History(), + connection = iD.Connection().version(iD.version), + controller = iD.Controller(), + container, + ui = iD.ui(context), + map = iD.Map().connection(connection).history(history); - var context = iD.Context(); + /* Straight accessors. Avoid using these if you can. */ + context.ui = function () { return ui; }; + context.connection = function () { return connection; }; + context.history = function () { return history; }; + context.controller = function () { return controller; }; + context.map = function () { return map; }; - var connection = context.connection(), - history = context.history(), - map = context.map(), - controller = context.controller(); + /* History delegation. */ + context.graph = history.graph; + context.perform = history.perform; + context.replace = history.replace; + context.pop = history.pop; + context.undo = history.undo; + context.redo = history.undo; + context.changes = history.changes; - context.connection() - .version(version); + context.entity = function (id) { return history.graph().entity(id); }; + context.geometry = function (id) { return context.entity(id).geometry(history.graph()); }; + + context.enter = controller.enter; + context.mode = function () { return controller.mode; }; + + context.install = function (behavior) { context.surface().call(behavior); }; + context.uninstall = function (behavior) { context.surface().call(behavior.off); }; + + context.background = function () { return map.background; }; + context.surface = function () { return map.surface; }; + context.projection = map.projection; + context.tail = map.tail; + + context.container = function (_) { + if (!arguments.length) return container; + container = _; + return context; + }; context.background() .source(iD.BackgroundSource.Bing); - function editor(container) { - context.container(container); - - if (!iD.supported()) { - container.html('This editor is supported in Firefox, Chrome, Safari, Opera, ' + - 'and Internet Explorer 9 and above. Please upgrade your browser ' + - 'or use Potlatch 2 to edit the map.') - .style('text-align:center;font-style:italic;'); - return; - } - - function hintprefix(x, y) { - return '' + y + '' + '
' + x + '
'; - } - - var m = container.append('div') - .attr('id', 'map') - .call(map); - - var bar = container.append('div') - .attr('id', 'bar') - .attr('class','pad1 fillD'); - - var limiter = bar.append('div') - .attr('class', 'limiter'); - - var buttons_joined = limiter.append('div') - .attr('class', 'button-wrap joined col4'); - - var modes = [ - iD.modes.Browse(context), - iD.modes.AddPoint(context), - iD.modes.AddLine(context), - iD.modes.AddArea(context)]; - - var buttons = buttons_joined.selectAll('button.add-button') - .data(modes) - .enter().append('button') - .attr('tabindex', -1) - .attr('class', function (mode) { return mode.title + ' add-button col3'; }) - .call(bootstrap.tooltip().placement('bottom').html(true)) - .attr('data-original-title', function (mode) { - return hintprefix(mode.key, mode.description); - }) - .on('click.editor', function (mode) { controller.enter(mode); }); - - function disableTooHigh() { - if (map.editable()) { - notice.message(false); - buttons.attr('disabled', null); - } else { - buttons.attr('disabled', 'disabled'); - notice.message(true); - controller.enter(iD.modes.Browse(context)); - } - } - - var notice = iD.ui.notice(limiter) - .message(false) - .on('zoom', function() { map.zoom(16); }); - - map.on('move.editor', _.debounce(function() { - disableTooHigh(); - contributors.call(iD.ui.contributors(map)); - }, 500)); - - buttons.append('span') - .attr('class', function(d) { - return d.id + ' icon icon-pre-text'; - }); - - buttons.append('span').attr('class', 'label').text(function (mode) { return mode.title; }); - - controller.on('enter.editor', function (entered) { - buttons.classed('active', function (mode) { return entered.button === mode.button; }); - container.classed("mode-" + entered.id, true); - }); - - controller.on('exit.editor', function (exited) { - container.classed("mode-" + exited.id, false); - }); - - var undo_buttons = limiter.append('div') - .attr('class', 'button-wrap joined col1'), - undo_tooltip = bootstrap.tooltip().placement('bottom').html(true); - - undo_buttons.append('button') - .attr({ id: 'undo', 'class': 'col6' }) - .property('disabled', true) - .html("") - .on('click.editor', history.undo) - .call(undo_tooltip); - - undo_buttons.append('button') - .attr({ id: 'redo', 'class': 'col6' }) - .property('disabled', true) - .html("") - .on('click.editor', history.redo) - .call(undo_tooltip); - - var save_button = limiter.append('div').attr('class','button-wrap col1').append('button') - .attr('class', 'save col12') - .call(iD.ui.save(context)); - - var zoom = container.append('div') - .attr('class', 'zoombuttons map-control') - .selectAll('button') - .data([['zoom-in', '+', map.zoomIn, 'Zoom In'], ['zoom-out', '-', map.zoomOut, 'Zoom Out']]) - .enter() - .append('button') - .attr('tabindex', -1) - .attr('class', function(d) { return d[0]; }) - .attr('title', function(d) { return d[3]; }) - .on('click.editor', function(d) { return d[2](); }) - .append('span') - .attr('class', function(d) { - return d[0] + ' icon'; - }); - - if (navigator.geolocation) { - container.append('div') - .call(iD.ui.geolocate(map)); - } - - var gc = container.append('div').attr('class', 'geocode-control map-control') - .call(iD.ui.geocoder().map(map)); - - container.append('div').attr('class', 'map-control layerswitcher-control') - .call(iD.ui.layerswitcher(map)); - - container.append('div') - .style('display', 'none') - .attr('class', 'inspector-wrap fr col5'); - - var about = container.append('div') - .attr('class','col12 about-block fillD pad1'); - - about.append('div') - .attr('class', 'user-container') - .append('div') - .attr('class', 'hello'); - - var aboutList = about.append('ul') - .attr('id','about') - .attr('class','link-list'); - - var linkList = aboutList.append('ul') - .attr('id','about') - .attr('class','pad1 fillD about-block link-list'); - linkList.append('li').append('a').attr('target', '_blank') - .attr('href', 'http://github.com/systemed/iD').text(version); - linkList.append('li').append('a').attr('target', '_blank') - .attr('href', 'http://github.com/systemed/iD/issues').text('report a bug'); - - var imagery = linkList.append('li').attr('id', 'attribution'); - imagery.append('span').text('imagery'); - imagery.append('a').attr('target', '_blank') - .attr('href', 'http://opengeodata.org/microsoft-imagery-details').text(' provided by bing'); - - linkList.append('li').attr('class', 'source-switch').append('a').attr('href', '#') - .text('dev') - .on('click.editor', function() { - d3.event.preventDefault(); - if (d3.select(this).classed('live')) { - map.flush().connection() - .url('http://api06.dev.openstreetmap.org'); - d3.select(this).text('dev').classed('live', false); - } else { - map.flush().connection() - .url('http://www.openstreetmap.org'); - d3.select(this).text('live').classed('live', true); - } - }); - - var contributors = linkList.append('li') - .attr('id', 'user-list'); - contributors.append('span') - .attr('class', 'icon nearby icon-pre-text'); - contributors.append('span') - .text('Viewing contributions by '); - contributors.append('span') - .attr('class', 'contributor-list'); - contributors.append('span') - .attr('class', 'contributor-count'); - - history.on('change.editor', function() { - window.onbeforeunload = history.hasChanges() ? function() { - return 'You have unsaved changes.'; - } : null; - - var undo = history.undoAnnotation(), - redo = history.redoAnnotation(); - - function refreshTooltip(selection) { - if (selection.property('disabled')) { - selection.call(undo_tooltip.hide); - } else if (selection.property('tooltipVisible')) { - selection.call(undo_tooltip.show); - } - } - - limiter.select('#undo') - .property('disabled', !undo) - .attr('data-original-title', hintprefix('⌘ + Z', undo)) - .call(refreshTooltip); - - limiter.select('#redo') - .property('disabled', !redo) - .attr('data-original-title', hintprefix('⌘ + ⇧ + Z', redo)) - .call(refreshTooltip); - }); - - d3.select(window).on('resize.editor', function() { - map.size(m.size()); - }); - - var keybinding = d3.keybinding('main') - .on('⌘+Z', function() { history.undo(); }) - .on('⌃+Z', function() { history.undo(); }) - .on('⌘+⇧+Z', function() { history.redo(); }) - .on('⌃+⇧+Z', function() { history.redo(); }) - .on('⌫', function() { d3.event.preventDefault(); }); - - modes.forEach(function(m) { - keybinding.on(m.key, function() { if (map.editable()) controller.enter(m); }); - }); - - d3.select(document) - .call(keybinding); - - var hash = iD.behavior.Hash(context); - - hash(); - - if (!hash.hadHash) { - map.centerZoom([-77.02271, 38.90085], 20); - } - - d3.select('.user-container').call(iD.ui.userpanel(connection) - .on('logout.editor', connection.logout) - .on('login.editor', connection.authenticate)); - - controller.enter(iD.modes.Browse(context)); - - if (!localStorage.sawSplash) { - iD.ui.splash(); - localStorage.sawSplash = true; - } - } - - editor.connection = function(_) { - if (!arguments.length) return connection; - connection = _; - return editor; - }; - - editor.map = function() { - return map; - }; - - editor.controller = function() { - return controller; - }; - - if (arguments.length) { - d3.select(container).call(editor); - } - - return editor; + return context; }; +iD.version = '0.0.0-alpha1'; + iD.supported = function() { if (navigator.appName !== 'Microsoft Internet Explorer') { return true; diff --git a/js/id/ui.js b/js/id/ui.js index 8901175f4..978d9175e 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -1 +1,256 @@ -iD.ui = {}; +iD.ui = function (context) { + return function(container) { + context.container(container); + + var connection = context.connection(), + history = context.history(), + map = context.map(), + controller = context.controller(); + + if (!iD.supported()) { + container.html('This editor is supported in Firefox, Chrome, Safari, Opera, ' + + 'and Internet Explorer 9 and above. Please upgrade your browser ' + + 'or use Potlatch 2 to edit the map.') + .style('text-align:center;font-style:italic;'); + return; + } + + function hintprefix(x, y) { + return '' + y + '' + '
' + x + '
'; + } + + var m = container.append('div') + .attr('id', 'map') + .call(map); + + var bar = container.append('div') + .attr('id', 'bar') + .attr('class','pad1 fillD'); + + var limiter = bar.append('div') + .attr('class', 'limiter'); + + var buttons_joined = limiter.append('div') + .attr('class', 'button-wrap joined col4'); + + var modes = [ + iD.modes.Browse(context), + iD.modes.AddPoint(context), + iD.modes.AddLine(context), + iD.modes.AddArea(context)]; + + var buttons = buttons_joined.selectAll('button.add-button') + .data(modes) + .enter().append('button') + .attr('tabindex', -1) + .attr('class', function (mode) { return mode.title + ' add-button col3'; }) + .call(bootstrap.tooltip().placement('bottom').html(true)) + .attr('data-original-title', function (mode) { + return hintprefix(mode.key, mode.description); + }) + .on('click.editor', function (mode) { controller.enter(mode); }); + + function disableTooHigh() { + if (map.editable()) { + notice.message(false); + buttons.attr('disabled', null); + } else { + buttons.attr('disabled', 'disabled'); + notice.message(true); + controller.enter(iD.modes.Browse(context)); + } + } + + var notice = iD.ui.notice(limiter) + .message(false) + .on('zoom', function() { map.zoom(16); }); + + map.on('move.editor', _.debounce(function() { + disableTooHigh(); + contributors.call(iD.ui.contributors(map)); + }, 500)); + + buttons.append('span') + .attr('class', function(d) { + return d.id + ' icon icon-pre-text'; + }); + + buttons.append('span').attr('class', 'label').text(function (mode) { return mode.title; }); + + controller.on('enter.editor', function (entered) { + buttons.classed('active', function (mode) { return entered.button === mode.button; }); + container.classed("mode-" + entered.id, true); + }); + + controller.on('exit.editor', function (exited) { + container.classed("mode-" + exited.id, false); + }); + + var undo_buttons = limiter.append('div') + .attr('class', 'button-wrap joined col1'), + undo_tooltip = bootstrap.tooltip().placement('bottom').html(true); + + undo_buttons.append('button') + .attr({ id: 'undo', 'class': 'col6' }) + .property('disabled', true) + .html("") + .on('click.editor', history.undo) + .call(undo_tooltip); + + undo_buttons.append('button') + .attr({ id: 'redo', 'class': 'col6' }) + .property('disabled', true) + .html("") + .on('click.editor', history.redo) + .call(undo_tooltip); + + var save_button = limiter.append('div').attr('class','button-wrap col1').append('button') + .attr('class', 'save col12') + .call(iD.ui.save(context)); + + var zoom = container.append('div') + .attr('class', 'zoombuttons map-control') + .selectAll('button') + .data([['zoom-in', '+', map.zoomIn, 'Zoom In'], ['zoom-out', '-', map.zoomOut, 'Zoom Out']]) + .enter() + .append('button') + .attr('tabindex', -1) + .attr('class', function(d) { return d[0]; }) + .attr('title', function(d) { return d[3]; }) + .on('click.editor', function(d) { return d[2](); }) + .append('span') + .attr('class', function(d) { + return d[0] + ' icon'; + }); + + if (navigator.geolocation) { + container.append('div') + .call(iD.ui.geolocate(map)); + } + + var gc = container.append('div').attr('class', 'geocode-control map-control') + .call(iD.ui.geocoder().map(map)); + + container.append('div').attr('class', 'map-control layerswitcher-control') + .call(iD.ui.layerswitcher(map)); + + container.append('div') + .style('display', 'none') + .attr('class', 'inspector-wrap fr col5'); + + var about = container.append('div') + .attr('class','col12 about-block fillD pad1'); + + about.append('div') + .attr('class', 'user-container') + .append('div') + .attr('class', 'hello'); + + var aboutList = about.append('ul') + .attr('id','about') + .attr('class','link-list'); + + var linkList = aboutList.append('ul') + .attr('id','about') + .attr('class','pad1 fillD about-block link-list'); + linkList.append('li').append('a').attr('target', '_blank') + .attr('href', 'http://github.com/systemed/iD').text(iD.version); + linkList.append('li').append('a').attr('target', '_blank') + .attr('href', 'http://github.com/systemed/iD/issues').text('report a bug'); + + var imagery = linkList.append('li').attr('id', 'attribution'); + imagery.append('span').text('imagery'); + imagery.append('a').attr('target', '_blank') + .attr('href', 'http://opengeodata.org/microsoft-imagery-details').text(' provided by bing'); + + linkList.append('li').attr('class', 'source-switch').append('a').attr('href', '#') + .text('dev') + .on('click.editor', function() { + d3.event.preventDefault(); + if (d3.select(this).classed('live')) { + map.flush().connection() + .url('http://api06.dev.openstreetmap.org'); + d3.select(this).text('dev').classed('live', false); + } else { + map.flush().connection() + .url('http://www.openstreetmap.org'); + d3.select(this).text('live').classed('live', true); + } + }); + + var contributors = linkList.append('li') + .attr('id', 'user-list'); + contributors.append('span') + .attr('class', 'icon nearby icon-pre-text'); + contributors.append('span') + .text('Viewing contributions by '); + contributors.append('span') + .attr('class', 'contributor-list'); + contributors.append('span') + .attr('class', 'contributor-count'); + + history.on('change.editor', function() { + window.onbeforeunload = history.hasChanges() ? function() { + return 'You have unsaved changes.'; + } : null; + + var undo = history.undoAnnotation(), + redo = history.redoAnnotation(); + + function refreshTooltip(selection) { + if (selection.property('disabled')) { + selection.call(undo_tooltip.hide); + } else if (selection.property('tooltipVisible')) { + selection.call(undo_tooltip.show); + } + } + + limiter.select('#undo') + .property('disabled', !undo) + .attr('data-original-title', hintprefix('⌘ + Z', undo)) + .call(refreshTooltip); + + limiter.select('#redo') + .property('disabled', !redo) + .attr('data-original-title', hintprefix('⌘ + ⇧ + Z', redo)) + .call(refreshTooltip); + }); + + d3.select(window).on('resize.editor', function() { + map.size(m.size()); + }); + + var keybinding = d3.keybinding('main') + .on('⌘+Z', function() { history.undo(); }) + .on('⌃+Z', function() { history.undo(); }) + .on('⌘+⇧+Z', function() { history.redo(); }) + .on('⌃+⇧+Z', function() { history.redo(); }) + .on('⌫', function() { d3.event.preventDefault(); }); + + modes.forEach(function(m) { + keybinding.on(m.key, function() { if (map.editable()) controller.enter(m); }); + }); + + d3.select(document) + .call(keybinding); + + var hash = iD.behavior.Hash(context); + + hash(); + + if (!hash.hadHash) { + map.centerZoom([-77.02271, 38.90085], 20); + } + + d3.select('.user-container').call(iD.ui.userpanel(connection) + .on('logout.editor', connection.logout) + .on('login.editor', connection.authenticate)); + + controller.enter(iD.modes.Browse(context)); + + if (!localStorage.sawSplash) { + iD.ui.splash(); + localStorage.sawSplash = true; + } + }; +}; diff --git a/test/index.html b/test/index.html index b56645ee7..879cc6171 100644 --- a/test/index.html +++ b/test/index.html @@ -119,7 +119,6 @@ - diff --git a/test/spec/behavior/hash.js b/test/spec/behavior/hash.js index 5aceb53fc..7f9200ae1 100644 --- a/test/spec/behavior/hash.js +++ b/test/spec/behavior/hash.js @@ -4,7 +4,7 @@ describe("iD.behavior.Hash", function () { var hash, context; beforeEach(function () { - context = iD.Context(); + context = iD(); // Neuter connection context.connection().loadTiles = function () {}; diff --git a/test/spec/modes/add_point.js b/test/spec/modes/add_point.js index 5c544c0c5..38a614198 100644 --- a/test/spec/modes/add_point.js +++ b/test/spec/modes/add_point.js @@ -4,7 +4,7 @@ describe("iD.modes.AddPoint", function () { beforeEach(function () { var container = d3.select(document.createElement('div')); - context = iD.Context() + context = iD() .container(container); container.call(context.map()) From a78aeeb62591770ed2856389d07bceeb09b9f8b5 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 11:46:46 -0500 Subject: [PATCH 004/332] Merge controller into iD --- Makefile | 1 - index.html | 1 - js/id/behavior/hash.js | 2 +- js/id/controller.js | 19 ------------------- js/id/id.js | 22 +++++++++++++++++----- js/id/ui.js | 15 +++++++-------- test/index.html | 1 - 7 files changed, 25 insertions(+), 36 deletions(-) delete mode 100644 js/id/controller.js diff --git a/Makefile b/Makefile index 1db4f2efb..3b0f2d1b7 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,6 @@ all: \ js/id/modes/*.js \ js/id/operations.js \ js/id/operations/*.js \ - js/id/controller.js \ js/id/graph/*.js \ js/id/renderer/*.js \ js/id/svg.js \ diff --git a/index.html b/index.html index ab6070fbc..52ff209f2 100644 --- a/index.html +++ b/index.html @@ -123,7 +123,6 @@ - diff --git a/js/id/behavior/hash.js b/js/id/behavior/hash.js index 871b73a35..9056db393 100644 --- a/js/id/behavior/hash.js +++ b/js/id/behavior/hash.js @@ -48,7 +48,7 @@ iD.behavior.Hash = function(context) { context.enter(iD.modes.Select([id])); }); - context.controller().on('enter.hash', function() { + context.on('enter.hash', function() { if (context.mode().id !== 'browse') selectoff(); }); } diff --git a/js/id/controller.js b/js/id/controller.js deleted file mode 100644 index af1e7c388..000000000 --- a/js/id/controller.js +++ /dev/null @@ -1,19 +0,0 @@ -// A controller holds a single action at a time and calls `.enter` and `.exit` -// to bind and unbind actions. -iD.Controller = function() { - var event = d3.dispatch('enter', 'exit'); - var controller = { mode: null }; - - controller.enter = function(mode) { - if (controller.mode) { - controller.mode.exit(); - event.exit(controller.mode); - } - - mode.enter(); - controller.mode = mode; - event.enter(mode); - }; - - return d3.rebind(controller, event, 'on'); -}; diff --git a/js/id/id.js b/js/id/id.js index 42a8cfb11..b2d28fb3f 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -2,7 +2,8 @@ window.iD = function () { var context = {}, history = iD.History(), connection = iD.Connection().version(iD.version), - controller = iD.Controller(), + dispatch = d3.dispatch('enter', 'exit'), + mode, container, ui = iD.ui(context), map = iD.Map().connection(connection).history(history); @@ -11,7 +12,6 @@ window.iD = function () { context.ui = function () { return ui; }; context.connection = function () { return connection; }; context.history = function () { return history; }; - context.controller = function () { return controller; }; context.map = function () { return map; }; /* History delegation. */ @@ -26,8 +26,20 @@ window.iD = function () { context.entity = function (id) { return history.graph().entity(id); }; context.geometry = function (id) { return context.entity(id).geometry(history.graph()); }; - context.enter = controller.enter; - context.mode = function () { return controller.mode; }; + context.enter = function(newMode) { + if (mode) { + mode.exit(); + dispatch.exit(mode); + } + + mode = newMode; + mode.enter(); + dispatch.enter(mode); + }; + + context.mode = function() { + return mode; + }; context.install = function (behavior) { context.surface().call(behavior); }; context.uninstall = function (behavior) { context.surface().call(behavior.off); }; @@ -46,7 +58,7 @@ window.iD = function () { context.background() .source(iD.BackgroundSource.Bing); - return context; + return d3.rebind(context, dispatch, 'on'); }; iD.version = '0.0.0-alpha1'; diff --git a/js/id/ui.js b/js/id/ui.js index 978d9175e..6b9b3cc08 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -4,8 +4,7 @@ iD.ui = function (context) { var connection = context.connection(), history = context.history(), - map = context.map(), - controller = context.controller(); + map = context.map(); if (!iD.supported()) { container.html('This editor is supported in Firefox, Chrome, Safari, Opera, ' + @@ -48,7 +47,7 @@ iD.ui = function (context) { .attr('data-original-title', function (mode) { return hintprefix(mode.key, mode.description); }) - .on('click.editor', function (mode) { controller.enter(mode); }); + .on('click.editor', function (mode) { context.enter(mode); }); function disableTooHigh() { if (map.editable()) { @@ -57,7 +56,7 @@ iD.ui = function (context) { } else { buttons.attr('disabled', 'disabled'); notice.message(true); - controller.enter(iD.modes.Browse(context)); + context.enter(iD.modes.Browse(context)); } } @@ -77,12 +76,12 @@ iD.ui = function (context) { buttons.append('span').attr('class', 'label').text(function (mode) { return mode.title; }); - controller.on('enter.editor', function (entered) { + context.on('enter.editor', function (entered) { buttons.classed('active', function (mode) { return entered.button === mode.button; }); container.classed("mode-" + entered.id, true); }); - controller.on('exit.editor', function (exited) { + context.on('exit.editor', function (exited) { container.classed("mode-" + exited.id, false); }); @@ -228,7 +227,7 @@ iD.ui = function (context) { .on('⌫', function() { d3.event.preventDefault(); }); modes.forEach(function(m) { - keybinding.on(m.key, function() { if (map.editable()) controller.enter(m); }); + keybinding.on(m.key, function() { if (map.editable()) context.enter(m); }); }); d3.select(document) @@ -246,7 +245,7 @@ iD.ui = function (context) { .on('logout.editor', connection.logout) .on('login.editor', connection.authenticate)); - controller.enter(iD.modes.Browse(context)); + context.enter(iD.modes.Browse(context)); if (!localStorage.sawSplash) { iD.ui.splash(); diff --git a/test/index.html b/test/index.html index 879cc6171..d32331ba9 100644 --- a/test/index.html +++ b/test/index.html @@ -119,7 +119,6 @@ - From 6188d128f8d12e73a874b71c46ad8f0c6c078ca6 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 11:50:07 -0500 Subject: [PATCH 005/332] Arrange --- js/id/id.js | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/js/id/id.js b/js/id/id.js index b2d28fb3f..ce8131bc9 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -14,7 +14,7 @@ window.iD = function () { context.history = function () { return history; }; context.map = function () { return map; }; - /* History delegation. */ + /* History */ context.graph = history.graph; context.perform = history.perform; context.replace = history.replace; @@ -23,9 +23,16 @@ window.iD = function () { context.redo = history.undo; context.changes = history.changes; - context.entity = function (id) { return history.graph().entity(id); }; - context.geometry = function (id) { return context.entity(id).geometry(history.graph()); }; + /* Graph */ + context.entity = function (id) { + return history.graph().entity(id); + }; + context.geometry = function (id) { + return context.entity(id).geometry(history.graph()); + }; + + /* Modes */ context.enter = function(newMode) { if (mode) { mode.exit(); @@ -41,9 +48,16 @@ window.iD = function () { return mode; }; - context.install = function (behavior) { context.surface().call(behavior); }; - context.uninstall = function (behavior) { context.surface().call(behavior.off); }; + /* Behaviors */ + context.install = function (behavior) { + context.surface().call(behavior); + }; + context.uninstall = function (behavior) { + context.surface().call(behavior.off); + }; + + /* Map */ context.background = function () { return map.background; }; context.surface = function () { return map.surface; }; context.projection = map.projection; From fe49e8fb77637e0598d9c381b7f24385de367c27 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 12:17:24 -0500 Subject: [PATCH 006/332] Pass around context rather than map --- js/id/behavior/draw.js | 2 +- js/id/behavior/draw_way.js | 2 +- js/id/geo.js | 6 +++--- js/id/id.js | 3 ++- js/id/modes/select.js | 2 +- js/id/renderer/map.js | 35 +++++++++++++---------------------- js/id/ui.js | 4 ++-- js/id/ui/commit.js | 4 ++-- js/id/ui/contributors.js | 8 ++++---- js/id/ui/layerswitcher.js | 20 ++++++++++---------- js/id/ui/save.js | 4 ++-- test/spec/renderer/map.js | 20 ++++---------------- 12 files changed, 45 insertions(+), 65 deletions(-) diff --git a/js/id/behavior/draw.js b/js/id/behavior/draw.js index 8405ac341..625fc04a4 100644 --- a/js/id/behavior/draw.js +++ b/js/id/behavior/draw.js @@ -29,7 +29,7 @@ iD.behavior.Draw = function(context) { function click() { var d = datum(); if (d.type === 'way') { - var choice = iD.geo.chooseIndex(d, d3.mouse(context.surface().node()), context.map()); + var choice = iD.geo.chooseIndex(d, d3.mouse(context.surface().node()), context); event.clickWay(d, choice.loc, choice.index); } else if (d.type === 'node') { diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 28a8fbb38..9efcd5bb9 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -19,7 +19,7 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { if (datum.type === 'node' || datum.type === 'midpoint') { loc = datum.loc; } else if (datum.type === 'way') { - loc = iD.geo.chooseIndex(datum, d3.mouse(context.surface().node()), context.map()).loc; + loc = iD.geo.chooseIndex(datum, d3.mouse(context.surface().node()), context).loc; } context.replace(iD.actions.MoveNode(nodeId, loc)); diff --git a/js/id/geo.js b/js/id/geo.js index fcce74b9a..dcc11717f 100644 --- a/js/id/geo.js +++ b/js/id/geo.js @@ -14,11 +14,11 @@ iD.geo.dist = function(a, b) { Math.pow(a[1] - b[1], 2)); }; -iD.geo.chooseIndex = function(way, point, map) { +iD.geo.chooseIndex = function(way, point, context) { var dist = iD.geo.dist, - graph = map.history().graph(), + graph = context.graph(), nodes = graph.childNodes(way), - projNodes = nodes.map(function(n) { return map.projection(n.loc); }); + projNodes = nodes.map(function(n) { return context.projection(n.loc); }); for (var i = 0, changes = []; i < projNodes.length - 1; i++) { changes[i] = diff --git a/js/id/id.js b/js/id/id.js index ce8131bc9..713377f15 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -6,7 +6,7 @@ window.iD = function () { mode, container, ui = iD.ui(context), - map = iD.Map().connection(connection).history(history); + map = iD.Map(context); /* Straight accessors. Avoid using these if you can. */ context.ui = function () { return ui; }; @@ -62,6 +62,7 @@ window.iD = function () { context.surface = function () { return map.surface; }; context.projection = map.projection; context.tail = map.tail; + context.redraw = map.redraw; context.container = function (_) { if (!arguments.length) return container; diff --git a/js/id/modes/select.js b/js/id/modes/select.js index df445862d..36066d7d0 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -108,7 +108,7 @@ iD.modes.Select = function(context, selection, initial) { if (datum instanceof iD.Way && !target.classed('fill')) { var choice = iD.geo.chooseIndex(datum, - d3.mouse(context.surface().node()), context.map()), + d3.mouse(context.surface().node()), context), node = iD.Node({ loc: choice.loc }); context.perform( diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index cd99de738..b738feca4 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -1,6 +1,5 @@ -iD.Map = function() { - var connection, history, - dimensions = [], +iD.Map = function(context) { + var dimensions = [], dispatch = d3.dispatch('move', 'drawn'), projection = d3.geo.mercator().scale(1024), roundedProjection = iD.svg.RoundProjection(projection), @@ -27,6 +26,12 @@ iD.Map = function() { surface, tilegroup; function map(selection) { + context.connection() + .on('load.tile', connectionLoad); + + context.history() + .on('change.map', redraw); + selection.call(zoom); tilegroup = selection.append('div') @@ -57,7 +62,7 @@ iD.Map = function() { function drawVector(difference) { var filter, all, extent = map.extent(), - graph = history.graph(); + graph = context.graph(); function addParents(parents) { for (var i = 0; i < parents.length; i++) { @@ -121,7 +126,7 @@ iD.Map = function() { } function connectionLoad(err, result) { - history.merge(result); + context.history().merge(result); redraw(Object.keys(result)); } @@ -182,7 +187,7 @@ iD.Map = function() { tilegroup.call(background); if (map.editable()) { - connection.loadTiles(projection, dimensions); + context.connection().loadTiles(projection, dimensions); drawVector(difference); } else { editOff(); @@ -347,15 +352,8 @@ iD.Map = function() { }; map.flush = function () { - connection.flush(); - history.reset(); - return map; - }; - - map.connection = function(_) { - if (!arguments.length) return connection; - connection = _; - connection.on('load.tile', connectionLoad); + context.connection().flush(); + context.history().reset(); return map; }; @@ -378,13 +376,6 @@ iD.Map = function() { return map; }; - map.history = function (_) { - if (!arguments.length) return history; - history = _; - history.on('change.map', redraw); - return map; - }; - map.background = background; map.projection = projection; map.redraw = redraw; diff --git a/js/id/ui.js b/js/id/ui.js index 6b9b3cc08..5714e05ca 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -66,7 +66,7 @@ iD.ui = function (context) { map.on('move.editor', _.debounce(function() { disableTooHigh(); - contributors.call(iD.ui.contributors(map)); + contributors.call(iD.ui.contributors(context)); }, 500)); buttons.append('span') @@ -131,7 +131,7 @@ iD.ui = function (context) { .call(iD.ui.geocoder().map(map)); container.append('div').attr('class', 'map-control layerswitcher-control') - .call(iD.ui.layerswitcher(map)); + .call(iD.ui.layerswitcher(context)); container.append('div') .style('display', 'none') diff --git a/js/id/ui/commit.js b/js/id/ui/commit.js index d883b1748..6c1133b35 100644 --- a/js/id/ui/commit.js +++ b/js/id/ui/commit.js @@ -1,4 +1,4 @@ -iD.ui.commit = function(map) { +iD.ui.commit = function(context) { var event = d3.dispatch('cancel', 'save', 'fix'); function zipSame(d) { @@ -89,7 +89,7 @@ iD.ui.commit = function(map) { cancelbutton.append('span').attr('class','label').text('Cancel'); var warnings = body.selectAll('div.warning-section') - .data(iD.validate(changes, map.history().graph())) + .data(iD.validate(changes, context.graph())) .enter() .append('div').attr('class', 'modal-section warning-section fillL'); diff --git a/js/id/ui/contributors.js b/js/id/ui/contributors.js index c64d8a2ac..e029bfd04 100644 --- a/js/id/ui/contributors.js +++ b/js/id/ui/contributors.js @@ -1,10 +1,10 @@ -iD.ui.contributors = function(map) { +iD.ui.contributors = function(context) { function contributors(selection) { var users = {}, limit = 3, - entities = map.history().graph().intersects(map.extent()); + entities = context.graph().intersects(context.map().extent()); for (var i in entities) { if (entities[i].user) users[entities[i].user] = true; @@ -21,7 +21,7 @@ iD.ui.contributors = function(map) { l.enter().append('a') .attr('class', 'user-link') - .attr('href', function(d) { return map.connection().userUrl(d); }) + .attr('href', function(d) { return context.connection().userUrl(d); }) .attr('target', '_blank') .text(String); @@ -37,7 +37,7 @@ iD.ui.contributors = function(map) { .append('a') .attr('target', '_blank') .attr('href', function() { - var ext = map.extent(); + var ext = context.map().extent(); return 'http://www.openstreetmap.org/browse/changesets?bbox=' + [ ext[0][0], ext[0][1], ext[1][0], ext[1][1]]; diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index 298c469e0..c64457ab8 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -1,4 +1,4 @@ -iD.ui.layerswitcher = function(map) { +iD.ui.layerswitcher = function(context) { var event = d3.dispatch('cancel', 'save'), sources = [{ name: 'Bing', @@ -94,7 +94,7 @@ iD.ui.layerswitcher = function(map) { function selectLayer(d) { content.selectAll('a.layer') .classed('selected', function(d) { - return d.source === map.background.source(); + return d.source === context.background().source(); }); d3.select('#attribution a') .attr('href', d.link) @@ -126,9 +126,9 @@ iD.ui.layerswitcher = function(map) { d.source = configured; d.name = 'Custom (configured)'; } - map.background.source(d.source); - map.history().imagery_used(d.name); - map.redraw(); + context.background().source(d.source); + context.history().imagery_used(d.name); + context.redraw(); selectLayer(d); }) .insert('span') @@ -145,8 +145,8 @@ iD.ui.layerswitcher = function(map) { ['bottom', [0, 1]]]; function nudge(d) { - map.background.nudge(d[1]); - map.redraw(); + context.background().nudge(d[1]); + context.redraw(); } adjustments.append('a') @@ -181,12 +181,12 @@ iD.ui.layerswitcher = function(map) { .text('reset') .attr('class', 'reset') .on('click', function() { - map.background.offset([0, 0]); - map.redraw(); + context.background().offset([0, 0]); + context.redraw(); }); selection.call(clickoutside); - selectLayer(map.background.source()); + selectLayer(context.background().source()); } return d3.rebind(layerswitcher, event, 'on'); diff --git a/js/id/ui/save.js b/js/id/ui/save.js index 6098519da..ba217adcf 100644 --- a/js/id/ui/save.js +++ b/js/id/ui/save.js @@ -50,12 +50,12 @@ iD.ui.save = function(context) { modal.select('.content') .classed('commit-modal', true) .datum(changes) - .call(iD.ui.commit(map) + .call(iD.ui.commit(context) .on('cancel', function() { modal.remove(); }) .on('fix', function(d) { - map.extent(d.entity.extent(map.history().graph())); + map.extent(d.entity.extent(context.graph())); if (map.zoom() > 19) map.zoom(19); context.enter(iD.modes.Select(context, [d.entity.id])); modal.remove(); diff --git a/test/spec/renderer/map.js b/test/spec/renderer/map.js index d7d0e3440..de8abdf61 100644 --- a/test/spec/renderer/map.js +++ b/test/spec/renderer/map.js @@ -1,22 +1,10 @@ describe('iD.Map', function() { - var container, map; + var map; beforeEach(function() { - container = d3.select('body').append('div'); - map = iD.Map(); - container.call(map); - }); - - afterEach(function() { - container.remove(); - }); - - describe('#connection', function() { - it('gets and sets connection', function() { - var connection = iD.Connection(); - expect(map.connection(connection)).to.equal(map); - expect(map.connection()).to.equal(connection); - }); + map = iD().map(); + d3.select(document.createElement('div')) + .call(map); }); describe('#zoom', function() { From 0bcb0520d8eb826298d532695f3fe83b3b165e6a Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 12:46:01 -0500 Subject: [PATCH 007/332] Use iD.version --- js/id/connection.js | 9 +-------- js/id/id.js | 2 +- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/js/id/connection.js b/js/id/connection.js index d2afa6281..399c48d86 100644 --- a/js/id/connection.js +++ b/js/id/connection.js @@ -4,7 +4,6 @@ iD.Connection = function() { url = 'http://www.openstreetmap.org', connection = {}, user = {}, - version, keys, inflight = {}, loadedTiles = {}, @@ -186,7 +185,7 @@ iD.Connection = function() { content: JXON.stringify(connection.changesetJXON({ imagery_used: imagery_used.join(';'), comment: comment, - created_by: 'iD ' + (version || '') + created_by: 'iD ' + iD.version })) }, function (err, changeset_id) { if (err) return callback(err); @@ -322,12 +321,6 @@ iD.Connection = function() { return oauth.authenticate(done); }; - connection.version = function(_) { - if (!arguments.length) return version; - version = _; - return connection; - }; - connection.bboxFromAPI = bboxFromAPI; connection.changesetUrl = changesetUrl; connection.loadFromURL = loadFromURL; diff --git a/js/id/id.js b/js/id/id.js index 713377f15..ef026a097 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -1,7 +1,7 @@ window.iD = function () { var context = {}, history = iD.History(), - connection = iD.Connection().version(iD.version), + connection = iD.Connection(), dispatch = d3.dispatch('enter', 'exit'), mode, container, From f1b6f5b14a10a958862942eacf452932bbd063f1 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 1 Feb 2013 12:47:57 -0500 Subject: [PATCH 008/332] Fix Google Analytics include --- index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index 52ff209f2..b0e46d55d 100644 --- a/index.html +++ b/index.html @@ -141,8 +141,7 @@ d3.select("#iD") .call(id.ui()) }); - - + + From 031c8d655e02f1686bf654d4a168f0decc16a7cf Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 1 Feb 2013 13:12:46 -0500 Subject: [PATCH 009/332] Fallback for browsers that do not support localStorage. Fixes #591 --- js/id/connection.js | 4 ++-- js/id/id.js | 44 ++++++++++++++++++++++++----------------- js/id/oauth.js | 15 ++++++-------- js/id/ui.js | 4 ++-- js/id/ui/commit.js | 2 +- test/spec/connection.js | 3 ++- test/spec/oauth.js | 3 ++- 7 files changed, 41 insertions(+), 34 deletions(-) diff --git a/js/id/connection.js b/js/id/connection.js index 399c48d86..794232682 100644 --- a/js/id/connection.js +++ b/js/id/connection.js @@ -1,4 +1,4 @@ -iD.Connection = function() { +iD.Connection = function(context) { var event = d3.dispatch('auth', 'load'), url = 'http://www.openstreetmap.org', @@ -7,7 +7,7 @@ iD.Connection = function() { keys, inflight = {}, loadedTiles = {}, - oauth = iD.OAuth().url(url); + oauth = iD.OAuth(context).url(url); function changesetUrl(changesetId) { return url + '/browse/changeset/' + changesetId; diff --git a/js/id/id.js b/js/id/id.js index ef026a097..0b2bbd617 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -1,34 +1,42 @@ window.iD = function () { var context = {}, history = iD.History(), - connection = iD.Connection(), + storage = localStorage || {}, dispatch = d3.dispatch('enter', 'exit'), mode, container, ui = iD.ui(context), map = iD.Map(context); + context.storage = function(k, v) { + if (arguments.length === 1) return storage[k]; + else storage[k] = v; + }; + + // the connection requires .storage() to be available on calling. + var connection = iD.Connection(context); + /* Straight accessors. Avoid using these if you can. */ - context.ui = function () { return ui; }; - context.connection = function () { return connection; }; - context.history = function () { return history; }; - context.map = function () { return map; }; + context.ui = function() { return ui; }; + context.connection = function() { return connection; }; + context.history = function() { return history; }; + context.map = function() { return map; }; /* History */ - context.graph = history.graph; + context.graph = history.graph; context.perform = history.perform; context.replace = history.replace; - context.pop = history.pop; - context.undo = history.undo; - context.redo = history.undo; + context.pop = history.pop; + context.undo = history.undo; + context.redo = history.undo; context.changes = history.changes; /* Graph */ - context.entity = function (id) { + context.entity = function(id) { return history.graph().entity(id); }; - context.geometry = function (id) { + context.geometry = function(id) { return context.entity(id).geometry(history.graph()); }; @@ -49,22 +57,22 @@ window.iD = function () { }; /* Behaviors */ - context.install = function (behavior) { + context.install = function(behavior) { context.surface().call(behavior); }; - context.uninstall = function (behavior) { + context.uninstall = function(behavior) { context.surface().call(behavior.off); }; /* Map */ - context.background = function () { return map.background; }; - context.surface = function () { return map.surface; }; + context.background = function() { return map.background; }; + context.surface = function() { return map.surface; }; context.projection = map.projection; - context.tail = map.tail; - context.redraw = map.redraw; + context.tail = map.tail; + context.redraw = map.redraw; - context.container = function (_) { + context.container = function(_) { if (!arguments.length) return container; container = _; return context; diff --git a/js/id/oauth.js b/js/id/oauth.js index a09f2affc..4aa1d1876 100644 --- a/js/id/oauth.js +++ b/js/id/oauth.js @@ -1,4 +1,4 @@ -iD.OAuth = function() { +iD.OAuth = function(context) { var baseurl = 'http://www.openstreetmap.org', o = {}, keys, @@ -6,10 +6,6 @@ iD.OAuth = function() { function keyclean(x) { return x.replace(/\W/g, ''); } - if (token('oauth_token')) { - o.oauth_token = token('oauth_token'); - } - function timenonce(o) { o.oauth_timestamp = ohauth.timestamp(); o.oauth_nonce = ohauth.nonce(); @@ -18,10 +14,11 @@ iD.OAuth = function() { // token getter/setter, namespaced to the current `apibase` value. function token(k, x) { - if (arguments.length == 2) { - localStorage[keyclean(baseurl) + k] = x; - } - return localStorage[keyclean(baseurl) + k]; + return context.storage(keyclean(baseurl) + k, x); + } + + if (token('oauth_token')) { + o.oauth_token = token('oauth_token'); } oauth.authenticated = function() { diff --git a/js/id/ui.js b/js/id/ui.js index 5714e05ca..c28ca1282 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -247,9 +247,9 @@ iD.ui = function (context) { context.enter(iD.modes.Browse(context)); - if (!localStorage.sawSplash) { + if (!context.storage('sawSplash')) { iD.ui.splash(); - localStorage.sawSplash = true; + context.storage('sawSplash', true); } }; }; diff --git a/js/id/ui/commit.js b/js/id/ui/commit.js index 6c1133b35..09bb99404 100644 --- a/js/id/ui/commit.js +++ b/js/id/ui/commit.js @@ -38,7 +38,7 @@ iD.ui.commit = function(context) { comment_section.append('textarea') .attr('class', 'changeset-comment') .attr('placeholder', 'Brief Description of your contributions') - .property('value', localStorage.comment || '') + .property('value', context.storage('comment') || '') .node().select(); var commit_info = diff --git a/test/spec/connection.js b/test/spec/connection.js index 652c1419b..cdc439a46 100644 --- a/test/spec/connection.js +++ b/test/spec/connection.js @@ -2,7 +2,8 @@ describe('iD.Connection', function () { var c; beforeEach(function () { - c = new iD.Connection(); + context = iD(); + c = new iD.Connection(context); }); it('is instantiated', function () { diff --git a/test/spec/oauth.js b/test/spec/oauth.js index 8fd57c18c..b7ae1074f 100644 --- a/test/spec/oauth.js +++ b/test/spec/oauth.js @@ -2,7 +2,8 @@ describe('iD.OAuth', function() { var o; beforeEach(function() { - o = iD.OAuth(); + context = iD(); + o = iD.OAuth(context); }); describe('#logout', function() { From 619216b33b27f5acae41881c499a4b32b61a1b02 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 13:13:21 -0500 Subject: [PATCH 010/332] Remove double dispatch --- js/id/renderer/map.js | 1 - 1 file changed, 1 deletion(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index b738feca4..1f22a7c81 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -263,7 +263,6 @@ iD.Map = function(context) { t[0] - ll[0] + c[0], t[1] - ll[1] + c[1]]); zoom.translate(projection.translate()); - dispatch.move(map); return true; } From 175cdc553d07477447299810375060ad39b7a073 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 1 Feb 2013 13:17:19 -0500 Subject: [PATCH 011/332] Fix localStorage number of args in oauth --- js/id/oauth.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/id/oauth.js b/js/id/oauth.js index 4aa1d1876..d7cb095a8 100644 --- a/js/id/oauth.js +++ b/js/id/oauth.js @@ -13,8 +13,8 @@ iD.OAuth = function(context) { } // token getter/setter, namespaced to the current `apibase` value. - function token(k, x) { - return context.storage(keyclean(baseurl) + k, x); + function token() { + return context.storage.apply(context, arguments); } if (token('oauth_token')) { From cdda561f6977482316d9cf5f4baf3d5c76bad607 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 13:39:39 -0500 Subject: [PATCH 012/332] Fix dev/live toggle --- js/id/ui.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index c28ca1282..8d764d695 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -167,11 +167,13 @@ iD.ui = function (context) { .on('click.editor', function() { d3.event.preventDefault(); if (d3.select(this).classed('live')) { - map.flush().connection() + map.flush(); + context.connection() .url('http://api06.dev.openstreetmap.org'); d3.select(this).text('dev').classed('live', false); } else { - map.flush().connection() + map.flush(); + context.connection() .url('http://www.openstreetmap.org'); d3.select(this).text('live').classed('live', true); } From 4fbbd1cf822a08310cdbdb819fde6f9cb5fa5162 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 1 Feb 2013 14:11:32 -0500 Subject: [PATCH 013/332] Update to pre-release 3.0.5 d3 --- js/lib/d3.v3.js | 1604 ++++++++++++++++++++++++++--------------------- 1 file changed, 900 insertions(+), 704 deletions(-) diff --git a/js/lib/d3.v3.js b/js/lib/d3.v3.js index 93b676f46..818bab9dc 100644 --- a/js/lib/d3.v3.js +++ b/js/lib/d3.v3.js @@ -12,12 +12,9 @@ }; } d3 = { - version: "3.0.0pre" + version: "3.0.5" }; - var π = Math.PI, ε = 1e-6, εε = .001, d3_radians = π / 180, d3_degrees = 180 / π; - function d3_zero() { - return 0; - } + var π = Math.PI, ε = 1e-6, d3_radians = π / 180, d3_degrees = 180 / π; function d3_target(d) { return d.target; } @@ -234,7 +231,7 @@ return s; }; d3.quantile = function(values, p) { - var H = (values.length - 1) * p + 1, h = Math.floor(H), v = values[h - 1], e = H - h; + var H = (values.length - 1) * p + 1, h = Math.floor(H), v = +values[h - 1], e = H - h; return e ? v + e * (values[h] - v) : v; }; d3.shuffle = function(array) { @@ -454,8 +451,13 @@ d3.rebind(xhr, dispatch, "on"); if (arguments.length === 2 && typeof mimeType === "function") callback = mimeType, mimeType = null; - return callback == null ? xhr : xhr.get(callback); + return callback == null ? xhr : xhr.get(d3_xhr_fixCallback(callback)); }; + function d3_xhr_fixCallback(callback) { + return callback.length === 1 ? function(error, request) { + callback(error == null ? request : null); + } : callback; + } d3.text = function() { return d3.xhr.apply(d3, arguments).response(d3_text); }; @@ -1687,13 +1689,13 @@ return value; } function bind(group, groupData) { - var i, n = group.length, m = groupData.length, n0 = Math.min(n, m), n1 = Math.max(n, m), updateNodes = [], enterNodes = [], exitNodes = [], node, nodeData; + var i, n = group.length, m = groupData.length, n0 = Math.min(n, m), updateNodes = new Array(m), enterNodes = new Array(m), exitNodes = new Array(n), node, nodeData; if (key) { - var nodeByKeyValue = new d3_Map(), keyValues = [], keyValue, j = groupData.length; + var nodeByKeyValue = new d3_Map(), dataByKeyValue = new d3_Map(), keyValues = [], keyValue; for (i = -1; ++i < n; ) { keyValue = key.call(node = group[i], node.__data__, i); if (nodeByKeyValue.has(keyValue)) { - exitNodes[j++] = node; + exitNodes[i] = node; } else { nodeByKeyValue.set(keyValue, node); } @@ -1701,14 +1703,13 @@ } for (i = -1; ++i < m; ) { keyValue = key.call(groupData, nodeData = groupData[i], i); - if (nodeByKeyValue.has(keyValue)) { - updateNodes[i] = node = nodeByKeyValue.get(keyValue); + if (node = nodeByKeyValue.get(keyValue)) { + updateNodes[i] = node; node.__data__ = nodeData; - enterNodes[i] = exitNodes[i] = null; - } else { + } else if (!dataByKeyValue.has(keyValue)) { enterNodes[i] = d3_selection_dataNode(nodeData); - updateNodes[i] = exitNodes[i] = null; } + dataByKeyValue.set(keyValue, nodeData); nodeByKeyValue.remove(keyValue); } for (i = -1; ++i < n; ) { @@ -1723,19 +1724,15 @@ if (node) { node.__data__ = nodeData; updateNodes[i] = node; - enterNodes[i] = exitNodes[i] = null; } else { enterNodes[i] = d3_selection_dataNode(nodeData); - updateNodes[i] = exitNodes[i] = null; } } for (;i < m; ++i) { enterNodes[i] = d3_selection_dataNode(groupData[i]); - updateNodes[i] = exitNodes[i] = null; } - for (;i < n1; ++i) { + for (;i < n; ++i) { exitNodes[i] = group[i]; - enterNodes[i] = updateNodes[i] = null; } } enterNodes.update = updateNodes; @@ -3769,7 +3766,7 @@ d3.behavior.zoom = function() { var translate = [ 0, 0 ], translate0, scale = 1, scale0, scaleExtent = d3_behavior_zoomInfinity, event = d3_eventDispatch(zoom, "zoom"), x0, x1, y0, y1, touchtime; function zoom() { - this.on("mousedown.zoom", mousedown).on("mousewheel.zoom", mousewheel).on("mousemove.zoom", mousemove).on("DOMMouseScroll.zoom", mousewheel).on("dblclick.zoom", dblclick).on("touchstart.zoom", touchstart).on("touchmove.zoom", touchmove).on("touchend.zoom", touchstart); + this.on("mousedown.zoom", mousedown).on("mousemove.zoom", mousemove).on(d3_behavior_zoomWheel + ".zoom", mousewheel).on("dblclick.zoom", dblclick).on("touchstart.zoom", touchstart).on("touchmove.zoom", touchmove).on("touchend.zoom", touchstart); } zoom.translate = function(x) { if (!arguments.length) return translate; @@ -3901,21 +3898,14 @@ } return d3.rebind(zoom, event, "on"); }; - var d3_behavior_zoomDiv, d3_behavior_zoomInfinity = [ 0, Infinity ]; - function d3_behavior_zoomDelta() { - if (!d3_behavior_zoomDiv) { - d3_behavior_zoomDiv = d3.select("body").append("div").style("visibility", "hidden").style("top", 0).style("height", 0).style("width", 0).style("overflow-y", "scroll").append("div").style("height", "2000px").node().parentNode; - } - var e = d3.event, delta; - try { - d3_behavior_zoomDiv.scrollTop = 1e3; - d3_behavior_zoomDiv.dispatchEvent(e); - delta = 1e3 - d3_behavior_zoomDiv.scrollTop; - } catch (error) { - delta = e.wheelDelta || -e.detail * 5; - } - return delta; - } + var d3_behavior_zoomInfinity = [ 0, Infinity ]; + var d3_behavior_zoomDelta, d3_behavior_zoomWheel = "onwheel" in document ? (d3_behavior_zoomDelta = function() { + return -d3.event.deltaY * (d3.event.deltaMode ? 120 : 1); + }, "wheel") : "onmousewheel" in document ? (d3_behavior_zoomDelta = function() { + return d3.event.wheelDelta; + }, "mousewheel") : (d3_behavior_zoomDelta = function() { + return -d3.event.detail; + }, "MozMousePixelScroll"); d3.layout = {}; d3.layout.bundle = function() { return function(links) { @@ -4625,10 +4615,8 @@ } d3.layout.hierarchy = function() { var sort = d3_layout_hierarchySort, children = d3_layout_hierarchyChildren, value = d3_layout_hierarchyValue; - function recurse(data, depth, nodes) { - var childs = children.call(hierarchy, data, depth), node = d3_layout_hierarchyInline ? data : { - data: data - }; + function recurse(node, depth, nodes) { + var childs = children.call(hierarchy, node, depth); node.depth = depth; nodes.push(node); if (childs && (n = childs.length)) { @@ -4642,7 +4630,7 @@ if (sort) c.sort(sort); if (value) node.value = v; } else if (value) { - node.value = +value.call(hierarchy, data, depth) || 0; + node.value = +value.call(hierarchy, node, depth) || 0; } return node; } @@ -4652,7 +4640,7 @@ var i = -1, n, j = depth + 1; while (++i < n) v += revalue(children[i], j); } else if (value) { - v = +value.call(hierarchy, d3_layout_hierarchyInline ? node : node.data, depth) || 0; + v = +value.call(hierarchy, node, depth) || 0; } if (value) node.value = v; return v; @@ -4685,11 +4673,8 @@ }; function d3_layout_hierarchyRebind(object, hierarchy) { d3.rebind(object, hierarchy, "sort", "children", "value"); + object.nodes = object; object.links = d3_layout_hierarchyLinks; - object.nodes = function(d) { - d3_layout_hierarchyInline = true; - return (object.nodes = object)(d); - }; return object; } function d3_layout_hierarchyChildren(d) { @@ -4711,7 +4696,6 @@ }); })); } - var d3_layout_hierarchyInline = false; d3.layout.pack = function() { var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort), padding = 0, size = [ 1, 1 ]; function pack(d, i) { @@ -5096,7 +5080,7 @@ function squarify(node) { var children = node.children; if (children && children.length) { - var rect = pad(node), row = [], remaining = children.slice(), child, best = Infinity, score, u = mode === "slice" ? rect.dx : mode === "dice" || mode === "slice-dice" && node.depth & 1 ? rect.dy : Math.min(rect.dx, rect.dy), n; + var rect = pad(node), row = [], remaining = children.slice(), child, best = Infinity, score, u = mode === "slice" ? rect.dx : mode === "dice" ? rect.dy : mode === "slice-dice" ? node.depth & 1 ? rect.dy : rect.dx : Math.min(rect.dx, rect.dy), n; scale(remaining, rect.dx * rect.dy / node.value); row.area = 0; while ((n = remaining.length) > 0) { @@ -5333,78 +5317,168 @@ d3.csv = d3_dsv(",", "text/csv"); d3.tsv = d3_dsv(" ", "text/tab-separated-values"); d3.geo = {}; - function d3_geo_type(types) { - for (var type in d3_geo_typeDefaults) { - if (!(type in types)) { - types[type] = d3_geo_typeDefaults[type]; + d3.geo.stream = function(object, listener) { + if (d3_geo_streamObjectType.hasOwnProperty(object.type)) { + d3_geo_streamObjectType[object.type](object, listener); + } else { + d3_geo_streamGeometry(object, listener); + } + }; + function d3_geo_streamGeometry(geometry, listener) { + if (d3_geo_streamGeometryType.hasOwnProperty(geometry.type)) { + d3_geo_streamGeometryType[geometry.type](geometry, listener); + } + } + var d3_geo_streamObjectType = { + Feature: function(feature, listener) { + d3_geo_streamGeometry(feature.geometry, listener); + }, + FeatureCollection: function(object, listener) { + var features = object.features, i = -1, n = features.length; + while (++i < n) d3_geo_streamGeometry(features[i].geometry, listener); + } + }; + var d3_geo_streamGeometryType = { + Sphere: function(object, listener) { + listener.sphere(); + }, + Point: function(object, listener) { + var coordinate = object.coordinates; + listener.point(coordinate[0], coordinate[1]); + }, + MultiPoint: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length, coordinate; + while (++i < n) coordinate = coordinates[i], listener.point(coordinate[0], coordinate[1]); + }, + LineString: function(object, listener) { + d3_geo_streamLine(object.coordinates, listener, 0); + }, + MultiLineString: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) d3_geo_streamLine(coordinates[i], listener, 0); + }, + Polygon: function(object, listener) { + d3_geo_streamPolygon(object.coordinates, listener); + }, + MultiPolygon: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) d3_geo_streamPolygon(coordinates[i], listener); + }, + GeometryCollection: function(object, listener) { + var geometries = object.geometries, i = -1, n = geometries.length; + while (++i < n) d3_geo_streamGeometry(geometries[i], listener); + } + }; + function d3_geo_streamLine(coordinates, listener, closed) { + var i = -1, n = coordinates.length - closed, coordinate; + listener.lineStart(); + while (++i < n) coordinate = coordinates[i], listener.point(coordinate[0], coordinate[1]); + listener.lineEnd(); + } + function d3_geo_streamPolygon(coordinates, listener) { + var i = -1, n = coordinates.length; + listener.polygonStart(); + while (++i < n) d3_geo_streamLine(coordinates[i], listener, 1); + listener.polygonEnd(); + } + function d3_geo_spherical(cartesian) { + return [ Math.atan2(cartesian[1], cartesian[0]), Math.asin(Math.max(-1, Math.min(1, cartesian[2]))) ]; + } + function d3_geo_sphericalEqual(a, b) { + return Math.abs(a[0] - b[0]) < ε && Math.abs(a[1] - b[1]) < ε; + } + function d3_geo_cartesian(spherical) { + var λ = spherical[0], φ = spherical[1], cosφ = Math.cos(φ); + return [ cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ) ]; + } + function d3_geo_cartesianDot(a, b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + } + function d3_geo_cartesianCross(a, b) { + return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0] ]; + } + function d3_geo_cartesianAdd(a, b) { + a[0] += b[0]; + a[1] += b[1]; + a[2] += b[2]; + } + function d3_geo_cartesianScale(vector, k) { + return [ vector[0] * k, vector[1] * k, vector[2] * k ]; + } + function d3_geo_cartesianNormalize(d) { + var l = Math.sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]); + d[0] /= l; + d[1] /= l; + d[2] /= l; + } + function d3_geo_resample(project) { + var δ2 = .5, maxDepth = 16; + function resample(stream) { + var λ0, x0, y0, a0, b0, c0; + var resample = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + stream.polygonStart(); + resample.lineStart = polygonLineStart; + }, + polygonEnd: function() { + stream.polygonEnd(); + resample.lineStart = lineStart; + } + }; + function point(x, y) { + x = project(x, y); + stream.point(x[0], x[1]); + } + function lineStart() { + x0 = NaN; + resample.point = linePoint; + stream.lineStart(); + } + function linePoint(λ, φ) { + var c = d3_geo_cartesian([ λ, φ ]), p = project(λ, φ); + resampleLineTo(x0, y0, λ0, a0, b0, c0, x0 = p[0], y0 = p[1], λ0 = λ, a0 = c[0], b0 = c[1], c0 = c[2], maxDepth, stream); + stream.point(x0, y0); + } + function lineEnd() { + resample.point = point; + stream.lineEnd(); + } + function polygonLineStart() { + var λ00, φ00, x00, y00, a00, b00, c00; + lineStart(); + resample.point = function(λ, φ) { + linePoint(λ00 = λ, φ00 = φ), x00 = x0, y00 = y0, a00 = a0, b00 = b0, c00 = c0; + resample.point = linePoint; + }; + resample.lineEnd = function() { + resampleLineTo(x0, y0, λ0, a0, b0, c0, x00, y00, λ00, a00, b00, c00, maxDepth, stream); + resample.lineEnd = lineEnd; + lineEnd(); + }; + } + return resample; + } + function resampleLineTo(x0, y0, λ0, a0, b0, c0, x1, y1, λ1, a1, b1, c1, depth, stream) { + var dx = x1 - x0, dy = y1 - y0, d2 = dx * dx + dy * dy; + if (d2 > 4 * δ2 && depth--) { + var a = a0 + a1, b = b0 + b1, c = c0 + c1, m = Math.sqrt(a * a + b * b + c * c), φ2 = Math.asin(c /= m), λ2 = Math.abs(Math.abs(c) - 1) < ε ? (λ0 + λ1) / 2 : Math.atan2(b, a), p = project(λ2, φ2), x2 = p[0], y2 = p[1], dx2 = x2 - x0, dy2 = y2 - y0, dz = dy * dx2 - dx * dy2; + if (dz * dz / d2 > δ2 || Math.abs((dx * dx2 + dy * dy2) / d2 - .5) > .3) { + resampleLineTo(x0, y0, λ0, a0, b0, c0, x2, y2, λ2, a /= m, b /= m, c, depth, stream); + stream.point(x2, y2); + resampleLineTo(x2, y2, λ2, a, b, c, x1, y1, λ1, a1, b1, c1, depth, stream); + } } } - return types; + resample.precision = function(_) { + if (!arguments.length) return Math.sqrt(δ2); + maxDepth = (δ2 = _ * _) > 0 && 16; + return resample; + }; + return resample; } - var d3_geo_typeDefaults = { - Feature: function(feature) { - this.geometry(feature.geometry); - }, - FeatureCollection: function(collection) { - var features = collection.features, i = -1, n = features.length; - while (++i < n) this.Feature(features[i]); - }, - GeometryCollection: function(collection) { - var geometries = collection.geometries, i = -1, n = geometries.length; - while (++i < n) this.geometry(geometries[i]); - }, - LineString: function(lineString) { - this.line(lineString.coordinates); - }, - MultiLineString: function(multiLineString) { - var coordinates = multiLineString.coordinates, i = -1, n = coordinates.length; - while (++i < n) this.line(coordinates[i]); - }, - MultiPoint: function(multiPoint) { - var coordinates = multiPoint.coordinates, i = -1, n = coordinates.length; - while (++i < n) this.point(coordinates[i]); - }, - MultiPolygon: function(multiPolygon) { - var coordinates = multiPolygon.coordinates, i = -1, n = coordinates.length; - while (++i < n) this.polygon(coordinates[i]); - }, - Point: function(point) { - this.point(point.coordinates); - }, - Polygon: function(polygon) { - this.polygon(polygon.coordinates); - }, - Sphere: d3_noop, - object: function(object) { - return d3_geo_typeObjects.hasOwnProperty(object.type) ? this[object.type](object) : this.geometry(object); - }, - geometry: function(geometry) { - return d3_geo_typeGeometries.hasOwnProperty(geometry.type) ? this[geometry.type](geometry) : null; - }, - point: d3_noop, - line: function(coordinates) { - var i = -1, n = coordinates.length; - while (++i < n) this.point(coordinates[i]); - }, - polygon: function(coordinates) { - var i = -1, n = coordinates.length; - while (++i < n) this.line(coordinates[i]); - } - }; - var d3_geo_typeGeometries = { - LineString: 1, - MultiLineString: 1, - MultiPoint: 1, - MultiPolygon: 1, - Point: 1, - Polygon: 1, - Sphere: 1 - }; - var d3_geo_typeObjects = { - Feature: 1, - FeatureCollection: 1, - GeometryCollection: 1 - }; d3.geo.albersUsa = function() { var lower48 = d3.geo.albers(); var alaska = d3.geo.albers().rotate([ 160, 0 ]).center([ 0, 60 ]).parallels([ 55, 65 ]); @@ -5417,15 +5491,6 @@ var lon = point[0], lat = point[1]; return lat > 50 ? alaska : lon < -140 ? hawaii : lat < 21 ? puertoRico : lower48; } - albersUsa.point = function(coordinates, context) { - return projection(coordinates).point(coordinates, context); - }; - albersUsa.line = function(coordinates, context) { - return projection(coordinates[0]).line(coordinates, context); - }; - albersUsa.polygon = function(coordinates, context) { - return projection(coordinates[0][0]).polygon(coordinates, context); - }; albersUsa.scale = function(x) { if (!arguments.length) return lower48.scale(); lower48.scale(x); @@ -5481,38 +5546,123 @@ return d3_geo_projection(d3_geo_azimuthalEquidistant); }).raw = d3_geo_azimuthalEquidistant; d3.geo.bounds = d3_geo_bounds(d3_identity); - function d3_geo_bounds(projection) { - var x0, y0, x1, y1, bounds = d3_geo_type({ - point: function(point) { - point = projection(point); - var x = point[0], y = point[1]; - if (x < x0) x0 = x; - if (x > x1) x1 = x; - if (y < y0) y0 = y; - if (y > y1) y1 = y; + function d3_geo_bounds(projectStream) { + var x0, y0, x1, y1; + var bound = { + point: boundPoint, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: function() { + bound.lineEnd = boundPolygonLineEnd; }, - polygon: function(coordinates) { - this.line(coordinates[0]); + polygonEnd: function() { + bound.point = boundPoint; } - }); + }; + function boundPoint(x, y) { + if (x < x0) x0 = x; + if (x > x1) x1 = x; + if (y < y0) y0 = y; + if (y > y1) y1 = y; + } + function boundPolygonLineEnd() { + bound.point = bound.lineEnd = d3_noop; + } return function(feature) { y1 = x1 = -(x0 = y0 = Infinity); - bounds.object(feature); + d3.geo.stream(feature, projectStream(bound)); return [ [ x0, y0 ], [ x1, y1 ] ]; }; } + d3.geo.centroid = function(object) { + d3_geo_centroidDimension = d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + d3.geo.stream(object, d3_geo_centroid); + var m; + if (d3_geo_centroidW && Math.abs(m = Math.sqrt(d3_geo_centroidX * d3_geo_centroidX + d3_geo_centroidY * d3_geo_centroidY + d3_geo_centroidZ * d3_geo_centroidZ)) > ε) { + return [ Math.atan2(d3_geo_centroidY, d3_geo_centroidX) * d3_degrees, Math.asin(Math.max(-1, Math.min(1, d3_geo_centroidZ / m))) * d3_degrees ]; + } + }; + var d3_geo_centroidDimension, d3_geo_centroidW, d3_geo_centroidX, d3_geo_centroidY, d3_geo_centroidZ; + var d3_geo_centroid = { + sphere: function() { + if (d3_geo_centroidDimension < 2) { + d3_geo_centroidDimension = 2; + d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + } + }, + point: d3_geo_centroidPoint, + lineStart: d3_geo_centroidLineStart, + lineEnd: d3_geo_centroidLineEnd, + polygonStart: function() { + if (d3_geo_centroidDimension < 2) { + d3_geo_centroidDimension = 2; + d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + } + d3_geo_centroid.lineStart = d3_geo_centroidRingStart; + }, + polygonEnd: function() { + d3_geo_centroid.lineStart = d3_geo_centroidLineStart; + } + }; + function d3_geo_centroidPoint(λ, φ) { + if (d3_geo_centroidDimension) return; + ++d3_geo_centroidW; + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians); + d3_geo_centroidX += (cosφ * Math.cos(λ) - d3_geo_centroidX) / d3_geo_centroidW; + d3_geo_centroidY += (cosφ * Math.sin(λ) - d3_geo_centroidY) / d3_geo_centroidW; + d3_geo_centroidZ += (Math.sin(φ) - d3_geo_centroidZ) / d3_geo_centroidW; + } + function d3_geo_centroidRingStart() { + var λ00, φ00; + d3_geo_centroidDimension = 1; + d3_geo_centroidLineStart(); + d3_geo_centroidDimension = 2; + var linePoint = d3_geo_centroid.point; + d3_geo_centroid.point = function(λ, φ) { + linePoint(λ00 = λ, φ00 = φ); + }; + d3_geo_centroid.lineEnd = function() { + d3_geo_centroid.point(λ00, φ00); + d3_geo_centroidLineEnd(); + d3_geo_centroid.lineEnd = d3_geo_centroidLineEnd; + }; + } + function d3_geo_centroidLineStart() { + var x0, y0, z0; + if (d3_geo_centroidDimension > 1) return; + if (d3_geo_centroidDimension < 1) { + d3_geo_centroidDimension = 1; + d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + } + d3_geo_centroid.point = function(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians); + x0 = cosφ * Math.cos(λ); + y0 = cosφ * Math.sin(λ); + z0 = Math.sin(φ); + d3_geo_centroid.point = nextPoint; + }; + function nextPoint(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians), x = cosφ * Math.cos(λ), y = cosφ * Math.sin(λ), z = Math.sin(φ), w = Math.atan2(Math.sqrt((w = y0 * z - z0 * y) * w + (w = z0 * x - x0 * z) * w + (w = x0 * y - y0 * x) * w), x0 * x + y0 * y + z0 * z); + d3_geo_centroidW += w; + d3_geo_centroidX += w * (x0 + (x0 = x)); + d3_geo_centroidY += w * (y0 + (y0 = y)); + d3_geo_centroidZ += w * (z0 + (z0 = z)); + } + } + function d3_geo_centroidLineEnd() { + d3_geo_centroid.point = d3_geo_centroidPoint; + } d3.geo.circle = function() { - var origin = [ 0, 0 ], angle, precision = 6, rotate, interpolate; + var origin = [ 0, 0 ], angle, precision = 6, interpolate; function circle() { - var o = typeof origin === "function" ? origin.apply(this, arguments) : origin; - rotate = d3_geo_rotation(-o[0] * d3_radians, -o[1] * d3_radians, 0); - var ring = []; + var center = typeof origin === "function" ? origin.apply(this, arguments) : origin, rotate = d3_geo_rotation(-center[0] * d3_radians, -center[1] * d3_radians, 0).invert, ring = []; interpolate(null, null, 1, { - lineTo: function(λ, φ) { - var point = rotate.invert(λ, φ); - point[0] *= d3_degrees; - point[1] *= d3_degrees; - ring.push(point); + point: function(x, y) { + ring.push(x = rotate(x, y)); + x[0] *= d3_degrees, x[1] *= d3_degrees; } }); return { @@ -5537,84 +5687,9 @@ }; return circle.angle(90); }; - function d3_geo_circleClip(degrees, rotate) { - var radians = degrees * d3_radians, cr = Math.cos(radians), interpolate = d3_geo_circleInterpolate(radians, 6 * d3_radians); - return { - point: function(coordinates, context) { - if (visible(coordinates = rotate(coordinates))) { - context.point(coordinates[0], coordinates[1]); - } - }, - line: function(coordinates, context) { - clipLine(coordinates, context); - }, - polygon: function(polygon, context) { - d3_geo_circleClipPolygon(polygon, context, clipLine, interpolate); - }, - sphere: function(context) { - d3_geo_projectionSphere(context, interpolate); - } - }; - function visible(point) { - return Math.cos(point[1]) * Math.cos(point[0]) > cr; - } - function clipLine(coordinates, context, ring) { - if (!(n = coordinates.length)) return [ ring && 0, false ]; - var point0 = rotate(coordinates[0]), point1, point2, v0 = visible(point0), v00 = ring && v0, v, n, clean = ring, area = 0, p, x0, x, y0, y; - if (clean) { - x0 = (p = d3_geo_stereographic(point0[0] + (v0 ? 0 : π), point0[1]))[0]; - y0 = p[1]; - } - if (v0) context.moveTo(point0[0], point0[1]); - for (var i = 1; i < n; i++) { - point1 = rotate(coordinates[i]); - v = visible(point1); - if (v !== v0) { - point2 = intersect(point0, point1); - if (d3_geo_circlePointsEqual(point0, point2) || d3_geo_circlePointsEqual(point1, point2)) { - point1[0] += ε; - point1[1] += ε; - v = visible(point1); - } - } - if (v !== v0) { - clean = false; - if (v0 = v) { - point2 = intersect(point1, point0); - context.moveTo(point2[0], point2[1]); - } else { - point2 = intersect(point0, point1); - context.lineTo(point2[0], point2[1]); - } - point0 = point2; - } - if (clean) { - p = d3_geo_stereographic(point1[0] + (v ? 0 : π), point1[1]); - x = p[0]; - y = p[1]; - area += y0 * x - x0 * y; - x0 = x; - y0 = y; - } - if (v && !d3_geo_circlePointsEqual(point0, point1)) context.lineTo(point1[0], point1[1]); - point0 = point1; - } - return [ clean && area * .5, v00 && v ]; - } - function intersect(a, b) { - var pa = d3_geo_circleCartesian(a, [ 0, 0, 0 ]), pb = d3_geo_circleCartesian(b, [ 0, 0, 0 ]); - var n1 = [ 1, 0, 0 ], n2 = d3_geo_circleCross(pa, pb), n2n2 = d3_geo_circleDot(n2, n2), n1n2 = n2[0], determinant = n2n2 - n1n2 * n1n2; - if (!determinant) return a; - var c1 = cr * n2n2 / determinant, c2 = -cr * n1n2 / determinant, n1xn2 = d3_geo_circleCross(n1, n2), A = d3_geo_circleScale(n1, c1), B = d3_geo_circleScale(n2, c2); - d3_geo_circleAdd(A, B); - var u = n1xn2, w = d3_geo_circleDot(A, u), uu = d3_geo_circleDot(u, u), t = Math.sqrt(w * w - uu * (d3_geo_circleDot(A, A) - 1)), q = d3_geo_circleScale(u, (-w - t) / uu); - d3_geo_circleAdd(q, A); - return d3_geo_circleSpherical(q); - } - } function d3_geo_circleInterpolate(radians, precision) { var cr = Math.cos(radians), sr = Math.sin(radians); - return function(from, to, direction, context) { + return function(from, to, direction, listener) { if (from != null) { from = d3_geo_circleAngle(cr, from); to = d3_geo_circleAngle(cr, to); @@ -5623,38 +5698,110 @@ from = radians + direction * 2 * π; to = radians; } + var point; for (var step = direction * precision, t = from; direction > 0 ? t > to : t < to; t -= step) { - var c = Math.cos(t), s = Math.sin(t), point = d3_geo_circleSpherical([ cr, -sr * c, -sr * s ]); - context.lineTo(point[0], point[1]); + listener.point((point = d3_geo_spherical([ cr, -sr * Math.cos(t), -sr * Math.sin(t) ]))[0], point[1]); } }; } - function d3_geo_circleClipPolygon(coordinates, context, clipLine, interpolate) { - var subject = [], clip = [], segments = [], buffer = d3_geo_circleBufferSegments(clipLine), draw = [], visibleArea = 0, invisibleArea = 0, invisible = false; - coordinates.forEach(function(ring) { - var x = buffer(ring, context), ringSegments = x[1], segment, n = ringSegments.length; - if (!n) { - invisible = true; - invisibleArea += x[0][0]; - return; + function d3_geo_circleAngle(cr, point) { + var a = d3_geo_cartesian(point); + a[0] -= cr; + d3_geo_cartesianNormalize(a); + var angle = Math.acos(Math.max(-1, Math.min(1, -a[1]))); + return ((-a[2] < 0 ? -angle : angle) + 2 * Math.PI - ε) % (2 * Math.PI); + } + function d3_geo_clip(pointVisible, clipLine, interpolate) { + return function(listener) { + var line = clipLine(listener); + var clip = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + clip.point = pointRing; + clip.lineStart = ringStart; + clip.lineEnd = ringEnd; + invisible = false; + invisibleArea = visibleArea = 0; + segments = []; + listener.polygonStart(); + }, + polygonEnd: function() { + clip.point = point; + clip.lineStart = lineStart; + clip.lineEnd = lineEnd; + segments = d3.merge(segments); + if (segments.length) { + d3_geo_clipPolygon(segments, interpolate, listener); + } else if (visibleArea < -ε || invisible && invisibleArea < -ε) { + listener.lineStart(); + interpolate(null, null, 1, listener); + listener.lineEnd(); + } + listener.polygonEnd(); + segments = null; + }, + sphere: function() { + listener.polygonStart(); + listener.lineStart(); + interpolate(null, null, 1, listener); + listener.lineEnd(); + listener.polygonEnd(); + } + }; + function point(λ, φ) { + if (pointVisible(λ, φ)) listener.point(λ, φ); } - if (x[0][0] !== false) { - visibleArea += x[0][0]; - draw.push(segment = ringSegments[0]); - var point = segment[0], n = segment.length - 1, i = 0; - context.moveTo(point[0], point[1]); - while (++i < n) context.lineTo((point = segment[i])[0], point[1]); - context.closePath(); - return; + function pointLine(λ, φ) { + line.point(λ, φ); } - if (n > 1 && x[0][1]) ringSegments.push(ringSegments.pop().concat(ringSegments.shift())); - segments = segments.concat(ringSegments.filter(d3_geo_circleSegmentLength1)); - }); - if (!segments.length) { - if (visibleArea < 0 || invisible && invisibleArea < 0) { - d3_geo_projectionSphere(context, interpolate); + function lineStart() { + clip.point = pointLine; + line.lineStart(); } - } + function lineEnd() { + clip.point = point; + line.lineEnd(); + } + var segments, visibleArea, invisibleArea, invisible; + var buffer = d3_geo_clipBufferListener(), ringListener = clipLine(buffer), ring; + function pointRing(λ, φ) { + ringListener.point(λ, φ); + ring.push([ λ, φ ]); + } + function ringStart() { + ringListener.lineStart(); + ring = []; + } + function ringEnd() { + pointRing(ring[0][0], ring[0][1]); + ringListener.lineEnd(); + var clean = ringListener.clean(), ringSegments = buffer.buffer(), segment, n = ringSegments.length; + if (!n) { + invisible = true; + invisibleArea += d3_geo_clipAreaRing(ring, -1); + ring = null; + return; + } + ring = null; + if (clean & 1) { + segment = ringSegments[0]; + visibleArea += d3_geo_clipAreaRing(segment, 1); + var n = segment.length - 1, i = -1, point; + listener.lineStart(); + while (++i < n) listener.point((point = segment[i])[0], point[1]); + listener.lineEnd(); + return; + } + if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift())); + segments.push(ringSegments.filter(d3_geo_clipSegmentLength1)); + } + return clip; + }; + } + function d3_geo_clipPolygon(segments, interpolate, listener) { + var subject = [], clip = []; segments.forEach(function(segment) { var n = segment.length; if (n <= 1) return; @@ -5696,112 +5843,224 @@ subject.push(a); clip.push(b); }); - clip.sort(d3_geo_circleClipSort); - d3_geo_circleLinkCircular(subject); - d3_geo_circleLinkCircular(clip); + clip.sort(d3_geo_clipSort); + d3_geo_clipLinkCircular(subject); + d3_geo_clipLinkCircular(clip); if (!subject.length) return; var start = subject[0], current, points, point; while (1) { current = start; while (current.visited) if ((current = current.next) === start) return; points = current.points; - context.moveTo((point = points.shift())[0], point[1]); + listener.lineStart(); do { current.visited = current.other.visited = true; if (current.entry) { if (current.subject) { - for (var i = 0; i < points.length; i++) context.lineTo((point = points[i])[0], point[1]); + for (var i = 0; i < points.length; i++) listener.point((point = points[i])[0], point[1]); } else { - interpolate(current.point, current.next.point, 1, context); + interpolate(current.point, current.next.point, 1, listener); } current = current.next; } else { if (current.subject) { points = current.prev.points; - for (var i = points.length; --i >= 0; ) context.lineTo((point = points[i])[0], point[1]); + for (var i = points.length; --i >= 0; ) listener.point((point = points[i])[0], point[1]); } else { - interpolate(current.point, current.prev.point, -1, context); + interpolate(current.point, current.prev.point, -1, listener); } current = current.prev; } current = current.other; points = current.points; } while (!current.visited); - context.closePath(); + listener.lineEnd(); } } - function d3_geo_circleLinkCircular(array) { - for (var i = 0, a = array[0], b, n = array.length; i < n; ) { - a.next = b = array[++i % n]; + function d3_geo_clipLinkCircular(array) { + if (!(n = array.length)) return; + var n, i = 0, a = array[0], b; + while (++i < n) { + a.next = b = array[i]; b.prev = a; a = b; } + a.next = b = array[0]; + b.prev = a; } - function d3_geo_circleClipSort(a, b) { + function d3_geo_clipSort(a, b) { return ((a = a.point)[0] < 0 ? a[1] - π / 2 - ε : π / 2 - a[1]) - ((b = b.point)[0] < 0 ? b[1] - π / 2 - ε : π / 2 - b[1]); } - function d3_geo_circleAngle(cr, point) { - var a = d3_geo_circleCartesian(point, [ cr, 0, 0 ]); - d3_geo_circleNormalize(a); - var angle = Math.acos(Math.max(-1, Math.min(1, -a[1]))); - return ((-a[2] < 0 ? -angle : angle) + 2 * Math.PI - ε) % (2 * Math.PI); - } - function d3_geo_circleCartesian(point, origin) { - var p0 = point[0], p1 = point[1], c1 = Math.cos(p1); - return [ c1 * Math.cos(p0) - origin[0], c1 * Math.sin(p0) - origin[1], Math.sin(p1) - origin[2] ]; - } - function d3_geo_circleSpherical(point) { - return [ Math.atan2(point[1], point[0]), Math.asin(Math.max(-1, Math.min(1, point[2]))) ]; - } - function d3_geo_circleDot(a, b) { - return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; - } - function d3_geo_circleCross(a, b) { - return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0] ]; - } - function d3_geo_circleAdd(a, b) { - a[0] += b[0]; - a[1] += b[1]; - a[2] += b[2]; - } - function d3_geo_circleScale(vector, s) { - return [ vector[0] * s, vector[1] * s, vector[2] * s ]; - } - function d3_geo_circleNormalize(d) { - var l = Math.sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]); - d[0] /= l; - d[1] /= l; - d[2] /= l; - } - function d3_geo_circleBufferSegments(f) { - return function(coordinates) { - var segments = [], segment; - return [ f(coordinates, { - moveTo: function(x, y) { - segments.push(segment = [ [ x, y ] ]); - }, - lineTo: function(x, y) { - segment.push([ x, y ]); - } - }, true), segments ]; - }; - } - function d3_geo_circlePointsEqual(a, b) { - return Math.abs(a[0] - b[0]) < ε && Math.abs(a[1] - b[1]) < ε; - } - function d3_geo_circleSegmentLength1(segment) { + function d3_geo_clipSegmentLength1(segment) { return segment.length > 1; } + function d3_geo_clipBufferListener() { + var lines = [], line; + return { + lineStart: function() { + lines.push(line = []); + }, + point: function(λ, φ) { + line.push([ λ, φ ]); + }, + lineEnd: d3_noop, + buffer: function() { + var buffer = lines; + lines = []; + line = null; + return buffer; + } + }; + } + function d3_geo_clipAreaRing(ring, invisible) { + if (!(n = ring.length)) return 0; + var n, i = 0, area = 0, p = ring[0], λ = p[0], φ = p[1], cosφ = Math.cos(φ), x0 = Math.atan2(invisible * Math.sin(λ) * cosφ, Math.sin(φ)), y0 = 1 - invisible * Math.cos(λ) * cosφ, x1 = x0, x, y; + while (++i < n) { + p = ring[i]; + cosφ = Math.cos(φ = p[1]); + x = Math.atan2(invisible * Math.sin(λ = p[0]) * cosφ, Math.sin(φ)); + y = 1 - invisible * Math.cos(λ) * cosφ; + if (Math.abs(y0 - 2) < ε && Math.abs(y - 2) < ε) continue; + if (Math.abs(y) < ε || Math.abs(y0) < ε) {} else if (Math.abs(Math.abs(x - x0) - π) < ε) { + if (y + y0 > 2) area += 4 * (x - x0); + } else if (Math.abs(y0 - 2) < ε) area += 4 * (x - x1); else area += ((3 * π + x - x0) % (2 * π) - π) * (y0 + y); + x1 = x0, x0 = x, y0 = y; + } + return area; + } + var d3_geo_clipAntimeridian = d3_geo_clip(d3_true, d3_geo_clipAntimeridianLine, d3_geo_clipAntimeridianInterpolate); + function d3_geo_clipAntimeridianLine(listener) { + var λ0 = NaN, φ0 = NaN, sλ0 = NaN, clean; + return { + lineStart: function() { + listener.lineStart(); + clean = 1; + }, + point: function(λ1, φ1) { + var sλ1 = λ1 > 0 ? π : -π, dλ = Math.abs(λ1 - λ0); + if (Math.abs(dλ - π) < ε) { + listener.point(λ0, φ0 = (φ0 + φ1) / 2 > 0 ? π / 2 : -π / 2); + listener.point(sλ0, φ0); + listener.lineEnd(); + listener.lineStart(); + listener.point(sλ1, φ0); + listener.point(λ1, φ0); + clean = 0; + } else if (sλ0 !== sλ1 && dλ >= π) { + if (Math.abs(λ0 - sλ0) < ε) λ0 -= sλ0 * ε; + if (Math.abs(λ1 - sλ1) < ε) λ1 -= sλ1 * ε; + φ0 = d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1); + listener.point(sλ0, φ0); + listener.lineEnd(); + listener.lineStart(); + listener.point(sλ1, φ0); + clean = 0; + } + listener.point(λ0 = λ1, φ0 = φ1); + sλ0 = sλ1; + }, + lineEnd: function() { + listener.lineEnd(); + λ0 = φ0 = NaN; + }, + clean: function() { + return 2 - clean; + } + }; + } + function d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1) { + var cosφ0, cosφ1, sinλ0_λ1 = Math.sin(λ0 - λ1); + return Math.abs(sinλ0_λ1) > ε ? Math.atan((Math.sin(φ0) * (cosφ1 = Math.cos(φ1)) * Math.sin(λ1) - Math.sin(φ1) * (cosφ0 = Math.cos(φ0)) * Math.sin(λ0)) / (cosφ0 * cosφ1 * sinλ0_λ1)) : (φ0 + φ1) / 2; + } + function d3_geo_clipAntimeridianInterpolate(from, to, direction, listener) { + var φ; + if (from == null) { + φ = direction * π / 2; + listener.point(-π, φ); + listener.point(0, φ); + listener.point(π, φ); + listener.point(π, 0); + listener.point(π, -φ); + listener.point(0, -φ); + listener.point(-π, -φ); + listener.point(-π, 0); + listener.point(-π, φ); + } else if (Math.abs(from[0] - to[0]) > ε) { + var s = (from[0] < to[0] ? 1 : -1) * π; + φ = direction * s / 2; + listener.point(-s, φ); + listener.point(0, φ); + listener.point(s, φ); + } else { + listener.point(to[0], to[1]); + } + } + function d3_geo_clipCircle(degrees) { + var radians = degrees * d3_radians, cr = Math.cos(radians), interpolate = d3_geo_circleInterpolate(radians, 6 * d3_radians); + return d3_geo_clip(visible, clipLine, interpolate); + function visible(λ, φ) { + return Math.cos(λ) * Math.cos(φ) > cr; + } + function clipLine(listener) { + var point0, v0, v00, clean; + return { + lineStart: function() { + v00 = v0 = false; + clean = 1; + }, + point: function(λ, φ) { + var point1 = [ λ, φ ], point2, v = visible(λ, φ); + if (!point0 && (v00 = v0 = v)) listener.lineStart(); + if (v !== v0) { + point2 = intersect(point0, point1); + if (d3_geo_sphericalEqual(point0, point2) || d3_geo_sphericalEqual(point1, point2)) { + point1[0] += ε; + point1[1] += ε; + v = visible(point1[0], point1[1]); + } + } + if (v !== v0) { + clean = 0; + if (v0 = v) { + listener.lineStart(); + point2 = intersect(point1, point0); + listener.point(point2[0], point2[1]); + } else { + point2 = intersect(point0, point1); + listener.point(point2[0], point2[1]); + listener.lineEnd(); + } + point0 = point2; + } + if (v && (!point0 || !d3_geo_sphericalEqual(point0, point1))) listener.point(point1[0], point1[1]); + point0 = point1; + }, + lineEnd: function() { + if (v0) listener.lineEnd(); + point0 = null; + }, + clean: function() { + return clean | (v00 && v0) << 1; + } + }; + } + function intersect(a, b) { + var pa = d3_geo_cartesian(a, 0), pb = d3_geo_cartesian(b, 0); + var n1 = [ 1, 0, 0 ], n2 = d3_geo_cartesianCross(pa, pb), n2n2 = d3_geo_cartesianDot(n2, n2), n1n2 = n2[0], determinant = n2n2 - n1n2 * n1n2; + if (!determinant) return a; + var c1 = cr * n2n2 / determinant, c2 = -cr * n1n2 / determinant, n1xn2 = d3_geo_cartesianCross(n1, n2), A = d3_geo_cartesianScale(n1, c1), B = d3_geo_cartesianScale(n2, c2); + d3_geo_cartesianAdd(A, B); + var u = n1xn2, w = d3_geo_cartesianDot(A, u), uu = d3_geo_cartesianDot(u, u), t = Math.sqrt(w * w - uu * (d3_geo_cartesianDot(A, A) - 1)), q = d3_geo_cartesianScale(u, (-w - t) / uu); + d3_geo_cartesianAdd(q, A); + return d3_geo_spherical(q); + } + } function d3_geo_compose(a, b) { - if (a === d3_geo_equirectangular) return b; - if (b === d3_geo_equirectangular) return a; - function compose(λ, φ) { - var coordinates = a(λ, φ); - return b(coordinates[0], coordinates[1]); + function compose(x, y) { + return x = a(x, y), b(x[0], x[1]); } if (a.invert && b.invert) compose.invert = function(x, y) { - var coordinates = b.invert(x, y); - return a.invert(coordinates[0], coordinates[1]); + return x = b.invert(x, y), x && a.invert(x[0], x[1]); }; return compose; } @@ -5821,12 +6080,15 @@ var x1, x0, y1, y0, dx = 22.5, dy = dx, x, y, precision = 2.5; function graticule() { return { - type: "GeometryCollection", - geometries: graticule.lines() + type: "MultiLineString", + coordinates: lines() }; } + function lines() { + return d3.range(Math.ceil(x0 / dx) * dx, x1, dx).map(x).concat(d3.range(Math.ceil(y0 / dy) * dy, y1, dy).map(y)); + } graticule.lines = function() { - return d3.range(Math.ceil(x0 / dx) * dx, x1, dx).map(x).concat(d3.range(Math.ceil(y0 / dy) * dy, y1, dy).map(y)).map(function(coordinates) { + return lines().map(function(coordinates) { return { type: "LineString", coordinates: coordinates @@ -5877,11 +6139,23 @@ }); }; } + d3.geo.interpolate = function(source, target) { + return d3_geo_interpolate(source[0] * d3_radians, source[1] * d3_radians, target[0] * d3_radians, target[1] * d3_radians); + }; + function d3_geo_interpolate(x0, y0, x1, y1) { + var cy0 = Math.cos(y0), sy0 = Math.sin(y0), cy1 = Math.cos(y1), sy1 = Math.sin(y1), kx0 = cy0 * Math.cos(x0), ky0 = cy0 * Math.sin(x0), kx1 = cy1 * Math.cos(x1), ky1 = cy1 * Math.sin(x1), d = Math.acos(Math.max(-1, Math.min(1, sy0 * sy1 + cy0 * cy1 * Math.cos(x1 - x0)))), k = 1 / Math.sin(d); + function interpolate(t) { + var B = Math.sin(t *= d) * k, A = Math.sin(d - t) * k, x = A * kx0 + B * kx1, y = A * ky0 + B * ky1, z = A * sy0 + B * sy1; + return [ Math.atan2(y, x) / d3_radians, Math.atan2(z, Math.sqrt(x * x + y * y)) / d3_radians ]; + } + interpolate.distance = d; + return interpolate; + } d3.geo.greatArc = function() { - var source = d3_source, p0, target = d3_target, p1, precision = 6 * d3_radians, interpolate = d3_geo_greatArcInterpolator(); + var source = d3_source, source_, target = d3_target, target_, precision = 6 * d3_radians, interpolate; function greatArc() { - var d = greatArc.distance.apply(this, arguments), t = 0, dt = precision / d, coordinates = [ p0 ]; - while ((t += dt) < 1) coordinates.push(interpolate(t)); + var p0 = source_ || source.apply(this, arguments), p1 = target_ || target.apply(this, arguments), i = interpolate || d3.geo.interpolate(p0, p1), t = 0, dt = precision / i.distance, coordinates = [ p0 ]; + while ((t += dt) < 1) coordinates.push(i(t)); coordinates.push(p1); return { type: "LineString", @@ -5889,20 +6163,18 @@ }; } greatArc.distance = function() { - if (typeof source === "function") interpolate.source(p0 = source.apply(this, arguments)); - if (typeof target === "function") interpolate.target(p1 = target.apply(this, arguments)); - return interpolate.distance(); + return (interpolate || d3.geo.interpolate(source_ || source.apply(this, arguments), target_ || target.apply(this, arguments))).distance; }; greatArc.source = function(_) { if (!arguments.length) return source; - source = _; - if (typeof source !== "function") interpolate.source(p0 = source); + source = _, source_ = typeof _ === "function" ? null : _; + interpolate = source_ && target_ ? d3.geo.interpolate(source_, target_) : null; return greatArc; }; greatArc.target = function(_) { if (!arguments.length) return target; - target = _; - if (typeof target !== "function") interpolate.target(p1 = target); + target = _, target_ = typeof _ === "function" ? null : _; + interpolate = source_ && target_ ? d3.geo.interpolate(source_, target_) : null; return greatArc; }; greatArc.precision = function(_) { @@ -5912,36 +6184,6 @@ }; return greatArc; }; - function d3_geo_greatArcInterpolator() { - var x0, y0, cy0, sy0, kx0, ky0, x1, y1, cy1, sy1, kx1, ky1, d, k; - function interpolate(t) { - var B = Math.sin(t *= d) * k, A = Math.sin(d - t) * k, x = A * kx0 + B * kx1, y = A * ky0 + B * ky1, z = A * sy0 + B * sy1; - return [ Math.atan2(y, x) / d3_radians, Math.atan2(z, Math.sqrt(x * x + y * y)) / d3_radians ]; - } - interpolate.distance = function() { - if (d == null) k = 1 / Math.sin(d = Math.acos(Math.max(-1, Math.min(1, sy0 * sy1 + cy0 * cy1 * Math.cos(x1 - x0))))); - return d; - }; - interpolate.source = function(_) { - var cx0 = Math.cos(x0 = _[0] * d3_radians), sx0 = Math.sin(x0); - cy0 = Math.cos(y0 = _[1] * d3_radians); - sy0 = Math.sin(y0); - kx0 = cy0 * cx0; - ky0 = cy0 * sx0; - d = null; - return interpolate; - }; - interpolate.target = function(_) { - var cx1 = Math.cos(x1 = _[0] * d3_radians), sx1 = Math.sin(x1); - cy1 = Math.cos(y1 = _[1] * d3_radians); - sy1 = Math.sin(y1); - kx1 = cy1 * cx1; - ky1 = cy1 * sx1; - d = null; - return interpolate; - }; - return interpolate; - } function d3_geo_mercator(λ, φ) { return [ λ / (2 * π), Math.max(-.5, Math.min(+.5, Math.log(Math.tan(π / 4 + φ / 2)) / (2 * π))) ]; } @@ -5958,203 +6200,290 @@ return d3_geo_projection(d3_geo_orthographic); }).raw = d3_geo_orthographic; d3.geo.path = function() { - var pointRadius = 4.5, pointCircle = d3_geo_pathCircle(pointRadius), projection = d3.geo.albersUsa(), bounds, buffer = []; - var bufferContext = { - point: function(x, y) { - buffer.push("M", x, ",", y, pointCircle); - }, - moveTo: function(x, y) { - buffer.push("M", x, ",", y); - }, - lineTo: function(x, y) { - buffer.push("L", x, ",", y); - }, - closePath: function() { - buffer.push("Z"); - } - }; - var area, centroidWeight, x00, y00, x0, y0, cx, cy; - var areaContext = { - point: d3_noop, - moveTo: moveTo, - lineTo: function(x, y) { - area += y0 * x - x0 * y; - x0 = x; - y0 = y; - }, - closePath: closePath - }; - var lineCentroidContext = { - point: function(x, y) { - cx += x; - cy += y; - ++centroidWeight; - }, - moveTo: moveTo, - lineTo: function(x, y) { - var dx = x - x0, dy = y - y0, δ = Math.sqrt(dx * dx + dy * dy); - centroidWeight += δ; - cx += δ * (x0 + x) / 2; - cy += δ * (y0 + y) / 2; - x0 = x; - y0 = y; - }, - closePath: closePath - }; - var polygonCentroidContext = { - point: d3_noop, - moveTo: moveTo, - lineTo: function(x, y) { - var δ = y0 * x - x0 * y; - centroidWeight += δ * 3; - cx += δ * (x0 + x); - cy += δ * (y0 + y); - x0 = x; - y0 = y; - }, - closePath: closePath - }; - function moveTo(x, y) { - x00 = x0 = x; - y00 = y0 = y; - } - function closePath() { - this.lineTo(x00, y00); - } - var context = bufferContext; + var pointRadius = 4.5, projection, context, projectStream, contextStream; function path(object) { - var result = null; - if (object != result) { - if (typeof pointRadius === "function") pointCircle = d3_geo_pathCircle(pointRadius.apply(this, arguments)); - pathType.object(object); - if (buffer.length) result = buffer.join(""), buffer = []; - } - return result; - } - var pathType = d3_geo_type({ - line: function(coordinates) { - projection.line(coordinates, context); - }, - polygon: function(coordinates) { - projection.polygon(coordinates, context); - }, - point: function(coordinates) { - projection.point(coordinates, context); - }, - Sphere: function() { - projection.sphere(context); - } - }); - var areaType = d3_geo_type({ - Feature: function(feature) { - return areaType.geometry(feature.geometry); - }, - FeatureCollection: function(collection) { - return d3.sum(collection.features, areaType.Feature); - }, - GeometryCollection: function(collection) { - return d3.sum(collection.geometries, areaType.geometry); - }, - LineString: d3_zero, - MultiLineString: d3_zero, - MultiPoint: d3_zero, - MultiPolygon: function(multiPolygon) { - return d3.sum(multiPolygon.coordinates, polygonArea); - }, - Point: d3_zero, - Polygon: function(polygon) { - return polygonArea(polygon.coordinates); - }, - Sphere: sphereArea - }); - function polygonArea(coordinates) { - area = 0; - projection.polygon(coordinates, areaContext); - return Math.abs(area) / 2; - } - function sphereArea() { - area = 0; - projection.sphere(areaContext); - return Math.abs(area) / 2; + if (object) d3.geo.stream(object, projectStream(contextStream.pointRadius(typeof pointRadius === "function" ? +pointRadius.apply(this, arguments) : pointRadius))); + return contextStream.result(); } path.area = function(object) { - return areaType.object(object); - }; - var centroidType = d3_geo_type({ - Feature: function(feature) { - return centroidType.geometry(feature.geometry); - }, - LineString: weightedCentroid(function(lineString) { - projection.line(lineString.coordinates, lineCentroidContext); - }), - MultiLineString: weightedCentroid(function(multiLineString) { - var coordinates = multiLineString.coordinates, i = -1, n = coordinates.length; - while (++i < n) projection.line(coordinates[i], lineCentroidContext); - }), - MultiPoint: weightedCentroid(function(multiPoint) { - var coordinates = multiPoint.coordinates, i = -1, n = coordinates.length; - while (++i < n) projection.point(coordinates[i], lineCentroidContext); - }), - MultiPolygon: weightedCentroid(function(multiPolygon) { - var coordinates = multiPolygon.coordinates, i = -1, n = coordinates.length; - while (++i < n) projection.polygon(coordinates[i], polygonCentroidContext); - }), - Point: weightedCentroid(function(point) { - projection.point(point.coordinates, lineCentroidContext); - }), - Polygon: weightedCentroid(function(polygon) { - projection.polygon(polygon.coordinates, polygonCentroidContext); - }), - Sphere: weightedCentroid(function() { - projection.sphere(polygonCentroidContext); - }) - }); - function weightedCentroid(f) { - return function() { - centroidWeight = cx = cy = 0; - f.apply(this, arguments); - return centroidWeight ? [ cx / centroidWeight, cy / centroidWeight ] : null; - }; - } - path.bounds = function(object) { - return (bounds || (bounds = d3_geo_bounds(projection)))(object); + d3_geo_pathAreaSum = 0; + d3.geo.stream(object, projectStream(d3_geo_pathArea)); + return d3_geo_pathAreaSum; }; path.centroid = function(object) { - return centroidType.object(object); + d3_geo_centroidDimension = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + d3.geo.stream(object, projectStream(d3_geo_pathCentroid)); + return d3_geo_centroidZ ? [ d3_geo_centroidX / d3_geo_centroidZ, d3_geo_centroidY / d3_geo_centroidZ ] : undefined; + }; + path.bounds = function(object) { + return d3_geo_bounds(projectStream)(object); }; path.projection = function(_) { if (!arguments.length) return projection; - projection = _; - bounds = null; + projectStream = (projection = _) ? _.stream || d3_geo_pathProjectStream(_) : d3_identity; return path; }; path.context = function(_) { - if (!arguments.length) return context === bufferContext ? null : context; - context = _; - if (context == null) context = bufferContext; + if (!arguments.length) return context; + contextStream = (context = _) == null ? new d3_geo_pathBuffer() : new d3_geo_pathContext(_); return path; }; - path.pointRadius = function(x) { + path.pointRadius = function(_) { if (!arguments.length) return pointRadius; - if (typeof x === "function") pointRadius = x; else pointCircle = d3_geo_pathCircle(pointRadius = +x); + pointRadius = typeof _ === "function" ? _ : +_; return path; }; - return path; + return path.projection(d3.geo.albersUsa()).context(null); }; function d3_geo_pathCircle(radius) { return "m0," + radius + "a" + radius + "," + radius + " 0 1,1 0," + -2 * radius + "a" + radius + "," + radius + " 0 1,1 0," + +2 * radius + "z"; } - var d3_geo_pathIdentity = d3.geo.path().projection({ - polygon: function(polygon, context) { - polygon.forEach(function(ring) { - var n = ring.length, i = 0, point; - context.moveTo((point = ring[0])[0], point[1]); - while (++i < n) context.lineTo((point = ring[i])[0], point[1]); - context.closePath(); - }); + function d3_geo_pathProjectStream(project) { + var resample = d3_geo_resample(function(λ, φ) { + return project([ λ * d3_degrees, φ * d3_degrees ]); + }); + return function(stream) { + stream = resample(stream); + return { + point: function(λ, φ) { + stream.point(λ * d3_radians, φ * d3_radians); + }, + sphere: function() { + stream.sphere(); + }, + lineStart: function() { + stream.lineStart(); + }, + lineEnd: function() { + stream.lineEnd(); + }, + polygonStart: function() { + stream.polygonStart(); + }, + polygonEnd: function() { + stream.polygonEnd(); + } + }; + }; + } + function d3_geo_pathBuffer() { + var pointCircle = d3_geo_pathCircle(4.5), buffer = []; + var stream = { + point: point, + lineStart: function() { + stream.point = pointLineStart; + }, + lineEnd: lineEnd, + polygonStart: function() { + stream.lineEnd = lineEndPolygon; + }, + polygonEnd: function() { + stream.lineEnd = lineEnd; + stream.point = point; + }, + pointRadius: function(_) { + pointCircle = d3_geo_pathCircle(_); + return stream; + }, + result: function() { + if (buffer.length) { + var result = buffer.join(""); + buffer = []; + return result; + } + } + }; + function point(x, y) { + buffer.push("M", x, ",", y, pointCircle); } - }); - d3.geo.centroid = d3_geo_pathIdentity.centroid; + function pointLineStart(x, y) { + buffer.push("M", x, ",", y); + stream.point = pointLine; + } + function pointLine(x, y) { + buffer.push("L", x, ",", y); + } + function lineEnd() { + stream.point = point; + } + function lineEndPolygon() { + buffer.push("Z"); + } + return stream; + } + function d3_geo_pathContext(context) { + var pointRadius = 4.5; + var stream = { + point: point, + lineStart: function() { + stream.point = pointLineStart; + }, + lineEnd: lineEnd, + polygonStart: function() { + stream.lineEnd = lineEndPolygon; + }, + polygonEnd: function() { + stream.lineEnd = lineEnd; + stream.point = point; + }, + pointRadius: function(_) { + pointRadius = _; + return stream; + }, + result: d3_noop + }; + function point(x, y) { + context.moveTo(x, y); + context.arc(x, y, pointRadius, 0, 2 * π); + } + function pointLineStart(x, y) { + context.moveTo(x, y); + stream.point = pointLine; + } + function pointLine(x, y) { + context.lineTo(x, y); + } + function lineEnd() { + stream.point = point; + } + function lineEndPolygon() { + context.closePath(); + } + return stream; + } + var d3_geo_pathAreaSum, d3_geo_pathAreaPolygon, d3_geo_pathArea = { + point: d3_noop, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: function() { + d3_geo_pathAreaPolygon = 0; + d3_geo_pathArea.lineStart = d3_geo_pathAreaRingStart; + }, + polygonEnd: function() { + d3_geo_pathArea.lineStart = d3_geo_pathArea.lineEnd = d3_geo_pathArea.point = d3_noop; + d3_geo_pathAreaSum += Math.abs(d3_geo_pathAreaPolygon / 2); + } + }; + function d3_geo_pathAreaRingStart() { + var x00, y00, x0, y0; + d3_geo_pathArea.point = function(x, y) { + d3_geo_pathArea.point = nextPoint; + x00 = x0 = x, y00 = y0 = y; + }; + function nextPoint(x, y) { + d3_geo_pathAreaPolygon += y0 * x - x0 * y; + x0 = x, y0 = y; + } + d3_geo_pathArea.lineEnd = function() { + nextPoint(x00, y00); + }; + } + var d3_geo_pathCentroid = { + point: d3_geo_pathCentroidPoint, + lineStart: d3_geo_pathCentroidLineStart, + lineEnd: d3_geo_pathCentroidLineEnd, + polygonStart: function() { + d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidRingStart; + }, + polygonEnd: function() { + d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint; + d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidLineStart; + d3_geo_pathCentroid.lineEnd = d3_geo_pathCentroidLineEnd; + } + }; + function d3_geo_pathCentroidPoint(x, y) { + if (d3_geo_centroidDimension) return; + d3_geo_centroidX += x; + d3_geo_centroidY += y; + ++d3_geo_centroidZ; + } + function d3_geo_pathCentroidLineStart() { + var x0, y0; + if (d3_geo_centroidDimension !== 1) { + if (d3_geo_centroidDimension < 1) { + d3_geo_centroidDimension = 1; + d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + } else return; + } + d3_geo_pathCentroid.point = function(x, y) { + d3_geo_pathCentroid.point = nextPoint; + x0 = x, y0 = y; + }; + function nextPoint(x, y) { + var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy); + d3_geo_centroidX += z * (x0 + x) / 2; + d3_geo_centroidY += z * (y0 + y) / 2; + d3_geo_centroidZ += z; + x0 = x, y0 = y; + } + } + function d3_geo_pathCentroidLineEnd() { + d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint; + } + function d3_geo_pathCentroidRingStart() { + var x00, y00, x0, y0; + if (d3_geo_centroidDimension < 2) { + d3_geo_centroidDimension = 2; + d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + } + d3_geo_pathCentroid.point = function(x, y) { + d3_geo_pathCentroid.point = nextPoint; + x00 = x0 = x, y00 = y0 = y; + }; + function nextPoint(x, y) { + var z = y0 * x - x0 * y; + d3_geo_centroidX += z * (x0 + x); + d3_geo_centroidY += z * (y0 + y); + d3_geo_centroidZ += z * 3; + x0 = x, y0 = y; + } + d3_geo_pathCentroid.lineEnd = function() { + nextPoint(x00, y00); + }; + } + d3.geo.area = function(object) { + d3_geo_areaSum = 0; + d3.geo.stream(object, d3_geo_area); + return d3_geo_areaSum; + }; + var d3_geo_areaSum, d3_geo_areaRing; + var d3_geo_area = { + sphere: function() { + d3_geo_areaSum += 4 * π; + }, + point: d3_noop, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: function() { + d3_geo_areaRing = 0; + d3_geo_area.lineStart = d3_geo_areaRingStart; + }, + polygonEnd: function() { + d3_geo_areaSum += d3_geo_areaRing < 0 ? 4 * π + d3_geo_areaRing : d3_geo_areaRing; + d3_geo_area.lineStart = d3_geo_area.lineEnd = d3_geo_area.point = d3_noop; + } + }; + function d3_geo_areaRingStart() { + var λ00, φ00, λ1, λ0, φ0, cosφ0, sinφ0; + d3_geo_area.point = function(λ, φ) { + d3_geo_area.point = nextPoint; + λ1 = λ0 = (λ00 = λ) * d3_radians, φ0 = (φ00 = φ) * d3_radians, cosφ0 = Math.cos(φ0), + sinφ0 = Math.sin(φ0); + }; + function nextPoint(λ, φ) { + λ *= d3_radians, φ *= d3_radians; + if (Math.abs(Math.abs(φ0) - π / 2) < ε && Math.abs(Math.abs(φ) - π / 2) < ε) return; + var cosφ = Math.cos(φ), sinφ = Math.sin(φ); + if (Math.abs(φ0 - π / 2) < ε) d3_geo_areaRing += (λ - λ1) * 2; else { + var dλ = λ - λ0, cosdλ = Math.cos(dλ), d = Math.atan2(Math.sqrt((d = cosφ * Math.sin(dλ)) * d + (d = cosφ0 * sinφ - sinφ0 * cosφ * cosdλ) * d), sinφ0 * sinφ + cosφ0 * cosφ * cosdλ), s = (d + π + φ0 + φ) / 4; + d3_geo_areaRing += (dλ < 0 && dλ > -π || dλ > π ? -4 : 4) * Math.atan(Math.sqrt(Math.abs(Math.tan(s) * Math.tan(s - d / 2) * Math.tan(s - π / 4 - φ0 / 2) * Math.tan(s - π / 4 - φ / 2)))); + } + λ1 = λ0, λ0 = λ, φ0 = φ, cosφ0 = cosφ, sinφ0 = sinφ; + } + d3_geo_area.lineEnd = function() { + nextPoint(λ00, φ00); + }; + } d3.geo.projection = d3_geo_projection; d3.geo.projectionMutator = d3_geo_projectionMutator; function d3_geo_projection(project) { @@ -6163,86 +6492,26 @@ })(); } function d3_geo_projectionMutator(projectAt) { - var project, rotate, projectRotate, k = 150, x = 480, y = 250, λ = 0, φ = 0, δλ = 0, δφ = 0, δγ = 0, δx = x, δy = y, δ2 = .5, clip = d3_geo_projectionCutAntemeridian(rotatePoint), clipAngle = null, context; - function projection(coordinates) { - coordinates = projectRotate(coordinates[0] * d3_radians, coordinates[1] * d3_radians); - return [ coordinates[0] * k + δx, δy - coordinates[1] * k ]; + var project, rotate, projectRotate, projectResample = d3_geo_resample(function(x, y) { + x = project(x, y); + return [ x[0] * k + δx, δy - x[1] * k ]; + }), k = 150, x = 480, y = 250, λ = 0, φ = 0, δλ = 0, δφ = 0, δγ = 0, δx, δy, clip = d3_geo_clipAntimeridian, clipAngle = null; + function projection(point) { + point = projectRotate(point[0] * d3_radians, point[1] * d3_radians); + return [ point[0] * k + δx, δy - point[1] * k ]; } - function invert(coordinates) { - coordinates = projectRotate.invert((coordinates[0] - δx) / k, (δy - coordinates[1]) / k); - return [ coordinates[0] * d3_degrees, coordinates[1] * d3_degrees ]; + function invert(point) { + point = projectRotate.invert((point[0] - δx) / k, (δy - point[1]) / k); + return point && [ point[0] * d3_degrees, point[1] * d3_degrees ]; } - projection.point = function(coordinates, c) { - context = c; - clip.point(coordinates, resample); - context = null; - }; - projection.line = function(coordinates, c) { - context = c; - clip.line(coordinates, resample); - context = null; - }; - projection.polygon = function(coordinates, c) { - context = c; - clip.polygon(coordinates, resample); - context = null; - }; - projection.sphere = function(c) { - context = c; - clip.sphere(resample); - context = null; + projection.stream = function(stream) { + return d3_geo_projectionRadiansRotate(rotate, clip(projectResample(stream))); }; projection.clipAngle = function(_) { if (!arguments.length) return clipAngle; - clip = _ == null ? (clipAngle = _, d3_geo_projectionCutAntemeridian(rotatePoint)) : d3_geo_circleClip(clipAngle = +_, rotatePoint); + clip = _ == null ? (clipAngle = _, d3_geo_clipAntimeridian) : d3_geo_clipCircle(clipAngle = +_); return projection; }; - var λ00, φ00, λ0, sinφ0, cosφ0, x0, y0, maxDepth = 16; - function point(λ, φ) { - var p = projectPoint(λ, φ); - context.point(p[0], p[1]); - } - function moveTo(λ, φ) { - var p = projectPoint(λ00 = λ0 = λ, φ00 = φ); - sinφ0 = Math.sin(φ); - cosφ0 = Math.cos(φ); - context.moveTo(x0 = p[0], y0 = p[1]); - } - function lineTo(λ, φ) { - var p = projectPoint(λ, φ); - resampleLineTo(x0, y0, λ0, sinφ0, cosφ0, x0 = p[0], y0 = p[1], λ0 = λ, sinφ0 = Math.sin(φ), cosφ0 = Math.cos(φ), maxDepth); - context.lineTo(x0, y0); - } - function resampleLineTo(x0, y0, λ0, sinφ0, cosφ0, x1, y1, λ1, sinφ1, cosφ1, depth) { - var dx = x1 - x0, dy = y1 - y0, distance2 = dx * dx + dy * dy; - if (distance2 > 4 * δ2 && depth--) { - var cosΩ = sinφ0 * sinφ1 + cosφ0 * cosφ1 * Math.cos(λ1 - λ0), k = 1 / (Math.SQRT2 * Math.sqrt(1 + cosΩ)), x = k * (cosφ0 * Math.cos(λ0) + cosφ1 * Math.cos(λ1)), y = k * (cosφ0 * Math.sin(λ0) + cosφ1 * Math.sin(λ1)), z = Math.max(-1, Math.min(1, k * (sinφ0 + sinφ1))), φ2 = Math.asin(z), zε = Math.abs(Math.abs(z) - 1), λ2 = zε < ε || zε < εε && (Math.abs(cosφ0) < εε || Math.abs(cosφ1) < εε) ? (λ0 + λ1) / 2 : Math.atan2(y, x), p = projectPoint(λ2, φ2), x2 = p[0], y2 = p[1], dx2 = x0 - x2, dy2 = y0 - y2, dz = dx * dy2 - dy * dx2; - if (dz * dz / distance2 > δ2) { - var cosφ2 = Math.cos(φ2); - resampleLineTo(x0, y0, λ0, sinφ0, cosφ0, x2, y2, λ2, z, cosφ2, depth); - context.lineTo(x2, y2); - resampleLineTo(x2, y2, λ2, z, cosφ2, x1, y1, λ1, sinφ1, cosφ1, depth); - } - } - } - function closePath() { - var p = projectPoint(λ00, φ00); - resampleLineTo(x0, y0, λ0, sinφ0, cosφ0, p[0], p[1], λ00, Math.sin(φ00), Math.cos(φ00), maxDepth); - context.closePath(); - } - var resample = { - point: point, - moveTo: moveTo, - lineTo: lineTo, - closePath: closePath - }; - function rotatePoint(coordinates) { - return rotate(coordinates[0] * d3_radians, coordinates[1] * d3_radians); - } - function projectPoint(λ, φ) { - var point = project(λ, φ); - return [ point[0] * k + δx, δy - point[1] * k ]; - } projection.scale = function(_) { if (!arguments.length) return k; k = +_; @@ -6267,11 +6536,7 @@ δγ = _.length > 2 ? _[2] % 360 * d3_radians : 0; return reset(); }; - projection.precision = function(_) { - if (!arguments.length) return Math.sqrt(δ2); - maxDepth = (δ2 = _ * _) > 0 && 16; - return projection; - }; + d3.rebind(projection, projectResample, "precision"); function reset() { projectRotate = d3_geo_compose(rotate = d3_geo_rotation(δλ, δφ, δγ), project); var center = project(λ, φ); @@ -6285,104 +6550,35 @@ return reset(); }; } - function d3_geo_projectionIntersectAntemeridian(λ0, φ0, λ1, φ1) { - var cosφ0, cosφ1, sinλ0_λ1 = Math.sin(λ0 - λ1); - return Math.abs(sinλ0_λ1) > ε ? Math.atan((Math.sin(φ0) * (cosφ1 = Math.cos(φ1)) * Math.sin(λ1) - Math.sin(φ1) * (cosφ0 = Math.cos(φ0)) * Math.sin(λ0)) / (cosφ0 * cosφ1 * sinλ0_λ1)) : (φ0 + φ1) / 2; - } - function d3_geo_projectionCutAntemeridian(rotatePoint) { - var clip = { - point: function(coordinates, context) { - var point = rotatePoint(coordinates); - context.point(point[0], point[1]); + function d3_geo_projectionRadiansRotate(rotate, stream) { + return { + point: function(x, y) { + y = rotate(x * d3_radians, y * d3_radians), x = y[0]; + stream.point(x > π ? x - 2 * π : x < -π ? x + 2 * π : x, y[1]); }, - line: function(coordinates, context, ring) { - if (!(n = coordinates.length)) return [ ring && 0, false ]; - var point = rotatePoint(coordinates[0]), λ0 = point[0], φ0 = point[1], λ1, φ1, sλ0 = λ0 > 0 ? π : -π, sλ1, dλ, i = 0, n, clean = ring, area = 0, x0 = (point = d3_geo_stereographic(λ0, φ0))[0], x, y0 = point[1], y; - context.moveTo(λ0, φ0); - while (++i < n) { - point = rotatePoint(coordinates[i]); - λ1 = point[0]; - φ1 = point[1]; - sλ1 = λ1 > 0 ? π : -π; - dλ = Math.abs(λ1 - λ0); - if (Math.abs(dλ - π) < ε) { - context.lineTo(λ0, φ0 = (φ0 + φ1) / 2 > 0 ? π / 2 : -π / 2); - context.lineTo(sλ0, φ0); - context.moveTo(sλ1, φ0); - context.lineTo(λ1, φ0); - clean = false; - } else if (sλ0 !== sλ1 && dλ >= π) { - if (Math.abs(λ0 - sλ0) < ε) λ0 -= sλ0 * ε; - if (Math.abs(λ1 - sλ1) < ε) λ1 -= sλ1 * ε; - φ0 = d3_geo_projectionIntersectAntemeridian(λ0, φ0, λ1, φ1); - context.lineTo(sλ0, φ0); - context.moveTo(sλ1, φ0); - clean = false; - } - if (clean) { - x = (point = d3_geo_stereographic(λ1, φ1))[0]; - y = point[1]; - area += y0 * x - x0 * y; - x0 = x; - y0 = y; - } - context.lineTo(λ0 = λ1, φ0 = φ1); - sλ0 = sλ1; - } - return [ clean && area, true ]; + sphere: function() { + stream.sphere(); }, - polygon: function(polygon, context) { - d3_geo_circleClipPolygon(polygon, context, clip.line, d3_geo_antemeridianInterpolate); + lineStart: function() { + stream.lineStart(); }, - sphere: function(context) { - d3_geo_projectionSphere(context, d3_geo_antemeridianInterpolate); + lineEnd: function() { + stream.lineEnd(); + }, + polygonStart: function() { + stream.polygonStart(); + }, + polygonEnd: function() { + stream.polygonEnd(); } }; - return clip; - } - function d3_geo_antemeridianInterpolate(from, to, direction, context) { - var φ; - if (from == null) { - φ = direction * π / 2; - context.lineTo(-π, φ); - context.lineTo(0, φ); - context.lineTo(π, φ); - context.lineTo(π, 0); - context.lineTo(π, -φ); - context.lineTo(0, -φ); - context.lineTo(-π, -φ); - context.lineTo(-π, 0); - } else if (Math.abs(from[0] - to[0]) > ε) { - var s = (from[0] < to[0] ? 1 : -1) * π; - φ = direction * s / 2; - context.lineTo(-s, φ); - context.lineTo(0, φ); - context.lineTo(s, φ); - } else { - context.lineTo(to[0], to[1]); - } - } - function d3_geo_projectionSphere(context, interpolate) { - var moved = false; - interpolate(null, null, 1, { - lineTo: function(x, y) { - (moved ? context.lineTo : (moved = true, context.moveTo))(x, y); - } - }); - context.closePath(); } function d3_geo_rotation(δλ, δφ, δγ) { - return δλ ? δφ || δγ ? d3_geo_compose(d3_geo_rotationλ(δλ), d3_geo_rotationφγ(δφ, δγ)) : d3_geo_rotationλ(δλ) : δφ || δγ ? d3_geo_rotationφγ(δφ, δγ) : d3_geo_identityRotation; + return δλ ? δφ || δγ ? d3_geo_compose(d3_geo_rotationλ(δλ), d3_geo_rotationφγ(δφ, δγ)) : d3_geo_rotationλ(δλ) : δφ || δγ ? d3_geo_rotationφγ(δφ, δγ) : d3_geo_equirectangular; } - function d3_geo_identityRotation(λ, φ) { - return [ λ > π ? λ - 2 * π : λ < -π ? λ + 2 * π : λ, φ ]; - } - d3_geo_identityRotation.invert = function(x, y) { - return [ x, y ]; - }; function d3_geo_forwardRotationλ(δλ) { return function(λ, φ) { - return [ (λ += δλ) > π ? λ - 2 * π : λ < -π ? λ + 2 * π : λ, φ ]; + return λ += δλ, [ λ > π ? λ - 2 * π : λ < -π ? λ + 2 * π : λ, φ ]; }; } function d3_geo_rotationλ(δλ) { From ae8891c48581e8b19b17b36c5d341d17658d779f Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 14:05:21 -0500 Subject: [PATCH 014/332] Unused vars --- js/id/ui.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index 8d764d695..87cc3745a 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -103,7 +103,7 @@ iD.ui = function (context) { .on('click.editor', history.redo) .call(undo_tooltip); - var save_button = limiter.append('div').attr('class','button-wrap col1').append('button') + limiter.append('div').attr('class','button-wrap col1').append('button') .attr('class', 'save col12') .call(iD.ui.save(context)); @@ -127,7 +127,7 @@ iD.ui = function (context) { .call(iD.ui.geolocate(map)); } - var gc = container.append('div').attr('class', 'geocode-control map-control') + container.append('div').attr('class', 'geocode-control map-control') .call(iD.ui.geocoder().map(map)); container.append('div').attr('class', 'map-control layerswitcher-control') From 3da0e70c0fdc663a25e8e4cb3043a2b154e3cc0f Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 14:30:46 -0500 Subject: [PATCH 015/332] Shift-selection --- js/id/behavior/select.js | 8 ++++-- js/id/id.js | 8 ++++++ test/index.html | 1 + test/index_packaged.html | 1 + test/spec/behavior/select.js | 54 ++++++++++++++++++++++++++++++++++++ 5 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 test/spec/behavior/select.js diff --git a/js/id/behavior/select.js b/js/id/behavior/select.js index e078fdbf1..b5276a4d0 100644 --- a/js/id/behavior/select.js +++ b/js/id/behavior/select.js @@ -2,8 +2,12 @@ iD.behavior.Select = function(context) { function click() { var datum = d3.select(d3.event.target).datum(); if (datum instanceof iD.Entity) { - context.enter(iD.modes.Select(context, [datum.id])); - } else { + if (d3.event.shiftKey) { + context.enter(iD.modes.Select(context, context.selection().concat([datum.id]))); + } else { + context.enter(iD.modes.Select(context, [datum.id])); + } + } else if (!d3.event.shiftKey) { context.enter(iD.modes.Browse(context)); } } diff --git a/js/id/id.js b/js/id/id.js index 0b2bbd617..358610525 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -56,6 +56,14 @@ window.iD = function () { return mode; }; + context.selection = function() { + if (mode.id === 'select') { + return mode.selection(); + } else { + return []; + } + }; + /* Behaviors */ context.install = function(behavior) { context.surface().call(behavior); diff --git a/test/index.html b/test/index.html index d32331ba9..16f86b781 100644 --- a/test/index.html +++ b/test/index.html @@ -185,6 +185,7 @@ + diff --git a/test/index_packaged.html b/test/index_packaged.html index 11f8368f4..588c8e0a3 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -80,6 +80,7 @@ + diff --git a/test/spec/behavior/select.js b/test/spec/behavior/select.js new file mode 100644 index 000000000..42c568cca --- /dev/null +++ b/test/spec/behavior/select.js @@ -0,0 +1,54 @@ +describe("iD.behavior.Select", function() { + var a, b, context, behavior, container; + + beforeEach(function() { + container = d3.select('body').append('div'); + + context = iD().container(container); + + a = iD.Node({loc: [0, 0]}); + b = iD.Node({loc: [0, 0]}); + + context.perform(iD.actions.AddEntity(a), iD.actions.AddEntity(b)); + + container.call(context.map()) + .append('div') + .attr('class', 'inspector-wrap'); + + context.surface().selectAll('circle') + .data([a, b]) + .enter().append('circle') + .attr('class', function(d) { return d.id; }); + + behavior = iD.behavior.Select(context); + context.install(behavior); + }); + + afterEach(function() { + context.uninstall(behavior); + container.remove(); + }); + + specify("click on entity selects the entity", function() { + happen.click(context.surface().select('.' + a.id).node()); + expect(context.selection()).to.eql([a.id]); + }); + + specify("click on empty space clears the selection", function() { + context.enter(iD.modes.Select(context, [a.id])); + happen.click(context.surface().node()); + expect(context.selection()).to.eql([]); + }); + + specify("shift-click on entity adds the entity to the selection", function() { + context.enter(iD.modes.Select(context, [a.id])); + happen.click(context.surface().select('.' + b.id).node(), {shiftKey: true}); + expect(context.selection()).to.eql([a.id, b.id]); + }); + + specify("shift-click on empty space leaves the selection unchanged", function() { + context.enter(iD.modes.Select(context, [a.id])); + happen.click(context.surface().node(), {shiftKey: true}); + expect(context.selection()).to.eql([a.id]); + }); +}); From 7ba31f05d2db3ec3d92f954cb97160cab5e1cc25 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 14:46:59 -0500 Subject: [PATCH 016/332] Support deleting relations --- index.html | 1 + js/id/actions/delete_relation.js | 13 +++++++++++++ js/id/operations/delete.js | 10 ++++++---- test/index.html | 2 ++ test/index_packaged.html | 1 + test/spec/actions/delete_relation.js | 17 +++++++++++++++++ 6 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 js/id/actions/delete_relation.js create mode 100644 test/spec/actions/delete_relation.js diff --git a/index.html b/index.html index b0e46d55d..5cd8ff735 100644 --- a/index.html +++ b/index.html @@ -77,6 +77,7 @@ + diff --git a/js/id/actions/delete_relation.js b/js/id/actions/delete_relation.js new file mode 100644 index 000000000..48c62f1e1 --- /dev/null +++ b/js/id/actions/delete_relation.js @@ -0,0 +1,13 @@ +// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/DeleteRelationAction.as +iD.actions.DeleteRelation = function(relationId) { + return function(graph) { + var relation = graph.entity(relationId); + + graph.parentRelations(relation) + .forEach(function(parent) { + graph = graph.replace(parent.removeMember(relationId)); + }); + + return graph.remove(relation); + }; +}; diff --git a/js/id/operations/delete.js b/js/id/operations/delete.js index d631a2ed7..33c638ce0 100644 --- a/js/id/operations/delete.js +++ b/js/id/operations/delete.js @@ -3,7 +3,11 @@ iD.operations.Delete = function(selection, context) { var operation = function() { var entity = context.entity(entityId), - action = {way: iD.actions.DeleteWay, node: iD.actions.DeleteNode}[entity.type], + action = { + way: iD.actions.DeleteWay, + node: iD.actions.DeleteNode, + relation: iD.actions.DeleteRelation + }[entity.type], annotation = t('operations.delete.annotation.' + context.geometry(entityId)); context.perform( @@ -12,9 +16,7 @@ iD.operations.Delete = function(selection, context) { }; operation.available = function() { - var entity = context.entity(entityId); - return selection.length === 1 && - (entity.type === 'way' || entity.type === 'node'); + return selection.length === 1; }; operation.enabled = function() { diff --git a/test/index.html b/test/index.html index 16f86b781..120938b5f 100644 --- a/test/index.html +++ b/test/index.html @@ -74,6 +74,7 @@ + @@ -141,6 +142,7 @@ + diff --git a/test/index_packaged.html b/test/index_packaged.html index 588c8e0a3..1764e5a9e 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -36,6 +36,7 @@ + diff --git a/test/spec/actions/delete_relation.js b/test/spec/actions/delete_relation.js new file mode 100644 index 000000000..c96909f3f --- /dev/null +++ b/test/spec/actions/delete_relation.js @@ -0,0 +1,17 @@ +describe("iD.actions.DeleteRelation", function () { + it("removes the relation from the graph", function () { + var relation = iD.Relation(), + action = iD.actions.DeleteRelation(relation.id), + graph = action(iD.Graph([relation])); + expect(graph.entity(relation.id)).to.be.undefined; + }); + + it("removes the relation from parent relations", function () { + var a = iD.Relation(), + b = iD.Relation(), + parent = iD.Relation({members: [{ id: a.id }, { id: b.id }]}), + action = iD.actions.DeleteRelation(a.id), + graph = action(iD.Graph([a, b, parent])); + expect(graph.entity(parent.id).members).to.eql([{ id: b.id }]); + }); +}); From 58fcf746a2f030210a79947f168434f4dbcb1627 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 15:08:41 -0500 Subject: [PATCH 017/332] Delete multiple --- index.html | 1 + js/id/actions/delete_multiple.js | 15 +++++++++++++++ js/id/operations/delete.js | 20 +++++++++----------- locale/en.js | 3 ++- test/index.html | 2 ++ test/index_packaged.html | 1 + test/spec/actions/delete_multiple.js | 12 ++++++++++++ 7 files changed, 42 insertions(+), 12 deletions(-) create mode 100644 js/id/actions/delete_multiple.js create mode 100644 test/spec/actions/delete_multiple.js diff --git a/index.html b/index.html index 5cd8ff735..f94a11019 100644 --- a/index.html +++ b/index.html @@ -76,6 +76,7 @@ + diff --git a/js/id/actions/delete_multiple.js b/js/id/actions/delete_multiple.js new file mode 100644 index 000000000..839c1d780 --- /dev/null +++ b/js/id/actions/delete_multiple.js @@ -0,0 +1,15 @@ +iD.actions.DeleteMultiple = function(ids) { + return function(graph) { + var actions = { + way: iD.actions.DeleteWay, + node: iD.actions.DeleteNode, + relation: iD.actions.DeleteRelation + }; + + ids.forEach(function (id) { + graph = actions[graph.entity(id).type](id)(graph); + }); + + return graph; + }; +}; diff --git a/js/id/operations/delete.js b/js/id/operations/delete.js index 33c638ce0..2c9765b62 100644 --- a/js/id/operations/delete.js +++ b/js/id/operations/delete.js @@ -1,22 +1,20 @@ iD.operations.Delete = function(selection, context) { - var entityId = selection[0]; - var operation = function() { - var entity = context.entity(entityId), - action = { - way: iD.actions.DeleteWay, - node: iD.actions.DeleteNode, - relation: iD.actions.DeleteRelation - }[entity.type], - annotation = t('operations.delete.annotation.' + context.geometry(entityId)); + var annotation; + + if (selection.length === 1) { + annotation = t('operations.delete.annotation.' + context.geometry(selection[0])); + } else { + annotation = t('operations.delete.annotation.multiple', {n: selection.length}); + } context.perform( - action(entityId), + iD.actions.DeleteMultiple(selection), annotation); }; operation.available = function() { - return selection.length === 1; + return true; }; operation.enabled = function() { diff --git a/locale/en.js b/locale/en.js index d7fc3628f..b32149ee3 100644 --- a/locale/en.js +++ b/locale/en.js @@ -73,7 +73,8 @@ locale.en = { point: "Deleted a point.", vertex: "Deleted a node from a way.", line: "Deleted a line.", - area: "Deleted an area." + area: "Deleted an area.", + multiple: "Deleted {n} objects." } }, move: { diff --git a/test/index.html b/test/index.html index 120938b5f..1ed505174 100644 --- a/test/index.html +++ b/test/index.html @@ -73,6 +73,7 @@ + @@ -141,6 +142,7 @@ + diff --git a/test/index_packaged.html b/test/index_packaged.html index 1764e5a9e..4b44086c5 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -35,6 +35,7 @@ + diff --git a/test/spec/actions/delete_multiple.js b/test/spec/actions/delete_multiple.js new file mode 100644 index 000000000..3a70adc38 --- /dev/null +++ b/test/spec/actions/delete_multiple.js @@ -0,0 +1,12 @@ +describe("iD.actions.DeleteMultiple", function () { + it("deletes multiple entities of heterogeneous types", function () { + var n = iD.Node(), + w = iD.Way(), + r = iD.Relation(), + action = iD.actions.DeleteMultiple([n.id, w.id, r.id]), + graph = action(iD.Graph([n, w, r])); + expect(graph.entity(n.id)).to.be.undefined; + expect(graph.entity(w.id)).to.be.undefined; + expect(graph.entity(r.id)).to.be.undefined; + }); +}); From 7235632a6304ce1916c4b60e56a61518e4842a7c Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 15:13:44 -0500 Subject: [PATCH 018/332] Update logic for multi-select --- js/id/modes/select.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 36066d7d0..02db6a57f 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -82,21 +82,22 @@ iD.modes.Select = function(context, selection, initial) { inspector .on('changeTags', changeTags) .on('close', function() { context.enter(iD.modes.Browse(context)); }); + } - context.history().on('change.select', function() { + context.history().on('change.select', function() { + context.surface().call(radialMenu.close); + + if (_.any(selection, function (id) { return !context.entity(id); })) { // Exit mode if selected entity gets undone - var oldEntity = entity, - newEntity = context.entity(selection[0]); + context.enter(iD.modes.Browse(context)); - if (!newEntity) { - context.enter(iD.modes.Browse(context)); - } else if (!_.isEqual(oldEntity.tags, newEntity.tags)) { + } else if (entity) { + var newEntity = context.entity(selection[0]); + if (!_.isEqual(entity.tags, newEntity.tags)) { inspector.tags(newEntity.tags); } - - context.surface().call(radialMenu.close); - }); - } + } + }); context.map().on('move.select', function() { context.surface().call(radialMenu.close); From 7bfdf4de1a232df610f1f543b3c30fcc2b7126c3 Mon Sep 17 00:00:00 2001 From: Dr Ian Date: Fri, 1 Feb 2013 22:23:30 +0100 Subject: [PATCH 019/332] Change search to use OSM Nominatim API --- js/id/renderer/map.js | 4 ++++ js/id/ui/geocoder.js | 13 +++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 1f22a7c81..e076e33a5 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -346,6 +346,10 @@ iD.Map = function(context) { vZoomDiff = Math.log(Math.abs(vFactor)) / Math.LN2, newZoom = map.zoom() - Math.max(hZoomDiff, vZoomDiff); + if(newZoom > 19){ + newZoom = 19; + } + map.centerZoom(extent.center(), newZoom); } }; diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index ed37d2cb1..c54b8d6ad 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -6,18 +6,19 @@ iD.ui.geocoder = function() { function keydown() { if (d3.event.keyCode !== 13) return; d3.event.preventDefault(); - d3.json('http://a.tiles.mapbox.com/v3/openstreetmap.map-hn253zqn/geocode/' + - encodeURIComponent(this.value) + '.json', function(err, resp) { + var searchVal = this.value; + d3.json('http://nominatim.openstreetmap.org/search/' + + encodeURIComponent(searchVal) + '?limit=10&format=json', function(err, resp) { if (err) return hide(); hide(); - if (!resp.results.length) { + if (!resp.length) { return iD.ui.flash() .select('.content') .append('h3') - .text('No location found for "' + resp.query[0] + '"'); + .text('No location found for "' + searchVal + '"'); } - var bounds = resp.results[0][0].bounds; - map.extent(iD.geo.Extent([bounds[0], bounds[1]], [bounds[2], bounds[3]])); + var bounds = resp[0].boundingbox; + map.extent(iD.geo.Extent([parseFloat(bounds[3]), parseFloat(bounds[0])], [parseFloat(bounds[2]), parseFloat(bounds[1])])); }); } From 839844de274d3d3f6bcce3f39460fe598330629e Mon Sep 17 00:00:00 2001 From: Dr Ian Date: Fri, 1 Feb 2013 22:28:43 +0100 Subject: [PATCH 020/332] Convert tabs to spaces --- js/id/renderer/map.js | 6 +++--- js/id/ui/geocoder.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index e076e33a5..5258f8701 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -346,9 +346,9 @@ iD.Map = function(context) { vZoomDiff = Math.log(Math.abs(vFactor)) / Math.LN2, newZoom = map.zoom() - Math.max(hZoomDiff, vZoomDiff); - if(newZoom > 19){ - newZoom = 19; - } + if(newZoom > 19){ + newZoom = 19; + } map.centerZoom(extent.center(), newZoom); } diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index c54b8d6ad..e6695792f 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -6,7 +6,7 @@ iD.ui.geocoder = function() { function keydown() { if (d3.event.keyCode !== 13) return; d3.event.preventDefault(); - var searchVal = this.value; + var searchVal = this.value; d3.json('http://nominatim.openstreetmap.org/search/' + encodeURIComponent(searchVal) + '?limit=10&format=json', function(err, resp) { if (err) return hide(); From f4975447f89cf9c2eba368ac03658d5e8951e53a Mon Sep 17 00:00:00 2001 From: Dr Ian Date: Fri, 1 Feb 2013 23:05:47 +0100 Subject: [PATCH 021/332] Limit zoom to 19 if geocode bounding box too small --- js/id/renderer/map.js | 4 ---- js/id/ui/geocoder.js | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 5258f8701..1f22a7c81 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -346,10 +346,6 @@ iD.Map = function(context) { vZoomDiff = Math.log(Math.abs(vFactor)) / Math.LN2, newZoom = map.zoom() - Math.max(hZoomDiff, vZoomDiff); - if(newZoom > 19){ - newZoom = 19; - } - map.centerZoom(extent.center(), newZoom); } }; diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index e6695792f..6d4d39d03 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -19,6 +19,7 @@ iD.ui.geocoder = function() { } var bounds = resp[0].boundingbox; map.extent(iD.geo.Extent([parseFloat(bounds[3]), parseFloat(bounds[0])], [parseFloat(bounds[2]), parseFloat(bounds[1])])); + if (map.zoom() > 19) map.zoom(19); }); } From 01e8f5f606ad5ce5affa54c8aac5fb7b91171af3 Mon Sep 17 00:00:00 2001 From: Dr Ian Date: Fri, 1 Feb 2013 23:08:11 +0100 Subject: [PATCH 022/332] Convert tabs to spaces --- js/id/ui/geocoder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index 6d4d39d03..d7de24b37 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -19,7 +19,7 @@ iD.ui.geocoder = function() { } var bounds = resp[0].boundingbox; map.extent(iD.geo.Extent([parseFloat(bounds[3]), parseFloat(bounds[0])], [parseFloat(bounds[2]), parseFloat(bounds[1])])); - if (map.zoom() > 19) map.zoom(19); + if (map.zoom() > 19) map.zoom(19); }); } From e1d5a0cb0324ae5334e6973bb959c56ad8137a79 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 15:45:45 -0500 Subject: [PATCH 023/332] Unjoin -> Disconnect --- combobox.html | 2 +- css/app.css | 2 +- index.html | 4 ++-- .../actions/{unjoin_node.js => disconnect.js} | 4 ++-- js/id/operations/disconnect.js | 24 +++++++++++++++++++ js/id/operations/unjoin.js | 24 ------------------- locale/en.js | 12 +++++----- test/index.html | 6 ++--- test/index_packaged.html | 2 +- .../actions/{unjoin_node.js => disconnect.js} | 10 ++++---- 10 files changed, 45 insertions(+), 45 deletions(-) rename js/id/actions/{unjoin_node.js => disconnect.js} (92%) create mode 100644 js/id/operations/disconnect.js delete mode 100644 js/id/operations/unjoin.js rename test/spec/actions/{unjoin_node.js => disconnect.js} (89%) diff --git a/combobox.html b/combobox.html index 0d97698b0..469e73823 100644 --- a/combobox.html +++ b/combobox.html @@ -79,13 +79,13 @@ + - diff --git a/css/app.css b/css/app.css index 1a880ad02..7a0751d02 100644 --- a/css/app.css +++ b/css/app.css @@ -476,7 +476,7 @@ button[disabled] .icon.nearby { background-position: -340px -40px;} .icon-operation-circularize { background-position: -20px -140px;} .icon-operation-straighten { background-position: -40px -140px;} .icon-operation-split { background-position: -60px -140px;} -.icon-operation-unjoin { background-position: -80px -140px;} +.icon-operation-disconnect { background-position: -80px -140px;} .icon-operation-reverse { background-position: -100px -140px;} .icon-operation-move { background-position: -120px -140px;} .icon-operation-merge { background-position: -140px -140px;} diff --git a/index.html b/index.html index f94a11019..3ef8abad5 100644 --- a/index.html +++ b/index.html @@ -80,13 +80,13 @@ + - @@ -112,10 +112,10 @@ + - diff --git a/js/id/actions/unjoin_node.js b/js/id/actions/disconnect.js similarity index 92% rename from js/id/actions/unjoin_node.js rename to js/id/actions/disconnect.js index 39e05a9cf..8645c436b 100644 --- a/js/id/actions/unjoin_node.js +++ b/js/id/actions/disconnect.js @@ -1,4 +1,4 @@ -// Unjoin the ways at the given node. +// Disconect the ways at the given node. // // For testing convenience, accepts an ID to assign to the (first) new node. // Normally, this will be undefined and the way will automatically @@ -8,7 +8,7 @@ // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/UnjoinNodeAction.as // https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/UnGlueAction.java // -iD.actions.UnjoinNode = function(nodeId, newNodeId) { +iD.actions.Disconnect = function(nodeId, newNodeId) { var action = function(graph) { if (!action.enabled(graph)) return graph; diff --git a/js/id/operations/disconnect.js b/js/id/operations/disconnect.js new file mode 100644 index 000000000..c8cc0d014 --- /dev/null +++ b/js/id/operations/disconnect.js @@ -0,0 +1,24 @@ +iD.operations.Disconnect = function(selection, context) { + var entityId = selection[0], + action = iD.actions.Disconnect(entityId); + + var operation = function() { + context.perform(action, t('operations.disconnect.annotation')); + }; + + operation.available = function() { + return selection.length === 1 && + context.geometry(entityId) === 'vertex'; + }; + + operation.enabled = function() { + return action.enabled(context.graph()); + }; + + operation.id = "disconnect"; + operation.key = t('operations.disconnect.key'); + operation.title = t('operations.disconnect.title'); + operation.description = t('operations.disconnect.description'); + + return operation; +}; diff --git a/js/id/operations/unjoin.js b/js/id/operations/unjoin.js deleted file mode 100644 index d4fcc1426..000000000 --- a/js/id/operations/unjoin.js +++ /dev/null @@ -1,24 +0,0 @@ -iD.operations.Unjoin = function(selection, context) { - var entityId = selection[0], - action = iD.actions.UnjoinNode(entityId); - - var operation = function() { - context.perform(action, 'Unjoined lines.'); - }; - - operation.available = function() { - return selection.length === 1 && - context.geometry(entityId) === 'vertex'; - }; - - operation.enabled = function() { - return action.enabled(context.graph()); - }; - - operation.id = "unjoin"; - operation.key = t('operations.unjoin.key'); - operation.title = t('operations.unjoin.title'); - operation.description = t('operations.unjoin.description'); - - return operation; -}; diff --git a/locale/en.js b/locale/en.js index b32149ee3..7d1bed947 100644 --- a/locale/en.js +++ b/locale/en.js @@ -77,6 +77,12 @@ locale.en = { multiple: "Deleted {n} objects." } }, + disconnect: { + title: "Disconnect", + description: "Disconnect these ways from each other.", + key: "D", + annotation: "Disconnected ways." + }, move: { title: "Move", description: "Move this to a different location.", @@ -99,12 +105,6 @@ locale.en = { description: "Split this into two ways at this point.", key: "X", annotation: "Split a way." - }, - unjoin: { - title: "Unjoin", - description: "Disconnect these ways from each other.", - key: "⇧-J", - annotation: "Unjoined ways." } }, diff --git a/test/index.html b/test/index.html index 1ed505174..e06e18a4b 100644 --- a/test/index.html +++ b/test/index.html @@ -77,12 +77,12 @@ + - @@ -108,10 +108,10 @@ + - @@ -146,12 +146,12 @@ + - diff --git a/test/index_packaged.html b/test/index_packaged.html index 4b44086c5..673ea46e8 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -39,12 +39,12 @@ + - diff --git a/test/spec/actions/unjoin_node.js b/test/spec/actions/disconnect.js similarity index 89% rename from test/spec/actions/unjoin_node.js rename to test/spec/actions/disconnect.js index 5901e97da..4ce298feb 100644 --- a/test/spec/actions/unjoin_node.js +++ b/test/spec/actions/disconnect.js @@ -1,9 +1,9 @@ -describe("iD.actions.UnjoinNode", function () { +describe("iD.actions.Disconnect", function () { describe("#enabled", function () { it("returns false for a node shared by less than two ways", function () { var graph = iD.Graph({'a': iD.Node()}); - expect(iD.actions.UnjoinNode('a').enabled(graph)).to.equal(false); + expect(iD.actions.Disconnect('a').enabled(graph)).to.equal(false); }); it("returns true for a node shared by two or more ways", function () { @@ -19,7 +19,7 @@ describe("iD.actions.UnjoinNode", function () { '|': iD.Way({id: '|', nodes: ['d', 'b']}) }); - expect(iD.actions.UnjoinNode('b').enabled(graph)).to.equal(true); + expect(iD.actions.Disconnect('b').enabled(graph)).to.equal(true); }); }); @@ -46,7 +46,7 @@ describe("iD.actions.UnjoinNode", function () { '|': iD.Way({id: '|', nodes: ['d', 'b']}) }); - graph = iD.actions.UnjoinNode('b', 'e')(graph); + graph = iD.actions.Disconnect('b', 'e')(graph); expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c']); expect(graph.entity('|').nodes).to.eql(['d', 'e']); @@ -64,7 +64,7 @@ describe("iD.actions.UnjoinNode", function () { '|': iD.Way({id: '|', nodes: ['d', 'b']}) }); - graph = iD.actions.UnjoinNode('b', 'e')(graph); + graph = iD.actions.Disconnect('b', 'e')(graph); // Immutable loc => should be shared by identity. expect(graph.entity('b').loc).to.equal(loc); From c86792a77b9e7e1c02cb0de6ec1cac6107faf9eb Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 15:56:48 -0500 Subject: [PATCH 024/332] SplitWay -> Split --- combobox.html | 2 +- index.html | 2 +- js/id/actions/{split_way.js => split.js} | 2 +- js/id/operations/split.js | 2 +- test/index.html | 4 +-- test/index_packaged.html | 2 +- test/spec/actions/{split_way.js => split.js} | 28 ++++++++++---------- 7 files changed, 21 insertions(+), 21 deletions(-) rename js/id/actions/{split_way.js => split.js} (97%) rename test/spec/actions/{split_way.js => split.js} (92%) diff --git a/combobox.html b/combobox.html index 469e73823..914d834e7 100644 --- a/combobox.html +++ b/combobox.html @@ -85,7 +85,7 @@ - + diff --git a/index.html b/index.html index 3ef8abad5..defb5403b 100644 --- a/index.html +++ b/index.html @@ -86,7 +86,7 @@ - + diff --git a/js/id/actions/split_way.js b/js/id/actions/split.js similarity index 97% rename from js/id/actions/split_way.js rename to js/id/actions/split.js index 6fbd08595..0c648bd26 100644 --- a/js/id/actions/split_way.js +++ b/js/id/actions/split.js @@ -7,7 +7,7 @@ // Reference: // https://github.com/systemed/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/SplitWayAction.as // -iD.actions.SplitWay = function(nodeId, newWayId) { +iD.actions.Split = function(nodeId, newWayId) { function candidateWays(graph) { var node = graph.entity(nodeId), parents = graph.parentWays(node); diff --git a/js/id/operations/split.js b/js/id/operations/split.js index 0d7ff6ae4..07d7d56ff 100644 --- a/js/id/operations/split.js +++ b/js/id/operations/split.js @@ -1,6 +1,6 @@ iD.operations.Split = function(selection, context) { var entityId = selection[0], - action = iD.actions.SplitWay(entityId); + action = iD.actions.Split(entityId); var operation = function() { context.perform(action, t('operations.split.annotation')); diff --git a/test/index.html b/test/index.html index e06e18a4b..ceb1e7fbc 100644 --- a/test/index.html +++ b/test/index.html @@ -82,7 +82,7 @@ - + @@ -151,7 +151,7 @@ - + diff --git a/test/index_packaged.html b/test/index_packaged.html index 673ea46e8..050badd7d 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -44,7 +44,7 @@ - + diff --git a/test/spec/actions/split_way.js b/test/spec/actions/split.js similarity index 92% rename from test/spec/actions/split_way.js rename to test/spec/actions/split.js index 2958521a9..5ee68e67e 100644 --- a/test/spec/actions/split_way.js +++ b/test/spec/actions/split.js @@ -1,4 +1,4 @@ -describe("iD.actions.SplitWay", function () { +describe("iD.actions.Split", function () { describe("#enabled", function () { it("returns true for a non-end node of a single way", function () { var graph = iD.Graph({ @@ -8,7 +8,7 @@ describe("iD.actions.SplitWay", function () { '-': iD.Way({id: '-', nodes: ['a', 'b', 'c']}) }); - expect(iD.actions.SplitWay('b').enabled(graph)).to.be.true; + expect(iD.actions.Split('b').enabled(graph)).to.be.true; }); it("returns false for the first node of a single way", function () { @@ -18,7 +18,7 @@ describe("iD.actions.SplitWay", function () { '-': iD.Way({id: '-', nodes: ['a', 'b']}) }); - expect(iD.actions.SplitWay('a').enabled(graph)).to.be.false; + expect(iD.actions.Split('a').enabled(graph)).to.be.false; }); it("returns false for the last node of a single way", function () { @@ -28,7 +28,7 @@ describe("iD.actions.SplitWay", function () { '-': iD.Way({id: '-', nodes: ['a', 'b']}) }); - expect(iD.actions.SplitWay('b').enabled(graph)).to.be.false; + expect(iD.actions.Split('b').enabled(graph)).to.be.false; }); }); @@ -48,7 +48,7 @@ describe("iD.actions.SplitWay", function () { '-': iD.Way({id: '-', nodes: ['a', 'b', 'c']}) }); - graph = iD.actions.SplitWay('b', '=')(graph); + graph = iD.actions.Split('b', '=')(graph); expect(graph.entity('-').nodes).to.eql(['a', 'b']); expect(graph.entity('=').nodes).to.eql(['b', 'c']); @@ -63,7 +63,7 @@ describe("iD.actions.SplitWay", function () { '-': iD.Way({id: '-', nodes: ['a', 'b', 'c'], tags: tags}) }); - graph = iD.actions.SplitWay('b', '=')(graph); + graph = iD.actions.Split('b', '=')(graph); // Immutable tags => should be shared by identity. expect(graph.entity('-').tags).to.equal(tags); @@ -92,7 +92,7 @@ describe("iD.actions.SplitWay", function () { '|': iD.Way({id: '|', nodes: ['d', 'b']}) }); - graph = iD.actions.SplitWay('b', '=')(graph); + graph = iD.actions.Split('b', '=')(graph); expect(graph.entity('-').nodes).to.eql(['a', 'b']); expect(graph.entity('=').nodes).to.eql(['b', 'c']); @@ -118,7 +118,7 @@ describe("iD.actions.SplitWay", function () { 'r': iD.Relation({id: 'r', members: [{id: '-', type: 'way'}]}) }); - graph = iD.actions.SplitWay('b', '=')(graph); + graph = iD.actions.Split('b', '=')(graph); expect(_.pluck(graph.entity('r').members, 'id')).to.eql(['-', '=']); }); @@ -144,7 +144,7 @@ describe("iD.actions.SplitWay", function () { 'r': iD.Relation({id: 'r', members: [{id: '-', type: 'way'}, {id: '~', type: 'way'}]}) }); - graph = iD.actions.SplitWay('b', '=')(graph); + graph = iD.actions.Split('b', '=')(graph); expect(_.pluck(graph.entity('r').members, 'id')).to.eql(['-', '=', '~']); }); @@ -170,7 +170,7 @@ describe("iD.actions.SplitWay", function () { 'r': iD.Relation({id: 'r', members: [{id: '~', type: 'way'}, {id: '-', type: 'way'}]}) }); - graph = iD.actions.SplitWay('b', '=')(graph); + graph = iD.actions.Split('b', '=')(graph); expect(_.pluck(graph.entity('r').members, 'id')).to.eql(['~', '=', '-']); }); @@ -184,7 +184,7 @@ describe("iD.actions.SplitWay", function () { 'r': iD.Relation({id: 'r', members: [{id: '~', type: 'way'}, {id: '-', type: 'way'}]}) }); - graph = iD.actions.SplitWay('b', '=')(graph); + graph = iD.actions.Split('b', '=')(graph); expect(_.pluck(graph.entity('r').members, 'id')).to.eql(['~', '-', '=']); }); @@ -214,7 +214,7 @@ describe("iD.actions.SplitWay", function () { {id: 'c', role: 'via'}]}) }); - graph = iD.actions.SplitWay('b', '=')(graph); + graph = iD.actions.Split('b', '=')(graph); expect(graph.entity('r').members).to.eql([ {id: '=', role: 'from'}, @@ -246,7 +246,7 @@ describe("iD.actions.SplitWay", function () { {id: 'c', role: 'via'}]}) }); - graph = iD.actions.SplitWay('b', '=')(graph); + graph = iD.actions.Split('b', '=')(graph); expect(graph.entity('r').members).to.eql([ {id: '~', role: 'from'}, @@ -278,7 +278,7 @@ describe("iD.actions.SplitWay", function () { {id: 'c', role: 'via'}]}) }); - graph = iD.actions.SplitWay('b', '=')(graph); + graph = iD.actions.Split('b', '=')(graph); expect(graph.entity('r').members).to.eql([ {id: '-', role: 'from'}, From 9120f33aa7c5ea68a09a7084b51abdaabe1dd471 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 15:58:34 -0500 Subject: [PATCH 025/332] ReverseWay -> Reverse --- combobox.html | 2 +- index.html | 2 +- js/id/actions/{reverse_way.js => reverse.js} | 2 +- js/id/operations/reverse.js | 2 +- test/index.html | 4 +- test/index_packaged.html | 2 +- .../actions/{reverse_way.js => reverse.js} | 40 +++++++++---------- 7 files changed, 27 insertions(+), 27 deletions(-) rename js/id/actions/{reverse_way.js => reverse.js} (98%) rename test/spec/actions/{reverse_way.js => reverse.js} (76%) diff --git a/combobox.html b/combobox.html index 914d834e7..c739f4898 100644 --- a/combobox.html +++ b/combobox.html @@ -84,7 +84,7 @@ - + diff --git a/index.html b/index.html index defb5403b..87e93c643 100644 --- a/index.html +++ b/index.html @@ -85,7 +85,7 @@ - + diff --git a/js/id/actions/reverse_way.js b/js/id/actions/reverse.js similarity index 98% rename from js/id/actions/reverse_way.js rename to js/id/actions/reverse.js index 160017637..5c8315467 100644 --- a/js/id/actions/reverse_way.js +++ b/js/id/actions/reverse.js @@ -27,7 +27,7 @@ http://wiki.openstreetmap.org/wiki/Route#Members http://josm.openstreetmap.de/browser/josm/trunk/src/org/openstreetmap/josm/corrector/ReverseWayTagCorrector.java */ -iD.actions.ReverseWay = function(wayId) { +iD.actions.Reverse = function(wayId) { var replacements = [ [/:right$/, ':left'], [/:left$/, ':right'], [/:forward$/, ':backward'], [/:backward$/, ':forward'] diff --git a/js/id/operations/reverse.js b/js/id/operations/reverse.js index 77bacc64e..f750c134d 100644 --- a/js/id/operations/reverse.js +++ b/js/id/operations/reverse.js @@ -3,7 +3,7 @@ iD.operations.Reverse = function(selection, context) { var operation = function() { context.perform( - iD.actions.ReverseWay(entityId), + iD.actions.Reverse(entityId), t('operations.reverse.annotation')); }; diff --git a/test/index.html b/test/index.html index ceb1e7fbc..512a1ee1e 100644 --- a/test/index.html +++ b/test/index.html @@ -81,7 +81,7 @@ - + @@ -150,7 +150,7 @@ - + diff --git a/test/index_packaged.html b/test/index_packaged.html index 050badd7d..5adaf06f6 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -43,7 +43,7 @@ - + diff --git a/test/spec/actions/reverse_way.js b/test/spec/actions/reverse.js similarity index 76% rename from test/spec/actions/reverse_way.js rename to test/spec/actions/reverse.js index ca61cb778..40d0f5500 100644 --- a/test/spec/actions/reverse_way.js +++ b/test/spec/actions/reverse.js @@ -1,9 +1,9 @@ -describe("iD.actions.ReverseWay", function () { +describe("iD.actions.Reverse", function () { it("reverses the order of nodes in the way", function () { var node1 = iD.Node(), node2 = iD.Node(), way = iD.Way({nodes: [node1.id, node2.id]}), - graph = iD.actions.ReverseWay(way.id)(iD.Graph([node1, node2, way])); + graph = iD.actions.Reverse(way.id)(iD.Graph([node1, node2, way])); expect(graph.entity(way.id).nodes).to.eql([node2.id, node1.id]); }); @@ -11,7 +11,7 @@ describe("iD.actions.ReverseWay", function () { var way = iD.Way({tags: {'highway': 'residential'}}), graph = iD.Graph([way]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'highway': 'residential'}); }); @@ -19,7 +19,7 @@ describe("iD.actions.ReverseWay", function () { var way = iD.Way({tags: {'oneway': 'yes'}}), graph = iD.Graph([way]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'oneway': 'yes'}); }); @@ -27,10 +27,10 @@ describe("iD.actions.ReverseWay", function () { var way = iD.Way({tags: {'cycleway:right': 'lane'}}), graph = iD.Graph([way]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'cycleway:left': 'lane'}); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'cycleway:right': 'lane'}); }); @@ -38,10 +38,10 @@ describe("iD.actions.ReverseWay", function () { var way = iD.Way({tags: {'maxspeed:forward': '25'}}), graph = iD.Graph([way]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'maxspeed:backward': '25'}); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'maxspeed:forward': '25'}); }); @@ -49,10 +49,10 @@ describe("iD.actions.ReverseWay", function () { var way = iD.Way({tags: {'incline': 'up'}}), graph = iD.Graph([way]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'incline': 'down'}); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'incline': 'up'}); }); @@ -60,10 +60,10 @@ describe("iD.actions.ReverseWay", function () { var way = iD.Way({tags: {'incline': 'up'}}), graph = iD.Graph([way]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'incline': 'down'}); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'incline': 'up'}); }); @@ -71,16 +71,16 @@ describe("iD.actions.ReverseWay", function () { var way = iD.Way({tags: {'incline': '5%'}}), graph = iD.Graph([way]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'incline': '-5%'}); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'incline': '5%'}); way = iD.Way({tags: {'incline': '.8°'}}); graph = iD.Graph([way]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'incline': '-.8°'}); }); @@ -88,10 +88,10 @@ describe("iD.actions.ReverseWay", function () { var way = iD.Way({tags: {'sidewalk': 'right'}}), graph = iD.Graph([way]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'sidewalk': 'left'}); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'sidewalk': 'right'}); }); @@ -99,7 +99,7 @@ describe("iD.actions.ReverseWay", function () { var way = iD.Way({tags: {'maxspeed:forward': '25', 'maxspeed:backward': '30'}}), graph = iD.Graph([way]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'maxspeed:backward': '25', 'maxspeed:forward': '30'}); }); @@ -108,10 +108,10 @@ describe("iD.actions.ReverseWay", function () { relation = iD.Relation({members: [{type: 'way', id: way.id, role: 'forward'}]}), graph = iD.Graph([way, relation]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(relation.id).members[0].role).to.eql('backward'); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(relation.id).members[0].role).to.eql('forward'); }); }); From 20730e5f1a442e766416951cb88a73d49709e9a7 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 17:24:16 -0500 Subject: [PATCH 026/332] Entity#mergeTags --- js/id/graph/entity.js | 14 ++++++++++++++ test/spec/graph/entity.js | 27 +++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/js/id/graph/entity.js b/js/id/graph/entity.js index f91bb89d9..ae612c268 100644 --- a/js/id/graph/entity.js +++ b/js/id/graph/entity.js @@ -66,6 +66,20 @@ iD.Entity.prototype = { return iD.Entity(this, attrs, {_updated: true}); }, + mergeTags: function(tags) { + var merged = _.clone(this.tags); + for (var k in tags) { + var t1 = merged[k], + t2 = tags[k]; + if (t1 && t1 !== t2) { + merged[k] = t1 + "; " + t2; + } else { + merged[k] = t2; + } + } + return this.update({tags: merged}); + }, + created: function() { return this._updated && this.osmId().charAt(0) === '-'; }, diff --git a/test/spec/graph/entity.js b/test/spec/graph/entity.js index a573f4c8c..088ab5516 100644 --- a/test/spec/graph/entity.js +++ b/test/spec/graph/entity.js @@ -69,6 +69,33 @@ describe('iD.Entity', function () { }); }); + describe("#mergeTags", function () { + it("returns a new Entity", function () { + var a = iD.Entity(), + b = a.mergeTags({}); + expect(b instanceof iD.Entity).to.be.true; + expect(a).not.to.equal(b); + }); + + it("merges tags", function () { + var a = iD.Entity({tags: {a: 'a'}}), + b = a.mergeTags({b: 'b'}); + expect(b.tags).to.eql({a: 'a', b: 'b'}); + }); + + it("combines non-conflicting tags", function () { + var a = iD.Entity({tags: {a: 'a'}}), + b = a.mergeTags({a: 'a'}); + expect(b.tags).to.eql({a: 'a'}); + }); + + it("combines conflicting tags with semicolons", function () { + var a = iD.Entity({tags: {a: 'a'}}), + b = a.mergeTags({a: 'b'}); + expect(b.tags).to.eql({a: 'a; b'}); + }); + }); + describe("#osmId", function () { it("returns an OSM ID as a string", function () { expect(iD.Entity({id: 'w1234'}).osmId()).to.eql('1234'); From a8410be6eb9042935fb25533888fcada2f6e06f0 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 17:24:50 -0500 Subject: [PATCH 027/332] iD.actions.Join --- index.html | 1 + js/id/actions/join.js | 65 ++++++++++++++ js/id/actions/split.js | 2 + test/index.html | 2 + test/index_packaged.html | 1 + test/spec/actions/join.js | 172 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 243 insertions(+) create mode 100644 js/id/actions/join.js create mode 100644 test/spec/actions/join.js diff --git a/index.html b/index.html index 87e93c643..07063cf81 100644 --- a/index.html +++ b/index.html @@ -81,6 +81,7 @@ + diff --git a/js/id/actions/join.js b/js/id/actions/join.js new file mode 100644 index 000000000..fd6862a9b --- /dev/null +++ b/js/id/actions/join.js @@ -0,0 +1,65 @@ +// Join ways at the end node they share. +// +// This is the inverse of `iD.actions.Split`. +// +// Reference: +// https://github.com/systemed/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MergeWaysAction.as +// https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/CombineWayAction.java +// +iD.actions.Join = function(idA, idB) { + var action = function(graph) { + var a = graph.entity(idA), + b = graph.entity(idB), + nodes, tags; + + if (a.first() === b.first()) { + // a <-- b ==> c + // Expected result: + // a <-- b <-- c + nodes = b.nodes.slice().reverse().concat(a.nodes.slice(1)); + + } else if (a.first() === b.last()) { + // a <-- b <== c + // Expected result: + // a <-- b <-- c + nodes = b.nodes.concat(a.nodes.slice(1)); + + } else if (a.last() === b.first()) { + // a --> b ==> c + // Expected result: + // a --> b --> c + nodes = a.nodes.concat(b.nodes.slice(1)); + + } else if (a.last() === b.last()) { + // a --> b <== c + // Expected result: + // a --> b --> c + nodes = a.nodes.concat(b.nodes.slice().reverse().slice(1)); + } + + graph.parentRelations(b) + .forEach(function (parent) { + var memberA = parent.memberById(idA), + memberB = parent.memberById(idB); + if (!memberA) { + graph = graph.replace(parent.addMember({id: idA, role: memberB.role})); + } + }); + + graph = graph.replace(a.mergeTags(b.tags).update({nodes: nodes})); + graph = iD.actions.DeleteWay(idB)(graph); + + return graph; + }; + + action.enabled = function(graph) { + var a = graph.entity(idA), + b = graph.entity(idB); + return a.first() === b.first() || + a.first() === b.last() || + a.last() === b.first() || + a.last() === b.last(); + }; + + return action; +}; diff --git a/js/id/actions/split.js b/js/id/actions/split.js index 0c648bd26..19a71c619 100644 --- a/js/id/actions/split.js +++ b/js/id/actions/split.js @@ -1,5 +1,7 @@ // Split a way at the given node. // +// This is the inverse of `iD.actions.Join`. +// // For testing convenience, accepts an ID to assign to the new way. // Normally, this will be undefined and the way will automatically // be assigned a new ID. diff --git a/test/index.html b/test/index.html index 512a1ee1e..524894c36 100644 --- a/test/index.html +++ b/test/index.html @@ -78,6 +78,7 @@ + @@ -147,6 +148,7 @@ + diff --git a/test/index_packaged.html b/test/index_packaged.html index 5adaf06f6..948ed0ae1 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -40,6 +40,7 @@ + diff --git a/test/spec/actions/join.js b/test/spec/actions/join.js new file mode 100644 index 000000000..1cddb94bf --- /dev/null +++ b/test/spec/actions/join.js @@ -0,0 +1,172 @@ +describe("iD.actions.Join", function () { + describe("#enabled", function () { + it("returns true for ways that share an end/start node", function () { + // a --> b ==> c + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['a', 'b']}), + '=': iD.Way({id: '=', nodes: ['b', 'c']}) + }); + + expect(iD.actions.Join('-', '=').enabled(graph)).to.be.true; + }); + + it("returns true for ways that share a start/end node", function () { + // a <-- b <== c + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['b', 'a']}), + '=': iD.Way({id: '=', nodes: ['c', 'b']}) + }); + + expect(iD.actions.Join('-', '=').enabled(graph)).to.be.true; + }); + + it("returns true for ways that share a start/start node", function () { + // a <-- b ==> c + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['b', 'a']}), + '=': iD.Way({id: '=', nodes: ['b', 'c']}) + }); + + expect(iD.actions.Join('-', '=').enabled(graph)).to.be.true; + }); + + it("returns true for ways that share an end/end node", function () { + // a --> b <== c + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['a', 'b']}), + '=': iD.Way({id: '=', nodes: ['c', 'b']}) + }); + + expect(iD.actions.Join('-', '=').enabled(graph)).to.be.true; + }); + + it("returns false for ways that don't share the necessary nodes", function () { + // a -- b -- c + // | + // d + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + 'd': iD.Node({id: 'd'}), + '-': iD.Way({id: '-', nodes: ['a', 'b', 'c']}), + '=': iD.Way({id: '=', nodes: ['b', 'd']}) + }); + + expect(iD.actions.Join('-', '=').enabled(graph)).to.be.false; + }); + }); + + it("joins a --> b ==> c", function () { + // Expected result: + // a --> b --> c + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['a', 'b']}), + '=': iD.Way({id: '=', nodes: ['b', 'c']}) + }); + + graph = iD.actions.Join('-', '=')(graph); + + expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c']); + expect(graph.entity('=')).to.be.undefined; + }); + + it("joins a <-- b <== c", function () { + // Expected result: + // a <-- b <-- c + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['b', 'a']}), + '=': iD.Way({id: '=', nodes: ['c', 'b']}) + }); + + graph = iD.actions.Join('-', '=')(graph); + + expect(graph.entity('-').nodes).to.eql(['c', 'b', 'a']); + expect(graph.entity('=')).to.be.undefined; + }); + + it("joins a <-- b ==> c", function () { + // Expected result: + // a <-- b <-- c + // tags on === reversed + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['b', 'a']}), + '=': iD.Way({id: '=', nodes: ['b', 'c']}) + }); + + graph = iD.actions.Join('-', '=')(graph); + + expect(graph.entity('-').nodes).to.eql(['c', 'b', 'a']); + expect(graph.entity('=')).to.be.undefined; + }); + + it("joins a --> b <== c", function () { + // Expected result: + // a --> b --> c + // tags on === reversed + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['a', 'b']}), + '=': iD.Way({id: '=', nodes: ['c', 'b']}) + }); + + graph = iD.actions.Join('-', '=')(graph); + + expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c']); + expect(graph.entity('=')).to.be.undefined; + }); + + it("merges tags", function () { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['a', 'b'], tags: {a: 'a', b: '-', c: 'c'}}), + '=': iD.Way({id: '=', nodes: ['b', 'c'], tags: {a: 'a', b: '=', d: 'd'}}) + }); + + graph = iD.actions.Join('-', '=')(graph); + + expect(graph.entity('-').tags).to.eql({a: 'a', b: '-; =', c: 'c', d: 'd'}); + }); + + it("merges relations", function () { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['a', 'b']}), + '=': iD.Way({id: '=', nodes: ['b', 'c']}), + 'r1': iD.Relation({id: 'r1', members: [{id: '=', role: 'r1'}]}), + 'r2': iD.Relation({id: 'r2', members: [{id: '=', role: 'r1'}, {id: '-', role: 'r2'}]}) + }); + + graph = iD.actions.Join('-', '=')(graph); + + expect(graph.entity('r1').members).to.eql([{id: '-', role: 'r1'}]); + expect(graph.entity('r2').members).to.eql([{id: '-', role: 'r2'}]); + }); +}); From f5036db97894bdca33f02349e93dd55f8bfb195d Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 17:28:50 -0500 Subject: [PATCH 028/332] Start iD.operations.Merge (#435) It's currently limited to merging (joining) exactly two lines. Fixes #370. --- index.html | 1 + js/id/operations/merge.js | 27 +++++++++++++++++++++++++++ locale/en.js | 6 ++++++ test/index.html | 1 + 4 files changed, 35 insertions(+) create mode 100644 js/id/operations/merge.js diff --git a/index.html b/index.html index 07063cf81..d8be52dcd 100644 --- a/index.html +++ b/index.html @@ -114,6 +114,7 @@ + diff --git a/js/id/operations/merge.js b/js/id/operations/merge.js new file mode 100644 index 000000000..08517e9b2 --- /dev/null +++ b/js/id/operations/merge.js @@ -0,0 +1,27 @@ +iD.operations.Merge = function(selection, context) { + var action = iD.actions.Join(selection[0], selection[1]); + + var operation = function() { + context.perform( + action, + t('operations.merge.annotation', {n: selection.length})); + }; + + operation.available = function() { + return selection.length === 2 && + _.all(selection, function (id) { + return context.geometry(id) === 'line'; + }); + }; + + operation.enabled = function() { + return action.enabled(context.graph()); + }; + + operation.id = "merge"; + operation.key = t('operations.merge.key'); + operation.title = t('operations.merge.title'); + operation.description = t('operations.merge.description'); + + return operation; +}; diff --git a/locale/en.js b/locale/en.js index 7d1bed947..9e4eb5b2a 100644 --- a/locale/en.js +++ b/locale/en.js @@ -83,6 +83,12 @@ locale.en = { key: "D", annotation: "Disconnected ways." }, + merge: { + title: "Merge", + description: "Merge these lines.", + key: "C", + annotation: "Merged {n} lines." + }, move: { title: "Move", description: "Move this to a different location.", diff --git a/test/index.html b/test/index.html index 524894c36..da3cd475c 100644 --- a/test/index.html +++ b/test/index.html @@ -110,6 +110,7 @@ + From 3e404df3394152c0234152af8b5061373b30f061 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 1 Feb 2013 19:16:01 -0500 Subject: [PATCH 029/332] Add more translations, opacity to brightness YOU WIN @ansis --- js/id/ui/geocoder.js | 4 ++-- js/id/ui/inspector.js | 8 ++++---- js/id/ui/layerswitcher.js | 4 ++-- locale/en.js | 20 +++++++++++++------- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index d7de24b37..4aac10bf2 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -45,7 +45,7 @@ iD.ui.geocoder = function() { var button = selection.append('button') .attr('tabindex', -1) - .attr('title', 'Find A Location') + .attr('title', t('geocoder.find_location')) .html('') .on('click', toggle); @@ -53,7 +53,7 @@ iD.ui.geocoder = function() { gcForm.attr('class','content fillD map-overlay hide') .append('input') - .attr({ type: 'text', placeholder: 'find a place' }) + .attr({ type: 'text', placeholder: t('geocoder.find_a_place') }) .on('keydown', keydown); selection.call(clickoutside); diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js index 9612cadd9..32e3fc857 100644 --- a/js/id/ui/inspector.js +++ b/js/id/ui/inspector.js @@ -35,7 +35,7 @@ iD.ui.inspector = function() { }); newTag.append('span').attr('class', 'icon icon-pre-text plus'); - newTag.append('span').attr('class','label').text('New tag') + newTag.append('span').attr('class','label').text(t('inspector.new_tag')); drawTags(entity.tags); @@ -63,7 +63,7 @@ iD.ui.inspector = function() { .attr('class', 'apply action') .on('click', apply); - inspectorButton.append('span').attr('class','label').text('Okay'); + inspectorButton.append('span').attr('class','label').text(t('okay')); var minorButtons = selection.append('div').attr('class','minor-buttons fl'); @@ -148,7 +148,7 @@ iD.ui.inspector = function() { iD.ui.flash() .select('.content') .append('h3') - .text(t('no_documentation_combination')); + .text(t('inspector.no_documentation_combination')); } }); } else if (d.key) { @@ -166,7 +166,7 @@ iD.ui.inspector = function() { iD.ui.flash() .select('.content') .append('h3') - .text(t('no_documentation_key')); + .text(t('inspector.no_documentation_key')); } }); } diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index c64457ab8..5c4835757 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -59,7 +59,7 @@ iD.ui.layerswitcher = function(context) { .append('div') .attr('class', 'opacity-options-wrapper'); - opa.append('h4').text(t('layers')); + opa.append('h4').text(t('layerswitcher.layers')); opa.append('ul') .attr('class', 'opacity-options') @@ -68,7 +68,7 @@ iD.ui.layerswitcher = function(context) { .enter() .append('li') .attr('data-original-title', function(d) { - return t('percent_opacity', { opacity: (d * 100) }); + return t('layerswitcher.percent_brightness', { opacity: (d * 100) }); }) .on('click.set-opacity', function(d) { d3.select('#tile-g') diff --git a/locale/en.js b/locale/en.js index 9e4eb5b2a..866c7eca3 100644 --- a/locale/en.js +++ b/locale/en.js @@ -136,8 +136,11 @@ locale.en = { "layer_settings": "Layer Settings", - "no_documentation_combination": "This is no documentation available for this tag combination", - "no_documentation_key": "This is no documentation available for this key", + inspector: { + no_documentation_combination: "This is no documentation available for this tag combination", + no_documentation_key: "This is no documentation available for this key", + new_tag: "New Tag" + }, "view_on_osm": "View on OSM", @@ -145,13 +148,16 @@ locale.en = { "edit_tags": "Edit tags", - "find_location": "Find A Location", - "find_placeholder": "find a place", + geocoder: { + "find_location": "Find A Location", + "find_a_place": "find a place" + }, "description": "Description", "logout": "logout", - - "layers": "Layers", - "percent_opacity": "{opacity}% opacity" + layerswitcher: { + layers: "Layers", + percent_brightness: "{opacity}% brightness" + } }; From 09dac581bebf0db7698da32eec8930256dbbb882 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 12:55:06 -0500 Subject: [PATCH 030/332] Fix MoveWay mode (fixes #602) --- js/id/modes/move_way.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/id/modes/move_way.js b/js/id/modes/move_way.js index 30c473406..986eef5bd 100644 --- a/js/id/modes/move_way.js +++ b/js/id/modes/move_way.js @@ -47,7 +47,7 @@ iD.modes.MoveWay = function(context, wayId) { context.enter(iD.modes.Browse(context)); } - context.selection() + context.surface() .on('mousemove.move-way', move) .on('click.move-way', finish); @@ -63,7 +63,7 @@ iD.modes.MoveWay = function(context, wayId) { }; mode.exit = function() { - context.selection() + context.surface() .on('mousemove.move-way', null) .on('click.move-way', null); From 8b6bb964ad85dc710dd3fb6c725afba89f4add59 Mon Sep 17 00:00:00 2001 From: Dr Ian Date: Sat, 2 Feb 2013 23:52:16 +0100 Subject: [PATCH 031/332] Added orthogonalize function (square corners) --- index.html | 2 + js/id/actions/orthogonalize.js | 132 ++++++++++++++++++++++++++++++ js/id/operations/orthogonalize.js | 25 ++++++ locale/en.js | 9 ++ 4 files changed, 168 insertions(+) create mode 100644 js/id/actions/orthogonalize.js create mode 100644 js/id/operations/orthogonalize.js diff --git a/index.html b/index.html index d8be52dcd..adfe92752 100644 --- a/index.html +++ b/index.html @@ -85,6 +85,7 @@ + @@ -112,6 +113,7 @@ + diff --git a/js/id/actions/orthogonalize.js b/js/id/actions/orthogonalize.js new file mode 100644 index 000000000..c0a9d64ed --- /dev/null +++ b/js/id/actions/orthogonalize.js @@ -0,0 +1,132 @@ +iD.actions.Orthogonalize = function(wayId, map) { + + var action = function(graph) { + var way = graph.entity(wayId), + nodes = graph.childNodes(way), + tags = {},key,role; + + var points = nodes.map(function(n) { + return map.projection(n.loc); + }), + quad_nodes = []; + + var score = squareness(); + for (var i = 0; i < 1000; ++i) { + var motions = points.map(stepMap); + //return false; + for (var j = 0; j < motions.length; ++j) { + points[j] = addPoints(points[j],motions[j]); + } + var newScore = squareness(); + if (newScore > score) { + return false; + } + score = newScore; + if (score < 1.0e-8) { + break; + } + } + for (var i = 0; i < points.length; i++) { + quad_nodes.push(iD.Node({ loc: map.projection.invert(points[i]) })); + } + quad_nodes.push(quad_nodes[0]); + + for (var i = 0; i < nodes.length; i++) { + graph = graph.remove(nodes[i]); + } + + for (var i = 0; i < quad_nodes.length; i++) { + graph = graph.replace(quad_nodes[i]); + } + + return graph.replace(way.update({ + nodes: _.pluck(quad_nodes, 'id') + })); + + + function stepMap(b,i,array){ + var a,c,p,q = []; + a = array[(i-1+array.length) % array.length]; + c = array[(i+1) % array.length]; + p = subtractPoints(a,b); + q = subtractPoints(c,b); + + + var scale = p.length + q.length; + p = normalizePoint(p,1.0); + q = normalizePoint(q,1.0); + var dotp = p[0]*q[0] + p[1]*q[1]; + // nasty hack to deal with almost-straight segments (angle is closer to 180 than to 90/270). + if (dotp < -0.707106781186547) { + dotp += 1.0; + } + var v = []; + v = addPoints(p,q); + v = normalizePoint(v,0.1 * dotp * scale); + return v; + } + + function squareness(){ + + var g = 0.0; + for (var i = 1; i < points.length - 1; i++) { + var score = scoreOfPoints(points[i-1], points[i], points[i+1]); + g += score; + } + var startScore = scoreOfPoints(points[points.length-1], points[0], points[1]); + var endScore = scoreOfPoints(points[points.length-2], points[points.length-1], points[0]); + g += startScore; + g += endScore; + return g; + } + + function scoreOfPoints(a, b, c) { + var p,q = []; + p = subtractPoints(a,b); + q = subtractPoints(c,b); + + p = normalizePoint(p,1.0); + q = normalizePoint(q,1.0); + + var dotp = p[0]*q[0] + p[1]*q[1]; + // score is constructed so that +1, -1 and 0 are all scored 0, any other angle + // is scored higher. + var score = 2.0 * Math.min(Math.abs(dotp-1.0), Math.min(Math.abs(dotp), Math.abs(dotp+1))); + return score; + } + + function subtractPoints(a,b){ + var vector = [0,0]; + vector[0] = a[0]-b[0]; + vector[1] = a[1]-b[1]; + return vector; + } + + function addPoints(a,b){ + var vector = [0,0]; + vector[0] = a[0]+b[0]; + vector[1] = a[1]+b[1]; + return vector; + } + + function normalizePoint(point,thickness){ + var vector = [0,0]; + var length = Math.sqrt( point[0] * point[0] + point[1] * point[1]); + if(length != 0){ + vector[0] = point[0]/length; + vector[1] = point[1]/length; + } + + vector[0] *= thickness; + vector[1] *= thickness; + + return vector; + } + }; + + action.enabled = function(graph) { + return graph.entity(wayId).isClosed(); + }; + + return action; +}; diff --git a/js/id/operations/orthogonalize.js b/js/id/operations/orthogonalize.js new file mode 100644 index 000000000..888d0015e --- /dev/null +++ b/js/id/operations/orthogonalize.js @@ -0,0 +1,25 @@ +iD.operations.Orthogonalize = function(selection, context) { + var entityId = selection[0], + action = iD.actions.Orthogonalize(entityId, context.map()); + + var operation = function() { + var annotation = t('operations.orthogonalize.annotation.' + context.geometry(entityId)); + context.perform(action, annotation); + }; + + operation.available = function() { + return selection.length === 1 && + context.entity(entityId).type === 'way'; + }; + + operation.enabled = function() { + return action.enabled(context.graph()); + }; + + operation.id = "orthogonalize"; + operation.key = t('operations.orthogonalize.key'); + operation.title = t('operations.orthogonalize.title'); + operation.description = t('operations.orthogonalize.description'); + + return operation; +}; diff --git a/locale/en.js b/locale/en.js index 866c7eca3..31d6a13de 100644 --- a/locale/en.js +++ b/locale/en.js @@ -65,6 +65,15 @@ locale.en = { area: "Made an area circular." } }, + orthogonalize: { + title: "Orthogonalize", + description: "Square these corners.", + key: "Q", + annotation: { + line: "Squared the corners of a line.", + area: "Squared the corners of an area." + } + }, delete: { title: "Delete", description: "Remove this from the map.", From 26b8b8789e1415c0e3adca277deb2131c929ca28 Mon Sep 17 00:00:00 2001 From: Dr Ian Date: Sun, 3 Feb 2013 00:05:11 +0100 Subject: [PATCH 032/332] Fix case where parentnodes present --- js/id/actions/orthogonalize.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/js/id/actions/orthogonalize.js b/js/id/actions/orthogonalize.js index c0a9d64ed..80c6a12c8 100644 --- a/js/id/actions/orthogonalize.js +++ b/js/id/actions/orthogonalize.js @@ -19,7 +19,7 @@ iD.actions.Orthogonalize = function(wayId, map) { } var newScore = squareness(); if (newScore > score) { - return false; + return graph; } score = newScore; if (score < 1.0e-8) { @@ -31,8 +31,22 @@ iD.actions.Orthogonalize = function(wayId, map) { } quad_nodes.push(quad_nodes[0]); - for (var i = 0; i < nodes.length; i++) { + for (i = 0; i < nodes.length; i++) { + if (graph.parentWays(nodes[i]).length > 1) { + var closest, closest_dist = Infinity, dist; + for (var j = 0; j < quad_nodes.length; j++) { + dist = iD.geo.dist(quad_nodes[j].loc, nodes[i].loc); + if (dist < closest_dist) { + closest_dist = dist; + closest = j; + } + } + quad_nodes.splice(closest, 1, nodes[i]); + if (closest === 0) quad_nodes.splice(quad_nodes.length - 1, 1, nodes[i]); + else if (closest === quad_nodes.length - 1) quad_nodes.splice(0, 1, nodes[i]); + } else { graph = graph.remove(nodes[i]); + } } for (var i = 0; i < quad_nodes.length; i++) { From ddc5e324f6cfa7fdcc25bfc679b0f25c70ad97b2 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 18:28:44 -0500 Subject: [PATCH 033/332] Extract iD.Difference iD.Difference represents the difference between two graphs. It knows how to calculate the set of entities that were created, modified, or deleted, and also contains the logic for recursively extending a difference to the complete set of entities that will require a redraw, taking into account child and parent relationships. Additionally, all history mutators now return a difference. --- index.html | 1 + js/id/graph/difference.js | 113 +++++++++++++++++++ js/id/graph/graph.js | 60 ---------- js/id/graph/history.js | 44 ++++---- js/id/renderer/map.js | 33 +----- test/index.html | 2 + test/index_packaged.html | 1 + test/spec/graph/difference.js | 202 ++++++++++++++++++++++++++++++++++ test/spec/graph/graph.js | 82 -------------- test/spec/graph/history.js | 41 +++++-- 10 files changed, 375 insertions(+), 204 deletions(-) create mode 100644 js/id/graph/difference.js create mode 100644 test/spec/graph/difference.js diff --git a/index.html b/index.html index d8be52dcd..f9b627c16 100644 --- a/index.html +++ b/index.html @@ -119,6 +119,7 @@ + diff --git a/js/id/graph/difference.js b/js/id/graph/difference.js new file mode 100644 index 000000000..8bea5664c --- /dev/null +++ b/js/id/graph/difference.js @@ -0,0 +1,113 @@ +/* + iD.Difference represents the difference between two graphs. + It knows how to calculate the set of entities that were + created, modified, or deleted, and also contains the logic + for recursively extending a difference to the complete set + of entities that will require a redraw, taking into account + child and parent relationships. + */ +iD.Difference = function (base, head) { + var changes = {}, length = 0; + + _.each(head.entities, function(h, id) { + var b = base.entities[id]; + if (h !== b) { + changes[id] = {base: b, head: h}; + length++; + } + }); + + _.each(base.entities, function(b, id) { + var h = head.entities[id]; + if (!changes[id] && h !== b) { + changes[id] = {base: b, head: h}; + length++; + } + }); + + var difference = {}; + + difference.length = function () { + return length; + }; + + difference.changes = function() { + return changes; + }; + + difference.modified = function() { + var result = []; + _.each(changes, function(change) { + if (change.base && change.head) result.push(change.head); + }); + return result; + }; + + difference.created = function() { + var result = []; + _.each(changes, function(change) { + if (!change.base && change.head) result.push(change.head); + }); + return result; + }; + + difference.deleted = function() { + var result = []; + _.each(changes, function(change) { + if (change.base && !change.head) result.push(change.base); + }); + return result; + }; + + difference.complete = function(extent) { + var result = {}, id, change; + + function addParents(parents) { + for (var i = 0; i < parents.length; i++) { + var parent = parents[i]; + + if (parent.id in result) + continue; + + result[parent.id] = parent; + addParents(head.parentRelations(parent)); + } + } + + for (id in changes) { + change = changes[id]; + + var h = change.head, + b = change.base, + entity = h || b; + + if (extent && !entity.intersects(extent, h ? head : base)) + continue; + + result[id] = h; + + if (entity.type === 'way') { + var nh = h ? h.nodes : [], + nb = b ? b.nodes : [], + diff; + + diff = _.difference(nh, nb); + for (var i = 0; i < diff.length; i++) { + result[diff[i]] = head.entity(diff[i]); + } + + diff = _.difference(nb, nh); + for (var i = 0; i < diff.length; i++) { + result[diff[i]] = head.entity(diff[i]); + } + } + + addParents(head.parentWays(entity)); + addParents(head.parentRelations(entity)); + } + + return result; + }; + + return difference; +}; diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index a19bfe094..2692aeba6 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -232,65 +232,5 @@ iD.Graph.prototype = { } } return items; - }, - - difference: function (graph) { - - function diff(a, b) { - var result = [], - keys = Object.keys(a.entities), - entity, oldentity, id, i; - - for (i = 0; i < keys.length; i++) { - id = keys[i]; - entity = a.entities[id]; - oldentity = b.entities[id]; - if (entity !== oldentity) { - - // maybe adding affected children better belongs in renderer/map.js? - if (entity && entity.type === 'way' && - oldentity && oldentity.type === 'way') { - result = result - .concat(_.difference(entity.nodes, oldentity.nodes)) - .concat(_.difference(oldentity.nodes, entity.nodes)); - - } else if (entity && entity.type === 'way') { - result = result.concat(entity.nodes); - - } else if (oldentity && oldentity.type === 'way') { - result = result.concat(oldentity.nodes); - } - - result.push(id); - } - } - return result; - } - - return _.unique(diff(this, graph).concat(diff(graph, this)).sort()); - }, - - modified: function() { - var result = [], base = this.base().entities; - _.each(this.entities, function(entity, id) { - if (entity && base[id]) result.push(id); - }); - return result; - }, - - created: function() { - var result = [], base = this.base().entities; - _.each(this.entities, function(entity, id) { - if (entity && !base[id]) result.push(id); - }); - return result; - }, - - deleted: function() { - var result = [], base = this.base().entities; - _.each(this.entities, function(entity, id) { - if (!entity && base[id]) result.push(id); - }); - return result; } }; diff --git a/js/id/graph/history.js b/js/id/graph/history.js index 42e1b1604..cbadbf87f 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -21,7 +21,9 @@ iD.History = function() { } function change(previous) { - dispatch.change(history.graph().difference(previous)); + var difference = iD.Difference(previous, history.graph()); + dispatch.change(difference); + return difference; } var history = { @@ -42,7 +44,7 @@ iD.History = function() { stack.push(perform(arguments)); index++; - change(previous); + return change(previous); }, replace: function () { @@ -51,7 +53,7 @@ iD.History = function() { // assert(index == stack.length - 1) stack[index] = perform(arguments); - change(previous); + return change(previous); }, pop: function () { @@ -60,7 +62,7 @@ iD.History = function() { if (index > 0) { index--; stack.pop(); - change(previous); + return change(previous); } }, @@ -80,7 +82,7 @@ iD.History = function() { } dispatch.undone(); - change(previous); + return change(previous); }, redo: function () { @@ -92,7 +94,7 @@ iD.History = function() { } dispatch.redone(); - change(previous); + return change(previous); }, undoAnnotation: function () { @@ -111,31 +113,27 @@ iD.History = function() { } }, - changes: function () { - var initial = stack[0].graph, - current = stack[index].graph; + difference: function () { + var base = stack[0].graph, + head = stack[index].graph; + return iD.Difference(base, head); + }, + changes: function () { + var difference = history.difference(); return { - modified: current.modified().map(function (id) { - return current.entity(id); - }), - created: current.created().map(function (id) { - return current.entity(id); - }), - deleted: current.deleted().map(function (id) { - return initial.entity(id); - }) - }; + modified: difference.modified(), + created: difference.created(), + deleted: difference.deleted() + } }, hasChanges: function() { - return !!this.numChanges(); + return this.difference().length() > 0; }, numChanges: function() { - return d3.sum(d3.values(this.changes()).map(function(c) { - return c.length; - })); + return this.difference().length(); }, imagery_used: function(source) { diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 1f22a7c81..839aa18a3 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -64,44 +64,19 @@ iD.Map = function(context) { extent = map.extent(), graph = context.graph(); - function addParents(parents) { - for (var i = 0; i < parents.length; i++) { - var parent = parents[i]; - if (only[parent.id] === undefined) { - only[parent.id] = parent; - addParents(graph.parentRelations(parent)); - } - } - } - if (!difference) { all = graph.intersects(extent); filter = d3.functor(true); } else { - var only = {}; - - for (var j = 0; j < difference.length; j++) { - var id = difference[j], - entity = graph.entity(id); - - // Even if the entity is false (deleted), it needs to be - // removed from the surface - only[id] = entity; - - if (entity && entity.intersects(extent, graph)) { - addParents(graph.parentWays(only[id])); - addParents(graph.parentRelations(only[id])); - } - } - - all = _.compact(_.values(only)); + var complete = difference.complete(extent); + all = _.compact(_.values(complete)); filter = function(d) { if (d.type === 'midpoint') { for (var i = 0; i < d.ways.length; i++) { - if (d.ways[i].id in only) return true; + if (d.ways[i].id in complete) return true; } } else { - return d.id in only; + return d.id in complete; } }; } diff --git a/test/index.html b/test/index.html index da3cd475c..b6d925923 100644 --- a/test/index.html +++ b/test/index.html @@ -115,6 +115,7 @@ + @@ -164,6 +165,7 @@ + diff --git a/test/index_packaged.html b/test/index_packaged.html index 948ed0ae1..ef91e2823 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -55,6 +55,7 @@ + diff --git a/test/spec/graph/difference.js b/test/spec/graph/difference.js new file mode 100644 index 000000000..f12a77254 --- /dev/null +++ b/test/spec/graph/difference.js @@ -0,0 +1,202 @@ +describe("iD.Difference", function () { + describe("#changes", function () { + it("includes created entities", function () { + var node = iD.Node({id: 'n'}), + base = iD.Graph(), + head = base.replace(node), + diff = iD.Difference(base, head); + expect(diff.changes()).to.eql({n: {base: undefined, head: node}}); + }); + + it("includes undone created entities", function () { + var node = iD.Node({id: 'n'}), + base = iD.Graph(), + head = base.replace(node), + diff = iD.Difference(head, base); + expect(diff.changes()).to.eql({n: {base: node, head: undefined}}); + }); + + it("includes modified entities", function () { + var n1 = iD.Node({id: 'n'}), + n2 = n1.update(), + base = iD.Graph([n1]), + head = base.replace(n2), + diff = iD.Difference(base, head); + expect(diff.changes()).to.eql({n: {base: n1, head: n2}}); + }); + + it("includes undone modified entities", function () { + var n1 = iD.Node({id: 'n'}), + n2 = n1.update(), + base = iD.Graph([n1]), + head = base.replace(n2), + diff = iD.Difference(head, base); + expect(diff.changes()).to.eql({n: {base: n2, head: n1}}); + }); + + it("includes deleted entities", function () { + var node = iD.Node({id: 'n'}), + base = iD.Graph([node]), + head = base.remove(node), + diff = iD.Difference(base, head); + expect(diff.changes()).to.eql({n: {base: node, head: undefined}}); + }); + + it("includes undone deleted entities", function () { + var node = iD.Node({id: 'n'}), + base = iD.Graph([node]), + head = base.remove(node), + diff = iD.Difference(head, base); + expect(diff.changes()).to.eql({n: {base: undefined, head: node}}); + }); + + it("doesn't include created entities that were subsequently deleted", function () { + var node = iD.Node(), + base = iD.Graph(), + head = base.replace(node).remove(node), + diff = iD.Difference(base, head); + expect(diff.changes()).to.eql({}); + }); + }); + + describe("#created", function () { + it("returns an array of created entities", function () { + var node = iD.Node({id: 'n'}), + base = iD.Graph(), + head = base.replace(node), + diff = iD.Difference(base, head); + expect(diff.created()).to.eql([node]); + }); + }); + + describe("#modified", function () { + it("returns an array of modified entities", function () { + var n1 = iD.Node({id: 'n'}), + n2 = n1.move([1, 2]), + base = iD.Graph([n1]), + head = base.replace(n2), + diff = iD.Difference(base, head); + expect(diff.modified()).to.eql([n2]); + }); + }); + + describe("#deleted", function () { + it("returns an array of deleted entities", function () { + var node = iD.Node({id: 'n'}), + base = iD.Graph([node]), + head = base.remove(node), + diff = iD.Difference(base, head); + expect(diff.deleted()).to.eql([node]); + }); + }); + + describe("#complete", function () { + it("includes created entities", function () { + var node = iD.Node({id: 'n'}), + base = iD.Graph(), + head = base.replace(node), + diff = iD.Difference(base, head); + expect(diff.complete()['n']).to.equal(node); + }); + + it("includes modified entities", function () { + var n1 = iD.Node({id: 'n'}), + n2 = n1.move([1, 2]), + base = iD.Graph([n1]), + head = base.replace(n2), + diff = iD.Difference(base, head); + expect(diff.complete()['n']).to.equal(n2); + }); + + it("includes deleted entities", function () { + var node = iD.Node({id: 'n'}), + base = iD.Graph([node]), + head = base.remove(node), + diff = iD.Difference(base, head); + expect(diff.complete()).to.eql({n: undefined}); + }); + + it("includes nodes added to a way", function () { + var n1 = iD.Node({id: 'n1'}), + n2 = iD.Node({id: 'n2'}), + w1 = iD.Way({id: 'w', nodes: ['n1']}), + w2 = w1.addNode('n2'), + base = iD.Graph([n1, n2, w1]), + head = base.replace(w2), + diff = iD.Difference(base, head); + + expect(diff.complete()['n2']).to.equal(n2); + }); + + it("includes nodes removed from a way", function () { + var n1 = iD.Node({id: 'n1'}), + n2 = iD.Node({id: 'n2'}), + w1 = iD.Way({id: 'w', nodes: ['n1', 'n2']}), + w2 = w1.removeNode('n2'), + base = iD.Graph([n1, n2, w1]), + head = base.replace(w2), + diff = iD.Difference(base, head); + + expect(diff.complete()['n2']).to.equal(n2); + }); + + it("includes parent ways of modified nodes", function () { + var n1 = iD.Node({id: 'n'}), + n2 = n1.move([1, 2]), + way = iD.Way({id: 'w', nodes: ['n']}), + base = iD.Graph([n1, way]), + head = base.replace(n2), + diff = iD.Difference(base, head); + + expect(diff.complete()['w']).to.equal(way); + }); + + it("includes parent relations of modified entities", function () { + var n1 = iD.Node({id: 'n'}), + n2 = n1.move([1, 2]), + rel = iD.Relation({id: 'r', members: [{id: 'n'}]}), + base = iD.Graph([n1, rel]), + head = base.replace(n2), + diff = iD.Difference(base, head); + + expect(diff.complete()['r']).to.equal(rel); + }); + + it("includes parent relations of modified entities, recursively", function () { + var n1 = iD.Node({id: 'n'}), + n2 = n1.move([1, 2]), + rel1 = iD.Relation({id: 'r1', members: [{id: 'n'}]}), + rel2 = iD.Relation({id: 'r2', members: [{id: 'r1'}]}), + base = iD.Graph([n1, rel1, rel2]), + head = base.replace(n2), + diff = iD.Difference(base, head); + + expect(diff.complete()['r2']).to.equal(rel2); + }); + + it("includes parent relations of parent ways of modified nodes", function () { + var n1 = iD.Node({id: 'n'}), + n2 = n1.move([1, 2]), + way = iD.Way({id: 'w', nodes: ['n']}), + rel = iD.Relation({id: 'r', members: [{id: 'w'}]}), + base = iD.Graph([n1, way, rel]), + head = base.replace(n2), + diff = iD.Difference(base, head); + + expect(diff.complete()['r']).to.equal(rel); + }); + + it("copes with recursive relations", function () { + var node = iD.Node({id: 'n'}), + rel1 = iD.Relation({id: 'r1', members: [{id: 'n'}, {id: 'r2'}]}), + rel2 = iD.Relation({id: 'r2', members: [{id: 'r1'}]}), + base = iD.Graph([node, rel1, rel2]), + head = base.replace(node.move([1, 2])), + diff = iD.Difference(base, head); + + expect(diff.complete()).to.be.ok; + }); + + it("limits changes to those within a given extent"); + }); +}); diff --git a/test/spec/graph/graph.js b/test/spec/graph/graph.js index 05bcbbe89..a357ff360 100644 --- a/test/spec/graph/graph.js +++ b/test/spec/graph/graph.js @@ -333,86 +333,4 @@ describe('iD.Graph', function() { expect(graph.childNodes(way)).to.eql([node]); }); }); - - describe("#difference", function () { - it("returns an Array of ids of changed entities", function () { - var initial = iD.Node({id: "n1"}), - updated = initial.update({}), - created = iD.Node(), - deleted = iD.Node({id: 'n2'}), - graph1 = iD.Graph([initial, deleted]), - graph2 = graph1.replace(updated).replace(created).remove(deleted); - expect(graph2.difference(graph1)).to.eql([created.id, updated.id, deleted.id]); - }); - - - it("includes created entities, and reverse", function () { - var node = iD.Node(), - graph1 = iD.Graph(), - graph2 = graph1.replace(node); - expect(graph2.difference(graph1)).to.eql([node.id]); - expect(graph1.difference(graph2)).to.eql([node.id]); - }); - - it("includes entities changed from base, and reverse", function () { - var node = iD.Node(), - graph1 = iD.Graph(node), - graph2 = graph1.replace(node.update()); - expect(graph2.difference(graph1)).to.eql([node.id]); - expect(graph1.difference(graph2)).to.eql([node.id]); - }); - - it("includes already changed entities that were updated, and reverse", function () { - var node = iD.Node(), - graph1 = iD.Graph().replace(node), - graph2 = graph1.replace(node.update()); - expect(graph2.difference(graph1)).to.eql([node.id]); - expect(graph1.difference(graph2)).to.eql([node.id]); - }); - - it("includes affected child nodes", function () { - var n = iD.Node({id: 'n'}), - n2 = iD.Node({id: 'n2'}), - w1 = iD.Way({id: 'w1', nodes: ['n']}), - w1_ = iD.Way({id: 'w1', nodes: ['n', 'n2']}), - graph1 = iD.Graph([n, n2, w1]), - graph2 = graph1.replace(w1_); - expect(graph2.difference(graph1)).to.eql(['n2', 'w1']); - expect(graph1.difference(graph2)).to.eql(['n2', 'w1']); - }); - - }); - - describe("#modified", function () { - it("returns an Array of ids of modified entities", function () { - var node = iD.Node({id: 'n1'}), - node_ = iD.Node({id: 'n1'}), - graph = iD.Graph([node]).replace(node_); - expect(graph.modified()).to.eql([node.id]); - }); - }); - - describe("#created", function () { - it("returns an Array of ids of created entities", function () { - var node1 = iD.Node({id: 'n-1'}), - node2 = iD.Node({id: 'n2'}), - graph = iD.Graph([node2]).replace(node1); - expect(graph.created()).to.eql([node1.id]); - }); - }); - - describe("#deleted", function () { - it("returns an Array of ids of deleted entities", function () { - var node1 = iD.Node({id: "n1"}), - node2 = iD.Node(), - graph = iD.Graph([node1, node2]).remove(node1); - expect(graph.deleted()).to.eql([node1.id]); - }); - - it("doesn't include created entities that were subsequently deleted", function () { - var node = iD.Node(), - graph = iD.Graph().replace(node).remove(node); - expect(graph.deleted()).to.eql([]); - }); - }); }); diff --git a/test/spec/graph/history.js b/test/spec/graph/history.js index dc685b446..3f28f437e 100644 --- a/test/spec/graph/history.js +++ b/test/spec/graph/history.js @@ -14,6 +14,10 @@ describe("iD.History", function () { }); describe("#perform", function () { + it("returns a difference", function () { + expect(history.perform(action).changes()).to.eql({}); + }); + it("updates the graph", function () { var node = iD.Node(); history.perform(function (graph) { return graph.replace(node); }); @@ -27,8 +31,8 @@ describe("iD.History", function () { it("emits a change event", function () { history.on('change', spy); - history.perform(action); - expect(spy).to.have.been.calledWith([]); + var difference = history.perform(action); + expect(spy).to.have.been.calledWith(difference); }); it("performs multiple actions", function () { @@ -42,6 +46,10 @@ describe("iD.History", function () { }); describe("#replace", function () { + it("returns a difference", function () { + expect(history.replace(action).changes()).to.eql({}); + }); + it("updates the graph", function () { var node = iD.Node(); history.replace(function (graph) { return graph.replace(node); }); @@ -56,8 +64,8 @@ describe("iD.History", function () { it("emits a change event", function () { history.on('change', spy); - history.replace(action); - expect(spy).to.have.been.calledWith([]); + var difference = history.replace(action); + expect(spy).to.have.been.calledWith(difference); }); it("performs multiple actions", function () { @@ -71,6 +79,11 @@ describe("iD.History", function () { }); describe("#pop", function () { + it("returns a difference", function () { + history.perform(action, "annotation"); + expect(history.pop().changes()).to.eql({}); + }); + it("updates the graph", function () { history.perform(action, "annotation"); history.pop(); @@ -86,12 +99,16 @@ describe("iD.History", function () { it("emits a change event", function () { history.perform(action); history.on('change', spy); - history.pop(); - expect(spy).to.have.been.calledWith([]); + var difference = history.pop(); + expect(spy).to.have.been.calledWith(difference); }); }); describe("#undo", function () { + it("returns a difference", function () { + expect(history.undo().changes()).to.eql({}); + }); + it("pops the undo stack", function () { history.perform(action, "annotation"); history.undo(); @@ -121,12 +138,16 @@ describe("iD.History", function () { it("emits a change event", function () { history.perform(action); history.on('change', spy); - history.undo(); - expect(spy).to.have.been.calledWith([]); + var difference = history.undo(); + expect(spy).to.have.been.calledWith(difference); }); }); describe("#redo", function () { + it("returns a difference", function () { + expect(history.redo().changes()).to.eql({}); + }); + it("emits an redone event", function () { history.perform(action); history.undo(); @@ -139,8 +160,8 @@ describe("iD.History", function () { history.perform(action); history.undo(); history.on('change', spy); - history.redo(); - expect(spy).to.have.been.calledWith([]); + var difference = history.redo(); + expect(spy).to.have.been.calledWith(difference); }); }); From 11d723819dc222e946074b70a5980c59c9890c1d Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 19:13:53 -0500 Subject: [PATCH 034/332] Difference#extantIDs --- js/id/graph/difference.js | 8 ++++++++ test/spec/graph/difference.js | 27 +++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/js/id/graph/difference.js b/js/id/graph/difference.js index 8bea5664c..e2159271a 100644 --- a/js/id/graph/difference.js +++ b/js/id/graph/difference.js @@ -35,6 +35,14 @@ iD.Difference = function (base, head) { return changes; }; + difference.extantIDs = function() { + var result = []; + _.each(changes, function(change, id) { + if (change.head) result.push(id); + }); + return result; + }; + difference.modified = function() { var result = []; _.each(changes, function(change) { diff --git a/test/spec/graph/difference.js b/test/spec/graph/difference.js index f12a77254..c9647bbea 100644 --- a/test/spec/graph/difference.js +++ b/test/spec/graph/difference.js @@ -59,6 +59,33 @@ describe("iD.Difference", function () { }); }); + describe("#extantIDs", function () { + it("includes the ids of created entities", function () { + var node = iD.Node({id: 'n'}), + base = iD.Graph(), + head = base.replace(node), + diff = iD.Difference(base, head); + expect(diff.extantIDs()).to.eql(['n']); + }); + + it("includes the ids of modified entities", function () { + var n1 = iD.Node({id: 'n'}), + n2 = n1.move([1, 2]), + base = iD.Graph([n1]), + head = base.replace(n2), + diff = iD.Difference(base, head); + expect(diff.extantIDs()).to.eql(['n']); + }); + + it("omits the ids of deleted entities", function () { + var node = iD.Node({id: 'n'}), + base = iD.Graph([node]), + head = base.remove(node), + diff = iD.Difference(base, head); + expect(diff.extantIDs()).to.eql([]); + }); + }); + describe("#created", function () { it("returns an array of created entities", function () { var node = iD.Node({id: 'n'}), From 1de05b518dcec864ca39429a3e0a394b31958763 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 19:15:28 -0500 Subject: [PATCH 035/332] Select result when splitting or joining ways Fixes #601. Fixes #603. --- js/id/operations/merge.js | 6 +++--- js/id/operations/split.js | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/js/id/operations/merge.js b/js/id/operations/merge.js index 08517e9b2..d02222368 100644 --- a/js/id/operations/merge.js +++ b/js/id/operations/merge.js @@ -2,9 +2,9 @@ iD.operations.Merge = function(selection, context) { var action = iD.actions.Join(selection[0], selection[1]); var operation = function() { - context.perform( - action, - t('operations.merge.annotation', {n: selection.length})); + var annotation = t('operations.merge.annotation', {n: selection.length}), + difference = context.perform(action, annotation); + context.enter(iD.modes.Select(context, difference.extantIDs())); }; operation.available = function() { diff --git a/js/id/operations/split.js b/js/id/operations/split.js index 07d7d56ff..f49f4b9ed 100644 --- a/js/id/operations/split.js +++ b/js/id/operations/split.js @@ -3,7 +3,9 @@ iD.operations.Split = function(selection, context) { action = iD.actions.Split(entityId); var operation = function() { - context.perform(action, t('operations.split.annotation')); + var annotation = t('operations.split.annotation'), + difference = context.perform(action, annotation); + context.enter(iD.modes.Select(context, difference.extantIDs())); }; operation.available = function() { From fc00f154a913788b5d16fed1a917a4c3fce80491 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 19:44:47 -0500 Subject: [PATCH 036/332] Dispatch a change event on merge --- js/id/graph/history.js | 2 ++ js/id/renderer/map.js | 1 - test/spec/graph/history.js | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/js/id/graph/history.js b/js/id/graph/history.js index cbadbf87f..3cc1ace01 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -35,6 +35,8 @@ iD.History = function() { for (var i = 0; i < stack.length; i++) { stack[i].graph.rebase(entities); } + + dispatch.change(); }, perform: function () { diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 839aa18a3..c3b528da3 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -102,7 +102,6 @@ iD.Map = function(context) { function connectionLoad(err, result) { context.history().merge(result); - redraw(Object.keys(result)); } function zoomPan() { diff --git a/test/spec/graph/history.js b/test/spec/graph/history.js index 3f28f437e..0ecbed5d3 100644 --- a/test/spec/graph/history.js +++ b/test/spec/graph/history.js @@ -13,6 +13,20 @@ describe("iD.History", function () { }); }); + describe("#merge", function () { + it("merges the entities into all graph versions", function () { + var n = iD.Node({id: 'n'}); + history.merge({n: n}); + expect(history.graph().entity('n')).to.equal(n); + }); + + it("emits a change event", function () { + history.on('change', spy); + history.merge({}); + expect(spy).to.have.been.called; + }); + }); + describe("#perform", function () { it("returns a difference", function () { expect(history.perform(action).changes()).to.eql({}); From ec602a7db7ef139baa1614bcb5c9c8cb0ec43f54 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 19:46:59 -0500 Subject: [PATCH 037/332] Hook up connection and history in context --- js/id/id.js | 4 ++++ js/id/renderer/map.js | 7 ------- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/js/id/id.js b/js/id/id.js index 358610525..56d3e481a 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -16,6 +16,10 @@ window.iD = function () { // the connection requires .storage() to be available on calling. var connection = iD.Connection(context); + connection.on('load.context', function (err, result) { + history.merge(result); + }); + /* Straight accessors. Avoid using these if you can. */ context.ui = function() { return ui; }; context.connection = function() { return connection; }; diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index c3b528da3..b2561f21f 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -26,9 +26,6 @@ iD.Map = function(context) { surface, tilegroup; function map(selection) { - context.connection() - .on('load.tile', connectionLoad); - context.history() .on('change.map', redraw); @@ -100,10 +97,6 @@ iD.Map = function(context) { surface.selectAll('.layer *').remove(); } - function connectionLoad(err, result) { - context.history().merge(result); - } - function zoomPan() { if (d3.event && d3.event.sourceEvent.type === 'dblclick') { if (!dblclickEnabled) { From 80a5a083b0affb576065facd7260073835ff4d0d Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 19:55:52 -0500 Subject: [PATCH 038/332] Remove unused --- js/id/graph/entity.js | 11 +--------- test/spec/graph/entity.js | 42 ------------------------------------- test/spec/graph/node.js | 9 -------- test/spec/graph/relation.js | 9 -------- test/spec/graph/way.js | 9 -------- 5 files changed, 1 insertion(+), 79 deletions(-) diff --git a/js/id/graph/entity.js b/js/id/graph/entity.js index ae612c268..bce9675ee 100644 --- a/js/id/graph/entity.js +++ b/js/id/graph/entity.js @@ -43,7 +43,6 @@ iD.Entity.prototype = { if (!this.id && this.type) { this.id = iD.Entity.id(this.type); - this._updated = true; } if (iD.debug) { @@ -63,7 +62,7 @@ iD.Entity.prototype = { }, update: function(attrs) { - return iD.Entity(this, attrs, {_updated: true}); + return iD.Entity(this, attrs); }, mergeTags: function(tags) { @@ -80,14 +79,6 @@ iD.Entity.prototype = { return this.update({tags: merged}); }, - created: function() { - return this._updated && this.osmId().charAt(0) === '-'; - }, - - modified: function() { - return this._updated && this.osmId().charAt(0) !== '-'; - }, - intersects: function(extent, resolver) { return this.extent(resolver).intersects(extent); }, diff --git a/test/spec/graph/entity.js b/test/spec/graph/entity.js index 088ab5516..f2ac85f17 100644 --- a/test/spec/graph/entity.js +++ b/test/spec/graph/entity.js @@ -52,12 +52,6 @@ describe('iD.Entity', function () { expect(e.id).to.equal('w1'); }); - it("tags the entity as updated", function () { - var tags = {foo: 'bar'}, - e = iD.Entity().update({tags: tags}); - expect(e._updated).to.to.be.true; - }); - it("doesn't modify the input", function () { var attrs = {tags: {foo: 'bar'}}, e = iD.Entity().update(attrs); @@ -104,42 +98,6 @@ describe('iD.Entity', function () { }); }); - describe("#created", function () { - it("returns falsy by default", function () { - expect(iD.Entity({id: 'w1234'}).created()).not.to.be.ok; - }); - - it("returns falsy for an unmodified Entity", function () { - expect(iD.Entity({id: 'w1234'}).created()).not.to.be.ok; - }); - - it("returns falsy for a modified Entity with positive ID", function () { - expect(iD.Entity({id: 'w1234'}).update({}).created()).not.to.be.ok; - }); - - it("returns truthy for a modified Entity with negative ID", function () { - expect(iD.Entity({id: 'w-1234'}).update({}).created()).to.be.ok; - }); - }); - - describe("#modified", function () { - it("returns falsy by default", function () { - expect(iD.Entity({id: 'w1234'}).modified()).not.to.be.ok; - }); - - it("returns falsy for an unmodified Entity", function () { - expect(iD.Entity({id: 'w1234'}).modified()).not.to.be.ok; - }); - - it("returns truthy for a modified Entity with positive ID", function () { - expect(iD.Entity({id: 'w1234'}).update({}).modified()).to.be.ok; - }); - - it("returns falsy for a modified Entity with negative ID", function () { - expect(iD.Entity({id: 'w-1234'}).update({}).modified()).not.to.be.ok; - }); - }); - describe("#intersects", function () { it("returns true for a way with a node within the given extent", function () { var node = iD.Node({loc: [0, 0]}), diff --git a/test/spec/graph/node.js b/test/spec/graph/node.js index ad149261e..71a2c5e95 100644 --- a/test/spec/graph/node.js +++ b/test/spec/graph/node.js @@ -4,15 +4,6 @@ describe('iD.Node', function () { expect(iD.Node().type).to.equal("node"); }); - it("returns a created Entity if no ID is specified", function () { - expect(iD.Node().created()).to.be.ok; - }); - - it("returns an unmodified Entity if ID is specified", function () { - expect(iD.Node({id: 'n1234'}).created()).not.to.be.ok; - expect(iD.Node({id: 'n1234'}).modified()).not.to.be.ok; - }); - it("defaults tags to an empty object", function () { expect(iD.Node().tags).to.eql({}); }); diff --git a/test/spec/graph/relation.js b/test/spec/graph/relation.js index 8edf9c1e7..6e00dcacb 100644 --- a/test/spec/graph/relation.js +++ b/test/spec/graph/relation.js @@ -10,15 +10,6 @@ describe('iD.Relation', function () { expect(iD.Relation().type).to.equal("relation"); }); - it("returns a created Entity if no ID is specified", function () { - expect(iD.Relation().created()).to.be.ok; - }); - - it("returns an unmodified Entity if ID is specified", function () { - expect(iD.Relation({id: 'r1234'}).created()).not.to.be.ok; - expect(iD.Relation({id: 'r1234'}).modified()).not.to.be.ok; - }); - it("defaults members to an empty array", function () { expect(iD.Relation().members).to.eql([]); }); diff --git a/test/spec/graph/way.js b/test/spec/graph/way.js index ec41edec6..6b6b1543f 100644 --- a/test/spec/graph/way.js +++ b/test/spec/graph/way.js @@ -10,15 +10,6 @@ describe('iD.Way', function() { expect(iD.Way().type).to.equal("way"); }); - it("returns a created Entity if no ID is specified", function () { - expect(iD.Way().created()).to.be.ok; - }); - - it("returns an unmodified Entity if ID is specified", function () { - expect(iD.Way({id: 'w1234'}).created()).not.to.be.ok; - expect(iD.Way({id: 'w1234'}).modified()).not.to.be.ok; - }); - it("defaults nodes to an empty array", function () { expect(iD.Way().nodes).to.eql([]); }); From b08722fd3fb3cbfdf19af48926d4b3185cbbcdf2 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 19:59:05 -0500 Subject: [PATCH 039/332] Pass just the projection to Circularize --- js/id/actions/circularize.js | 6 +++--- js/id/operations/circularize.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/js/id/actions/circularize.js b/js/id/actions/circularize.js index edbd0d8b3..d844134eb 100644 --- a/js/id/actions/circularize.js +++ b/js/id/actions/circularize.js @@ -1,4 +1,4 @@ -iD.actions.Circularize = function(wayId, map) { +iD.actions.Circularize = function(wayId, projection) { var action = function(graph) { var way = graph.entity(wayId), @@ -6,7 +6,7 @@ iD.actions.Circularize = function(wayId, map) { tags = {}, key, role; var points = nodes.map(function(n) { - return map.projection(n.loc); + return projection(n.loc); }), centroid = d3.geom.polygon(points).centroid(), radius = d3.median(points, function(p) { @@ -15,7 +15,7 @@ iD.actions.Circularize = function(wayId, map) { circular_nodes = []; for (var i = 0; i < 12; i++) { - circular_nodes.push(iD.Node({ loc: map.projection.invert([ + circular_nodes.push(iD.Node({ loc: projection.invert([ centroid[0] + Math.cos((i / 12) * Math.PI * 2) * radius, centroid[1] + Math.sin((i / 12) * Math.PI * 2) * radius]) })); diff --git a/js/id/operations/circularize.js b/js/id/operations/circularize.js index 419a6e9eb..2680da4ec 100644 --- a/js/id/operations/circularize.js +++ b/js/id/operations/circularize.js @@ -1,6 +1,6 @@ iD.operations.Circularize = function(selection, context) { var entityId = selection[0], - action = iD.actions.Circularize(entityId, context.map()); + action = iD.actions.Circularize(entityId, context.projection); var operation = function() { var annotation = t('operations.circularize.annotation.' + context.geometry(entityId)); From 9e878b1cf927817c4cf7e619bcf5722278bd72e8 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 20:00:05 -0500 Subject: [PATCH 040/332] Remove redundant conditional --- js/id/validate.js | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/js/id/validate.js b/js/id/validate.js index aa7d8771b..6329b626f 100644 --- a/js/id/validate.js +++ b/js/id/validate.js @@ -15,31 +15,29 @@ iD.validate = function(changes, graph) { if (tags.building && tags.building === 'yes') return 'building=yes'; } - if (changes.created.length) { - for (var i = 0; i < changes.created.length; i++) { - change = changes.created[i]; + for (var i = 0; i < changes.created.length; i++) { + change = changes.created[i]; - if (change.geometry(graph) === 'point' && _.isEmpty(change.tags)) { - warnings.push({ - message: t('validations.untagged_point'), - entity: change - }); - } + if (change.geometry(graph) === 'point' && _.isEmpty(change.tags)) { + warnings.push({ + message: t('validations.untagged_point'), + entity: change + }); + } - if (change.geometry(graph) === 'line' && _.isEmpty(change.tags)) { - warnings.push({ message: t('validations.untagged_line'), entity: change }); - } + if (change.geometry(graph) === 'line' && _.isEmpty(change.tags)) { + warnings.push({ message: t('validations.untagged_line'), entity: change }); + } - if (change.geometry(graph) === 'area' && _.isEmpty(change.tags)) { - warnings.push({ message: t('validations.untagged_area'), entity: change }); - } + if (change.geometry(graph) === 'area' && _.isEmpty(change.tags)) { + warnings.push({ message: t('validations.untagged_area'), entity: change }); + } - if (change.geometry(graph) === 'line' && tagSuggestsArea(change)) { - warnings.push({ - message: t('validations.tag_suggests_area', {tag: tagSuggestsArea(change)}), - entity: change - }); - } + if (change.geometry(graph) === 'line' && tagSuggestsArea(change)) { + warnings.push({ + message: t('validations.tag_suggests_area', {tag: tagSuggestsArea(change)}), + entity: change + }); } } From 0b3e0fb3db7556438da8d560d9fab7dffd829e7f Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 20:29:01 -0500 Subject: [PATCH 041/332] Use iD.actions.DeleteNode when removing nodes They need to be removed from any parent relations. Also, make sure to uniq child nodes, otherwise the start/end node over-contributes to the centroid calculation. This action needs tests. --- js/id/actions/circularize.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/js/id/actions/circularize.js b/js/id/actions/circularize.js index d844134eb..2c72047d9 100644 --- a/js/id/actions/circularize.js +++ b/js/id/actions/circularize.js @@ -2,7 +2,7 @@ iD.actions.Circularize = function(wayId, projection) { var action = function(graph) { var way = graph.entity(wayId), - nodes = graph.childNodes(way), + nodes = _.uniq(graph.childNodes(way)), tags = {}, key, role; var points = nodes.map(function(n) { @@ -36,8 +36,6 @@ iD.actions.Circularize = function(wayId, projection) { circular_nodes.splice(closest, 1, nodes[i]); if (closest === 0) circular_nodes.splice(circular_nodes.length - 1, 1, nodes[i]); else if (closest === circular_nodes.length - 1) circular_nodes.splice(0, 1, nodes[i]); - } else { - graph = graph.remove(nodes[i]); } } @@ -45,9 +43,16 @@ iD.actions.Circularize = function(wayId, projection) { graph = graph.replace(circular_nodes[i]); } - return graph.replace(way.update({ - nodes: _.pluck(circular_nodes, 'id') - })); + var ids = _.pluck(circular_nodes, 'id'), + difference = _.difference(_.uniq(way.nodes), ids); + + graph = graph.replace(way.update({nodes: ids})); + + for (i = 0; i < difference.length; i++) { + graph = iD.actions.DeleteNode(difference[i])(graph); + } + + return graph; }; action.enabled = function(graph) { From 33f5db4d96b317be8c24d1b2db52405d2063d584 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 21:17:43 -0500 Subject: [PATCH 042/332] Fix hash with selected id --- js/id/behavior/hash.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/behavior/hash.js b/js/id/behavior/hash.js index 9056db393..c8762fb13 100644 --- a/js/id/behavior/hash.js +++ b/js/id/behavior/hash.js @@ -45,7 +45,7 @@ iD.behavior.Hash = function(context) { context.map().on('drawn.hash', function() { if (!context.entity(id)) return; selectoff(); - context.enter(iD.modes.Select([id])); + context.enter(iD.modes.Select(context, [id])); }); context.on('enter.hash', function() { From 441be74539c1b78d95b143109add18ab48504fba Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 21:26:29 -0500 Subject: [PATCH 043/332] Simplify --- js/id/actions/circularize.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/js/id/actions/circularize.js b/js/id/actions/circularize.js index 2c72047d9..532dcdb45 100644 --- a/js/id/actions/circularize.js +++ b/js/id/actions/circularize.js @@ -2,8 +2,7 @@ iD.actions.Circularize = function(wayId, projection) { var action = function(graph) { var way = graph.entity(wayId), - nodes = _.uniq(graph.childNodes(way)), - tags = {}, key, role; + nodes = _.uniq(graph.childNodes(way)); var points = nodes.map(function(n) { return projection(n.loc); @@ -21,8 +20,6 @@ iD.actions.Circularize = function(wayId, projection) { })); } - circular_nodes.push(circular_nodes[0]); - for (i = 0; i < nodes.length; i++) { if (graph.parentWays(nodes[i]).length > 1) { var closest, closest_dist = Infinity, dist; @@ -34,8 +31,6 @@ iD.actions.Circularize = function(wayId, projection) { } } circular_nodes.splice(closest, 1, nodes[i]); - if (closest === 0) circular_nodes.splice(circular_nodes.length - 1, 1, nodes[i]); - else if (closest === circular_nodes.length - 1) circular_nodes.splice(0, 1, nodes[i]); } } @@ -46,6 +41,8 @@ iD.actions.Circularize = function(wayId, projection) { var ids = _.pluck(circular_nodes, 'id'), difference = _.difference(_.uniq(way.nodes), ids); + ids.push(ids[0]); + graph = graph.replace(way.update({nodes: ids})); for (i = 0; i < difference.length; i++) { From 13b0b540a7cbc15d0c67f067f72a3f3f16092e62 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Sat, 2 Feb 2013 23:47:29 -0500 Subject: [PATCH 044/332] Don't snap to midpoints, snap to their parent way --- js/id/behavior/draw_way.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 9efcd5bb9..f724613da 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -16,10 +16,13 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { function move(datum) { var loc = context.map().mouseCoordinates(); - if (datum.type === 'node' || datum.type === 'midpoint') { + if (datum.type === 'node') { loc = datum.loc; - } else if (datum.type === 'way') { - loc = iD.geo.chooseIndex(datum, d3.mouse(context.surface().node()), context).loc; + } else if (datum.type === 'midpoint' || datum.type === 'way') { + var way = datum.type === 'way' ? + datum : + baseGraph.entity(datum.ways[0].id); + loc = iD.geo.chooseIndex(way, d3.mouse(context.surface().node()), context).loc; } context.replace(iD.actions.MoveNode(nodeId, loc)); From 3000bc89ceeb9693a8ab258a103e2edea46a1475 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sun, 3 Feb 2013 08:49:43 -0800 Subject: [PATCH 045/332] shadow should be above fill Makes it much easier to select lines that are within areas. --- js/id/svg/surface.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/svg/surface.js b/js/id/svg/surface.js index 7561802e5..a11f8fbae 100644 --- a/js/id/svg/surface.js +++ b/js/id/svg/surface.js @@ -3,7 +3,7 @@ iD.svg.Surface = function() { selection.append('defs'); var layers = selection.selectAll('.layer') - .data(['shadow', 'fill', 'casing', 'stroke', 'text', 'hit', 'halo', 'label']); + .data(['fill', 'shadow', 'casing', 'stroke', 'text', 'hit', 'halo', 'label']); layers.enter().append('g') .attr('class', function(d) { return 'layer layer-' + d; }); From b912097ee42fc540be636542c03be88b235a8f7d Mon Sep 17 00:00:00 2001 From: Dr Ian Date: Sun, 3 Feb 2013 08:17:03 +0100 Subject: [PATCH 046/332] Tidy up: fix indents, spaces etc. - Update orthogonalize to match new circularize. - Add orthoganalize to test index.html - Revert whitespace on index.html --- js/id/actions/orthogonalize.js | 109 ++++++++++++++---------------- js/id/operations/orthogonalize.js | 2 +- test/index.html | 2 + 3 files changed, 54 insertions(+), 59 deletions(-) diff --git a/js/id/actions/orthogonalize.js b/js/id/actions/orthogonalize.js index 80c6a12c8..830d1a1eb 100644 --- a/js/id/actions/orthogonalize.js +++ b/js/id/actions/orthogonalize.js @@ -1,20 +1,18 @@ -iD.actions.Orthogonalize = function(wayId, map) { +iD.actions.Orthogonalize = function(wayId, projection) { var action = function(graph) { var way = graph.entity(wayId), - nodes = graph.childNodes(way), - tags = {},key,role; + nodes = graph.childNodes(way); var points = nodes.map(function(n) { - return map.projection(n.loc); - }), - quad_nodes = []; + return projection(n.loc); + }), + quad_nodes = []; var score = squareness(); - for (var i = 0; i < 1000; ++i) { + for (var i = 0; i < 1000; i++) { var motions = points.map(stepMap); - //return false; - for (var j = 0; j < motions.length; ++j) { + for (var j = 0; j < motions.length; j++) { points[j] = addPoints(points[j],motions[j]); } var newScore = squareness(); @@ -26,10 +24,9 @@ iD.actions.Orthogonalize = function(wayId, map) { break; } } - for (var i = 0; i < points.length; i++) { - quad_nodes.push(iD.Node({ loc: map.projection.invert(points[i]) })); + for (i = 0; i < points.length - 1; i++) { + quad_nodes.push(iD.Node({ loc: projection.invert(points[i]) })); } - quad_nodes.push(quad_nodes[0]); for (i = 0; i < nodes.length; i++) { if (graph.parentWays(nodes[i]).length > 1) { @@ -42,93 +39,89 @@ iD.actions.Orthogonalize = function(wayId, map) { } } quad_nodes.splice(closest, 1, nodes[i]); - if (closest === 0) quad_nodes.splice(quad_nodes.length - 1, 1, nodes[i]); - else if (closest === quad_nodes.length - 1) quad_nodes.splice(0, 1, nodes[i]); - } else { - graph = graph.remove(nodes[i]); } } - for (var i = 0; i < quad_nodes.length; i++) { - graph = graph.replace(quad_nodes[i]); + for (i = 0; i < quad_nodes.length; i++) { + graph = graph.replace(quad_nodes[i]); } - return graph.replace(way.update({ - nodes: _.pluck(quad_nodes, 'id') - })); + var ids = _.pluck(quad_nodes, 'id'), + difference = _.difference(_.uniq(way.nodes), ids); + ids.push(ids[0]); - function stepMap(b,i,array){ - var a,c,p,q = []; - a = array[(i-1+array.length) % array.length]; - c = array[(i+1) % array.length]; - p = subtractPoints(a,b); - q = subtractPoints(c,b); + graph = graph.replace(way.update({nodes: ids})); + for (i = 0; i < difference.length; i++) { + graph = iD.actions.DeleteNode(difference[i])(graph); + } + + return graph; + + function stepMap(b, i, array) { + var a, c, p, q = []; + a = array[(i - 1 + array.length) % array.length]; + c = array[(i + 1) % array.length]; + p = subtractPoints(a, b); + q = subtractPoints(c, b); var scale = p.length + q.length; - p = normalizePoint(p,1.0); - q = normalizePoint(q,1.0); - var dotp = p[0]*q[0] + p[1]*q[1]; + p = normalizePoint(p, 1.0); + q = normalizePoint(q, 1.0); + var dotp = p[0] *q[0] + p[1] *q[1]; // nasty hack to deal with almost-straight segments (angle is closer to 180 than to 90/270). if (dotp < -0.707106781186547) { dotp += 1.0; } var v = []; - v = addPoints(p,q); - v = normalizePoint(v,0.1 * dotp * scale); + v = addPoints(p, q); + v = normalizePoint(v, 0.1 * dotp * scale); return v; } - function squareness(){ + function squareness() { var g = 0.0; for (var i = 1; i < points.length - 1; i++) { - var score = scoreOfPoints(points[i-1], points[i], points[i+1]); + var score = scoreOfPoints(points[i - 1], points[i], points[i + 1]); g += score; } - var startScore = scoreOfPoints(points[points.length-1], points[0], points[1]); - var endScore = scoreOfPoints(points[points.length-2], points[points.length-1], points[0]); + var startScore = scoreOfPoints(points[points.length - 1], points[0], points[1]); + var endScore = scoreOfPoints(points[points.length - 2], points[points.length - 1], points[0]); g += startScore; g += endScore; return g; } function scoreOfPoints(a, b, c) { - var p,q = []; - p = subtractPoints(a,b); - q = subtractPoints(c,b); + var p, q = []; + p = subtractPoints(a, b); + q = subtractPoints(c, b); + p = normalizePoint(p, 1.0); + q = normalizePoint(q, 1.0); - p = normalizePoint(p,1.0); - q = normalizePoint(q,1.0); - - var dotp = p[0]*q[0] + p[1]*q[1]; + var dotp = p[0] * q[0] + p[1] * q[1]; // score is constructed so that +1, -1 and 0 are all scored 0, any other angle // is scored higher. - var score = 2.0 * Math.min(Math.abs(dotp-1.0), Math.min(Math.abs(dotp), Math.abs(dotp+1))); + var score = 2.0 * Math.min(Math.abs(dotp - 1.0), Math.min(Math.abs(dotp), Math.abs(dotp + 1))); return score; } - function subtractPoints(a,b){ - var vector = [0,0]; - vector[0] = a[0]-b[0]; - vector[1] = a[1]-b[1]; - return vector; + function subtractPoints(a, b) { + return [a[0] - b[0], a[1] - b[1]]; } - function addPoints(a,b){ - var vector = [0,0]; - vector[0] = a[0]+b[0]; - vector[1] = a[1]+b[1]; - return vector; + function addPoints(a, b) { + return [a[0]+b[0],a[1]+b[1]]; } - function normalizePoint(point,thickness){ + function normalizePoint(point, thickness) { var vector = [0,0]; - var length = Math.sqrt( point[0] * point[0] + point[1] * point[1]); + var length = Math.sqrt(point[0] * point[0] + point[1] * point[1]); if(length != 0){ - vector[0] = point[0]/length; - vector[1] = point[1]/length; + vector[0] = point[0] / length; + vector[1] = point[1] / length; } vector[0] *= thickness; diff --git a/js/id/operations/orthogonalize.js b/js/id/operations/orthogonalize.js index 888d0015e..c59ed13e4 100644 --- a/js/id/operations/orthogonalize.js +++ b/js/id/operations/orthogonalize.js @@ -1,6 +1,6 @@ iD.operations.Orthogonalize = function(selection, context) { var entityId = selection[0], - action = iD.actions.Orthogonalize(entityId, context.map()); + action = iD.actions.Orthogonalize(entityId, context.projection); var operation = function() { var annotation = t('operations.orthogonalize.annotation.' + context.geometry(entityId)); diff --git a/test/index.html b/test/index.html index b6d925923..551a87621 100644 --- a/test/index.html +++ b/test/index.html @@ -73,6 +73,7 @@ + @@ -108,6 +109,7 @@ + From 03800ec8419b01ede93d1f2d509572ebc730a8fd Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sun, 3 Feb 2013 13:21:27 -0800 Subject: [PATCH 047/332] Don't force point to area boundary (fixes #614) --- css/map.css | 1 + 1 file changed, 1 insertion(+) diff --git a/css/map.css b/css/map.css index 8ad55c790..34d56f0a9 100644 --- a/css/map.css +++ b/css/map.css @@ -725,6 +725,7 @@ text.point { } /* Ensure drawing doesn't interact with area fills. */ +.mode-add-point .area, .mode-draw-line .area, .mode-draw-area .area, .mode-add-line .area, From e0d4f5e87d19a1e9d19a4bb70f2959688c6d4b5f Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 4 Feb 2013 07:13:49 -0800 Subject: [PATCH 048/332] Use default shape-rendering On Firefox, optimizeSpeed = aliased. Fixes #592. --- css/map.css | 5 ----- 1 file changed, 5 deletions(-) diff --git a/css/map.css b/css/map.css index 34d56f0a9..ab2a352c9 100644 --- a/css/map.css +++ b/css/map.css @@ -150,11 +150,6 @@ path.stroke { stroke-width: 2; } -path.stroke, -path.casing { - shape-rendering: optimizeSpeed; -} - path.shadow { pointer-events: stroke; stroke-width: 10; From 240d83c1fd9b6cca93314ace4a591c81cc9f62b8 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 4 Feb 2013 10:54:39 -0500 Subject: [PATCH 049/332] Fix #623, get enitity from current graph --- js/id/behavior/draw_way.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index f724613da..3089bd6ee 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -21,7 +21,7 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { } else if (datum.type === 'midpoint' || datum.type === 'way') { var way = datum.type === 'way' ? datum : - baseGraph.entity(datum.ways[0].id); + context.entity(datum.ways[0].id); loc = iD.geo.chooseIndex(way, d3.mouse(context.surface().node()), context).loc; } From 3a075e68702af0f21aab713319b28222442d8726 Mon Sep 17 00:00:00 2001 From: Saman Bemel-Benrud Date: Mon, 4 Feb 2013 11:30:25 -0500 Subject: [PATCH 050/332] updated source svg. --- img/source/sprite.svg | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/img/source/sprite.svg b/img/source/sprite.svg index 6248e28cc..29e87e2b8 100644 --- a/img/source/sprite.svg +++ b/img/source/sprite.svg @@ -40,14 +40,14 @@ inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="1" - inkscape:cx="581.64693" - inkscape:cy="71.693021" + inkscape:cx="329.64693" + inkscape:cy="58.693021" inkscape:document-units="px" - inkscape:current-layer="layer12" + inkscape:current-layer="layer1" showgrid="false" inkscape:window-width="1280" inkscape:window-height="700" - inkscape:window-x="0" + inkscape:window-x="48" inkscape:window-y="0" inkscape:window-maximized="0" fit-margin-top="0" From 285ef7584d0f42560ea8f8a3e5c887580756026a Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 4 Feb 2013 11:55:25 -0500 Subject: [PATCH 051/332] Contributing --- CONTRIBUTING.md | 77 +++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 3 +- 2 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..5a4bdcca8 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,77 @@ +# Contributing to iD + +Thinking of contributing to iD? High five! Here are some basics for our habits +so that you can write code that fits in perfectly. + +## Javascript + +We use the [Airbnb style for Javascript](https://github.com/airbnb/javascript) with +only one difference: + +**4 space soft tabs always for Javascript, not 2.** + +No aligned `=`, no aligned arguments, spaces are either indents or the 1 +space between expressions. No hard tabs, ever. + +Javascript code should pass through [JSHint](http://www.jshint.com/) with no +warnings. + +## HTML + +There isn't much HTML in iD, but what there is is similar to JS: 4 spaces +always, indented by the level of the tree: + +```html +
+
+
+``` + +## CSS + +Just like HTML and Javascript, 4 space soft tabs always. + +```css +.radial-menu-tooltip { + background: rgba(255, 255, 255, 0.8); +} +``` + +We write vanilla CSS with no preprocessing step. Since iD targets modern browsers, +feel free to use newer features wisely. + +## Tests + +Test your code and make sure it passes. Our testing harness requires [node.js](http://nodejs.org/) +and a few modules: + +1. [Install node.js](http://nodejs.org/) - 'Install' will download a package for your OS +2. Go to the directory where you have checked out `iD` +3. Run `npm install` +4. Run `npm test` to see whether your tests pass or fail. + +## Licensing + +iD is under the [WTFPL](http://www.wtfpl.net/). Some of the libraries it uses +are under different licenses. If you're contributing to iD, you're contributing +WTFPL code. + +## Submitting Changes + +Let's say that you've thought of a great improvement to iD - a change that +turns everything red (please do not do this, we like colors other than red). + +In your local copy, make a branch for this change: + + git checkout -b make-red + +Make your changes to source files. By source files we mean the files in `js/`. +the `iD.js` and `iD.min.js` files in this project are autogenerated - don't edit +them. + +So let's say you've changed `js/ui/confirm.js`. + +1. Run `jshint src` to make sure your code is clean +2. Run tests with `npm test` +3. Commit your changes with an informative commit message +4. [Submit a pull request](https://help.github.com/articles/using-pull-requests) to the `systemed/iD` project. diff --git a/README.md b/README.md index 711cb5418..08c6764c0 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,7 @@ ## Participate! -* [Read NOTES.md, our ongoing dev journal](https://github.com/systemed/iD/blob/master/NOTES.md) -* Fork this project. We eagerly accept pull requests. +* [Read up on Contributing and the code style of iD](CONTRIBUTING.md) * See [open issues in the issue tracker if you're looking for something to do](https://github.com/systemed/iD/issues?state=open) To run the code locally, just fork this project and run it from a local webserver. From e5ad28feb75e79173892b1f54208851deea2f2cc Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 4 Feb 2013 12:10:23 -0500 Subject: [PATCH 052/332] JShint fixups --- js/id/actions/split.js | 2 +- js/id/behavior/draw_way.js | 2 +- js/id/graph/history.js | 2 +- js/id/graph/node.js | 2 +- js/id/operations.js | 2 +- js/id/services/taginfo.js | 2 +- js/id/svg.js | 18 ++++++++++-------- js/id/ui/splash.js | 13 +++++++++---- 8 files changed, 25 insertions(+), 18 deletions(-) diff --git a/js/id/actions/split.js b/js/id/actions/split.js index 19a71c619..24f2b4318 100644 --- a/js/id/actions/split.js +++ b/js/id/actions/split.js @@ -17,7 +17,7 @@ iD.actions.Split = function(nodeId, newWayId) { return parents.filter(function (parent) { return parent.first() !== nodeId && parent.last() !== nodeId; - }) + }); } var action = function(graph) { diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 3089bd6ee..1cc52ab1f 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -82,7 +82,7 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { return graph .replace(way.removeNode(nodeId).addNode(newNode.id, index)) .remove(node); - } + }; } // Accept the current position of the temporary node and continue drawing. diff --git a/js/id/graph/history.js b/js/id/graph/history.js index 3cc1ace01..7d1098027 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -127,7 +127,7 @@ iD.History = function() { modified: difference.modified(), created: difference.created(), deleted: difference.deleted() - } + }; }, hasChanges: function() { diff --git a/js/id/graph/node.js b/js/id/graph/node.js index b9a171752..56fc14eab 100644 --- a/js/id/graph/node.js +++ b/js/id/graph/node.js @@ -47,6 +47,6 @@ _.extend(iD.Node.prototype, { type: 'Point', coordinates: this.loc } - } + }; } }); diff --git a/js/id/operations.js b/js/id/operations.js index 2786d046f..a72fe1d82 100644 --- a/js/id/operations.js +++ b/js/id/operations.js @@ -1 +1 @@ -iD.operations = {} +iD.operations = {}; diff --git a/js/id/services/taginfo.js b/js/id/services/taginfo.js index aa89a37fa..fc4e72542 100644 --- a/js/id/services/taginfo.js +++ b/js/id/services/taginfo.js @@ -40,7 +40,7 @@ iD.taginfo = function() { } function popularValues(parameters) { - return function(d) { return parseFloat(d['fraction']) > 0.01; }; + return function(d) { return parseFloat(d.fraction) > 0.01; }; } function valKey(d) { return { value: d.key }; } diff --git a/js/id/svg.js b/js/id/svg.js index 5b07b3a53..aadb6b830 100644 --- a/js/id/svg.js +++ b/js/id/svg.js @@ -1,19 +1,19 @@ iD.svg = { - RoundProjection: function (projection) { - return function (d) { + RoundProjection: function(projection) { + return function(d) { return iD.geo.roundCoords(projection(d)); }; }, - PointTransform: function (projection) { - return function (entity) { + PointTransform: function(projection) { + return function(entity) { return 'translate(' + projection(entity.loc) + ')'; }; }, - LineString: function (projection, graph) { + LineString: function(projection, graph) { var cache = {}; - return function (entity) { + return function(entity) { if (cache[entity.id] !== undefined) { return cache[entity.id]; } @@ -23,7 +23,9 @@ iD.svg = { } return (cache[entity.id] = - 'M' + graph.childNodes(entity).map(function (n) { return projection(n.loc); }).join('L')); - } + 'M' + graph.childNodes(entity).map(function(n) { + return projection(n.loc); + }).join('L')); + }; } }; diff --git a/js/id/ui/splash.js b/js/id/ui/splash.js index 43489fab1..a4d2a915e 100644 --- a/js/id/ui/splash.js +++ b/js/id/ui/splash.js @@ -2,15 +2,20 @@ iD.ui.splash = function() { var modal = iD.ui.modal(); modal.select('.modal') - .attr('class', 'modal-splash modal') + .attr('class', 'modal-splash modal'); var introModal = modal.select('.content') .append('div') .attr('class', 'modal-section fillL'); - introModal.append('div').attr('class','logo'); + introModal.append('div') + .attr('class','logo'); - introModal.append('div').html("

Welcome to the iD OpenStreetMap editor

This is development version 0.0.0-alpha1. For more information see ideditor.com and report bugs at github.com.systemed/iD.

"); + introModal.append('div') + .html("

Welcome to the iD OpenStreetMap editor

" + + "This is development version 0.0.0-alpha1. " + + "For more information see ideditor.com" + + " and report bugs at github.com.systemed/iD.

"); return modal; -}; \ No newline at end of file +}; From 8699b4a49e62108a9bf31b845534c68209cade9a Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 4 Feb 2013 09:17:00 -0800 Subject: [PATCH 053/332] Adjust for style, formatting, and jshint --- js/id/actions/orthogonalize.js | 51 +++++++++++++++------------------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/js/id/actions/orthogonalize.js b/js/id/actions/orthogonalize.js index 830d1a1eb..068db55bd 100644 --- a/js/id/actions/orthogonalize.js +++ b/js/id/actions/orthogonalize.js @@ -1,18 +1,14 @@ iD.actions.Orthogonalize = function(wayId, projection) { - var action = function(graph) { var way = graph.entity(wayId), - nodes = graph.childNodes(way); - - var points = nodes.map(function(n) { - return projection(n.loc); - }), - quad_nodes = []; + nodes = graph.childNodes(way), + points = nodes.map(function(n) { return projection(n.loc); }), + quad_nodes = [], i, j; var score = squareness(); - for (var i = 0; i < 1000; i++) { + for (i = 0; i < 1000; i++) { var motions = points.map(stepMap); - for (var j = 0; j < motions.length; j++) { + for (j = 0; j < motions.length; j++) { points[j] = addPoints(points[j],motions[j]); } var newScore = squareness(); @@ -24,6 +20,7 @@ iD.actions.Orthogonalize = function(wayId, projection) { break; } } + for (i = 0; i < points.length - 1; i++) { quad_nodes.push(iD.Node({ loc: projection.invert(points[i]) })); } @@ -31,7 +28,7 @@ iD.actions.Orthogonalize = function(wayId, projection) { for (i = 0; i < nodes.length; i++) { if (graph.parentWays(nodes[i]).length > 1) { var closest, closest_dist = Infinity, dist; - for (var j = 0; j < quad_nodes.length; j++) { + for (j = 0; j < quad_nodes.length; j++) { dist = iD.geo.dist(quad_nodes[j].loc, nodes[i].loc); if (dist < closest_dist) { closest_dist = dist; @@ -47,7 +44,7 @@ iD.actions.Orthogonalize = function(wayId, projection) { } var ids = _.pluck(quad_nodes, 'id'), - difference = _.difference(_.uniq(way.nodes), ids); + difference = _.difference(_.uniq(way.nodes), ids); ids.push(ids[0]); @@ -60,28 +57,25 @@ iD.actions.Orthogonalize = function(wayId, projection) { return graph; function stepMap(b, i, array) { - var a, c, p, q = []; - a = array[(i - 1 + array.length) % array.length]; - c = array[(i + 1) % array.length]; - p = subtractPoints(a, b); - q = subtractPoints(c, b); + var a = array[(i - 1 + array.length) % array.length], + c = array[(i + 1) % array.length], + p = subtractPoints(a, b), + q = subtractPoints(c, b); var scale = p.length + q.length; p = normalizePoint(p, 1.0); q = normalizePoint(q, 1.0); + var dotp = p[0] *q[0] + p[1] *q[1]; // nasty hack to deal with almost-straight segments (angle is closer to 180 than to 90/270). if (dotp < -0.707106781186547) { dotp += 1.0; } - var v = []; - v = addPoints(p, q); - v = normalizePoint(v, 0.1 * dotp * scale); - return v; + + return normalizePoint(addPoints(p, q), 0.1 * dotp * scale); } function squareness() { - var g = 0.0; for (var i = 1; i < points.length - 1; i++) { var score = scoreOfPoints(points[i - 1], points[i], points[i + 1]); @@ -95,17 +89,16 @@ iD.actions.Orthogonalize = function(wayId, projection) { } function scoreOfPoints(a, b, c) { - var p, q = []; - p = subtractPoints(a, b); - q = subtractPoints(c, b); + var p = subtractPoints(a, b), + q = subtractPoints(c, b); + p = normalizePoint(p, 1.0); q = normalizePoint(q, 1.0); var dotp = p[0] * q[0] + p[1] * q[1]; // score is constructed so that +1, -1 and 0 are all scored 0, any other angle // is scored higher. - var score = 2.0 * Math.min(Math.abs(dotp - 1.0), Math.min(Math.abs(dotp), Math.abs(dotp + 1))); - return score; + return 2.0 * Math.min(Math.abs(dotp - 1.0), Math.min(Math.abs(dotp), Math.abs(dotp + 1))); } function subtractPoints(a, b) { @@ -113,13 +106,13 @@ iD.actions.Orthogonalize = function(wayId, projection) { } function addPoints(a, b) { - return [a[0]+b[0],a[1]+b[1]]; + return [a[0] + b[0], a[1] + b[1]]; } function normalizePoint(point, thickness) { - var vector = [0,0]; + var vector = [0, 0]; var length = Math.sqrt(point[0] * point[0] + point[1] * point[1]); - if(length != 0){ + if (length !== 0) { vector[0] = point[0] / length; vector[1] = point[1] / length; } From 7036758833aedff52e37af03753132d298a9cbda Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 4 Feb 2013 09:20:56 -0800 Subject: [PATCH 054/332] Fix directory --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5a4bdcca8..952266aa2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -71,7 +71,7 @@ them. So let's say you've changed `js/ui/confirm.js`. -1. Run `jshint src` to make sure your code is clean +1. Run `jshint js/id` to make sure your code is clean 2. Run tests with `npm test` 3. Commit your changes with an informative commit message 4. [Submit a pull request](https://help.github.com/articles/using-pull-requests) to the `systemed/iD` project. From 4ee620cb09835425945a1076b18abc10313b63ac Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 4 Feb 2013 12:23:46 -0500 Subject: [PATCH 055/332] Namespace selectors to iD-owned elements --- js/id/behavior/drag_midpoint.js | 2 +- js/id/renderer/map.js | 1 + js/id/svg/labels.js | 3 +-- js/id/ui/commit.js | 9 +++++---- js/id/ui/layerswitcher.js | 11 ++++++----- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/js/id/behavior/drag_midpoint.js b/js/id/behavior/drag_midpoint.js index 42099963c..4e39be2c6 100644 --- a/js/id/behavior/drag_midpoint.js +++ b/js/id/behavior/drag_midpoint.js @@ -9,7 +9,7 @@ iD.behavior.DragMidpoint = function(context) { context.perform(iD.actions.AddMidpoint(d, node)); - var vertex = d3.selectAll('.vertex') + var vertex = context.surface().selectAll('.vertex') .filter(function(data) { return data.id === node.id; }); behavior.target(vertex.node(), vertex.datum()); diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index b2561f21f..19354d7f3 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -49,6 +49,7 @@ iD.Map = function(context) { map.size(selection.size()); map.surface = surface; + map.tilesurface = tilegroup; supersurface .call(tail); diff --git a/js/id/svg/labels.js b/js/id/svg/labels.js index eb4297bca..551c29e81 100644 --- a/js/id/svg/labels.js +++ b/js/id/svg/labels.js @@ -226,7 +226,7 @@ iD.svg.Labels = function(projection) { d3.select(surface.node().parentNode) .on('mousemove.hidelabels', hideOnMouseover); - var hidePoints = !d3.select('.node.point').node(); + var hidePoints = !surface.select('.node.point').node(); var labelable = [], i, k, entity; for (i = 0; i < label_stack.length; i++) labelable.push([]); @@ -254,7 +254,6 @@ iD.svg.Labels = function(projection) { } } - var positions = { point: [], line: [], diff --git a/js/id/ui/commit.js b/js/id/ui/commit.js index 09bb99404..8d724a732 100644 --- a/js/id/ui/commit.js +++ b/js/id/ui/commit.js @@ -35,11 +35,12 @@ iD.ui.commit = function(context) { // Comment Box var comment_section = body.append('div').attr('class','modal-section fillD'); - comment_section.append('textarea') + var commentField = comment_section.append('textarea') .attr('class', 'changeset-comment') .attr('placeholder', 'Brief Description of your contributions') - .property('value', context.storage('comment') || '') - .node().select(); + .property('value', context.storage('comment') || ''); + + commentField.node().select(); var commit_info = comment_section @@ -73,7 +74,7 @@ iD.ui.commit = function(context) { .append('button') .attr('class', 'save action col6 button') .on('click.save', function() { - var comment = d3.select('textarea.changeset-comment').node().value; + var comment = commentField.node().value; localStorage.comment = comment; event.save({ comment: comment diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index 5c4835757..9e5e4dd4f 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -61,9 +61,10 @@ iD.ui.layerswitcher = function(context) { opa.append('h4').text(t('layerswitcher.layers')); - opa.append('ul') - .attr('class', 'opacity-options') - .selectAll('div.opacity') + var opacityList = opa.append('ul') + .attr('class', 'opacity-options'); + + opacityList.selectAll('div.opacity') .data(opacities) .enter() .append('li') @@ -71,11 +72,11 @@ iD.ui.layerswitcher = function(context) { return t('layerswitcher.percent_brightness', { opacity: (d * 100) }); }) .on('click.set-opacity', function(d) { - d3.select('#tile-g') + context.map().tilesurface .transition() .style('opacity', d) .attr('data-opacity', d); - d3.selectAll('.opacity-options li') + opacityList.selectAll('li') .classed('selected', false); d3.select(this) .classed('selected', true); From 8d90fb777b29b6d6b49afe7aea48382639eb95d2 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 4 Feb 2013 12:32:14 -0500 Subject: [PATCH 056/332] Purge a few more uses of d3.select, refs #595 --- js/id/ui.js | 8 +++++--- js/id/ui/geocoder.js | 7 +++---- js/id/ui/save.js | 2 ++ 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index 87cc3745a..69f20c2fd 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -140,8 +140,10 @@ iD.ui = function (context) { var about = container.append('div') .attr('class','col12 about-block fillD pad1'); - about.append('div') - .attr('class', 'user-container') + var userContainer = about.append('div') + .attr('class', 'user-container'); + + userContainer .append('div') .attr('class', 'hello'); @@ -243,7 +245,7 @@ iD.ui = function (context) { map.centerZoom([-77.02271, 38.90085], 20); } - d3.select('.user-container').call(iD.ui.userpanel(connection) + userContainer.call(iD.ui.userpanel(connection) .on('logout.editor', connection.logout) .on('login.editor', connection.authenticate)); diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index 4aac10bf2..063e8b517 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -38,9 +38,8 @@ iD.ui.geocoder = function() { function setVisible(show) { button.classed('active', show); gcForm.classed('hide', !show); - var input_node = d3.select('.map-overlay input').node(); - if (show) input_node.focus(); - else input_node.blur(); + if (show) inputNode.node().focus(); + else inputNode.node().blur(); } var button = selection.append('button') @@ -51,7 +50,7 @@ iD.ui.geocoder = function() { var gcForm = selection.append('form'); - gcForm.attr('class','content fillD map-overlay hide') + var inputNode = gcForm.attr('class','content fillD map-overlay hide') .append('input') .attr({ type: 'text', placeholder: t('geocoder.find_a_place') }) .on('keydown', keydown); diff --git a/js/id/ui/save.js b/js/id/ui/save.js index ba217adcf..773ae4d50 100644 --- a/js/id/ui/save.js +++ b/js/id/ui/save.js @@ -14,6 +14,7 @@ iD.ui.save = function(context) { .on('click', function() { function commit(e) { + d3.select('.shaded').remove(); var l = iD.ui.loading(t('uploading_changes'), true); connection.putChangeset(history.changes(), e.comment, history.imagery_used(), function(err, changeset_id) { @@ -40,6 +41,7 @@ iD.ui.save = function(context) { })); } }); + } if (history.hasChanges()) { From 4bfc91cc8508f7ba7fb248b4e5ecdbd1e1be490e Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 4 Feb 2013 12:34:11 -0500 Subject: [PATCH 057/332] Fix dragging cursor off of edge while drawing --- js/id/behavior/draw.js | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/js/id/behavior/draw.js b/js/id/behavior/draw.js index 625fc04a4..6536b590f 100644 --- a/js/id/behavior/draw.js +++ b/js/id/behavior/draw.js @@ -1,8 +1,7 @@ iD.behavior.Draw = function(context) { var event = d3.dispatch('move', 'click', 'clickWay', 'clickNode', 'clickMidpoint', 'undo', 'cancel', 'finish'), keybinding = d3.keybinding('draw'), - hover = iD.behavior.Hover(), - down; + hover = iD.behavior.Hover(); function datum() { if (d3.event.altKey) { @@ -13,17 +12,17 @@ iD.behavior.Draw = function(context) { } function mousedown() { - down = true; - } + var selection = d3.select(this); + selection.on('mousemove.draw', null); - function mouseup() { - down = false; + d3.select(window) + .on('mouseup.draw', function() { + selection.on('mousemove.draw', mousemove); + }); } function mousemove() { - if (!down) { - event.move(datum()); - } + event.move(datum()); } function click() { @@ -81,7 +80,6 @@ iD.behavior.Draw = function(context) { selection .on('mousedown.draw', mousedown) - .on('mouseup.draw', mouseup) .on('mousemove.draw', mousemove) .on('click.draw', click); @@ -98,7 +96,6 @@ iD.behavior.Draw = function(context) { selection .on('mousedown.draw', null) - .on('mouseup.draw', null) .on('mousemove.draw', null) .on('click.draw', null); From 59b6acdb08b0de54099c0837225e27ac4adc2c63 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 4 Feb 2013 12:52:58 -0500 Subject: [PATCH 058/332] Add notes on reporting issues --- CONTRIBUTING.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 952266aa2..09dbab7ba 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,6 +3,30 @@ Thinking of contributing to iD? High five! Here are some basics for our habits so that you can write code that fits in perfectly. +## Reporting Issues + +We'd love to hear what you think about iD, about any specific problems or +concerns you have. Here's a quick list of things to consider: + +Please [search for your issue before filing it: many bugs and improvements have already been reported](https://github.com/systemed/iD/issues/search?q=) + +To report a bug: + +* Write specifically what browser (type and version, like Firefox 22), OS, and browser extensions you have installed +* Write steps to replicate the error: when did it happen? What did you expect to happen? What happened instead? +* Please keep bug reports professional and straightforward: trust us, we share your dismay at software breaking. +* If you can, [enable web developer extensions](http://macwright.org/enable-web-developer-extensions/) and report the + Javascript error message. + +When in doubt, be over-descriptive of the bug and how you discovered it. + +To request a feature: + +* If the feature is available in some other software (like Potlatch), link to that software and the implementation. + We care about prior art. +* Understand that iD is meant to be a simple editor and doesn't aim to be + as complete or complicated as JOSM or similar. + ## Javascript We use the [Airbnb style for Javascript](https://github.com/airbnb/javascript) with From 2ee0cac3752ea514d19f592c0d87db2c98cb9d7f Mon Sep 17 00:00:00 2001 From: Saman Bemel-Benrud Date: Mon, 4 Feb 2013 13:15:07 -0500 Subject: [PATCH 059/332] fix intro modal position. --- css/app.css | 1 - 1 file changed, 1 deletion(-) diff --git a/css/app.css b/css/app.css index 7a0751d02..e660d5989 100644 --- a/css/app.css +++ b/css/app.css @@ -1060,7 +1060,6 @@ div.typeahead a:first-child { .modal-splash { width: 33.3333%; - left: 33.3333%; } .logo { From e4a8fbd0f6de74edbca05734d142ab368a2b9750 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 4 Feb 2013 13:34:14 -0500 Subject: [PATCH 060/332] Eliminate flickering --- js/id/behavior/draw_way.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 1cc52ab1f..a0a6aa308 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -17,7 +17,13 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { var loc = context.map().mouseCoordinates(); if (datum.type === 'node') { - loc = datum.loc; + if (datum.id === nodeId) { + context.surface().selectAll('.way, .node') + .filter(function (d) { return d.id === nodeId; }) + .classed('active', true); + } else { + loc = datum.loc; + } } else if (datum.type === 'midpoint' || datum.type === 'way') { var way = datum.type === 'way' ? datum : From c2ddf67cc0639661d2534ab9c9c90c2b0374a03b Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 4 Feb 2013 11:20:23 -0800 Subject: [PATCH 061/332] Show appropriate tooltips for disabled undo/redo buttons Implementing this cross-browser requires using a `.disabled` class rather than the `disabled` property. No browsers except Opera dispatch mouse events on disabled buttons. Fixes #620. --- css/app.css | 11 ++++++++--- js/id/ui.js | 12 ++++++------ locale/en.js | 3 +++ 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/css/app.css b/css/app.css index e660d5989..a43133f3d 100644 --- a/css/app.css +++ b/css/app.css @@ -273,7 +273,12 @@ button.active { cursor:url(../img/cursor-pointing.png) 6 1, auto; } -button.active:not([disabled]) { +button.disabled { + background: #6c6c6c; + cursor: auto; +} + +button.active:not([disabled]):not(.disabled) { background: #6bc641; } @@ -450,8 +455,8 @@ button[disabled] .icon.browse { background-position: 0px -40px;} button[disabled] .icon.add-point { background-position: -20px -40px;} button[disabled] .icon.add-line { background-position: -40px -40px;} button[disabled] .icon.add-area { background-position: -60px -40px;} -button[disabled] .icon.undo { background-position: -80px -40px;} -button[disabled] .icon.redo { background-position: -100px -40px;} +button.disabled .icon.undo { background-position: -80px -40px;} +button.disabled .icon.redo { background-position: -100px -40px;} button[disabled] .apply.icon { background-position: -120px -40px;} button[disabled] .save.icon { background-position: -140px -40px;} button[disabled] .close.icon { background-position: -160px -40px;} diff --git a/js/id/ui.js b/js/id/ui.js index 69f20c2fd..f8c58903f 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -91,14 +91,14 @@ iD.ui = function (context) { undo_buttons.append('button') .attr({ id: 'undo', 'class': 'col6' }) - .property('disabled', true) + .classed('disabled', true) .html("") .on('click.editor', history.undo) .call(undo_tooltip); undo_buttons.append('button') .attr({ id: 'redo', 'class': 'col6' }) - .property('disabled', true) + .classed('disabled', true) .html("") .on('click.editor', history.redo) .call(undo_tooltip); @@ -209,13 +209,13 @@ iD.ui = function (context) { } limiter.select('#undo') - .property('disabled', !undo) - .attr('data-original-title', hintprefix('⌘ + Z', undo)) + .classed('disabled', !undo) + .attr('data-original-title', hintprefix('⌘ + Z', undo || t('nothing_to_undo'))) .call(refreshTooltip); limiter.select('#redo') - .property('disabled', !redo) - .attr('data-original-title', hintprefix('⌘ + ⇧ + Z', redo)) + .classed('disabled', !redo) + .attr('data-original-title', hintprefix('⌘ + ⇧ + Z', redo || t('nothing_to_redo'))) .call(refreshTooltip); }); diff --git a/locale/en.js b/locale/en.js index 31d6a13de..b446c4ae4 100644 --- a/locale/en.js +++ b/locale/en.js @@ -141,6 +141,9 @@ locale.en = { "zoom-in": "Zoom In", "zoom-out": "Zoom Out", + nothing_to_undo: "Nothing to undo.", + nothing_to_redo: "Nothing to redo.", + "browser_notice": "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", "layer_settings": "Layer Settings", From 6e74456b3134c49e72391fa6b3133dcf6ab07d93 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 4 Feb 2013 14:57:43 -0500 Subject: [PATCH 062/332] Fix dragging around resized edges. Fixes #552 --- js/id/behavior/drag_node.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/js/id/behavior/drag_node.js b/js/id/behavior/drag_node.js index b62162e94..a0cb06e75 100644 --- a/js/id/behavior/drag_node.js +++ b/js/id/behavior/drag_node.js @@ -1,8 +1,7 @@ iD.behavior.DragNode = function(context) { - var size = context.map().size(), - nudgeInterval; + var nudgeInterval; - function edge(point) { + function edge(point, size) { var pad = [30, 100, 30, 100]; if (point[0] > size[0] - pad[0]) return [-10, 0]; else if (point[0] < pad[2]) return [10, 0]; @@ -39,7 +38,7 @@ iD.behavior.DragNode = function(context) { .on('move', function(entity) { d3.event.sourceEvent.stopPropagation(); - var nudge = edge(d3.event.point); + var nudge = edge(d3.event.point, context.map().size()); if (nudge) startNudge(nudge); else stopNudge(); From ad5c819f49617bf5626045978af43e4c9ffeb39c Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 4 Feb 2013 15:01:18 -0500 Subject: [PATCH 063/332] Do not use keywords --- locale/en.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locale/en.js b/locale/en.js index b446c4ae4..d99e88908 100644 --- a/locale/en.js +++ b/locale/en.js @@ -44,7 +44,7 @@ locale.en = { area: "Started an area." } }, - continue: { + 'continue': { annotation: { line: "Continued a line.", area: "Continued an area." @@ -74,7 +74,7 @@ locale.en = { area: "Squared the corners of an area." } }, - delete: { + 'delete': { title: "Delete", description: "Remove this from the map.", key: "⌫", From 0450e57acfc2ad217f8b946660f18586540a1c15 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 4 Feb 2013 12:05:41 -0800 Subject: [PATCH 064/332] Layers -> Background (#525) --- js/id/ui/layerswitcher.js | 4 ++-- locale/en.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index 9e5e4dd4f..ecdbab0f9 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -35,7 +35,7 @@ iD.ui.layerswitcher = function(context) { .append('button') .attr('tabindex', -1) .attr('class', 'fillD') - .attr('title', t('layer_settings')) + .attr('title', t('layerswitcher.description')) .html("") .on('click.layerswitcher-toggle', toggle); @@ -59,7 +59,7 @@ iD.ui.layerswitcher = function(context) { .append('div') .attr('class', 'opacity-options-wrapper'); - opa.append('h4').text(t('layerswitcher.layers')); + opa.append('h4').text(t('layerswitcher.title')); var opacityList = opa.append('ul') .attr('class', 'opacity-options'); diff --git a/locale/en.js b/locale/en.js index d99e88908..88861bdee 100644 --- a/locale/en.js +++ b/locale/en.js @@ -146,8 +146,6 @@ locale.en = { "browser_notice": "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", - "layer_settings": "Layer Settings", - inspector: { no_documentation_combination: "This is no documentation available for this tag combination", no_documentation_key: "This is no documentation available for this key", @@ -168,8 +166,10 @@ locale.en = { "description": "Description", "logout": "logout", + layerswitcher: { - layers: "Layers", + title: "Background", + description: "Background Settings", percent_brightness: "{opacity}% brightness" } }; From 7ddfcaed3962536ce11308aaf3a1679745fe672d Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 4 Feb 2013 12:08:30 -0800 Subject: [PATCH 065/332] i18n --- js/id/ui/layerswitcher.js | 4 ++-- locale/en.js | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index ecdbab0f9..9eb063517 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -151,7 +151,7 @@ iD.ui.layerswitcher = function(context) { } adjustments.append('a') - .text('Fix misalignment') + .text(t('layerswitcher.fix_misalignment')) .attr('href', '#') .classed('alignment-toggle', true) .classed('expanded', false) @@ -179,7 +179,7 @@ iD.ui.layerswitcher = function(context) { .on('click', nudge); nudge_container.append('button') - .text('reset') + .text(t('layerswitcher.reset')) .attr('class', 'reset') .on('click', function() { context.background().offset([0, 0]); diff --git a/locale/en.js b/locale/en.js index 88861bdee..0fa3c1ea2 100644 --- a/locale/en.js +++ b/locale/en.js @@ -170,6 +170,8 @@ locale.en = { layerswitcher: { title: "Background", description: "Background Settings", - percent_brightness: "{opacity}% brightness" + percent_brightness: "{opacity}% brightness", + fix_misalignment: "Fix misalignment", + reset: "reset" } }; From 940a7a9a6af301c38fdb949fcf474547899da4f8 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 4 Feb 2013 12:18:11 -0800 Subject: [PATCH 066/332] Revise README --- README.md | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 08c6764c0..8cf1daeeb 100644 --- a/README.md +++ b/README.md @@ -2,30 +2,28 @@ [![Build Status](https://secure.travis-ci.org/systemed/iD.png)](https://travis-ci.org/systemed/iD) -[![](http://ideditor.com/img/editor.png)](http://geowiki.com/iD/) - -[Try the online demo of the most recent code.](http://geowiki.com/iD/) and -[open issues for bugs and ideas!](https://github.com/systemed/iD/issues) +[![](http://ideditor.com/img/editor.png)](http://ideditor.com/) ## Basics * iD is a JavaScript [OpenStreetMap](http://www.openstreetmap.org/) editor. * It's intentionally simple. It lets you do the most basic tasks while not breaking other people's data. -* We support modern browsers. Data is rendered with [d3](http://d3js.org/). +* It supports modern browsers. Data is rendered with [d3](http://d3js.org/). ## Participate! +* [Try out the latest stable release](http://geowiki.com/iD/) * [Read up on Contributing and the code style of iD](CONTRIBUTING.md) -* See [open issues in the issue tracker if you're looking for something to do](https://github.com/systemed/iD/issues?state=open) +* See [open issues in the issue tracker](https://github.com/systemed/iD/issues?state=open) if you're looking for something to do -To run the code locally, just fork this project and run it from a local webserver. -With a Mac, you can enable Web Sharing and drop this in your website directory. - -If you have Python handy, just `cd` into `iD` and run +To run the current development version, fork this project and serve it locally. +If you have Python handy, just `cd` into the project root directory and run python -m SimpleHTTPServer +Or, with a Mac, you can enable Web Sharing and clone iD into your website directory. + Come on in, the water's lovely. More help? Ping RichardF, tmcw, or jfire on IRC (`irc.oftc.net`, in `#osm-dev` or `#osm`), on the OSM mailing lists or at richard@systemeD.net. From 6dd1bebe89b8d7456114ca9c8e10eaff47a16fd8 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 4 Feb 2013 12:26:57 -0800 Subject: [PATCH 067/332] Fix drawing in Firefox (fixes #628) --- js/id/behavior/draw_way.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index a0a6aa308..717218c8d 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -165,5 +165,5 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { context.enter(iD.modes.Browse(context)); }; - return d3.rebind(drawWay, event, 'on'); + return drawWay; }; From 83e241083d5cd5d678a56b874c8b13711327065f Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 4 Feb 2013 15:30:59 -0500 Subject: [PATCH 068/332] Remove event --- js/id/behavior/draw.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/js/id/behavior/draw.js b/js/id/behavior/draw.js index 6536b590f..eb207eeb5 100644 --- a/js/id/behavior/draw.js +++ b/js/id/behavior/draw.js @@ -99,6 +99,8 @@ iD.behavior.Draw = function(context) { .on('mousemove.draw', null) .on('click.draw', null); + d3.select(window).on('mouseup.draw', null); + d3.select(document) .call(keybinding.off) .on('keydown.draw', null) From 3449a680a75cea1ea45e360a4bb61d97926b1379 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 4 Feb 2013 16:02:34 -0500 Subject: [PATCH 069/332] Add tag deprecation action and data, not yet integrated. --- data/data.js | 1 + data/deprecated.js | 112 ++++++++++++++++++++++++++++ index.html | 3 + js/id/actions/deprecate_tags.js | 36 +++++++++ test/index.html | 5 ++ test/spec/actions/deprecate_tags.js | 40 ++++++++++ 6 files changed, 197 insertions(+) create mode 100644 data/data.js create mode 100644 data/deprecated.js create mode 100644 js/id/actions/deprecate_tags.js create mode 100644 test/spec/actions/deprecate_tags.js diff --git a/data/data.js b/data/data.js new file mode 100644 index 000000000..279b1ea86 --- /dev/null +++ b/data/data.js @@ -0,0 +1 @@ +iD.data = {}; diff --git a/data/deprecated.js b/data/deprecated.js new file mode 100644 index 000000000..53457c9f3 --- /dev/null +++ b/data/deprecated.js @@ -0,0 +1,112 @@ +// from http://wiki.openstreetmap.org/wiki/Deprecated_features +// TODO: deal with deprecated 'class' tag +// does not deal with landuse=wood because of indecision +// we will not care about http://taginfo.openstreetmap.org/tags/bicycle_parking=sheffield +iD.data.deprecated = [ + { + old: { barrier: 'wire_fence' }, + replace: { + barrier: 'fence', + fence_type: 'chain' + } + }, + { + old: { barrier: 'wood_fence' }, + replace: { + barrier: 'fence', + fence_type: 'wood' + } + }, + { + old: { highway: 'ford' }, + replace: { + ford: 'yes' + } + }, + { + old: { highway: 'ford' }, + replace: { + ford: 'yes' + } + }, + { + old: { highway: 'ford' }, + replace: { + ford: 'yes' + } + }, + { + old: { highway: 'stile' }, + replace: { + barrier: 'stile' + } + }, + { + old: { highway: 'incline' }, + replace: { + highway: 'road', + incline: 'up' + } + }, + { + old: { highway: 'incline_steep' }, + replace: { + highway: 'road', + incline: 'up' + } + }, + { + old: { highway: 'unsurfaced' }, + replace: { + highway: 'road', + incline: 'unpaved' + } + }, + { + old: { highway: 'unsurfaced' }, + replace: { + highway: 'road', + incline: 'unpaved' + } + }, + { + old: { landuse: 'wood' }, + replace: { + highway: 'road', + incline: 'unpaved' + } + }, + { + old: { natural: 'marsh' }, + replace: { + natural: 'wetland', + wetland: 'marsh' + } + }, + { + old: { shop: 'organic' }, + replace: { + shop: 'supermarket', + organic: 'only' + } + }, + { + old: { power_source: '*' }, + replace: { + 'generator:source': '$1' + } + }, + { + old: { power_rating: '*' }, + replace: { + 'generator:output': '$1' + } + }, + { + old: { bicycle_parking: 'organic' }, + replace: { + shop: 'supermarket', + organic: 'only' + } + } +]; diff --git a/index.html b/index.html index 83ae066eb..c03ac29ae 100644 --- a/index.html +++ b/index.html @@ -134,6 +134,9 @@ + + +
+ @@ -130,6 +131,9 @@ + + + + diff --git a/test/spec/actions/deprecate_tags.js b/test/spec/actions/deprecate_tags.js new file mode 100644 index 000000000..4f202e97f --- /dev/null +++ b/test/spec/actions/deprecate_tags.js @@ -0,0 +1,40 @@ +describe('iD.actions.DeprecateTags', function () { + it('deprecates tags', function () { + var entity = iD.Entity({ tags: { barrier: 'wire_fence' } }), + graph = iD.actions.DeprecateTags(entity.id)(iD.Graph([entity])), + undeprecated = { + barrier: 'fence', + fence_type: 'chain' + }; + expect(graph.entity(entity.id).tags).to.eql(undeprecated); + }); + + it('does not overwrite explicit tags', function () { + var entity = iD.Entity({ tags: { barrier: 'wire_fence', fence_type: 'foo' } }), + graph = iD.actions.DeprecateTags(entity.id)(iD.Graph([entity])), + undeprecated = { + barrier: 'fence', + fence_type: 'foo' + }; + expect(graph.entity(entity.id).tags).to.eql(undeprecated); + }); + + it('leaves other tags alone', function () { + var entity = iD.Entity({ tags: { highway: 'ford', name: 'Foo' } }), + graph = iD.actions.DeprecateTags(entity.id)(iD.Graph([entity])), + undeprecated = { + ford: 'yes', + name: 'Foo' + }; + expect(graph.entity(entity.id).tags).to.eql(undeprecated); + }); + + it('replaces keys', function () { + var entity = iD.Entity({ tags: { power_rating: '1 billion volts' } }), + graph = iD.actions.DeprecateTags(entity.id)(iD.Graph([entity])), + undeprecated = { + 'generator:output': '1 billion volts' + }; + expect(graph.entity(entity.id).tags).to.eql(undeprecated); + }); +}); From 3d8f2ffb84646a81fa8458c470e18322a01e97a4 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 4 Feb 2013 16:08:34 -0500 Subject: [PATCH 070/332] Consistify syntax --- js/id/graph/history.js | 24 ++++++++++++------------ js/id/modes/select.js | 12 ++++++------ js/id/ui.js | 16 ++++++++-------- js/id/ui/save.js | 4 +++- 4 files changed, 29 insertions(+), 27 deletions(-) diff --git a/js/id/graph/history.js b/js/id/graph/history.js index 7d1098027..a0930203c 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -27,11 +27,11 @@ iD.History = function() { } var history = { - graph: function () { + graph: function() { return stack[index].graph; }, - merge: function (entities) { + merge: function(entities) { for (var i = 0; i < stack.length; i++) { stack[i].graph.rebase(entities); } @@ -39,7 +39,7 @@ iD.History = function() { dispatch.change(); }, - perform: function () { + perform: function() { var previous = stack[index].graph; stack = stack.slice(0, index + 1); @@ -49,7 +49,7 @@ iD.History = function() { return change(previous); }, - replace: function () { + replace: function() { var previous = stack[index].graph; // assert(index == stack.length - 1) @@ -58,7 +58,7 @@ iD.History = function() { return change(previous); }, - pop: function () { + pop: function() { var previous = stack[index].graph; if (index > 0) { @@ -68,7 +68,7 @@ iD.History = function() { } }, - undo: function () { + undo: function() { var previous = stack[index].graph; // Pop to the first annotated state. @@ -87,7 +87,7 @@ iD.History = function() { return change(previous); }, - redo: function () { + redo: function() { var previous = stack[index].graph; while (index < stack.length - 1) { @@ -99,7 +99,7 @@ iD.History = function() { return change(previous); }, - undoAnnotation: function () { + undoAnnotation: function() { var i = index; while (i >= 0) { if (stack[i].annotation) return stack[i].annotation; @@ -107,7 +107,7 @@ iD.History = function() { } }, - redoAnnotation: function () { + redoAnnotation: function() { var i = index + 1; while (i <= stack.length - 1) { if (stack[i].annotation) return stack[i].annotation; @@ -115,13 +115,13 @@ iD.History = function() { } }, - difference: function () { + difference: function() { var base = stack[0].graph, head = stack[index].graph; return iD.Difference(base, head); }, - changes: function () { + changes: function() { var difference = history.difference(); return { modified: difference.modified(), @@ -145,7 +145,7 @@ iD.History = function() { undefined, 'Custom'); }, - reset: function () { + reset: function() { stack = [{graph: iD.Graph()}]; index = 0; dispatch.change(); diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 02db6a57f..ec6017ae3 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -39,11 +39,11 @@ iD.modes.Select = function(context, selection, initial) { }); var operations = d3.values(iD.operations) - .map(function (o) { return o(selection, context); }) - .filter(function (o) { return o.available(); }); + .map(function(o) { return o(selection, context); }) + .filter(function(o) { return o.available(); }); operations.forEach(function(operation) { - keybinding.on(operation.key, function () { + keybinding.on(operation.key, function() { if (operation.enabled()) { operation(); } @@ -87,7 +87,7 @@ iD.modes.Select = function(context, selection, initial) { context.history().on('change.select', function() { context.surface().call(radialMenu.close); - if (_.any(selection, function (id) { return !context.entity(id); })) { + if (_.any(selection, function(id) { return !context.entity(id); })) { // Exit mode if selected entity gets undone context.enter(iD.modes.Browse(context)); @@ -128,7 +128,7 @@ iD.modes.Select = function(context, selection, initial) { context.surface() .on('dblclick.select', dblclick) .selectAll("*") - .filter(function (d) { return d && selection.indexOf(d.id) >= 0; }) + .filter(function(d) { return d && selection.indexOf(d.id) >= 0; }) .classed('selected', true); radialMenu = iD.ui.RadialMenu(operations); @@ -144,7 +144,7 @@ iD.modes.Select = function(context, selection, initial) { } }; - mode.exit = function () { + mode.exit = function() { if (singular()) { changeTags(singular(), inspector.tags()); } diff --git a/js/id/ui.js b/js/id/ui.js index f8c58903f..03e5c6199 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -1,4 +1,4 @@ -iD.ui = function (context) { +iD.ui = function(context) { return function(container) { context.container(container); @@ -42,12 +42,12 @@ iD.ui = function (context) { .data(modes) .enter().append('button') .attr('tabindex', -1) - .attr('class', function (mode) { return mode.title + ' add-button col3'; }) + .attr('class', function(mode) { return mode.title + ' add-button col3'; }) .call(bootstrap.tooltip().placement('bottom').html(true)) - .attr('data-original-title', function (mode) { + .attr('data-original-title', function(mode) { return hintprefix(mode.key, mode.description); }) - .on('click.editor', function (mode) { context.enter(mode); }); + .on('click.editor', function(mode) { context.enter(mode); }); function disableTooHigh() { if (map.editable()) { @@ -74,14 +74,14 @@ iD.ui = function (context) { return d.id + ' icon icon-pre-text'; }); - buttons.append('span').attr('class', 'label').text(function (mode) { return mode.title; }); + buttons.append('span').attr('class', 'label').text(function(mode) { return mode.title; }); - context.on('enter.editor', function (entered) { - buttons.classed('active', function (mode) { return entered.button === mode.button; }); + context.on('enter.editor', function(entered) { + buttons.classed('active', function(mode) { return entered.button === mode.button; }); container.classed("mode-" + entered.id, true); }); - context.on('exit.editor', function (exited) { + context.on('exit.editor', function(exited) { container.classed("mode-" + exited.id, false); }); diff --git a/js/id/ui/save.js b/js/id/ui/save.js index 773ae4d50..fc593c463 100644 --- a/js/id/ui/save.js +++ b/js/id/ui/save.js @@ -17,7 +17,9 @@ iD.ui.save = function(context) { d3.select('.shaded').remove(); var l = iD.ui.loading(t('uploading_changes'), true); - connection.putChangeset(history.changes(), e.comment, history.imagery_used(), function(err, changeset_id) { + connection.putChangeset(history.changes(), + e.comment, + history.imagery_used(), function(err, changeset_id) { l.remove(); history.reset(); map.flush().redraw(); From d756e3c2a2b76139c397c76abf9bc8c19d20ae20 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 4 Feb 2013 16:30:19 -0500 Subject: [PATCH 071/332] Let ways be self-intersected --- js/id/behavior/draw_way.js | 45 ++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 717218c8d..32d05538f 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -1,25 +1,37 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { var way = context.entity(wayId), + isArea = way.geometry() === 'area', finished = false, annotation = t((way.isDegenerate() ? 'operations.start.annotation.' : 'operations.continue.annotation.') + context.geometry(wayId)), draw = iD.behavior.Draw(context); - var node = iD.Node({loc: context.map().mouseCoordinates()}), - nodeId = node.id; + var startIndex = typeof index === 'undefined' ? way.nodes.length - 1 : 0, + start = iD.Node({loc: context.graph().entity(way.nodes[startIndex]).loc}), + end = iD.Node({loc: context.map().mouseCoordinates()}), + segment = iD.Way({ + nodes: [start.id, end.id], + tags: _.clone(way.tags) + }); - context[way.isDegenerate() ? 'replace' : 'perform']( - iD.actions.AddEntity(node), - iD.actions.AddVertex(wayId, node.id, index)); + var f = context[way.isDegenerate() ? 'replace' : 'perform']; + if (isArea) { + f(iD.actions.AddEntity(end), + iD.actions.AddVertex(wayId, end.id, index)); + } else { + f(iD.actions.AddEntity(start), + iD.actions.AddEntity(end), + iD.actions.AddEntity(segment)); + } function move(datum) { var loc = context.map().mouseCoordinates(); if (datum.type === 'node') { - if (datum.id === nodeId) { + if (datum.id === end.id) { context.surface().selectAll('.way, .node') - .filter(function (d) { return d.id === nodeId; }) + .filter(function (d) { return d.id === end.id; }) .classed('active', true); } else { loc = datum.loc; @@ -31,7 +43,7 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { loc = iD.geo.chooseIndex(way, d3.mouse(context.surface().node()), context).loc; } - context.replace(iD.actions.MoveNode(nodeId, loc)); + context.replace(iD.actions.MoveNode(end.id, loc)); } function undone() { @@ -55,7 +67,7 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { surface.call(draw) .selectAll('.way, .node') - .filter(function (d) { return d.id === wayId || d.id === nodeId; }) + .filter(function (d) { return d.id === segment.id || d.id === start.id || d.id === end.id; }) .classed('active', true); context.history() @@ -85,9 +97,18 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { function ReplaceTemporaryNode(newNode) { return function(graph) { - return graph - .replace(way.removeNode(nodeId).addNode(newNode.id, index)) - .remove(node); + if (isArea) { + return graph + .replace(way.removeNode(end.id).addNode(newNode.id, index)) + .remove(end); + + } else { + return graph + .replace(graph.entity(wayId).addNode(newNode.id, index)) + .remove(end) + .remove(segment) + .remove(start); + } }; } From bd7f30273006de51326b58e5a6a76ed2ad0bfecc Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 4 Feb 2013 16:45:18 -0500 Subject: [PATCH 072/332] Show deprecated tags in save commit, validate them, add deprecatedTags to entity type. --- js/id/graph/entity.js | 27 ++++++++++++++++++++++----- js/id/ui/save.js | 1 + js/id/util.js | 4 ++-- js/id/validate.js | 8 ++++++++ locale/en.js | 3 ++- test/spec/graph/entity.js | 10 ++++++++++ test/spec/util.js | 4 ++-- 7 files changed, 47 insertions(+), 10 deletions(-) diff --git a/js/id/graph/entity.js b/js/id/graph/entity.js index bce9675ee..bd402552e 100644 --- a/js/id/graph/entity.js +++ b/js/id/graph/entity.js @@ -9,22 +9,22 @@ iD.Entity = function(attrs) { return (new iD.Entity()).initialize(arguments); }; -iD.Entity.id = function (type) { +iD.Entity.id = function(type) { return iD.Entity.id.fromOSM(type, iD.Entity.id.next[type]--); }; iD.Entity.id.next = {node: -1, way: -1, relation: -1}; -iD.Entity.id.fromOSM = function (type, id) { +iD.Entity.id.fromOSM = function(type, id) { return type[0] + id; }; -iD.Entity.id.toOSM = function (id) { +iD.Entity.id.toOSM = function(id) { return id.slice(1); }; // A function suitable for use as the second argument to d3.selection#data(). -iD.Entity.key = function (entity) { +iD.Entity.key = function(entity) { return entity.id; }; @@ -84,7 +84,7 @@ iD.Entity.prototype = { }, hasInterestingTags: function() { - return _.keys(this.tags).some(function (key) { + return _.keys(this.tags).some(function(key) { return key != "attribution" && key != "created_by" && key != "source" && @@ -93,6 +93,23 @@ iD.Entity.prototype = { }); }, + deprecatedTags: function() { + var tags = _.pairs(this.tags); + var deprecated = {}; + + iD.data.deprecated.forEach(function(d) { + var match = _.pairs(d.old)[0]; + tags.forEach(function(t) { + if (t[0] == match[0] && + (t[1] == match[1] || match[1] == '*')) { + deprecated[t[0]] = t[1]; + } + }); + }); + + return deprecated; + }, + friendlyName: function() { // Generate a string such as 'river' or 'Fred's House' for an entity. if (!this.tags || !Object.keys(this.tags).length) { return ''; } diff --git a/js/id/ui/save.js b/js/id/ui/save.js index fc593c463..4e3bf883c 100644 --- a/js/id/ui/save.js +++ b/js/id/ui/save.js @@ -17,6 +17,7 @@ iD.ui.save = function(context) { d3.select('.shaded').remove(); var l = iD.ui.loading(t('uploading_changes'), true); + connection.putChangeset(history.changes(), e.comment, history.imagery_used(), function(err, changeset_id) { diff --git a/js/id/util.js b/js/id/util.js index 645d15363..9ebd4d761 100644 --- a/js/id/util.js +++ b/js/id/util.js @@ -8,8 +8,8 @@ iD.util.trueObj = function(arr) { iD.util.tagText = function(entity) { return d3.entries(entity.tags).map(function(e) { - return e.key + ': ' + e.value; - }).join('\n'); + return e.key + '=' + e.value; + }).join(', '); }; iD.util.stringQs = function(str) { diff --git a/js/id/validate.js b/js/id/validate.js index 6329b626f..dacff7889 100644 --- a/js/id/validate.js +++ b/js/id/validate.js @@ -29,6 +29,14 @@ iD.validate = function(changes, graph) { warnings.push({ message: t('validations.untagged_line'), entity: change }); } + var deprecatedTags = change.deprecatedTags(); + if (!_.isEmpty(deprecatedTags)) { + warnings.push({ + message: t('validations.deprecated_tags', { + tags: iD.util.tagText({ tags: deprecatedTags }) + }), entity: change }); + } + if (change.geometry(graph) === 'area' && _.isEmpty(change.tags)) { warnings.push({ message: t('validations.untagged_area'), entity: change }); } diff --git a/locale/en.js b/locale/en.js index 0fa3c1ea2..8f00590c6 100644 --- a/locale/en.js +++ b/locale/en.js @@ -127,7 +127,8 @@ locale.en = { untagged_point: "Untagged point which is not part of a line or area", untagged_line: "Untagged line", untagged_area: "Untagged area", - tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area" + tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area", + deprecated_tags: "Deprecated tags: {tags}" }, "save": "Save", diff --git a/test/spec/graph/entity.js b/test/spec/graph/entity.js index f2ac85f17..f0b7e0b19 100644 --- a/test/spec/graph/entity.js +++ b/test/spec/graph/entity.js @@ -114,6 +114,16 @@ describe('iD.Entity', function () { }); }); + describe("#hasDeprecatedTags", function () { + it("returns false if entity has no tags", function () { + expect(iD.Entity().deprecatedTags()).to.eql({}); + }); + + it("returns true if entity has deprecated tags", function () { + expect(iD.Entity({ tags: { barrier: 'wire_fence' } }).deprecatedTags()).to.eql({ barrier: 'wire_fence' }); + }); + }); + describe("#hasInterestingTags", function () { it("returns false if the entity has no tags", function () { expect(iD.Entity().hasInterestingTags()).to.equal(false); diff --git a/test/spec/util.js b/test/spec/util.js index de3fab735..d866a7b39 100644 --- a/test/spec/util.js +++ b/test/spec/util.js @@ -6,8 +6,8 @@ describe('iD.Util', function() { it('.tagText', function() { expect(iD.util.tagText({})).to.eql(''); - expect(iD.util.tagText({tags:{foo:'bar'}})).to.eql('foo: bar'); - expect(iD.util.tagText({tags:{foo:'bar',two:'three'}})).to.eql('foo: bar\ntwo: three'); + expect(iD.util.tagText({tags:{foo:'bar'}})).to.eql('foo=bar'); + expect(iD.util.tagText({tags:{foo:'bar',two:'three'}})).to.eql('foo=bar, two=three'); }); it('.stringQs', function() { From 9ec7491645dd5977646e6b23fef64074f64e4097 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 4 Feb 2013 16:45:34 -0500 Subject: [PATCH 073/332] Fix baseline shifting for opera --- css/map.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/css/map.css b/css/map.css index ab2a352c9..f1998277c 100644 --- a/css/map.css +++ b/css/map.css @@ -620,7 +620,7 @@ text.pointlabel { } .pathlabel .textpath { - dominant-baseline: middle; + baseline-shift: -33%; } .pointlabel-halo, From b7cfaf08da7db5507944d012c4d8a4998b1d90bb Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 4 Feb 2013 16:48:44 -0500 Subject: [PATCH 074/332] userDetails should handle errors properly --- js/id/connection.js | 1 + js/id/ui/userpanel.js | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/js/id/connection.js b/js/id/connection.js index 794232682..5f5f17482 100644 --- a/js/id/connection.js +++ b/js/id/connection.js @@ -208,6 +208,7 @@ iD.Connection = function(context) { function userDetails(callback) { function done(err, user_details) { + if (err) return callback(err); var u = user_details.getElementsByTagName('user')[0], img = u.getElementsByTagName('img'), image_url = ''; diff --git a/js/id/ui/userpanel.js b/js/id/ui/userpanel.js index 2b28411a6..fda2fc463 100644 --- a/js/id/ui/userpanel.js +++ b/js/id/ui/userpanel.js @@ -5,10 +5,12 @@ iD.ui.userpanel = function(connection) { function update() { if (connection.authenticated()) { selection.style('display', 'block'); - connection.userDetails(function(user_details) { + connection.userDetails(function(err, user_details) { selection.html(''); + if (err) return; + // Link var userLink = selection.append('a') .attr('href', connection.url() + '/user/' + From d0d1a16c0ac4ff8d419b51406a9a8026a91b1c7e Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 4 Feb 2013 17:04:33 -0500 Subject: [PATCH 075/332] Fix label halo sizing --- js/id/svg/labels.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/js/id/svg/labels.js b/js/id/svg/labels.js index 551c29e81..9b2cbd1e9 100644 --- a/js/id/svg/labels.js +++ b/js/id/svg/labels.js @@ -33,8 +33,11 @@ iD.svg.Labels = function(projection) { var font_sizes = label_stack.map(function(d) { var style = iD.util.getStyle('text.' + d[0] + '.tag-' + d[1]); var m = style && style.cssText.match("font-size: ([0-9]{1,2})px;"); - if (!m) return default_size; - return parseInt(m[1], 10); + if (m) return parseInt(m[1], 10); + style = iD.util.getStyle('text.' + d[0]); + m = style && style.cssText.match("font-size: ([0-9]{1,2})px;"); + if (m) return parseInt(m[1], 10); + return default_size; }); var pointOffsets = [ @@ -281,7 +284,7 @@ iD.svg.Labels = function(projection) { p = getAreaLabel(entity, width, font_size); } if (p) { - p.classes = entity.geometry(graph) + ' tag-' + label_stack[k].slice(1).join('-'); + p.classes = entity.geometry(graph) + ' tag-' + label_stack[k][1]; positions[entity.geometry(graph)].push(p); labelled[entity.geometry(graph)].push(entity); } From 1b70a68214c559b7c4d0bbde1f5cfbc8ba3adf85 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 4 Feb 2013 17:14:56 -0500 Subject: [PATCH 076/332] Revert "Don't snap to midpoints, snap to their parent way" This reverts commit 13b0b540a7cbc15d0c67f067f72a3f3f16092e62. Conflicts: js/id/behavior/draw_way.js --- js/id/behavior/draw_way.js | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 32d05538f..64ec15e51 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -28,19 +28,10 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { function move(datum) { var loc = context.map().mouseCoordinates(); - if (datum.type === 'node') { - if (datum.id === end.id) { - context.surface().selectAll('.way, .node') - .filter(function (d) { return d.id === end.id; }) - .classed('active', true); - } else { - loc = datum.loc; - } - } else if (datum.type === 'midpoint' || datum.type === 'way') { - var way = datum.type === 'way' ? - datum : - context.entity(datum.ways[0].id); - loc = iD.geo.chooseIndex(way, d3.mouse(context.surface().node()), context).loc; + if (datum.type === 'node' || datum.type === 'midpoint') { + loc = datum.loc; + } else if (datum.type === 'way') { + loc = iD.geo.chooseIndex(datum, d3.mouse(context.surface().node()), context).loc; } context.replace(iD.actions.MoveNode(end.id, loc)); From 3417a1639c1a3fd60c11a46782bb833e48ab3746 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 4 Feb 2013 17:16:14 -0500 Subject: [PATCH 077/332] Hide midpoints when drawing (no snapping) --- css/map.css | 8 ++++++++ js/id/behavior/draw_way.js | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/css/map.css b/css/map.css index f1998277c..63950671f 100644 --- a/css/map.css +++ b/css/map.css @@ -117,6 +117,14 @@ g.vertex.selected .shadow { /* midpoints */ +.mode-draw-area g.midpoint, +.mode-draw-line g.midpoint, +.mode-add-area g.midpoint, +.mode-add-line g.midpoint, +.mode-add-point g.midpoint { + display: none; +} + g.midpoint .fill { fill:#aaa; } diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 64ec15e51..fa2c24c87 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -28,7 +28,7 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { function move(datum) { var loc = context.map().mouseCoordinates(); - if (datum.type === 'node' || datum.type === 'midpoint') { + if (datum.type === 'node') { loc = datum.loc; } else if (datum.type === 'way') { loc = iD.geo.chooseIndex(datum, d3.mouse(context.surface().node()), context).loc; From 3e71dd56cd90915938f9d3fe433d53b69be8cd00 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 4 Feb 2013 17:20:06 -0500 Subject: [PATCH 078/332] Wipe out some tags entirely, refs #585 --- data/deprecated.js | 10 +++++++++- js/id/actions/deprecate_tags.js | 6 +++--- test/spec/actions/deprecate_tags.js | 7 +++++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/data/deprecated.js b/data/deprecated.js index 53457c9f3..7cc6904ed 100644 --- a/data/deprecated.js +++ b/data/deprecated.js @@ -108,5 +108,13 @@ iD.data.deprecated = [ shop: 'supermarket', organic: 'only' } - } + }, + // entirely discarded tags + { old: { 'tiger:upload_uuid': '*' } }, + { old: { 'tiger:tlid': '*' } }, + { old: { 'tiger:source': '*' } }, + { old: { 'tiger:separated': '*' } }, + { old: { 'geobase:datasetName': '*' } }, + { old: { 'geobase:uuid': '*' } }, + { old: { 'sub_sea:type': '*' } } ]; diff --git a/js/id/actions/deprecate_tags.js b/js/id/actions/deprecate_tags.js index 8f7a56404..68b9f9d2e 100644 --- a/js/id/actions/deprecate_tags.js +++ b/js/id/actions/deprecate_tags.js @@ -10,19 +10,19 @@ iD.actions.DeprecateTags = function(entityId) { rule = iD.data.deprecated[i]; var match = _.pairs(rule.old)[0], - replacements = _.pairs(rule.replace); + replacements = rule.replace ? _.pairs(rule.replace) : null; if (entity.tags[match[0]] && match[1] === '*') { var value = entity.tags[match[0]]; - if (!newtags[replacements[0][0]]) { + if (replacements && !newtags[replacements[0][0]]) { newtags[replacements[0][0]] = value; } delete newtags[match[0]]; change = true; } else if (entity.tags[match[0]] === match[1]) { - newtags = _.assign({}, rule.replace, _.omit(newtags, match[0])); + newtags = _.assign({}, rule.replace || {}, _.omit(newtags, match[0])); change = true; } } diff --git a/test/spec/actions/deprecate_tags.js b/test/spec/actions/deprecate_tags.js index 4f202e97f..61d1da4fb 100644 --- a/test/spec/actions/deprecate_tags.js +++ b/test/spec/actions/deprecate_tags.js @@ -29,6 +29,13 @@ describe('iD.actions.DeprecateTags', function () { expect(graph.entity(entity.id).tags).to.eql(undeprecated); }); + it('wipes out tags that should be entirely removed', function () { + var entity = iD.Entity({ tags: { 'tiger:source': 'foo' } }), + graph = iD.actions.DeprecateTags(entity.id)(iD.Graph([entity])), + undeprecated = { }; + expect(graph.entity(entity.id).tags).to.eql(undeprecated); + }); + it('replaces keys', function () { var entity = iD.Entity({ tags: { power_rating: '1 billion volts' } }), graph = iD.actions.DeprecateTags(entity.id)(iD.Graph([entity])), From 7e997af9b8391e7189b9eac023cbdea60ff758d6 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 4 Feb 2013 17:24:00 -0500 Subject: [PATCH 079/332] Hide midpoints when vertices hidden --- js/id/svg/midpoints.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/js/id/svg/midpoints.js b/js/id/svg/midpoints.js index 935f84719..c59ef0bca 100644 --- a/js/id/svg/midpoints.js +++ b/js/id/svg/midpoints.js @@ -2,6 +2,10 @@ iD.svg.Midpoints = function(projection) { return function drawMidpoints(surface, graph, entities, filter) { var midpoints = {}; + if (!surface.select('.layer-hit').select('g.vertex').node()) { + return surface.select('.layer-hit').selectAll('g.midpoint').remove(); + } + for (var i = 0; i < entities.length; i++) { if (entities[i].type !== 'way') continue; From d5937907a45f7c405a072e96ff5e39eb9c2b71e4 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 4 Feb 2013 17:39:16 -0500 Subject: [PATCH 080/332] Hide midpoints without breaking drawing I always forget d3 works this way. --- js/id/svg/midpoints.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/id/svg/midpoints.js b/js/id/svg/midpoints.js index c59ef0bca..96048b1ed 100644 --- a/js/id/svg/midpoints.js +++ b/js/id/svg/midpoints.js @@ -2,8 +2,8 @@ iD.svg.Midpoints = function(projection) { return function drawMidpoints(surface, graph, entities, filter) { var midpoints = {}; - if (!surface.select('.layer-hit').select('g.vertex').node()) { - return surface.select('.layer-hit').selectAll('g.midpoint').remove(); + if (!surface.select('.layer-hit.g.vertex').node()) { + return surface.select('.layer-hit.g.midpoint').remove(); } for (var i = 0; i < entities.length; i++) { From c8ab057f19b58e724fba124360c4d05840a28726 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 4 Feb 2013 18:26:45 -0500 Subject: [PATCH 081/332] Remove unused midpoint code --- js/id/behavior/add_way.js | 3 +-- js/id/behavior/draw.js | 5 +---- js/id/behavior/draw_way.js | 14 -------------- js/id/modes/add_area.js | 15 --------------- js/id/modes/add_line.js | 14 -------------- js/id/modes/add_point.js | 1 - 6 files changed, 2 insertions(+), 50 deletions(-) diff --git a/js/id/behavior/add_way.js b/js/id/behavior/add_way.js index 207c88566..782d7f183 100644 --- a/js/id/behavior/add_way.js +++ b/js/id/behavior/add_way.js @@ -1,12 +1,11 @@ iD.behavior.AddWay = function(context) { - var event = d3.dispatch('start', 'startFromWay', 'startFromNode', 'startFromMidpoint'), + var event = d3.dispatch('start', 'startFromWay', 'startFromNode') draw = iD.behavior.Draw(context); var addWay = function(surface) { draw.on('click', event.start) .on('clickWay', event.startFromWay) .on('clickNode', event.startFromNode) - .on('clickMidpoint', event.startFromMidpoint) .on('cancel', addWay.cancel) .on('finish', addWay.cancel); diff --git a/js/id/behavior/draw.js b/js/id/behavior/draw.js index eb207eeb5..656eb7988 100644 --- a/js/id/behavior/draw.js +++ b/js/id/behavior/draw.js @@ -1,5 +1,5 @@ iD.behavior.Draw = function(context) { - var event = d3.dispatch('move', 'click', 'clickWay', 'clickNode', 'clickMidpoint', 'undo', 'cancel', 'finish'), + var event = d3.dispatch('move', 'click', 'clickWay', 'clickNode', 'undo', 'cancel', 'finish'), keybinding = d3.keybinding('draw'), hover = iD.behavior.Hover(); @@ -34,9 +34,6 @@ iD.behavior.Draw = function(context) { } else if (d.type === 'node') { event.clickNode(d); - } else if (d.type === 'midpoint') { - event.clickMidpoint(d); - } else { event.click(context.map().mouseCoordinates()); } diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index fa2c24c87..6b648db0e 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -46,7 +46,6 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { .on('click', drawWay.add) .on('clickWay', drawWay.addWay) .on('clickNode', drawWay.addNode) - .on('clickMidpoint', drawWay.addMidpoint) .on('undo', context.undo) .on('cancel', drawWay.cancel) .on('finish', drawWay.finish); @@ -140,19 +139,6 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { context.enter(mode); }; - // Add a midpoint, connect the way to it, and continue drawing. - drawWay.addMidpoint = function(midpoint) { - var node = iD.Node(); - - context.perform( - iD.actions.AddMidpoint(midpoint, node), - ReplaceTemporaryNode(node), - annotation); - - finished = true; - context.enter(mode); - }; - // Finish the draw operation, removing the temporary node. If the way has enough // nodes to be valid, it's selected. Otherwise, return to browse mode. drawWay.finish = function() { diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js index 0a9496bb6..ae83423cb 100644 --- a/js/id/modes/add_area.js +++ b/js/id/modes/add_area.js @@ -11,7 +11,6 @@ iD.modes.AddArea = function(context) { .on('start', start) .on('startFromWay', startFromWay) .on('startFromNode', startFromNode) - .on('startFromMidpoint', startFromMidpoint), defaultTags = {area: 'yes'}; function start(loc) { @@ -55,20 +54,6 @@ iD.modes.AddArea = function(context) { context.enter(iD.modes.DrawArea(context, way.id, graph)); } - function startFromMidpoint(midpoint) { - var graph = context.graph(), - node = iD.Node(), - way = iD.Way({tags: defaultTags}); - - context.perform( - iD.actions.AddMidpoint(midpoint, node), - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id), - iD.actions.AddVertex(way.id, node.id)); - - context.enter(iD.modes.DrawArea(context, way.id, graph)); - } - mode.enter = function() { context.install(behavior); context.tail(t('modes.add_area.tail')); diff --git a/js/id/modes/add_line.js b/js/id/modes/add_line.js index 7845e6e6f..3d45df2a0 100644 --- a/js/id/modes/add_line.js +++ b/js/id/modes/add_line.js @@ -11,7 +11,6 @@ iD.modes.AddLine = function(context) { .on('start', start) .on('startFromWay', startFromWay) .on('startFromNode', startFromNode) - .on('startFromMidpoint', startFromMidpoint), defaultTags = {highway: 'residential'}; function start(loc) { @@ -63,19 +62,6 @@ iD.modes.AddLine = function(context) { } } - function startFromMidpoint(midpoint) { - var graph = context.graph(), - node = iD.Node(), - way = iD.Way({tags: defaultTags}); - - context.perform( - iD.actions.AddMidpoint(midpoint, node), - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id)); - - context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); - } - mode.enter = function() { context.install(behavior); context.tail(t('modes.add_line.tail')); diff --git a/js/id/modes/add_point.js b/js/id/modes/add_point.js index 5a24dad25..457ed87a4 100644 --- a/js/id/modes/add_point.js +++ b/js/id/modes/add_point.js @@ -10,7 +10,6 @@ iD.modes.AddPoint = function(context) { .on('click', add) .on('clickWay', addWay) .on('clickNode', addNode) - .on('clickMidpoint', addNode) .on('cancel', cancel) .on('finish', cancel); From ab7c1fa80aa41cee16ef29daed005c6b8171ff0f Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 4 Feb 2013 18:32:16 -0500 Subject: [PATCH 082/332] Fix midpoint hiding. really --- js/id/svg/midpoints.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/id/svg/midpoints.js b/js/id/svg/midpoints.js index 96048b1ed..1db622612 100644 --- a/js/id/svg/midpoints.js +++ b/js/id/svg/midpoints.js @@ -2,8 +2,8 @@ iD.svg.Midpoints = function(projection) { return function drawMidpoints(surface, graph, entities, filter) { var midpoints = {}; - if (!surface.select('.layer-hit.g.vertex').node()) { - return surface.select('.layer-hit.g.midpoint').remove(); + if (!surface.select('.layer-hit g.vertex').node()) { + return surface.selectAll('.layer-hit g.midpoint').remove(); } for (var i = 0; i < entities.length; i++) { From d3c7c4be4b1e04eb594d174ed0a1182707a06ee8 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 4 Feb 2013 18:48:49 -0500 Subject: [PATCH 083/332] Add imagery convert script, source, and output --- data/imagery.json | 490 ++++++++++++++++++++++++++++++++++++++++ data/imagery.xml | 231 +++++++++++++++++++ data/imagery_convert.js | 45 ++++ 3 files changed, 766 insertions(+) create mode 100644 data/imagery.json create mode 100644 data/imagery.xml create mode 100644 data/imagery_convert.js diff --git a/data/imagery.json b/data/imagery.json new file mode 100644 index 000000000..2055c1cf5 --- /dev/null +++ b/data/imagery.json @@ -0,0 +1,490 @@ +[ + { + "name": "Bing aerial imagery", + "url": "http://ecn.t0.tiles.virtualearth.net/tiles/a{q}uadkey.jpeg?g=587&mkt=en-gb&n=z", + "sourcetag": "Bing", + "logo": "bing_maps.png", + "logo_url": "http://www.bing.com/maps", + "terms_url": "http://opengeodata.org/microsoft-imagery-details" + }, + { + "name": "MapBox Satellite", + "url": "http://{t}.tiles.mapbox.com/v3/openstreetmap.map-4wvf9l0l/{z}/{x}/{y}.png", + "subdomains": [ + "a", + "b", + "c" + ] + }, + { + "name": "MapQuest Open Aerial", + "url": "http://oatile1.mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpg" + }, + { + "name": "OSM - Mapnik", + "url": "http://{t}.tile.openstreetmap.org/{z}/{x}/{y}.png", + "subdomains": [ + "a", + "b", + "c" + ] + }, + { + "name": "OSM - OpenCycleMap", + "url": "http://tile.opencyclemap.org/cycle/{z}/{x}/{y}.png" + }, + { + "name": "OSM - MapQuest", + "url": "http://otile1.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.jpg" + }, + { + "name": "OSM - Tiger Edited Map", + "url": "http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/{z}/{x}/{y}.png", + "extent": [ + 24.055, + -124.81, + 49.386, + -66.865 + ] + }, + { + "name": "OSM - Tiger Edited Map", + "url": "http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/{z}/{x}/{y}.png", + "extent": [ + 50.858, + -179.754, + 71.463, + -129.899 + ] + }, + { + "name": "OSM - Tiger Edited Map", + "url": "http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/{z}/{x}/{y}.png", + "extent": [ + 18.702, + -174.46, + 26.501, + -154.516 + ] + }, + { + "name": "OSM US TIGER 2012 Roads Overlay", + "url": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", + "subdomains": [ + "a", + "b", + "c" + ], + "extent": [ + 24.055, + -124.81, + 49.386, + -66.865 + ] + }, + { + "name": "OSM US TIGER 2012 Roads Overlay", + "url": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", + "subdomains": [ + "a", + "b", + "c" + ], + "extent": [ + 50.858, + -179.754, + 71.463, + -129.899 + ] + }, + { + "name": "OSM US TIGER 2012 Roads Overlay", + "url": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", + "subdomains": [ + "a", + "b", + "c" + ], + "extent": [ + 18.702, + -174.46, + 26.501, + -154.516 + ] + }, + { + "name": "OSM US USGS Topographic Maps", + "url": "http://{t}.tile.openstreetmap.us/usgs_scanned_topos/{z}/{x}/{y}.png", + "subdomains": [ + "a", + "b", + "c" + ], + "extent": [ + 24.005, + -125.991, + 50.009, + -65.988 + ] + }, + { + "name": "OSM US USGS Topographic Maps", + "url": "http://{t}.tile.openstreetmap.us/usgs_scanned_topos/{z}/{x}/{y}.png", + "subdomains": [ + "a", + "b", + "c" + ], + "extent": [ + 18.902, + -160.579, + 22.508, + -154.793 + ] + }, + { + "name": "OSM US USGS Topographic Maps", + "url": "http://{t}.tile.openstreetmap.us/usgs_scanned_topos/{z}/{x}/{y}.png", + "subdomains": [ + "a", + "b", + "c" + ], + "extent": [ + 51.255, + -178.001, + 71.999, + -130.004 + ] + }, + { + "name": "OSM US USGS Large Scale Aerial Imagery", + "url": "http://{t}.tile.openstreetmap.us/usgs_large_scale/{z}/{x}/{y}.jpg", + "subdomains": [ + "a", + "b", + "c" + ], + "extent": [ + 24.496, + -124.819, + 49.443, + -66.931 + ] + }, + { + "name": "British Columbia bc_mosaic", + "url": "http://{t}.imagery.paulnorman.ca/tiles/bc_mosaic/{z}/{x}/{y}.png", + "subdomains": [ + "a", + "b", + "c", + "d" + ], + "extent": [ + 48.995, + -123.441, + 50.426, + -121.346 + ], + "sourcetag": "bc_mosaic", + "terms_url": "http://imagery.paulnorman.ca/tiles/about.html" + }, + { + "name": "OS OpenData Streetview", + "url": "http://os.openstreetmap.org/sv/{z}/{x}/{y}.png", + "extent": [ + 49.86, + -8.72, + 60.92, + 1.84 + ], + "sourcetag": "OS_OpenData_StreetView" + }, + { + "name": "OS OpenData Locator", + "url": "http://tiles.itoworld.com/os_locator/{z}/{x}/{y}.png", + "extent": [ + 49.8, + -9, + 61.1, + 1.9 + ], + "sourcetag": "OS_OpenData_Locator" + }, + { + "name": "OS 1:25k historic (OSM)", + "url": "http://ooc.openstreetmap.org/os1/{z}/{x}/{y}.jpg", + "extent": [ + 49.8, + -9, + 61.1, + 1.9 + ], + "sourcetag": "OS 1:25k" + }, + { + "name": "OS 1:25k historic (NLS)", + "url": "http://geo.nls.uk/mapdata2/os/25000/{z}/{x}/{y}.png", + "extent": [ + 49.8, + -9, + 61.1, + 1.9 + ], + "sourcetag": "OS 1:25k", + "logo": "icons/logo_nls70-nq8.png", + "logo_url": "http://geo.nls.uk/maps/" + }, + { + "name": "OS 7th Series historic (OSM)", + "url": "http://ooc.openstreetmap.org/os7/{z}/{x}/{y}.jpg", + "extent": [ + 49.8, + -9, + 61.1, + 1.9 + ], + "sourcetag": "OS7" + }, + { + "name": "OS 7th Series historic (NLS)", + "url": "http://geo.nls.uk/mapdata2/os/seventh/{z}/{x}/{y}.png", + "extent": [ + 49.8, + -9, + 61.1, + 1.9 + ], + "sourcetag": "OS7", + "logo": "icons/logo_nls70-nq8.png", + "logo_url": "http://geo.nls.uk/maps/" + }, + { + "name": "OS New Popular Edition historic", + "url": "http://ooc.openstreetmap.org/npe/{z}/{x}/{y}.png", + "extent": [ + 49.8, + -5.8, + 55.8, + 1.9 + ], + "sourcetag": "NPE" + }, + { + "name": "OS Scottish Popular historic", + "url": "http://ooc.openstreetmap.org/npescotland/tiles/{z}/{x}/{y}.jpg", + "extent": [ + 54.5, + -7.8, + 61.1, + -1.1 + ], + "sourcetag": "NPE" + }, + { + "name": "Surrey aerial", + "url": "http://gravitystorm.dev.openstreetmap.org/surrey/{z}/{x}/{y}.png", + "extent": [ + 51.071, + -0.856, + 51.473, + 0.062 + ], + "sourcetag": "Surrey aerial" + }, + { + "name": "Haiti - GeoEye Jan 13", + "url": "http://gravitystorm.dev.openstreetmap.org/imagery/haiti/{z}/{x}/{y}.jpg", + "extent": [ + 17.95, + -74.5, + 20.12, + -71.58 + ], + "sourcetag": "Haiti GeoEye" + }, + { + "name": "Haiti - GeoEye Jan 13+", + "url": "http://maps.nypl.org/tilecache/1/geoeye/{z}/{x}/{y}.jpg", + "extent": [ + 17.95, + -74.5, + 20.12, + -71.58 + ], + "sourcetag": "Haiti GeoEye" + }, + { + "name": "Haiti - DigitalGlobe", + "url": "http://maps.nypl.org/tilecache/1/dg_crisis/{z}/{x}/{y}.jpg", + "extent": [ + 17.95, + -74.5, + 20.12, + -71.58 + ], + "sourcetag": "Haiti DigitalGlobe" + }, + { + "name": "Haiti - Street names", + "url": "http://hypercube.telascience.org/tiles/1.0.0/haiti-city/{z}/{x}/{y}.jpg", + "extent": [ + 17.95, + -74.5, + 20.12, + -71.58 + ], + "sourcetag": "Haiti streetnames" + }, + { + "name": "National Agriculture Imagery Program", + "url": "http://cube.telascience.org/tilecache/tilecache.py/NAIP_ALL/{z}/{x}/{y}.png", + "extent": [ + 24.2, + -125.8, + 49.5, + -62.3 + ], + "sourcetag": "NAIP" + }, + { + "name": "National Agriculture Imagery Program", + "url": "http://cube.telascience.org/tilecache/tilecache.py/NAIP_ALL/{z}/{x}/{y}.png", + "extent": [ + 55.3, + -168.5, + 71.5, + -140 + ], + "sourcetag": "NAIP" + }, + { + "name": "Ireland - NLS Historic Maps", + "url": "http://geo.nls.uk/maps/ireland/gsgs4136/{z}/{x}/{y}.png", + "extent": [ + 51.32, + -10.71, + 55.46, + -5.37 + ], + "sourcetag": "NLS Historic Maps", + "logo": "icons/logo_nls70-nq8.png", + "logo_url": "http://geo.nls.uk/maps/" + }, + { + "name": "Denmark - Fugro Aerial Imagery", + "url": "http://tile.openstreetmap.dk/fugro2005/{z}/{x}/{y}.jpg", + "extent": [ + 54.44, + 7.81, + 57.86, + 15.49 + ], + "sourcetag": "Fugro (2005)" + }, + { + "name": "Denmark - Stevns Kommune", + "url": "http://tile.openstreetmap.dk/stevns/2009/{z}/{x}/{y}.jpg", + "extent": [ + 55.23403, + 12.09144, + 55.43647, + 12.47712 + ], + "sourcetag": "Stevns Kommune (2009)" + }, + { + "name": "Austria - geoimage.at", + "url": "http://geoimage.openstreetmap.at/4d80de696cd562a63ce463a58a61488d/{z}/{x}/{y}.jpg", + "extent": [ + 46.33, + 9.36, + 49.09, + 17.28 + ], + "sourcetag": "geoimage.at" + }, + { + "name": "Russia - Kosmosnimki.ru IRS Satellite", + "url": "http://irs.gis-lab.info/?layers=irs&request=GetTile&z={z}&x={x}&y={y}", + "extent": [ + 40.96, + 19.02, + 70.48, + 77.34 + ], + "sourcetag": "Kosmosnimki.ru IRS" + }, + { + "name": "Belarus - Kosmosnimki.ru SPOT4 Satellite", + "url": "http://irs.gis-lab.info/?layers=spot&request=GetTile&z={z}&x={x}&y={y}", + "extent": [ + 51.25, + 23.16, + 56.19, + 32.83 + ], + "sourcetag": "Kosmosnimki.ru SPOT4" + }, + { + "name": "Australia - Geographic Reference Image", + "url": "http://agri.openstreetmap.org/{z}/{x}/{y}.png", + "extent": [ + -44, + 96, + -9, + 168 + ], + "sourcetag": "AGRI" + }, + { + "name": "Switzerland - Canton Aargau - AGIS 25cm 2011", + "url": "http://tiles.poole.ch/AGIS/OF2011/{z}/{x}/{y}.png", + "extent": [ + 47.13, + 7.69, + 47.63, + 8.48 + ], + "sourcetag": "AGIS OF2011" + }, + { + "name": "Switzerland - Canton Solothurn - SOGIS 2007", + "url": "http://mapproxy.sosm.ch:8080/tiles/sogis2007/EPSG900913/{z}/{x}/{y}.png?origin=nw", + "extent": [ + 47.06, + 7.33, + 47.5, + 8.04 + ], + "sourcetag": "Orthofoto 2007 WMS Solothurn" + }, + { + "name": "Poland - Media-Lab fleet GPS masstracks", + "url": "http://masstracks.media-lab.com.pl/{z}/{x}/{y}.png", + "extent": [ + 48.9, + 14, + 55, + 24.2 + ], + "sourcetag": "masstracks" + }, + { + "name": "South Africa - CD:NGI Aerial", + "url": "http://{t}.aerial.openstreetmap.org.za/ngi-aerial/{z}/{x}/{y}.jpg", + "subdomains": [ + "a", + "b", + "c" + ], + "extent": [ + -34.95, + 17.64, + -22.05, + 32.87 + ], + "sourcetag": "ngi-aerial" + } +] \ No newline at end of file diff --git a/data/imagery.xml b/data/imagery.xml new file mode 100644 index 000000000..c829ab141 --- /dev/null +++ b/data/imagery.xml @@ -0,0 +1,231 @@ + + + + Bing aerial imagery + http://ecn.t0.tiles.virtualearth.net/tiles/a$quadkey.jpeg?g=587&mkt=en-gb&n=z + microsoft + Bing + http://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial/0,0?zl=1&mapVersion=v1&key=Arzdiw4nlOJzRwOz__qailc8NiR31Tt51dN2D7cm57NrnceZnCpgOkmJhNpGoppU&include=ImageryProviders&output=xml + bing_maps.png + http://www.bing.com/maps + http://opengeodata.org/microsoft-imagery-details + yes + + + MapBox Satellite + http://${a|b|c}.tiles.mapbox.com/v3/openstreetmap.map-4wvf9l0l/$z/$x/$y.png + + + MapQuest Open Aerial + http://oatile1.mqcdn.com/tiles/1.0.0/sat/$z/$x/$y.jpg + + + OSM - Mapnik + http://${a|b|c}.tile.openstreetmap.org/$z/$x/$y.png + + + OSM - OpenCycleMap + http://tile.opencyclemap.org/cycle/$z/$x/$y.png + + + OSM - MapQuest + http://otile1.mqcdn.com/tiles/1.0.0/osm/$z/$x/$y.jpg + + + OSM - Tiger Edited Map + 900913 + http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/$z/$x/$y.png + + + OSM - Tiger Edited Map + 900913 + http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/$z/$x/$y.png + + + OSM - Tiger Edited Map + 900913 + http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/$z/$x/$y.png + + + OSM US TIGER 2012 Roads Overlay + 900913 + http://${a|b|c}.tile.openstreetmap.us/tiger2012_roads_expanded/$z/$x/$y.png + + + OSM US TIGER 2012 Roads Overlay + 900913 + http://${a|b|c}.tile.openstreetmap.us/tiger2012_roads_expanded/$z/$x/$y.png + + + OSM US TIGER 2012 Roads Overlay + 900913 + http://${a|b|c}.tile.openstreetmap.us/tiger2012_roads_expanded/$z/$x/$y.png + + + OSM US USGS Topographic Maps + 900913 + http://${a|b|c}.tile.openstreetmap.us/usgs_scanned_topos/$z/$x/$y.png + + + OSM US USGS Topographic Maps + 900913 + http://${a|b|c}.tile.openstreetmap.us/usgs_scanned_topos/$z/$x/$y.png + + + OSM US USGS Topographic Maps + 900913 + http://${a|b|c}.tile.openstreetmap.us/usgs_scanned_topos/$z/$x/$y.png + + + OSM US USGS Large Scale Aerial Imagery + 900913 + http://${a|b|c}.tile.openstreetmap.us/usgs_large_scale/$z/$x/$y.jpg + + + British Columbia bc_mosaic + 900913 + http://${a|b|c|d}.imagery.paulnorman.ca/tiles/bc_mosaic/$z/$x/$y.png + http://imagery.paulnorman.ca/tiles/about.html + bc_mosaic + + + OS OpenData Streetview + http://os.openstreetmap.org/sv/$z/$x/$y.png + OS_OpenData_StreetView + + + OS OpenData Locator + http://tiles.itoworld.com/os_locator/$z/$x/$y.png + OS_OpenData_Locator + source:name + + + OS 1:25k historic (OSM) + http://ooc.openstreetmap.org/os1/$z/$x/$y.jpg + OS 1:25k + + + OS 1:25k historic (NLS) + tms + http://geo.nls.uk/mapdata2/os/25000/$z/$x/$y.png + icons/logo_nls70-nq8.png + http://geo.nls.uk/maps/ + OS 1:25k + + + OS 7th Series historic (OSM) + http://ooc.openstreetmap.org/os7/$z/$x/$y.jpg + OS7 + + + OS 7th Series historic (NLS) + tms + http://geo.nls.uk/mapdata2/os/seventh/$z/$x/$y.png + icons/logo_nls70-nq8.png + http://geo.nls.uk/maps/ + OS7 + + + OS New Popular Edition historic + http://ooc.openstreetmap.org/npe/$z/$x/$y.png + NPE + + + OS Scottish Popular historic + http://ooc.openstreetmap.org/npescotland/tiles/$z/$x/$y.jpg + NPE + + + Surrey aerial + http://gravitystorm.dev.openstreetmap.org/surrey/$z/$x/$y.png + Surrey aerial + + + Haiti - GeoEye Jan 13 + http://gravitystorm.dev.openstreetmap.org/imagery/haiti/$z/$x/$y.jpg + Haiti GeoEye + + + Haiti - GeoEye Jan 13+ + http://maps.nypl.org/tilecache/1/geoeye/$z/$x/$y.jpg + Haiti GeoEye + + + Haiti - DigitalGlobe + http://maps.nypl.org/tilecache/1/dg_crisis/$z/$x/$y.jpg + Haiti DigitalGlobe + + + Haiti - Street names + http://hypercube.telascience.org/tiles/1.0.0/haiti-city/$z/$x/$y.jpg + Haiti streetnames + + + National Agriculture Imagery Program + http://cube.telascience.org/tilecache/tilecache.py/NAIP_ALL/$z/$x/$y.png + NAIP + + + National Agriculture Imagery Program + http://cube.telascience.org/tilecache/tilecache.py/NAIP_ALL/$z/$x/$y.png + NAIP + + + Ireland - NLS Historic Maps + tms + NLS Historic Maps + http://geo.nls.uk/maps/ireland/gsgs4136/$z/$x/$y.png + icons/logo_nls70-nq8.png + http://geo.nls.uk/maps/ + + + Denmark - Fugro Aerial Imagery + http://tile.openstreetmap.dk/fugro2005/$z/$x/$y.jpg + Fugro (2005) + + + Denmark - Stevns Kommune + http://tile.openstreetmap.dk/stevns/2009/$z/$x/$y.jpg + Stevns Kommune (2009) + + + Austria - geoimage.at + http://geoimage.openstreetmap.at/4d80de696cd562a63ce463a58a61488d/$z/$x/$y.jpg + geoimage.at + + + Russia - Kosmosnimki.ru IRS Satellite + http://irs.gis-lab.info/?layers=irs&request=GetTile&z=$z&x=$x&y=$y + Kosmosnimki.ru IRS + + + Belarus - Kosmosnimki.ru SPOT4 Satellite + http://irs.gis-lab.info/?layers=spot&request=GetTile&z=$z&x=$x&y=$y + Kosmosnimki.ru SPOT4 + + + Australia - Geographic Reference Image + http://agri.openstreetmap.org/$z/$x/$y.png + AGRI + + + Switzerland - Canton Aargau - AGIS 25cm 2011 + http://tiles.poole.ch/AGIS/OF2011/$z/$x/$y.png + AGIS OF2011 + + + Switzerland - Canton Solothurn - SOGIS 2007 + http://mapproxy.sosm.ch:8080/tiles/sogis2007/EPSG900913/$z/$x/$y.png?origin=nw + Orthofoto 2007 WMS Solothurn + + + Poland - Media-Lab fleet GPS masstracks + http://masstracks.media-lab.com.pl/$z/$x/$y.png + masstracks + + + South Africa - CD:NGI Aerial + http://${a|b|c}.aerial.openstreetmap.org.za/ngi-aerial/$z/$x/$y.jpg + ngi-aerial + + diff --git a/data/imagery_convert.js b/data/imagery_convert.js new file mode 100644 index 000000000..16cc23317 --- /dev/null +++ b/data/imagery_convert.js @@ -0,0 +1,45 @@ +var fs = require('fs'), + cheerio = require('cheerio'); + +$ = cheerio.load(fs.readFileSync('imagery.xml')); + +var imagery = []; + +$('set').each(function(i) { + var elem = $(this); + + var im = { + name: $(this).find('name').first().text(), + url: $(this).find('url').first().text() + }; + + var subdomains = []; + + im.url = im.url + .replace(/\$(\w)/g, function(m) { + return '{' + m[1] + '}'; + }) + .replace(/\$\{([^}.]+)\}/g, function(m) { + subdomains = m.slice(2, m.length - 1).split('|'); + return '{t}'; + }); + + if (subdomains.length) im.subdomains = subdomains; + + if (elem.attr('minlat')) { + im.extent = [ + +elem.attr('minlat'), + +elem.attr('minlon'), + +elem.attr('maxlat'), + +elem.attr('maxlon')]; + } + + ['sourcetag', 'logo', 'logo_url', 'terms_url'].forEach(function(a) { + if (elem.find(a).length) { + im[a] = elem.find(a).first().text(); + } + }); + imagery.push(im); +}); + +fs.writeFileSync('imagery.json', JSON.stringify(imagery, null, 4)); From 5764012b2328e36eea7ee3e6425bfdc7eb93608b Mon Sep 17 00:00:00 2001 From: Saman Bemel-Benrud Date: Mon, 4 Feb 2013 19:37:10 -0500 Subject: [PATCH 084/332] better styles for map features. --- css/map.css | 136 +++-- icons/tree.png | Bin 102 -> 158 bytes icons/unknown.png | Bin 1476 -> 338 bytes img/source/sprite.svg | 1284 +++++++--------------------------------- js/id/svg/lines.js | 2 +- js/id/svg/midpoints.js | 2 +- js/id/svg/vertices.js | 2 +- 7 files changed, 306 insertions(+), 1120 deletions(-) diff --git a/css/map.css b/css/map.css index ab2a352c9..0b2da4397 100644 --- a/css/map.css +++ b/css/map.css @@ -17,11 +17,11 @@ g.point .shadow { -moz-transition: fill 100ms linear; } .behavior-hover g.point.hover:not(.selected) .shadow { - fill: #E96666; - fill-opacity: 0.3; + fill: #f6634f; + fill-opacity: 0.5; } g.point.selected .shadow { - fill: #E96666; + fill: #f6634f; fill-opacity: 0.7; } @@ -30,8 +30,10 @@ g.point.selected .shadow { g.vertex .fill { fill:white; } + g.vertex .stroke { - stroke:#333; + stroke:black; + stroke-opacity: .5; stroke-width:2; fill:white; } @@ -60,69 +62,72 @@ svg[data-zoom="17"] g.vertex .fill { transform:scale(0.7, 0.7); } -g.vertex.shared .shadow { +g.vertex.sha#f6634f .shadow { -webkit-transform:scale(1.2, 1.2); -moz-transform:scale(1.2, 1.2); transform:scale(1.2, 1.2); } -g.vertex.shared .fill, -g.vertex.shared .stroke { +g.vertex.sha#f6634f .fill, +g.vertex.sha#f6634f .stroke { -webkit-transform:scale(1.1, 1.1); -moz-transform:scale(1.1, 1.1); transform:scale(1.1, 1.1); } -svg[data-zoom="16"] g.vertex.shared .shadow { +svg[data-zoom="16"] g.vertex.sha#f6634f .shadow { -webkit-transform:scale(0.9, 0.9); -moz-transform:scale(0.9, 0.9); transform:scale(0.9, 0.9); } -svg[data-zoom="16"] g.vertex.shared .fill, -svg[data-zoom="16"] g.vertex.shared .stroke { +svg[data-zoom="16"] g.vertex.sha#f6634f .fill, +svg[data-zoom="16"] g.vertex.sha#f6634f .stroke { -webkit-transform:scale(0.8, 0.8); -moz-transform:scale(0.8, 0.8); transform:scale(0.8, 0.8); } -svg[data-zoom="17"] g.vertex.shared .shadow { +svg[data-zoom="17"] g.vertex.sha#f6634f .shadow { -webkit-transform:scale(1, 1); -moz-transform:scale(1, 1); transform:scale(1, 1); } -svg[data-zoom="17"] g.vertex.shared .fill, -svg[data-zoom="17"] g.vertex.shared .stroke { +svg[data-zoom="17"] g.vertex.sha#f6634f .fill, +svg[data-zoom="17"] g.vertex.sha#f6634f .stroke { -webkit-transform:scale(0.9, 0.9); -moz-transform:scale(0.9, 0.9); transform:scale(0.9, 0.9); } -g.vertex.shared .fill { +g.vertex.sha#f6634f .fill { fill:#aaa; } g.vertex .shadow { fill: none; pointer-events: all; - stroke-width: 10; + stroke-width: 20; -webkit-transition: -webkit-transform 100ms linear; transition: transform 100ms linear; -moz-transition: fill 100ms linear; } .behavior-hover g.vertex.hover:not(.selected) .shadow { - fill: #E96666; + fill: #f6634f; fill-opacity: 0.3; -} +} g.vertex.selected .shadow { - fill: #E96666; - fill-opacity: 0.7; + fill: #f6634f; + fill-opacity: 0.5; } /* midpoints */ g.midpoint .fill { - fill:#aaa; + fill:#ddd; + stroke:black; + stroke-opacity: .5; + opacity: .5; } .behavior-hover g.midpoint .fill.hover:not(.selected) { - fill:#fff; - stroke:#000; + fill:white; + opacity: .75; } g.midpoint .shadow { @@ -134,7 +139,7 @@ g.midpoint .shadow { -moz-transition: fill 100ms linear; } .behavior-hover g.midpoint .shadow.hover:not(.selected) { - fill:#E96666; + fill:#f6634f; fill-opacity: 0.3; } @@ -146,8 +151,8 @@ path.line { } path.stroke { - stroke: #222; - stroke-width: 2; + stroke: black; + stroke-width: 4; } path.shadow { @@ -157,12 +162,12 @@ path.shadow { } .behavior-hover path.shadow.hover:not(.selected) { - stroke: #E96666; + stroke: #f6634f; stroke-opacity: 0.3; } path.shadow.selected { - stroke: #E96666; + stroke: #f6634f; stroke-opacity: 0.7; } @@ -191,31 +196,31 @@ path.area.stroke.selected { path.area.stroke.tag-natural, path.multipolygon.tag-natural { - stroke: #ADD6A5; + stroke: #b6e199; stroke-width:1; } path.area.fill.tag-natural, path.multipolygon.tag-natural { - fill: #ADD6A5; + fill: #b6e199; } path.area.stroke.tag-natural-water, path.multipolygon.tag-natural-water { - stroke: #6382FF; + stroke: #77d3de; } path.area.fill.tag-natural-water, path.multipolygon.tag-natural-water { - fill: #ADBEFF; + fill: #77d3de; } path.area.stroke.tag-building, path.multipolygon.tag-building { - stroke: #9E176A; + stroke: #e06e5f; stroke-width: 1; } path.area.fill.tag-building, path.multipolygon.tag-building { - fill: #ff6ec7; + fill: #e06e5f; } path.area.stroke.tag-landuse, @@ -228,7 +233,7 @@ path.multipolygon.tag-natural-wood, path.multipolygon.tag-natural-tree, path.multipolygon.tag-natural-grassland, path.multipolygon.tag-leisure-park { - stroke: #006B34; + stroke: #8cd05f; stroke-width: 1; } path.area.fill.tag-landuse, @@ -241,18 +246,18 @@ path.multipolygon.tag-natural-wood, path.multipolygon.tag-natural-tree, path.multipolygon.tag-natural-grassland, path.multipolygon.tag-leisure-park { - fill: #189E59; + fill: #8cd05f; fill-opacity: 0.2; } path.area.stroke.tag-amenity-parking, path.multipolygon.tag-amenity-parking { - stroke: #beb267; + stroke: #aaa; stroke-width: 1; } path.area.fill.tag-amenity-parking, path.multipolygon.tag-amenity-parking { - fill: #edecc0; + fill: #aaa; } path.multipolygon.tag-boundary { @@ -286,56 +291,57 @@ svg[data-zoom="16"] path.stroke.tag-highway { path.stroke.tag-highway-motorway, path.stroke.tag-highway-motorway_link, path.stroke.tag-construction-motorway { - stroke:#809bc0; + stroke:#58a9ed; } + path.casing.tag-highway-motorway, path.casing.tag-highway-motorway_link, path.casing.tag-construction-motorway { - stroke:#506077; + stroke:#2c5476; } path.stroke.tag-highway-trunk, path.stroke.tag-highway-trunk_link, path.stroke.tag-construction-trunk { - stroke:#97d397; + stroke:#8cd05f; } path.casing.tag-highway-trunk, path.casing.tag-highway-trunk_link, path.casing.tag-construction-trunk { - stroke:#477147; + stroke:#46682f; } path.stroke.tag-highway-primary, path.stroke.tag-highway-primary_link, path.stroke.tag-construction-primary { - stroke:#ec989a; + stroke:#e06d5f; } path.casing.tag-highway-primary, path.casing.tag-highway-primary_link, path.casing.tag-construction-primary { - stroke:#8d4346; + stroke:#70372f; } path.stroke.tag-highway-secondary, path.stroke.tag-highway-secondary_link, path.stroke.tag-construction-secondary { - stroke:#fecc8b; + stroke:#eab056; } path.casing.tag-highway-secondary, path.casing.tag-highway-secondary_link, path.casing.tag-construction-secondary { - stroke:#a37b48; + stroke:#75582b; } path.stroke.tag-highway-tertiary, path.stroke.tag-highway-tertiary_link, path.stroke.tag-construction-tertiary { - stroke:#ffffb3; + stroke:#ffff7e; } path.casing.tag-highway-tertiary, path.casing.tag-highway-tertiary_link, path.casing.tag-construction-tertiary { - stroke:#bbb; + stroke:#7f7f3f; } path.stroke.tag-highway-unclassified, @@ -372,7 +378,7 @@ path.stroke.tag-highway-pedestrian { shapeRendering: auto; } path.casing.tag-highway-pedestrian { - stroke:#84C382; + stroke:#8cd05f; stroke-width:6 !important; } @@ -445,17 +451,17 @@ svg[data-zoom="16"] path.casing.tag-highway-bridleway { } path.stroke.tag-highway-footway { - stroke: #996600; + stroke: #ae8681; } path.stroke.tag-highway-cycleway { - stroke: #69f; + stroke: #58a9ed; } path.stroke.tag-highway-bridleway { - stroke: green; + stroke: #e06d5f; } path.stroke.tag-highway-steps { - stroke: #ff6257; + stroke: #81d25c; stroke-width: 4; stroke-linecap: butt; stroke-dasharray: 3, 3; @@ -467,7 +473,7 @@ path.casing.tag-highway-steps { path.casing.tag-bridge-yes { stroke-width: 14; - stroke: #000; + stroke: #333; } path.stroke.tag-highway-construction, @@ -511,12 +517,16 @@ path.casing.tag-railway-subway { /* waterways */ +path.area.fill.tag-waterway { + fill: #77d3de; +} + path.stroke.tag-waterway { - stroke: #10539a; + stroke: #77d3de; stroke-width: 2; } path.casing.tag-waterway { - stroke: #6AA2FF; + stroke: #77d3de; stroke-width: 4; } @@ -535,11 +545,11 @@ svg[data-zoom="16"] path.casing.tag-waterway-river { } path.stroke.tag-waterway-ditch { - stroke: #10539a; + stroke: #6591ff; stroke-width: 1; } path.casing.tag-waterway-ditch { - stroke: #999692; + stroke: #6591ff; stroke-width: 3; } @@ -568,17 +578,22 @@ path.casing.tag-boundary { path.casing.tag-boundary-protected_area, path.casing.tag-boundary-national_park { - stroke: #4D9849; + stroke: #b0e298; } text { font-size:10px; pointer-events: none; + color: #222; + opacity: 1; } .oneway .textpath { pointer-events: none; + font-size: 7px; + baseline-shift: 2px; + opacity: .7; } text.tag-oneway { @@ -606,7 +621,7 @@ text.pathlabel, text.pointlabel { font-size: 12px; font-weight: bold; - fill: black; + fill: #333; text-anchor: middle; pointer-events: none; } @@ -632,7 +647,8 @@ text.pointlabel { text.point { - font-size: 9px; + font-size: 10px; + baseline-shift: 2px; } /* Cursors */ diff --git a/icons/tree.png b/icons/tree.png index 7575bd63b3fe7db6fb5747751fcf545792102bad..d88c945d4682230d400dae67eabaa572a96141ad 100644 GIT binary patch literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1SGw4HSYi^wj^(N7l!{JxM1({$v}}{PZ!4! zj+x1S{{OdUHapM~I;F9(k=5z$?~=s(mzVPJ@SMA}v8$$vL$-RJ&aWS@zaEcY|J^3G zBk4*#+X~+T?S(??j6T||VX|4%w9bfAK+tVcEGxso(=xyAAM=$28qeVA>gTe~DWM4f DxBfYi literal 102 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1SD^IDZKzvoCO|{#S9GGLLkg|>2BR0prC}O wi(^Q|oaBhlC5u1Fge#n6SfbFusK>@|nT1bw>bJ?OfNB^#UHx3vIVCg!06AzGssI20 diff --git a/icons/unknown.png b/icons/unknown.png index 404602aa4de44786ef78c6551f51c3e23a3ec3da..03fff0c0e1eb35671712f853dd8ee43f0420828d 100644 GIT binary patch delta 292 zcmV+<0o(q>3(^9RBn<>}LP=Bz2nYy#2xN$nAs2rD8FWQhbW?9;ba!ELWdL_~cP?pe zYja~^aAhuUa%Y?FJQ@H10MAK8K~y-6rISqx!Y~kp--u`GLhvX(h9FEzs%1d%4sN|d z_ks&g;-Ywh;5i|2;R1lv0Fot8vKHek&!`f5tWfPcxkpTtflQ6m9vp|qAiGYaibJIDK z2>QOi0q7>?yTI52@BJG<3BbCZ3ZM*(EdT)LTuVf?v3l*CYxCaxWXya9AW< zB%&n3*T*V3KUXg?B|j-uuOhbqsG5Pnrosxy%uOvxRH(?!$t$+1uvG%9umZ9{!um=I zU?nBlwn`Dc0SeCfMX3s=dWL#NN_Jcd3JNwwDQQ+gE^bimK%T8qMoCG5mA-y?dAVM> zv0i>ry1t>MrKP@sk-m|UE>MMTab;dfVufyAu`tKo-FP)SbBnaEtPap}qq8Pro9uK;KZ$Kp$>0P@@gdk5<0FM2bZyT0xdlb3#l;|NOrh$L#n9CwYzfWFEP=ZWO&DEQ z1VY{p&2h+5P;FET_|%F_903oK!3=nis1-PUM7U(;rsjb|#n8+~AFBkCC&BX0`8oMT z!3BxQsdi?jrpCa~L>ETa0k$dwEs=+y_)3i1SltFNa%`2Fun>D}<9SD1g#C<>qU z)av}_{MqZ@_;)ON!5hJ|aR%QF)*CvWlcr`W@l*NrrP+B7@W4v%HFamrI&$L|i{Iz45v8$bT8BvmJYEg;$@?_^@=V5Pw#uY>3ai-4;iV@a2?qh#&dz>sguq|97Sr jmh8KA%Ta#k>hJu2F7G+w=fBYeRL*+3`njxgN@xNAqdD_9 diff --git a/img/source/sprite.svg b/img/source/sprite.svg index 29e87e2b8..64f682c3d 100644 --- a/img/source/sprite.svg +++ b/img/source/sprite.svg @@ -39,12 +39,12 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="1" - inkscape:cx="329.64693" - inkscape:cy="58.693021" + inkscape:zoom="0.5" + inkscape:cx="279.87773" + inkscape:cy="136.54467" inkscape:document-units="px" inkscape:current-layer="layer1" - showgrid="false" + showgrid="true" inkscape:window-width="1280" inkscape:window-height="700" inkscape:window-x="48" @@ -186,7 +186,7 @@ image/svg+xml - + @@ -195,11 +195,200 @@ inkscape:groupmode="layer" id="layer1" transform="translate(-25,-62.362183)" - style="display:inline"> + style="display:none"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fix misalignment + + + + + RESET + style="display:inline;fill:#1a1a1a;fill-opacity:1" + transform="translate(505,-653.36218)"> + style="display:inline;color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;overflow:visible;enable-background:accumulate" /> - - - - - - - - - - - - - + style="opacity:0.50000000000000000;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + style="opacity:0.50000000000000000;fill:#000000;fill-opacity:1;display:inline"> + style="opacity:0.50000000000000000;fill:#000000;fill-opacity:1;display:inline"> @@ -778,9 +932,9 @@ inkscape:connector-curvature="0" id="path58971" d="m 35,44 c 0,1 0,4 0,4 0,0 0,0.5 -0.5,0.5 -0.5,0 -0.429283,-0.27516 -0.5,-0.5 -0.304688,-0.96875 -0.867187,-2.36459 -1,-3 -0.204595,-0.97885 -0.666667,-1 -1,-1 -1,0 -1,1 -1,1 l 1,4 0,3 C 32,52 31.5,51.5 30.5,50.5 29.945312,49.94531 29.257659,49.7508 28.8125,50.00781 28.377049,50.25922 28.150942,50.89541 28.5,51.5 28.853553,52.11237 32,56 32,56 c 1,1 2,1 4,1 2.666667,0 1,0 3,0 2,0 2.288488,-2.86546 3,-5 1,-3 1.5,-5 1.5,-5 0.113427,-0.42332 -0.04289,-0.846 -0.5,-1 -0.880461,-0.29662 -1.36006,0.35278 -1.5,1 -0.25,1.15625 -0.5,2 -0.5,2 -0.09375,0.31383 0.0013,0.5 -0.5,0.5 C 39.99086,49.5 40,49 40,49 c 0,0 0,-2.66667 0,-4 0,-1 -1,-1 -1,-1 0,0 -1,0 -1,1 0,1 0,1.66667 0,3 0,0 0.01305,0.5 -0.5,0.5 C 36.998673,48.5 37,48 37,48 c 0,0 0,-3 0,-4 0,-1 -1,-1 -1,-1 0,0 -1,0 -1,1 z" - style="opacity:0.5;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;filter:url(#filter8013-4);enable-background:accumulate" /> + style="opacity:0.50000000000000000;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;filter:url(#filter8013-4);enable-background:accumulate" /> + transform="translate(25,-3.0625001e-6)" /> - - - - - - - - - - - - - - - - fix misalignment - - - - - RESET diff --git a/js/id/svg/lines.js b/js/id/svg/lines.js index 83c533255..624237d93 100644 --- a/js/id/svg/lines.js +++ b/js/id/svg/lines.js @@ -1,6 +1,6 @@ iD.svg.Lines = function(projection) { - var arrowtext = '►\u3000\u3000', + var arrowtext = '►\u3000\u3000\u3000', alength; var highway_stack = { diff --git a/js/id/svg/midpoints.js b/js/id/svg/midpoints.js index 935f84719..c721f191c 100644 --- a/js/id/svg/midpoints.js +++ b/js/id/svg/midpoints.js @@ -38,7 +38,7 @@ iD.svg.Midpoints = function(projection) { .attr('class', 'midpoint'); group.append('circle') - .attr('r', 7) + .attr('r', 8) .attr('class', 'shadow'); group.append('circle') diff --git a/js/id/svg/vertices.js b/js/id/svg/vertices.js index ba28c968a..f7090d892 100644 --- a/js/id/svg/vertices.js +++ b/js/id/svg/vertices.js @@ -26,7 +26,7 @@ iD.svg.Vertices = function(projection) { .attr('class', 'shadow'); group.append('circle') - .attr('r', 6) + .attr('r', 4) .attr('class', 'stroke'); group.append('circle') From 4d0a42344db8009ed29bd360c75e53ac41381d7b Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 4 Feb 2013 19:47:25 -0500 Subject: [PATCH 085/332] Start dynamic layers work in branch --- data/imagery.json | 45 ++++++++++++++++++++--------- data/imagery_convert.js | 37 +++++++++++++++++++++++- index.html | 7 +++-- js/id/id.js | 5 +++- js/id/renderer/background_source.js | 29 +++++++------------ js/id/renderer/layers.js | 1 + js/id/ui/layerswitcher.js | 38 ++++++++---------------- 7 files changed, 100 insertions(+), 62 deletions(-) create mode 100644 js/id/renderer/layers.js diff --git a/data/imagery.json b/data/imagery.json index 2055c1cf5..44d62cb97 100644 --- a/data/imagery.json +++ b/data/imagery.json @@ -2,6 +2,12 @@ { "name": "Bing aerial imagery", "url": "http://ecn.t0.tiles.virtualearth.net/tiles/a{q}uadkey.jpeg?g=587&mkt=en-gb&n=z", + "description": "Satellite imagery.", + "scaleExtent": [ + 0, + 20 + ], + "default": "yes", "sourcetag": "Bing", "logo": "bing_maps.png", "logo_url": "http://www.bing.com/maps", @@ -10,6 +16,11 @@ { "name": "MapBox Satellite", "url": "http://{t}.tiles.mapbox.com/v3/openstreetmap.map-4wvf9l0l/{z}/{x}/{y}.png", + "description": "Satellite and aerial imagery", + "scaleExtent": [ + 0, + 16 + ], "subdomains": [ "a", "b", @@ -17,26 +28,19 @@ ] }, { - "name": "MapQuest Open Aerial", - "url": "http://oatile1.mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpg" - }, - { - "name": "OSM - Mapnik", + "name": "OpenStreetMap", "url": "http://{t}.tile.openstreetmap.org/{z}/{x}/{y}.png", + "description": "The default OpenStreetMap layer.", + "scaleExtent": [ + 0, + 18 + ], "subdomains": [ "a", "b", "c" ] }, - { - "name": "OSM - OpenCycleMap", - "url": "http://tile.opencyclemap.org/cycle/{z}/{x}/{y}.png" - }, - { - "name": "OSM - MapQuest", - "url": "http://otile1.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.jpg" - }, { "name": "OSM - Tiger Edited Map", "url": "http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/{z}/{x}/{y}.png", @@ -70,6 +74,11 @@ { "name": "OSM US TIGER 2012 Roads Overlay", "url": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", + "description": "Public domain road data from the US Government.", + "scaleExtent": [ + 0, + 17 + ], "subdomains": [ "a", "b", @@ -85,6 +94,11 @@ { "name": "OSM US TIGER 2012 Roads Overlay", "url": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", + "description": "Public domain road data from the US Government.", + "scaleExtent": [ + 0, + 17 + ], "subdomains": [ "a", "b", @@ -100,6 +114,11 @@ { "name": "OSM US TIGER 2012 Roads Overlay", "url": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", + "description": "Public domain road data from the US Government.", + "scaleExtent": [ + 0, + 17 + ], "subdomains": [ "a", "b", diff --git a/data/imagery_convert.js b/data/imagery_convert.js index 16cc23317..a7253aee4 100644 --- a/data/imagery_convert.js +++ b/data/imagery_convert.js @@ -5,6 +5,32 @@ $ = cheerio.load(fs.readFileSync('imagery.xml')); var imagery = []; +// CENSORSHIP! No, these are just layers that essentially duplicate other layers +// or which have no clear use case. +var censor = { + 'MapQuest Open Aerial': true, + 'OSM - OpenCycleMap': true, + 'OSM - MapQuest': true +}; + +var replace = { + 'OSM - Mapnik': 'OpenStreetMap' +}; + +var description = { + 'MapBox Satellite': 'Satellite and aerial imagery', + 'OpenStreetMap': 'The default OpenStreetMap layer.', + 'OSM US TIGER 2012 Roads Overlay': 'Public domain road data from the US Government.', + 'Bing aerial imagery': 'Satellite imagery.' +}; + +var scaleExtent = { + 'MapBox Satellite': [0, 16], + 'OpenStreetMap': [0, 18], + 'OSM US TIGER 2012 Roads Overlay': [0, 17], + 'Bing aerial imagery': [0, 20] +}; + $('set').each(function(i) { var elem = $(this); @@ -13,6 +39,14 @@ $('set').each(function(i) { url: $(this).find('url').first().text() }; + if (censor[im.name]) return; + + if (replace[im.name]) im.name = replace[im.name]; + + if (description[im.name]) im.description = description[im.name]; + + if (scaleExtent[im.name]) im.scaleExtent = scaleExtent[im.name]; + var subdomains = []; im.url = im.url @@ -34,7 +68,7 @@ $('set').each(function(i) { +elem.attr('maxlon')]; } - ['sourcetag', 'logo', 'logo_url', 'terms_url'].forEach(function(a) { + ['default', 'sourcetag', 'logo', 'logo_url', 'terms_url'].forEach(function(a) { if (elem.find(a).length) { im[a] = elem.find(a).first().text(); } @@ -43,3 +77,4 @@ $('set').each(function(i) { }); fs.writeFileSync('imagery.json', JSON.stringify(imagery, null, 4)); +fs.writeFileSync('imagery.js', 'iD.data.imagery = ' + JSON.stringify(imagery, null, 4) + ';'); diff --git a/index.html b/index.html index c03ac29ae..3be283a1f 100644 --- a/index.html +++ b/index.html @@ -32,12 +32,17 @@ + + + + + @@ -135,8 +140,6 @@ - -
+ diff --git a/js/id/actions/connect.js b/js/id/actions/connect.js new file mode 100644 index 000000000..88bf35932 --- /dev/null +++ b/js/id/actions/connect.js @@ -0,0 +1,54 @@ +// Connect the ways at the given nodes. +// +// The last node will survive. All other nodes will be replaced with +// the surviving node in parent ways, and then removed. +// +// Tags and relation memberships of of non-surviving nodes are merged +// to the survivor. +// +// This is the inverse of `iD.actions.Disconnect`. +// +// Reference: +// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MergeNodesAction.as +// https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/MergeNodesAction.java +// +iD.actions.Connect = function(nodeIds) { + var action = function(graph) { + var survivor = graph.entity(_.last(nodeIds)); + + for (var i = 0; i < nodeIds.length - 1; i++) { + var node = graph.entity(nodeIds[i]), index; + + graph.parentWays(node).forEach(function (parent) { + while (true) { + index = parent.nodes.indexOf(node.id); + if (index < 0) + break; + parent = parent.updateNode(survivor.id, index); + } + graph = graph.replace(parent); + }); + + graph.parentRelations(node).forEach(function (parent) { + var memberA = parent.memberById(survivor.id), + memberB = parent.memberById(node.id); + if (!memberA) { + graph = graph.replace(parent.addMember({id: survivor.id, role: memberB.role, type: 'node'})); + } + }); + + survivor = survivor.mergeTags(node.tags); + graph = iD.actions.DeleteNode(node.id)(graph); + } + + graph = graph.replace(survivor); + + return graph; + }; + + action.enabled = function(graph) { + return nodeIds.length > 1; + }; + + return action; +}; diff --git a/js/id/actions/disconnect.js b/js/id/actions/disconnect.js index 8645c436b..415730e77 100644 --- a/js/id/actions/disconnect.js +++ b/js/id/actions/disconnect.js @@ -4,6 +4,8 @@ // Normally, this will be undefined and the way will automatically // be assigned a new ID. // +// This is the inverse of `iD.actions.Connect`. +// // Reference: // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/UnjoinNodeAction.as // https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/UnGlueAction.java diff --git a/js/id/behavior/drag_node.js b/js/id/behavior/drag_node.js index a0cb06e75..92318756c 100644 --- a/js/id/behavior/drag_node.js +++ b/js/id/behavior/drag_node.js @@ -17,39 +17,96 @@ iD.behavior.DragNode = function(context) { }, 50); } - function stopNudge(nudge) { + function stopNudge() { if (nudgeInterval) window.clearInterval(nudgeInterval); nudgeInterval = null; } - function annotation(entity) { + function moveAnnotation(entity) { return t('operations.move.annotation.' + entity.geometry(context.graph())); } - return iD.behavior.drag() - .delegate(".node") - .origin(function(entity) { - return context.projection(entity.loc); - }) - .on('start', function() { - context.perform( - iD.actions.Noop()); - }) - .on('move', function(entity) { - d3.event.sourceEvent.stopPropagation(); + function connectAnnotation(datum) { + return t('operations.connect.annotation.' + datum.geometry(context.graph())); + } - var nudge = edge(d3.event.point, context.map().size()); - if (nudge) startNudge(nudge); - else stopNudge(); + function origin(entity) { + return context.projection(entity.loc); + } + function start(entity) { + var activeIDs = _.pluck(context.graph().parentWays(entity), 'id'); + activeIDs.push(entity.id); + + context.surface() + .classed('behavior-drag-node', true) + .selectAll('.node, .way') + .filter(function (d) { return activeIDs.indexOf(d.id) >= 0; }) + .classed('active', true); + + context.perform( + iD.actions.Noop()); + } + + function datum() { + if (d3.event.sourceEvent.altKey) { + return {}; + } else { + return d3.event.sourceEvent.target.__data__ || {}; + } + } + + function move(entity) { + d3.event.sourceEvent.stopPropagation(); + + var nudge = edge(d3.event.point, context.map().size()); + if (nudge) startNudge(nudge); + else stopNudge(); + + var loc = context.map().mouseCoordinates(); + + var d = datum(); + if (d.type === 'node') { + loc = d.loc; + } else if (d.type === 'way') { + loc = iD.geo.chooseIndex(d, d3.mouse(context.surface().node()), context).loc; + } + + context.replace(iD.actions.MoveNode(entity.id, loc)); + } + + function end(entity) { + context.surface() + .classed('behavior-drag-node', false) + .selectAll('.active') + .classed('active', false); + + stopNudge(); + + var d = datum(); + if (d.type === 'way') { + var choice = iD.geo.chooseIndex(d, d3.mouse(context.surface().node()), context); context.replace( - iD.actions.MoveNode(entity.id, context.projection.invert(d3.event.point)), - annotation(entity)); - }) - .on('end', function(entity) { - stopNudge(); + iD.actions.MoveNode(entity.id, choice.loc), + iD.actions.AddVertex(d.id, entity.id, choice.index), + connectAnnotation(d)); + + } else if (d.type === 'node' && d.id !== entity.id) { + context.replace( + iD.actions.Connect([entity.id, d.id]), + connectAnnotation(d)); + + } else { context.replace( iD.actions.Noop(), - annotation(entity)); - }); + moveAnnotation(entity)); + } + } + + return iD.behavior.drag() + .delegate("g.node") + .origin(origin) + .on('start', start) + .on('move', move) + .on('end', end); }; diff --git a/js/id/svg/vertices.js b/js/id/svg/vertices.js index f7090d892..e9769a374 100644 --- a/js/id/svg/vertices.js +++ b/js/id/svg/vertices.js @@ -23,15 +23,15 @@ iD.svg.Vertices = function(projection) { group.append('circle') .attr('r', 10) - .attr('class', 'shadow'); + .attr('class', 'node vertex shadow'); group.append('circle') .attr('r', 4) - .attr('class', 'stroke'); + .attr('class', 'node vertex stroke'); group.append('circle') .attr('r', 3) - .attr('class', 'fill'); + .attr('class', 'node vertex fill'); groups.attr('transform', iD.svg.PointTransform(projection)) .call(iD.svg.TagClasses()) diff --git a/locale/en.js b/locale/en.js index 8f00590c6..516a43475 100644 --- a/locale/en.js +++ b/locale/en.js @@ -86,6 +86,14 @@ locale.en = { multiple: "Deleted {n} objects." } }, + connect: { + annotation: { + point: "Connected a way to a point.", + vertex: "Connected a way to another.", + line: "Connected a way to a line.", + area: "Connected a way to an area." + } + }, disconnect: { title: "Disconnect", description: "Disconnect these ways from each other.", diff --git a/test/index.html b/test/index.html index e6df6a963..5f7c19b5a 100644 --- a/test/index.html +++ b/test/index.html @@ -72,6 +72,7 @@ + @@ -151,6 +152,7 @@ + diff --git a/test/index_packaged.html b/test/index_packaged.html index ef91e2823..d21d48ff2 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -35,6 +35,7 @@ + diff --git a/test/spec/actions/connect.js b/test/spec/actions/connect.js new file mode 100644 index 000000000..3838d83c2 --- /dev/null +++ b/test/spec/actions/connect.js @@ -0,0 +1,110 @@ +describe("iD.actions.Connect", function() { + describe("#enabled", function () { + it("returns true for two or more nodes", function () { + expect(iD.actions.Connect(['a', 'b']).enabled()).to.be.true; + }); + + it("returns false for less than two nodes", function () { + expect(iD.actions.Connect(['a']).enabled()).to.be.false; + }); + }); + + it("removes all but the final node", function() { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}) + }); + + graph = iD.actions.Connect(['a', 'b', 'c'])(graph); + + expect(graph.entity('a')).to.be.undefined; + expect(graph.entity('b')).to.be.undefined; + expect(graph.entity('c')).not.to.be.undefined; + }); + + it("replaces non-surviving nodes in parent ways", function() { + // a --- b --- c + // + // e + // | + // d + // + // Connect [e, b]. + // + // Expected result: + // + // a --- b --- c + // | + // d + // + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + 'd': iD.Node({id: 'd'}), + 'e': iD.Node({id: 'e'}), + '-': iD.Way({id: '-', nodes: ['a', 'b', 'c']}), + '|': iD.Way({id: '|', nodes: ['d', 'e']}) + }); + + graph = iD.actions.Connect(['e', 'b'])(graph); + + expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c']); + expect(graph.entity('|').nodes).to.eql(['d', 'b']); + }); + + it("handles circular ways", function() { + // c -- a d === e + // | / + // | / + // | / + // b + // + // Connect [a, d]. + // + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + 'd': iD.Node({id: 'd'}), + 'e': iD.Node({id: 'e'}), + '-': iD.Way({id: '-', nodes: ['a', 'b', 'c', 'a']}), + '=': iD.Way({id: '=', nodes: ['d', 'e']}) + }); + + graph = iD.actions.Connect(['a', 'd'])(graph); + + expect(graph.entity('-').nodes).to.eql(['d', 'b', 'c', 'd']); + }); + + it("merges tags to the surviving node", function() { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a', tags: {a: 'a'}}), + 'b': iD.Node({id: 'b', tags: {b: 'b'}}), + 'c': iD.Node({id: 'c', tags: {c: 'c'}}) + }); + + graph = iD.actions.Connect(['a', 'b', 'c'])(graph); + + expect(graph.entity('c').tags).to.eql({a: 'a', b: 'b', c: 'c'}); + }); + + it("merges memberships to the surviving node", function() { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + 'd': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['a', 'b']}), + '=': iD.Way({id: '=', nodes: ['c', 'd']}), + 'r1': iD.Relation({id: 'r1', members: [{id: 'b', role: 'r1', type: 'node'}]}), + 'r2': iD.Relation({id: 'r2', members: [{id: 'b', role: 'r1', type: 'node'}, {id: 'c', role: 'r2', type: 'node'}]}) + }); + + graph = iD.actions.Connect(['b', 'c'])(graph); + + expect(graph.entity('r1').members).to.eql([{id: 'c', role: 'r1', type: 'node'}]); + expect(graph.entity('r2').members).to.eql([{id: 'c', role: 'r2', type: 'node'}]); + }); +}); From 4fed3e5daca0bff016ca1861e33719ad61d426e8 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 4 Feb 2013 16:55:46 -0800 Subject: [PATCH 088/332] Fix rogue s/red/#f6634f/g --- css/map.css | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/css/map.css b/css/map.css index 66d1e4496..749aeb7b3 100644 --- a/css/map.css +++ b/css/map.css @@ -62,41 +62,41 @@ svg[data-zoom="17"] g.vertex .fill { transform:scale(0.7, 0.7); } -g.vertex.sha#f6634f .shadow { +g.vertex.shared .shadow { -webkit-transform:scale(1.2, 1.2); -moz-transform:scale(1.2, 1.2); transform:scale(1.2, 1.2); } -g.vertex.sha#f6634f .fill, -g.vertex.sha#f6634f .stroke { +g.vertex.shared .fill, +g.vertex.shared .stroke { -webkit-transform:scale(1.1, 1.1); -moz-transform:scale(1.1, 1.1); transform:scale(1.1, 1.1); } -svg[data-zoom="16"] g.vertex.sha#f6634f .shadow { +svg[data-zoom="16"] g.vertex.shared .shadow { -webkit-transform:scale(0.9, 0.9); -moz-transform:scale(0.9, 0.9); transform:scale(0.9, 0.9); } -svg[data-zoom="16"] g.vertex.sha#f6634f .fill, -svg[data-zoom="16"] g.vertex.sha#f6634f .stroke { +svg[data-zoom="16"] g.vertex.shared .fill, +svg[data-zoom="16"] g.vertex.shared .stroke { -webkit-transform:scale(0.8, 0.8); -moz-transform:scale(0.8, 0.8); transform:scale(0.8, 0.8); } -svg[data-zoom="17"] g.vertex.sha#f6634f .shadow { +svg[data-zoom="17"] g.vertex.shared .shadow { -webkit-transform:scale(1, 1); -moz-transform:scale(1, 1); transform:scale(1, 1); } -svg[data-zoom="17"] g.vertex.sha#f6634f .fill, -svg[data-zoom="17"] g.vertex.sha#f6634f .stroke { +svg[data-zoom="17"] g.vertex.shared .fill, +svg[data-zoom="17"] g.vertex.shared .stroke { -webkit-transform:scale(0.9, 0.9); -moz-transform:scale(0.9, 0.9); transform:scale(0.9, 0.9); } -g.vertex.sha#f6634f .fill { +g.vertex.shared .fill { fill:#aaa; } From 0bd864751ae1dd4756542d2869edd4ba4a05ee42 Mon Sep 17 00:00:00 2001 From: Saman Bemel-Benrud Date: Mon, 4 Feb 2013 19:58:28 -0500 Subject: [PATCH 089/332] new background icon. --- img/source/sprite.svg | 173 +++++------------------------------------- img/sprite.png | Bin 14084 -> 13761 bytes 2 files changed, 21 insertions(+), 152 deletions(-) diff --git a/img/source/sprite.svg b/img/source/sprite.svg index 64f682c3d..a8c29982b 100644 --- a/img/source/sprite.svg +++ b/img/source/sprite.svg @@ -7,7 +7,6 @@ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" - xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="420" @@ -39,12 +38,12 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="0.5" - inkscape:cx="279.87773" - inkscape:cy="136.54467" + inkscape:zoom="1" + inkscape:cx="304.46947" + inkscape:cy="116.03146" inkscape:document-units="px" - inkscape:current-layer="layer1" - showgrid="true" + inkscape:current-layer="layer12" + showgrid="false" inkscape:window-width="1280" inkscape:window-height="700" inkscape:window-x="48" @@ -254,137 +253,6 @@ id="g11041" style="fill:#ffffff;fill-opacity:1;display:inline" />
- - - - - - - - - - - - fix misalignment - - - - - RESET - -
- + + + +
diff --git a/img/sprite.png b/img/sprite.png index f14b74ac9f6a58c5728374dcb880a75fe7e9cb49..02aadf2a38265d2b23b22afe2961255769ef2a05 100644 GIT binary patch literal 13761 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%0Dt<^zknM6K!s$2` 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 E0T3gT6aWAK literal 14084 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 Date: Tue, 5 Feb 2013 00:41:07 -0500 Subject: [PATCH 090/332] Fix drawing, leaking globals --- js/id/modes/add_area.js | 2 +- js/id/modes/add_line.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js index ae83423cb..4cb98a3bc 100644 --- a/js/id/modes/add_area.js +++ b/js/id/modes/add_area.js @@ -10,7 +10,7 @@ iD.modes.AddArea = function(context) { var behavior = iD.behavior.AddWay(context) .on('start', start) .on('startFromWay', startFromWay) - .on('startFromNode', startFromNode) + .on('startFromNode', startFromNode), defaultTags = {area: 'yes'}; function start(loc) { diff --git a/js/id/modes/add_line.js b/js/id/modes/add_line.js index 3d45df2a0..76e31e83b 100644 --- a/js/id/modes/add_line.js +++ b/js/id/modes/add_line.js @@ -10,7 +10,7 @@ iD.modes.AddLine = function(context) { var behavior = iD.behavior.AddWay(context) .on('start', start) .on('startFromWay', startFromWay) - .on('startFromNode', startFromNode) + .on('startFromNode', startFromNode), defaultTags = {highway: 'residential'}; function start(loc) { From d3d08515969e846a2777415e15b2c9b48e76930d Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Tue, 5 Feb 2013 00:51:54 -0500 Subject: [PATCH 091/332] Fix svg/midpoint tests Proper solution waiting on 369 --- test/spec/svg/midpoints.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/spec/svg/midpoints.js b/test/spec/svg/midpoints.js index c34ac0350..e00f6f78b 100644 --- a/test/spec/svg/midpoints.js +++ b/test/spec/svg/midpoints.js @@ -14,6 +14,8 @@ describe("iD.svg.Midpoints", function () { line = iD.Way({nodes: [a.id, b.id]}), graph = iD.Graph([a, b, line]); + // If no vertices are drawn, no midpoints are drawn. This dependence needs to be removed + surface.call(iD.svg.Vertices(projection), graph, [a], filter); surface.call(iD.svg.Midpoints(projection), graph, [line], filter); expect(surface.select('.midpoint').datum().loc).to.eql([25, 0]); @@ -42,6 +44,8 @@ describe("iD.svg.Midpoints", function () { graph = iD.Graph([a, b, c, d, l1, l2, l3, l4]), ab = function (d) { return d.id === [a.id, b.id].sort().join("-"); }; + // If no vertices are drawn, no midpoints are drawn. This dependence needs to be removed + surface.call(iD.svg.Vertices(projection), graph, [a], filter); surface.call(iD.svg.Midpoints(projection), graph, [l1, l2, l3, l4], filter); expect(surface.selectAll('.midpoint').filter(ab).datum().ways).to.eql([ From 4cbdadfbbc6d25a26d17d5e6db4f207b6906bb21 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 5 Feb 2013 06:56:23 -0800 Subject: [PATCH 092/332] Fix undo of multipolygon deletion --- locale/en.js | 1 + 1 file changed, 1 insertion(+) diff --git a/locale/en.js b/locale/en.js index 516a43475..b92dc75ee 100644 --- a/locale/en.js +++ b/locale/en.js @@ -83,6 +83,7 @@ locale.en = { vertex: "Deleted a node from a way.", line: "Deleted a line.", area: "Deleted an area.", + relation: "Deleted a relation.", multiple: "Deleted {n} objects." } }, From c37adc617fc8873313e805fafe9e7ba59e323d12 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 5 Feb 2013 07:30:58 -0800 Subject: [PATCH 093/332] Fix build --- Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3b0f2d1b7..9fd5afb0e 100644 --- a/Makefile +++ b/Makefile @@ -50,7 +50,9 @@ all: \ js/id/validate.js \ js/id/end.js \ locale/locale.js \ - locale/en.js + locale/en.js \ + data/data.js \ + data/deprecated.js iD.js: Makefile @rm -f $@ From 3576a99eb5d4354160591359323165ea8255bde0 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Tue, 5 Feb 2013 11:00:13 -0500 Subject: [PATCH 094/332] Always have delete as first op --- js/id/modes/select.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/id/modes/select.js b/js/id/modes/select.js index ec6017ae3..126e01029 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -38,9 +38,10 @@ iD.modes.Select = function(context, selection, initial) { context.install(behavior); }); - var operations = d3.values(iD.operations) + var operations = _.without(d3.values(iD.operations), iD.operations.Delete) .map(function(o) { return o(selection, context); }) .filter(function(o) { return o.available(); }); + operations.unshift(iD.operations.Delete(selection, context)); operations.forEach(function(operation) { keybinding.on(operation.key, function() { From db7f42145e9ddbeaac936c872ee4ff45155100c7 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Tue, 5 Feb 2013 12:00:10 -0500 Subject: [PATCH 095/332] Continue removing any non-scoped selectors. Refs #595 Last hits will be combobox and layerswitcher. --- index.html | 8 +++--- js/id/modes/select.js | 2 +- js/id/oauth.js | 4 +-- js/id/ui.js | 2 +- js/id/ui/confirm.js | 4 +-- js/id/ui/flash.js | 4 +-- js/id/ui/geocoder.js | 10 +++++-- js/id/ui/inspector.js | 20 +++++++------- js/id/ui/loading.js | 9 ++++--- js/id/ui/modal.js | 9 ++++--- js/id/ui/save.js | 61 +++++++++++++++++++++++-------------------- 11 files changed, 73 insertions(+), 60 deletions(-) diff --git a/index.html b/index.html index b0a1cea53..afcb8e6be 100644 --- a/index.html +++ b/index.html @@ -9,8 +9,8 @@ - - + + @@ -140,7 +140,7 @@ -
- + + + + + @@ -132,8 +137,6 @@ - - - diff --git a/js/id/graph/relation.js b/js/id/graph/relation.js index a404d1981..f8540db1f 100644 --- a/js/id/graph/relation.js +++ b/js/id/graph/relation.js @@ -26,7 +26,7 @@ _.extend(iD.Relation.prototype, { }, geometry: function() { - return 'relation'; + return this.isMultipolygon() ? 'area' : 'relation'; }, // Return the first member with the given role. A copy of the member object diff --git a/js/id/graph/way.js b/js/id/graph/way.js index 1f7c46065..1830e61a4 100644 --- a/js/id/graph/way.js +++ b/js/id/graph/way.js @@ -49,6 +49,7 @@ _.extend(iD.Way.prototype, { isArea: function() { return this.tags.area === 'yes' || (this.isClosed() && + !_.isEmpty(this.tags) && this.tags.area !== 'no' && !this.tags.highway && !this.tags.barrier); diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 3b0ad82dd..532faaca8 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -143,13 +143,22 @@ iD.modes.Select = function(context, selection, initial) { } } + function selected(entity) { + if (!entity) return false; + if (selection.indexOf(entity.id) >= 0) return true; + return d3.select(this).classed('stroke') && + _.any(context.graph().parentRelations(entity), function(parent) { + return selection.indexOf(parent.id) >= 0; + }); + } + d3.select(document) .call(keybinding); context.surface() .on('dblclick.select', dblclick) .selectAll("*") - .filter(function(d) { return d && selection.indexOf(d.id) >= 0; }) + .filter(selected) .classed('selected', true); radialMenu = iD.ui.RadialMenu(operations); diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 19354d7f3..5074abd92 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -19,7 +19,6 @@ iD.Map = function(context) { vertices = iD.svg.Vertices(roundedProjection), lines = iD.svg.Lines(roundedProjection), areas = iD.svg.Areas(roundedProjection), - multipolygons = iD.svg.Multipolygons(roundedProjection), midpoints = iD.svg.Midpoints(roundedProjection), labels = iD.svg.Labels(roundedProjection), tail = d3.tail(), @@ -87,7 +86,6 @@ iD.Map = function(context) { .call(vertices, graph, all, filter) .call(lines, graph, all, filter) .call(areas, graph, all, filter) - .call(multipolygons, graph, all, filter) .call(midpoints, graph, all, filter) .call(labels, graph, all, filter, dimensions, !difference); } diff --git a/js/id/svg.js b/js/id/svg.js index aadb6b830..3b662b2e7 100644 --- a/js/id/svg.js +++ b/js/id/svg.js @@ -27,5 +27,17 @@ iD.svg = { return projection(n.loc); }).join('L')); }; + }, + + MultipolygonMemberTags: function (graph) { + return function (entity) { + var tags = entity.tags; + graph.parentRelations(entity).forEach(function (relation) { + if (relation.isMultipolygon()) { + tags = _.extend({}, relation.tags, tags); + } + }); + return tags; + } } }; diff --git a/js/id/svg/areas.js b/js/id/svg/areas.js index 991b5644c..cd9525d4d 100644 --- a/js/id/svg/areas.js +++ b/js/id/svg/areas.js @@ -1,38 +1,39 @@ iD.svg.Areas = function(projection) { return function drawAreas(surface, graph, entities, filter) { - var areas = []; + var path = d3.geo.path().projection(projection), + areas = []; for (var i = 0; i < entities.length; i++) { var entity = entities[i]; if (entity.geometry(graph) === 'area') { - var points = graph.childNodes(entity).map(function(n) { - return projection(n.loc); - }); - areas.push({ entity: entity, - area: entity.isDegenerate() ? 0 : Math.abs(d3.geom.polygon(points).area()) + area: Math.abs(path.area(entity.asGeoJSON(graph))) }); } } areas.sort(function(a, b) { return b.area - a.area; }); - var lineString = iD.svg.LineString(projection, graph); + function drawPaths(group, areas, filter, klass) { + var tagClasses = iD.svg.TagClasses(); + + if (klass === 'stroke') { + tagClasses.tags(iD.svg.MultipolygonMemberTags(graph)); + } - function drawPaths(group, areas, filter, classes) { var paths = group.selectAll('path.area') .filter(filter) .data(areas, iD.Entity.key); paths.enter() .append('path') - .attr('class', classes); + .attr('class', function (d) { return d.type + ' area ' + klass; }); paths .order() - .attr('d', lineString) - .call(iD.svg.TagClasses()) + .attr('d', function (entity) { return path(entity.asGeoJSON(graph)); }) + .call(tagClasses) .call(iD.svg.MemberClasses(graph)); paths.exit() @@ -43,9 +44,14 @@ iD.svg.Areas = function(projection) { areas = _.pluck(areas, 'entity'); + var strokes = areas.filter(function (area) { + return area.type === 'way'; + }); + var fill = surface.select('.layer-fill'), - stroke = surface.select('.layer-stroke'), - fills = drawPaths(fill, areas, filter, 'way area fill'), - strokes = drawPaths(stroke, areas, filter, 'way area stroke'); + stroke = surface.select('.layer-stroke'); + + drawPaths(fill, areas, filter, 'fill'); + drawPaths(stroke, strokes, filter, 'stroke'); }; }; diff --git a/js/id/svg/labels.js b/js/id/svg/labels.js index 9b2cbd1e9..31d2f03d5 100644 --- a/js/id/svg/labels.js +++ b/js/id/svg/labels.js @@ -335,9 +335,8 @@ iD.svg.Labels = function(projection) { } function getAreaLabel(entity, width, height) { - var nodes = _.pluck(graph.childNodes(entity), 'loc') - .map(iD.svg.RoundProjection(projection)), - centroid = d3.geom.polygon(nodes).centroid(), + var path = d3.geo.path().projection(projection), + centroid = path.centroid(entity.asGeoJSON(graph)), extent = entity.extent(graph), entitywidth = projection(extent[1])[0] - projection(extent[0])[0]; diff --git a/js/id/svg/lines.js b/js/id/svg/lines.js index a1151204b..2fd7b9152 100644 --- a/js/id/svg/lines.js +++ b/js/id/svg/lines.js @@ -34,19 +34,25 @@ iD.svg.Lines = function(projection) { } return function drawLines(surface, graph, entities, filter) { - function drawPaths(group, lines, filter, classes, lineString) { + function drawPaths(group, lines, filter, klass, lineString) { + var tagClasses = iD.svg.TagClasses(); + + if (klass === 'stroke') { + tagClasses.tags(iD.svg.MultipolygonMemberTags(graph)); + } + var paths = group.selectAll('path.line') .filter(filter) .data(lines, iD.Entity.key); paths.enter() .append('path') - .attr('class', classes); + .attr('class', 'way line ' + klass); paths .order() .attr('d', lineString) - .call(iD.svg.TagClasses()) + .call(tagClasses) .call(iD.svg.MemberClasses(graph)); paths.exit() @@ -65,8 +71,7 @@ iD.svg.Lines = function(projection) { container.remove(); } - var lines = [], - lineStrings = {}; + var lines = []; for (var i = 0; i < entities.length; i++) { var entity = entities[i]; @@ -84,9 +89,9 @@ iD.svg.Lines = function(projection) { stroke = surface.select('.layer-stroke'), defs = surface.select('defs'), text = surface.select('.layer-text'), - shadows = drawPaths(shadow, lines, filter, 'way line shadow', lineString), - casings = drawPaths(casing, lines, filter, 'way line casing', lineString), - strokes = drawPaths(stroke, lines, filter, 'way line stroke', lineString); + shadows = drawPaths(shadow, lines, filter, 'shadow', lineString), + casings = drawPaths(casing, lines, filter, 'casing', lineString), + strokes = drawPaths(stroke, lines, filter, 'stroke', lineString); // Determine the lengths of oneway paths var lengths = {}, diff --git a/js/id/svg/multipolygons.js b/js/id/svg/multipolygons.js deleted file mode 100644 index 092f65805..000000000 --- a/js/id/svg/multipolygons.js +++ /dev/null @@ -1,55 +0,0 @@ -iD.svg.Multipolygons = function(projection) { - return function(surface, graph, entities, filter) { - var multipolygons = []; - - for (var i = 0; i < entities.length; i++) { - var entity = entities[i]; - if (entity.geometry(graph) === 'relation' && entity.tags.type === 'multipolygon') { - multipolygons.push(entity); - } - } - - var lineStrings = {}; - - function lineString(entity) { - if (lineStrings[entity.id] !== undefined) { - return lineStrings[entity.id]; - } - - var multipolygon = entity.multipolygon(graph); - if (entity.members.length === 0 || !multipolygon) { - return (lineStrings[entity.id] = null); - } - - multipolygon = _.flatten(multipolygon, true); - return (lineStrings[entity.id] = - multipolygon.map(function (ring) { - return 'M' + ring.map(projection).join('L'); - }).join("")); - } - - function drawPaths(group, multipolygons, filter, classes) { - var paths = group.selectAll('path.multipolygon') - .filter(filter) - .data(multipolygons, iD.Entity.key); - - paths.enter() - .append('path') - .attr('class', classes); - - paths - .order() - .attr('d', lineString) - .call(iD.svg.TagClasses()) - .call(iD.svg.MemberClasses(graph)); - - paths.exit() - .remove(); - - return paths; - } - - var fill = surface.select('.layer-fill'), - paths = drawPaths(fill, multipolygons, filter, 'relation multipolygon'); - }; -}; diff --git a/js/id/svg/tag_classes.js b/js/id/svg/tag_classes.js index 5ce585add..dad388ffe 100644 --- a/js/id/svg/tag_classes.js +++ b/js/id/svg/tag_classes.js @@ -3,10 +3,11 @@ iD.svg.TagClasses = function() { 'highway', 'railway', 'waterway', 'power', 'motorway', 'amenity', 'natural', 'landuse', 'building', 'oneway', 'bridge', 'boundary', 'leisure', 'construction' - ]), tagClassRe = /^tag-/; + ]), tagClassRe = /^tag-/, + tags = function(entity) { return entity.tags; }; - return function tagClassesSelection(selection) { - selection.each(function tagClassesEach(d, i) { + var tagClasses = function(selection) { + selection.each(function tagClassesEach(entity) { var classes, value = this.className; if (value.baseVal !== undefined) value = value.baseVal; @@ -15,11 +16,10 @@ iD.svg.TagClasses = function() { return name.length && !tagClassRe.test(name); }).join(' '); - var tags = d.tags; - for (var k in tags) { + var t = tags(entity); + for (var k in t) { if (!keys[k]) continue; - classes += ' tag-' + k + ' ' + - 'tag-' + k + '-' + tags[k]; + classes += ' tag-' + k + ' ' + 'tag-' + k + '-' + t[k]; } classes = classes.trim(); @@ -29,4 +29,12 @@ iD.svg.TagClasses = function() { } }); }; + + tagClasses.tags = function(_) { + if (!arguments.length) return tags; + tags = _; + return tagClasses; + }; + + return tagClasses; }; diff --git a/test/index.html b/test/index.html index 5f7c19b5a..522f26092 100644 --- a/test/index.html +++ b/test/index.html @@ -47,7 +47,6 @@ - @@ -184,7 +183,6 @@ - diff --git a/test/index_packaged.html b/test/index_packaged.html index d21d48ff2..8dd73d666 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -66,7 +66,6 @@ - diff --git a/test/spec/graph/way.js b/test/spec/graph/way.js index 39393ab16..05864d5c3 100644 --- a/test/spec/graph/way.js +++ b/test/spec/graph/way.js @@ -95,8 +95,12 @@ describe('iD.Way', function() { expect(iD.Way({tags: { area: 'yes' }}).isArea()).to.equal(true); }); - it('returns true if the way is closed and has no tags', function() { - expect(iD.Way({nodes: ['n1', 'n1']}).isArea()).to.equal(true); + it('returns false if the way is closed and has no tags', function() { + expect(iD.Way({nodes: ['n1', 'n1']}).isArea()).to.equal(false); + }); + + it('returns true if the way is closed and has tags', function() { + expect(iD.Way({nodes: ['n1', 'n1'], tags: {a: 'b'}}).isArea()).to.equal(true); }); it('returns false if the way is closed and has tag area=no', function() { diff --git a/test/spec/svg/areas.js b/test/spec/svg/areas.js index 807a799f4..bbf8674ae 100644 --- a/test/spec/svg/areas.js +++ b/test/spec/svg/areas.js @@ -69,4 +69,46 @@ describe("iD.svg.Areas", function () { expect(surface.select('.area:nth-child(1)')).to.be.classed('tag-landuse-park'); expect(surface.select('.area:nth-child(2)')).to.be.classed('tag-building-yes'); }); + + it("renders fills for multipolygon areas", function () { + var a = iD.Node({loc: [1, 1]}), + b = iD.Node({loc: [2, 2]}), + c = iD.Node({loc: [3, 3]}), + w = iD.Way({nodes: [a.id, b.id, c.id, a.id]}), + r = iD.Relation({tags: {type: 'multipolygon'}, members: [{id: w.id, type: 'way'}]}), + graph = iD.Graph([a, b, c, w, r]), + areas = [w, r]; + + surface.call(iD.svg.Areas(projection), graph, areas, filter); + + expect(surface.select('.fill')).to.be.classed('relation'); + }); + + it("renders no strokes for multipolygon areas", function () { + var a = iD.Node({loc: [1, 1]}), + b = iD.Node({loc: [2, 2]}), + c = iD.Node({loc: [3, 3]}), + w = iD.Way({nodes: [a.id, b.id, c.id, a.id]}), + r = iD.Relation({tags: {type: 'multipolygon'}, members: [{id: w.id, type: 'way'}]}), + graph = iD.Graph([a, b, c, w, r]), + areas = [w, r]; + + surface.call(iD.svg.Areas(projection), graph, areas, filter); + + expect(surface.selectAll('.stroke')[0].length).to.equal(0); + }); + + it("adds stroke classes for the tags of the parent relation of multipolygon members", function() { + var a = iD.Node({loc: [1, 1]}), + b = iD.Node({loc: [2, 2]}), + c = iD.Node({loc: [3, 3]}), + w = iD.Way({tags: {area: 'yes'}, nodes: [a.id, b.id, c.id, a.id]}), + r = iD.Relation({members: [{id: w.id}], tags: {type: 'multipolygon', natural: 'wood'}}), + graph = iD.Graph([a, b, c, w, r]); + + surface.call(iD.svg.Areas(projection), graph, [w], filter); + + expect(surface.select('.stroke')).to.be.classed('tag-natural-wood'); + expect(surface.select('.fill')).not.to.be.classed('tag-natural-wood'); + }); }); diff --git a/test/spec/svg/lines.js b/test/spec/svg/lines.js index 61c3229a9..989022632 100644 --- a/test/spec/svg/lines.js +++ b/test/spec/svg/lines.js @@ -39,6 +39,16 @@ describe("iD.svg.Lines", function () { expect(surface.select('.line')).to.be.classed('member-type-route'); }); + it("adds stroke classes for the tags of the parent relation of multipolygon members", function() { + var line = iD.Way(), + relation = iD.Relation({members: [{id: line.id}], tags: {type: 'multipolygon', natural: 'wood'}}), + graph = iD.Graph([line, relation]); + + surface.call(iD.svg.Lines(projection), graph, [line], filter); + + expect(surface.select('.stroke')).to.be.classed('tag-natural-wood'); + }); + it("preserves non-line paths", function () { var line = iD.Way(), graph = iD.Graph([line]); diff --git a/test/spec/svg/multipolygons.js b/test/spec/svg/multipolygons.js deleted file mode 100644 index 67f44ebae..000000000 --- a/test/spec/svg/multipolygons.js +++ /dev/null @@ -1,43 +0,0 @@ -describe("iD.svg.Multipolygons", function () { - var surface, - projection = Object, - filter = d3.functor(true); - - beforeEach(function () { - surface = d3.select(document.createElementNS('http://www.w3.org/2000/svg', 'svg')) - .call(iD.svg.Surface()); - }); - - it("adds relation and multipolygon classes", function () { - var relation = iD.Relation({tags: {type: 'multipolygon'}}), - graph = iD.Graph([relation]); - - surface.call(iD.svg.Multipolygons(projection), graph, [relation], filter); - - expect(surface.select('path')).to.be.classed('relation'); - expect(surface.select('path')).to.be.classed('multipolygon'); - }); - - it("adds tag classes", function () { - var relation = iD.Relation({tags: {type: 'multipolygon', boundary: "administrative"}}), - graph = iD.Graph([relation]); - - surface.call(iD.svg.Multipolygons(projection), graph, [relation], filter); - - expect(surface.select('.relation')).to.be.classed('tag-boundary'); - expect(surface.select('.relation')).to.be.classed('tag-boundary-administrative'); - }); - - it("preserves non-multipolygon paths", function () { - var relation = iD.Relation({tags: {type: 'multipolygon'}}), - graph = iD.Graph([relation]); - - surface.select('.layer-fill') - .append('path') - .attr('class', 'other'); - - surface.call(iD.svg.Multipolygons(projection), graph, [relation], filter); - - expect(surface.selectAll('.other')[0].length).to.equal(1); - }); -}); diff --git a/test/spec/svg/tag_classes.js b/test/spec/svg/tag_classes.js index dd899e08e..048378815 100644 --- a/test/spec/svg/tag_classes.js +++ b/test/spec/svg/tag_classes.js @@ -19,6 +19,13 @@ describe("iD.svg.TagClasses", function () { expect(selection.attr('class')).to.equal('tag-highway tag-highway-primary'); }); + it('adds tags based on the result of the `tags` accessor', function() { + selection + .datum(iD.Entity()) + .call(iD.svg.TagClasses().tags(d3.functor({highway: 'primary'}))); + expect(selection.attr('class')).to.equal('tag-highway tag-highway-primary'); + }); + it('removes classes for tags that are no longer present', function() { selection .attr('class', 'tag-highway tag-highway-primary') From 14fc1d9c0d1fed338a5ea99a279fe74eb1f3bea8 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Tue, 5 Feb 2013 18:47:28 -0500 Subject: [PATCH 117/332] Fix flickering after redrawing active elems --- js/id/behavior/draw_way.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 6b648db0e..8c4c4a5cc 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -28,7 +28,13 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { function move(datum) { var loc = context.map().mouseCoordinates(); - if (datum.type === 'node') { + if (datum.id === end.id || datum.id === segment.id) { + context.surface().selectAll('.way, .node') + .filter(function (d) { + return d.id === end.id || d.id === segment.id; + }) + .classed('active', true); + } else if (datum.type === 'node') { loc = datum.loc; } else if (datum.type === 'way') { loc = iD.geo.chooseIndex(datum, d3.mouse(context.surface().node()), context).loc; From 8f17628190b603f243c81e04fc2efb518be49800 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Tue, 5 Feb 2013 19:09:22 -0500 Subject: [PATCH 118/332] Support nudging while moving ways. Fixes #533 --- js/id/modes/move_way.js | 42 ++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/js/id/modes/move_way.js b/js/id/modes/move_way.js index 986eef5bd..e21dded78 100644 --- a/js/id/modes/move_way.js +++ b/js/id/modes/move_way.js @@ -6,7 +6,8 @@ iD.modes.MoveWay = function(context, wayId) { var keybinding = d3.keybinding('move-way'); mode.enter = function() { - var origin = point(), + var origin = context.map().mouseCoordinates(), + nudgeInterval, annotation = t('operations.move.annotation.' + context.geometry(wayId)); // If intiated via keyboard @@ -16,17 +17,44 @@ iD.modes.MoveWay = function(context, wayId) { iD.actions.Noop(), annotation); + function edge(point, size) { + var pad = [30, 100, 30, 100]; + if (point[0] > size[0] - pad[0]) return [-10, 0]; + else if (point[0] < pad[2]) return [10, 0]; + else if (point[1] > size[1] - pad[1]) return [0, -10]; + else if (point[1] < pad[3]) return [0, 10]; + return null; + } + + function startNudge(nudge) { + if (nudgeInterval) window.clearInterval(nudgeInterval); + nudgeInterval = window.setInterval(function() { + context.map().pan(nudge).redraw(); + }, 50); + } + + function stopNudge() { + if (nudgeInterval) window.clearInterval(nudgeInterval); + nudgeInterval = null; + } + function point() { - return d3.mouse(context.surface().node()); + return d3.mouse(context.map().surface.node()); } function move() { - var p = point(), - delta = origin ? - [p[0] - origin[0], p[1] - origin[1]] : - [0, 0]; + var p = point(); - origin = p; + var delta = origin ? + [p[0] - context.projection(origin)[0], + p[1] - context.projection(origin)[1]] : + [0, 0]; + + var nudge = edge(p, context.map().size()); + if (nudge) startNudge(nudge); + else stopNudge(); + + origin = context.map().mouseCoordinates(); context.replace( iD.actions.MoveWay(wayId, delta, context.projection), From 8bee68dfbd9300326216d8b979c481e5babd8195 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 5 Feb 2013 16:25:20 -0800 Subject: [PATCH 119/332] Fix includes --- test/rendering.html | 1 - 1 file changed, 1 deletion(-) diff --git a/test/rendering.html b/test/rendering.html index 196122b2c..ff624305a 100644 --- a/test/rendering.html +++ b/test/rendering.html @@ -21,7 +21,6 @@ - From 4dbd8f5efcde8643fd2b7974d7a64bd7a6b99fa4 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 5 Feb 2013 16:25:26 -0800 Subject: [PATCH 120/332] Fix #634 --- js/id/svg/points.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/svg/points.js b/js/id/svg/points.js index 471bc4b4d..a7fdb10fe 100644 --- a/js/id/svg/points.js +++ b/js/id/svg/points.js @@ -79,7 +79,7 @@ iD.svg.Points.imageIndex = [ }, { tags: { man_made: 'lighthouse' }, - icon: 'lighthouselevel_crossing' + icon: 'lighthouse' }, { tags: { natural: 'peak' }, From a4bf7c689fce0d63914b9ecf52740ca130c48f59 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 6 Feb 2013 10:49:58 -0500 Subject: [PATCH 121/332] Merge DragNode and DragMidpoint Adds shared behaviors such as snapping to DragMidpoint --- combobox.html | 1 - index.html | 1 - js/id/behavior/drag_midpoint.js | 29 ---------------------------- js/id/behavior/drag_node.js | 34 +++++++++++++++++++++++++++------ js/id/modes/browse.js | 3 +-- js/id/modes/select.js | 3 +-- test/index.html | 1 - 7 files changed, 30 insertions(+), 42 deletions(-) delete mode 100644 js/id/behavior/drag_midpoint.js diff --git a/combobox.html b/combobox.html index c739f4898..92949ecfa 100644 --- a/combobox.html +++ b/combobox.html @@ -90,7 +90,6 @@ - diff --git a/index.html b/index.html index a58aeb8f2..807e6a413 100644 --- a/index.html +++ b/index.html @@ -93,7 +93,6 @@ - diff --git a/js/id/behavior/drag_midpoint.js b/js/id/behavior/drag_midpoint.js deleted file mode 100644 index 4e39be2c6..000000000 --- a/js/id/behavior/drag_midpoint.js +++ /dev/null @@ -1,29 +0,0 @@ -iD.behavior.DragMidpoint = function(context) { - var behavior = iD.behavior.drag() - .delegate(".midpoint") - .origin(function(d) { - return context.projection(d.loc); - }) - .on('start', function(d) { - var node = iD.Node(); - - context.perform(iD.actions.AddMidpoint(d, node)); - - var vertex = context.surface().selectAll('.vertex') - .filter(function(data) { return data.id === node.id; }); - - behavior.target(vertex.node(), vertex.datum()); - }) - .on('move', function(d) { - d3.event.sourceEvent.stopPropagation(); - context.replace( - iD.actions.MoveNode(d.id, context.projection.invert(d3.event.point))); - }) - .on('end', function() { - context.replace( - iD.actions.Noop(), - t('operations.add.annotation.vertex')); - }); - - return behavior; -}; diff --git a/js/id/behavior/drag_node.js b/js/id/behavior/drag_node.js index 6df1fbf28..acba7919d 100644 --- a/js/id/behavior/drag_node.js +++ b/js/id/behavior/drag_node.js @@ -1,5 +1,6 @@ iD.behavior.DragNode = function(context) { - var nudgeInterval; + var nudgeInterval, + wasMidpoint; function edge(point, size) { var pad = [30, 100, 30, 100]; @@ -35,6 +36,23 @@ iD.behavior.DragNode = function(context) { } function start(entity) { + + wasMidpoint = entity.type === 'midpoint'; + if (wasMidpoint) { + var midpoint = entity; + entity = iD.Node(); + context.perform(iD.actions.AddMidpoint(midpoint, entity)); + + var vertex = context.surface() + .selectAll('.vertex') + .filter(function(d) { return d.id === entity.id; }); + behavior.target(vertex.node(), entity); + + } else { + context.perform( + iD.actions.Noop()); + } + var activeIDs = _.pluck(context.graph().parentWays(entity), 'id'); activeIDs.push(entity.id); @@ -43,9 +61,6 @@ iD.behavior.DragNode = function(context) { .selectAll('.node, .way') .filter(function (d) { return activeIDs.indexOf(d.id) >= 0; }) .classed('active', true); - - context.perform( - iD.actions.Noop()); } function datum() { @@ -96,6 +111,11 @@ iD.behavior.DragNode = function(context) { iD.actions.Connect([entity.id, d.id]), connectAnnotation(d)); + } else if (wasMidpoint) { + context.replace( + iD.actions.Noop(), + t('operations.add.annotation.vertex')); + } else { context.replace( iD.actions.Noop(), @@ -103,10 +123,12 @@ iD.behavior.DragNode = function(context) { } } - return iD.behavior.drag() - .delegate("g.node") + var behavior = iD.behavior.drag() + .delegate("g.node, g.midpoint") .origin(origin) .on('start', start) .on('move', move) .on('end', end); + + return behavior; }; diff --git a/js/id/modes/browse.js b/js/id/modes/browse.js index 33add1262..97f0d6935 100644 --- a/js/id/modes/browse.js +++ b/js/id/modes/browse.js @@ -10,8 +10,7 @@ iD.modes.Browse = function(context) { var behaviors = [ iD.behavior.Hover(), iD.behavior.Select(context), - iD.behavior.DragNode(context), - iD.behavior.DragMidpoint(context)]; + iD.behavior.DragNode(context)]; mode.enter = function() { behaviors.forEach(function(behavior) { diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 532faaca8..0bcfa8c0b 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -9,8 +9,7 @@ iD.modes.Select = function(context, selection, initial) { behaviors = [ iD.behavior.Hover(), iD.behavior.Select(context), - iD.behavior.DragNode(context), - iD.behavior.DragMidpoint(context)], + iD.behavior.DragNode(context)], radialMenu; function changeTags(d, tags) { diff --git a/test/index.html b/test/index.html index 522f26092..f17829653 100644 --- a/test/index.html +++ b/test/index.html @@ -90,7 +90,6 @@ - From a21da4f15fef4b149d2ea88d5927093ef6ec4e98 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 6 Feb 2013 12:48:42 -0500 Subject: [PATCH 122/332] Saving graph to and reinstating from localStorage --- js/id/graph/graph.js | 45 +++++++++++++++++++++++++++++++++++++++++- js/id/graph/history.js | 45 +++++++++++++++++++++++++++++++++++++++++- js/id/id.js | 15 +++++++------- js/id/ui.js | 3 ++- js/id/ui/splash.js | 4 ++-- 5 files changed, 100 insertions(+), 12 deletions(-) diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index 2692aeba6..d9118045a 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -227,10 +227,53 @@ iD.Graph.prototype = { var items = []; for (var i in this.entities) { var entity = this.entities[i]; - if (entity && entity.intersects(extent, this)) { + if (entity && this.hasAllChildren(entity) && entity.intersects(extent, this)) { items.push(entity); } } return items; + }, + + hasAllChildren: function(entity) { + // we're only checking changed entities, since we assume fetched data + // must have all children present + if (this.entities.hasOwnProperty(entity.id)) { + if (entity.type === 'way') { + for (i = 0; i < entity.nodes.length; i++) { + if (!this.entities[entity.nodes[i]]) return false; + } + } else if (entity.type === 'relation') { + for (i = 0; i < entity.members.length; i++) { + if (!this.entities[entity.members[i].id]) return false; + } + } + } + return true; + }, + + // Obliterates any existing entities + load: function(entities) { + + var base = this.base(), + i, entity, prefix; + this.entities = Object.create(base.entities); + + for (i in entities) { + entity = entities[i]; + prefix = i[0]; + + if (prefix == 'n') { + this.entities[i] = new iD.Node(entity); + + } else if (prefix == 'w') { + this.entities[i] = new iD.Way(entity); + + } else if (prefix == 'r') { + this.entities[i] = new iD.Relation(entity); + } + this._updateCalculated(base.entities[i], this.entities[i]); + } + return this; } + }; diff --git a/js/id/graph/history.js b/js/id/graph/history.js index a0930203c..dba04ec7e 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -1,4 +1,4 @@ -iD.History = function() { +iD.History = function(context) { var stack, index, imagery_used = 'Bing', dispatch = d3.dispatch('change', 'undone', 'redone'); @@ -26,6 +26,10 @@ iD.History = function() { return difference; } + function getKey(n) { + return 'iD_' + window.location.origin + '_' + n; + } + var history = { graph: function() { return stack[index].graph; @@ -148,8 +152,47 @@ iD.History = function() { reset: function() { stack = [{graph: iD.Graph()}]; index = 0; + this.load(); dispatch.change(); + }, + + save: function() { + var json = JSON.stringify(stack.map(function(i) { + return _.extend(i, { + graph: i.graph.entities + }); + })); + + context.storage(getKey('history'), json); + context.storage(getKey('nextIDs'), JSON.stringify(iD.Entity.id.next)); + context.storage(getKey('index'), index); + context.storage(getKey('lock'), ''); + }, + + lock: function() { + if (context.storage(getKey('lock'))) return false; + context.storage(getKey('lock'), true); + return true; + }, + + load: function() { + if (!this.lock()) return; + + var json = context.storage(getKey('history')), + nextIDs = context.storage(getKey('nextIDs')), + index_ = context.storage(getKey('index')); + + if (!json) return; + if (nextIDs) iD.Entity.id.next = JSON.parse(nextIDs); + if (index_ !== null) index = parseInt(index_, 10); + + stack = JSON.parse(json).map(function(d) { + d.graph = iD.Graph().load(d.graph); + return d; + }); + } + }; history.reset(); diff --git a/js/id/id.js b/js/id/id.js index 56d3e481a..e20b3b111 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -1,18 +1,19 @@ window.iD = function () { var context = {}, - history = iD.History(), - storage = localStorage || {}, - dispatch = d3.dispatch('enter', 'exit'), - mode, - container, - ui = iD.ui(context), - map = iD.Map(context); + storage = localStorage || {}; context.storage = function(k, v) { if (arguments.length === 1) return storage[k]; else storage[k] = v; }; + var history = iD.History(context), + dispatch = d3.dispatch('enter', 'exit'), + mode, + container, + ui = iD.ui(context), + map = iD.Map(context); + // the connection requires .storage() to be available on calling. var connection = iD.Connection(context); diff --git a/js/id/ui.js b/js/id/ui.js index ace66703d..dbbff15c8 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -194,6 +194,7 @@ iD.ui = function(context) { history.on('change.editor', function() { window.onbeforeunload = history.hasChanges() ? function() { + history.save(); return 'You have unsaved changes.'; } : null; @@ -252,7 +253,7 @@ iD.ui = function(context) { context.enter(iD.modes.Browse(context)); if (!context.storage('sawSplash')) { - iD.ui.splash(); + iD.ui.splash(context.container()); context.storage('sawSplash', true); } }; diff --git a/js/id/ui/splash.js b/js/id/ui/splash.js index a4d2a915e..bfcbf3e05 100644 --- a/js/id/ui/splash.js +++ b/js/id/ui/splash.js @@ -1,5 +1,5 @@ -iD.ui.splash = function() { - var modal = iD.ui.modal(); +iD.ui.splash = function(selection) { + var modal = iD.ui.modal(selection); modal.select('.modal') .attr('class', 'modal-splash modal'); From 449c4d235da1f974f2b9c660baeb63a3c431c7b9 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 6 Feb 2013 14:03:31 -0500 Subject: [PATCH 123/332] Add option to restore or reset unsaved changes --- index.html | 1 + js/id/graph/history.js | 34 +++++++++++++++++++++++++++------- js/id/id.js | 1 + js/id/ui.js | 5 +++++ js/id/ui/restore.js | 34 ++++++++++++++++++++++++++++++++++ 5 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 js/id/ui/restore.js diff --git a/index.html b/index.html index 807e6a413..18f1b275b 100644 --- a/index.html +++ b/index.html @@ -67,6 +67,7 @@ + diff --git a/js/id/graph/history.js b/js/id/graph/history.js index dba04ec7e..ac36d564f 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -1,7 +1,8 @@ iD.History = function(context) { var stack, index, imagery_used = 'Bing', - dispatch = d3.dispatch('change', 'undone', 'redone'); + dispatch = d3.dispatch('change', 'undone', 'redone'), + lock = false; function perform(actions) { actions = Array.prototype.slice.call(actions); @@ -152,11 +153,20 @@ iD.History = function(context) { reset: function() { stack = [{graph: iD.Graph()}]; index = 0; - this.load(); dispatch.change(); }, save: function() { + if (!lock) return; + context.storage(getKey('lock'), null); + + if (!stack.length) { + context.storage(getKey('history'), null); + context.storage(getKey('nextIDs'), null); + context.storage(getKey('index'), null); + return; + } + var json = JSON.stringify(stack.map(function(i) { return _.extend(i, { graph: i.graph.entities @@ -166,17 +176,22 @@ iD.History = function(context) { context.storage(getKey('history'), json); context.storage(getKey('nextIDs'), JSON.stringify(iD.Entity.id.next)); context.storage(getKey('index'), index); - context.storage(getKey('lock'), ''); }, lock: function() { if (context.storage(getKey('lock'))) return false; context.storage(getKey('lock'), true); - return true; + lock = true; + return lock; + }, + + restorableChanges: function() { + if (!this.lock()) return false; + return !!context.storage(getKey('history')); }, load: function() { - if (!this.lock()) return; + if (!lock) return; var json = context.storage(getKey('history')), nextIDs = context.storage(getKey('nextIDs')), @@ -186,10 +201,15 @@ iD.History = function(context) { if (nextIDs) iD.Entity.id.next = JSON.parse(nextIDs); if (index_ !== null) index = parseInt(index_, 10); - stack = JSON.parse(json).map(function(d) { - d.graph = iD.Graph().load(d.graph); + context.storage(getKey('history', null)); + context.storage(getKey('nextIDs', null)); + context.storage(getKey('index', null)); + + stack = JSON.parse(json).map(function(d, i) { + d.graph = iD.Graph(stack[0].graph).load(d.graph); return d; }); + dispatch.change(); } diff --git a/js/id/id.js b/js/id/id.js index e20b3b111..d58464ecb 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -4,6 +4,7 @@ window.iD = function () { context.storage = function(k, v) { if (arguments.length === 1) return storage[k]; + else if (v === null) delete storage[k]; else storage[k] = v; }; diff --git a/js/id/ui.js b/js/id/ui.js index dbbff15c8..c22ca6b83 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -256,5 +256,10 @@ iD.ui = function(context) { iD.ui.splash(context.container()); context.storage('sawSplash', true); } + + if (history.restorableChanges()) { + iD.ui.restore(context.container(), history); + } + }; }; diff --git a/js/id/ui/restore.js b/js/id/ui/restore.js new file mode 100644 index 000000000..3df275d89 --- /dev/null +++ b/js/id/ui/restore.js @@ -0,0 +1,34 @@ +iD.ui.restore = function(selection, history) { + var modal = iD.ui.modal(selection); + + modal.select('.modal') + .attr('class', 'modal-splash modal'); + + var introModal = modal.select('.content') + .append('div') + .attr('class', 'modal-section fillL') + .text('You have unsaved changes from a previous editing session. Do you wish to restore these changes?'); + + buttons = introModal + .append('div') + .attr('class', 'buttons cf') + .append('div') + .attr('class', 'button-wrap joined col4'); + + buttons.append('button') + .attr('class', 'save action button col6') + .text('Restore') + .on('click', function() { + history.load(); + modal.remove(); + }); + + buttons.append('button') + .attr('class', 'cancel button col6') + .text('Reset') + .on('click', function() { + modal.remove(); + }); + + return modal; +}; From 0d70e466de619185d6752a7f609663b513f9e502 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Wed, 6 Feb 2013 14:37:04 -0500 Subject: [PATCH 124/332] Add crazyegg, will remove at release --- index.html | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/index.html b/index.html index 18f1b275b..09d73b8f6 100644 --- a/index.html +++ b/index.html @@ -152,6 +152,8 @@ .call(id.ui()) }); + + + + + From 0acab34054131544587b486c35ee6ee5cee73ae7 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 6 Feb 2013 15:06:50 -0500 Subject: [PATCH 125/332] Draw click event triggered by click instead of up --- js/id/behavior/draw.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/js/id/behavior/draw.js b/js/id/behavior/draw.js index a4fb53a1a..b0da771a4 100644 --- a/js/id/behavior/draw.js +++ b/js/id/behavior/draw.js @@ -27,7 +27,7 @@ iD.behavior.Draw = function(context) { target.on('mousemove.draw', null); - d3.select(window).on('mouseup.draw', function() { + d3.select(window).on('click.draw', function() { target.on('mousemove.draw', mousemove); if (iD.geo.dist(pos, point()) < closeTolerance || (iD.geo.dist(pos, point()) < tolerance && @@ -35,7 +35,6 @@ iD.behavior.Draw = function(context) { click(); } }); - } function mousemove() { @@ -111,7 +110,7 @@ iD.behavior.Draw = function(context) { .on('mousedown.draw', null) .on('mousemove.draw', null); - d3.select(window).on('mouseup.draw', null); + d3.select(window).on('click.draw', null); d3.select(document) .call(keybinding.off) From a9632a2c7a05275315e25d3983110e9cf38cad5e Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Wed, 6 Feb 2013 15:09:40 -0500 Subject: [PATCH 126/332] Do not trigger radial on double click --- js/id/behavior/select.js | 72 +++++++++++++++++++++++++++++++--------- 1 file changed, 57 insertions(+), 15 deletions(-) diff --git a/js/id/behavior/select.js b/js/id/behavior/select.js index f7a388396..3d10db144 100644 --- a/js/id/behavior/select.js +++ b/js/id/behavior/select.js @@ -1,24 +1,66 @@ iD.behavior.Select = function(context) { - function click() { - var datum = d3.select(d3.event.target).datum(); - if (datum instanceof iD.Entity) { - if (d3.event.shiftKey) { - context.enter(iD.modes.Select(context, context.selection().concat([datum.id]))); - } else { - context.enter(iD.modes.Select(context, [datum.id])); - } - } else if (!d3.event.shiftKey) { - context.enter(iD.modes.Browse(context)); - } - } - var behavior = function(selection) { - selection.on('click.select', click); + + var timeout = null, + // the position of the first mousedown + pos = null; + + function click(event) { + d3.event = event; + var datum = d3.select(d3.event.target).datum(); + if (datum instanceof iD.Entity) { + if (d3.event.shiftKey) { + context.enter(iD.modes.Select(context, context.selection().concat([datum.id]))); + } else { + context.enter(iD.modes.Select(context, [datum.id])); + } + } else if (!d3.event.shiftKey) { + context.enter(iD.modes.Browse(context)); + } + } + + function mousedown() { + pos = d3.mouse(context.surface().node()); + selection + .on('mousemove.select', mousemove) + .on('touchmove.select', mousemove); + + // we've seen a mousedown within 400ms of this one, so ignore + // both because they will be a double click + if (timeout !== null) { + window.clearTimeout(timeout); + selection.on('mousemove.select', null); + timeout = null; + } else { + // queue the click handler to fire in 400ms if no other clicks + // are detected + timeout = window.setTimeout((function(event) { + return function() { + click(event); + timeout = null; + selection.on('mousemove.select', null); + }; + // save the event for the click handler + })(d3.event), 400); + } + } + + // allow mousemoves to cancel the click + function mousemove() { + if (iD.geo.dist(d3.mouse(context.surface().node()), pos) > 4) { + window.clearTimeout(timeout); + timeout = null; + } + } + + selection + .on('mousedown.select', mousedown) + .on('touchstart.select', mousedown); }; behavior.off = function(selection) { - selection.on('click.select', null); + selection.on('mousedown.select', null); }; return behavior; From 83224d0f87ec11a62903a0efba7896cf60a13702 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 6 Feb 2013 15:38:20 -0500 Subject: [PATCH 127/332] imagery_used includes full custom template --- js/id/renderer/background_source.js | 1 + js/id/ui/layerswitcher.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/js/id/renderer/background_source.js b/js/id/renderer/background_source.js index cfed23294..e740525c0 100644 --- a/js/id/renderer/background_source.js +++ b/js/id/renderer/background_source.js @@ -24,6 +24,7 @@ iD.BackgroundSource.template = function(template, subdomains, scaleExtent) { }; generator.scaleExtent = scaleExtent; + generator.template = template; return generator; }; diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index 9eb063517..a15cd33c3 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -125,7 +125,7 @@ iD.ui.layerswitcher = function(context) { var configured = d.source(); if (!configured) return; d.source = configured; - d.name = 'Custom (configured)'; + d.name = 'Custom (' + d.source.template + ')'; } context.background().source(d.source); context.history().imagery_used(d.name); From fbe3a41d570441f0cc32ae414c5f858db9d885f9 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Wed, 6 Feb 2013 15:47:16 -0500 Subject: [PATCH 128/332] Update tests for faux click events in more places. --- test/index.html | 2 +- test/spec/behavior/select.js | 26 ++++++++++++++++++-------- test/spec/modes/add_point.js | 4 ++-- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/test/index.html b/test/index.html index f17829653..3f2eba72e 100644 --- a/test/index.html +++ b/test/index.html @@ -137,7 +137,7 @@ iD.debug = true; mocha.setup({ ui: 'bdd', - globals: ['__onresize.tail-size'] + globals: ['__onresize.tail-size', '__onmousemove.zoom', '__onmouseup.zoom', '__onclick.draw'] }); var expect = chai.expect; diff --git a/test/spec/behavior/select.js b/test/spec/behavior/select.js index 42c568cca..e794cda8e 100644 --- a/test/spec/behavior/select.js +++ b/test/spec/behavior/select.js @@ -29,21 +29,31 @@ describe("iD.behavior.Select", function() { container.remove(); }); - specify("click on entity selects the entity", function() { - happen.click(context.surface().select('.' + a.id).node()); - expect(context.selection()).to.eql([a.id]); + specify("click on entity selects the entity", function(done) { + happen.mousedown(context.surface().select('.' + a.id).node()); + window.setTimeout(function() { + expect(context.selection()).to.eql([a.id]); + done(); + }, 600); }); - specify("click on empty space clears the selection", function() { + specify("click on empty space clears the selection", function(done) { context.enter(iD.modes.Select(context, [a.id])); happen.click(context.surface().node()); - expect(context.selection()).to.eql([]); + happen.mousedown(context.surface().node()); + window.setTimeout(function() { + expect(context.selection()).to.eql([]); + done(); + }, 600); }); - specify("shift-click on entity adds the entity to the selection", function() { + specify("shift-click on entity adds the entity to the selection", function(done) { context.enter(iD.modes.Select(context, [a.id])); - happen.click(context.surface().select('.' + b.id).node(), {shiftKey: true}); - expect(context.selection()).to.eql([a.id, b.id]); + happen.mousedown(context.surface().select('.' + b.id).node(), {shiftKey: true}); + window.setTimeout(function() { + expect(context.selection()).to.eql([a.id, b.id]); + done(); + }, 600); }); specify("shift-click on empty space leaves the selection unchanged", function() { diff --git a/test/spec/modes/add_point.js b/test/spec/modes/add_point.js index 62291f8ee..5807a3c0b 100644 --- a/test/spec/modes/add_point.js +++ b/test/spec/modes/add_point.js @@ -17,13 +17,13 @@ describe("iD.modes.AddPoint", function() { describe("clicking the map", function () { it("adds a node", function() { happen.mousedown(context.surface().node(), {}); - happen.mouseup(window, {}); + happen.click(window, {}); expect(context.changes().created).to.have.length(1); }); it("selects the node", function() { happen.mousedown(context.surface().node(), {}); - happen.mouseup(window, {}); + happen.click(window, {}); expect(context.mode().id).to.equal('select'); expect(context.mode().selection()).to.eql([context.changes().created[0].id]); }); From 3440f4254ddb468e6d4453bf2f9f7c9c839adac6 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Wed, 6 Feb 2013 16:22:39 -0500 Subject: [PATCH 129/332] Discard tags --- data/deprecated.js | 10 +--------- data/discarded.js | 10 ++++++++++ index.html | 2 ++ js/id/behavior/select.js | 2 +- js/id/graph/history.js | 10 +++++++--- 5 files changed, 21 insertions(+), 13 deletions(-) create mode 100644 data/discarded.js diff --git a/data/deprecated.js b/data/deprecated.js index 7cc6904ed..53457c9f3 100644 --- a/data/deprecated.js +++ b/data/deprecated.js @@ -108,13 +108,5 @@ iD.data.deprecated = [ shop: 'supermarket', organic: 'only' } - }, - // entirely discarded tags - { old: { 'tiger:upload_uuid': '*' } }, - { old: { 'tiger:tlid': '*' } }, - { old: { 'tiger:source': '*' } }, - { old: { 'tiger:separated': '*' } }, - { old: { 'geobase:datasetName': '*' } }, - { old: { 'geobase:uuid': '*' } }, - { old: { 'sub_sea:type': '*' } } + } ]; diff --git a/data/discarded.js b/data/discarded.js new file mode 100644 index 000000000..7a1e4580e --- /dev/null +++ b/data/discarded.js @@ -0,0 +1,10 @@ +// entirely discarded tags +iD.data.discarded = [ + 'tiger:upload_uuid', + 'tiger:tlid', + 'tiger:source', + 'tiger:separated', + 'geobase:datasetName', + 'geobase:uuid', + 'sub_sea:type' +]; diff --git a/index.html b/index.html index 09d73b8f6..a4a2ab5f5 100644 --- a/index.html +++ b/index.html @@ -90,6 +90,7 @@ + @@ -137,6 +138,7 @@ +
+ diff --git a/js/id/actions/merge.js b/js/id/actions/merge.js new file mode 100644 index 000000000..8ba2a2318 --- /dev/null +++ b/js/id/actions/merge.js @@ -0,0 +1,35 @@ +iD.actions.Merge = function(ids) { + function groupEntitiesByGeometry(graph) { + var entities = ids.map(function(id) { return graph.entity(id); }); + return _.extend({point: [], area: []}, _.groupBy(entities, function(entity) { return entity.geometry(graph); })); + } + + var action = function(graph) { + var geometries = groupEntitiesByGeometry(graph), + area = geometries['area'][0], + points = geometries['point']; + + points.forEach(function (point) { + area = area.mergeTags(point.tags); + + graph.parentRelations(point).forEach(function (parent) { + graph = graph.replace(parent.replaceMember(point, area)); + }); + + graph = graph.remove(point); + }); + + graph = graph.replace(area); + + return graph; + }; + + action.enabled = function(graph) { + var geometries = groupEntitiesByGeometry(graph); + return geometries['area'].length === 1 && + geometries['point'].length > 0 && + (geometries['area'].length + geometries['point'].length) === ids.length; + }; + + return action; +}; diff --git a/js/id/operations/merge.js b/js/id/operations/merge.js index b2073db52..6202f1b3f 100644 --- a/js/id/operations/merge.js +++ b/js/id/operations/merge.js @@ -1,18 +1,28 @@ iD.operations.Merge = function(selection, context) { - var action = iD.actions.Join(selection); + var join = iD.actions.Join(selection), + merge = iD.actions.Merge(selection); var operation = function() { var annotation = t('operations.merge.annotation', {n: selection.length}), - difference = context.perform(action, annotation); + action; + + if (join.enabled(context.graph())) { + action = join; + } else { + action = merge; + } + + var difference = context.perform(action, annotation); context.enter(iD.modes.Select(context, difference.extantIDs())); }; operation.available = function() { - return selection.length > 1; + return selection.length >= 2; }; operation.enabled = function() { - return action.enabled(context.graph()); + return join.enabled(context.graph()) || + merge.enabled(context.graph()); }; operation.id = "merge"; diff --git a/test/index.html b/test/index.html index 3f2eba72e..ecad5ca75 100644 --- a/test/index.html +++ b/test/index.html @@ -80,6 +80,7 @@ + @@ -157,6 +158,7 @@ + diff --git a/test/index_packaged.html b/test/index_packaged.html index 8dd73d666..f85ac1f0b 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -42,6 +42,7 @@ + diff --git a/test/spec/actions/merge.js b/test/spec/actions/merge.js new file mode 100644 index 000000000..5ea0b3dea --- /dev/null +++ b/test/spec/actions/merge.js @@ -0,0 +1,20 @@ +describe("iD.actions.Merge", function () { + it("merges multiple points to an area", function () { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a', tags: {a: 'a'}}), + 'b': iD.Node({id: 'b', tags: {b: 'b'}}), + 'w': iD.Way({id: 'w', tags: {area: 'yes'}}), + 'r': iD.Relation({id: 'r', members: [{id: 'a', role: 'r', type: 'node'}]}) + }), + action = iD.actions.Merge(['a', 'b', 'w']); + + expect(action.enabled(graph)).to.be.true; + + graph = action(graph); + + expect(graph.entity('a')).to.be.undefined; + expect(graph.entity('b')).to.be.undefined; + expect(graph.entity('w').tags).to.eql({a: 'a', b: 'b', area: 'yes'}); + expect(graph.entity('r').members).to.eql([{id: 'w', role: 'r', type: 'way'}]); + }); +}); From 104b02eda110844c9b2c84359e2668367f30c0df Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 6 Feb 2013 14:04:19 -0800 Subject: [PATCH 135/332] Move common setup to spec_helpers.js --- test/index.html | 9 --------- test/index_packaged.html | 9 --------- test/spec/spec_helpers.js | 9 +++++++++ 3 files changed, 9 insertions(+), 18 deletions(-) diff --git a/test/index.html b/test/index.html index ecad5ca75..87d799279 100644 --- a/test/index.html +++ b/test/index.html @@ -134,15 +134,6 @@ - - diff --git a/test/index_packaged.html b/test/index_packaged.html index f85ac1f0b..778231edd 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -18,15 +18,6 @@ - - diff --git a/test/spec/spec_helpers.js b/test/spec/spec_helpers.js index b8ebdf307..7a62e76b9 100644 --- a/test/spec/spec_helpers.js +++ b/test/spec/spec_helpers.js @@ -1,3 +1,12 @@ +iD.debug = true; + +mocha.setup({ + ui: 'bdd', + globals: ['__onresize.tail-size', '__onmousemove.zoom', '__onmouseup.zoom', '__onclick.draw'] +}); + +var expect = chai.expect; + chai.use(function (chai, utils) { var flag = utils.flag; From e868c071ac329accc8cfa19d4db800186660d536 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Wed, 6 Feb 2013 17:16:00 -0500 Subject: [PATCH 136/332] Remove more unreliable or slow layers, select the right layer initially, fix null tooltips --- css/app.css | 6 +++ data/imagery.js | 77 ++++++--------------------------------- data/imagery.json | 77 ++++++--------------------------------- data/imagery_convert.js | 10 ++++- js/id/renderer/layers.js | 1 + js/id/ui/layerswitcher.js | 8 +++- 6 files changed, 44 insertions(+), 135 deletions(-) diff --git a/css/app.css b/css/app.css index a43133f3d..ef42bb28c 100644 --- a/css/app.css +++ b/css/app.css @@ -186,6 +186,8 @@ ul.toggle-list li a { border-top: 1px solid white; display:block; border-top: 1px solid rgba(0, 0, 0, .5); + text-wrap:no-wrap; + overflow:hidden; } ul.toggle-list li a:hover { background-color: #ececec;} @@ -687,6 +689,10 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} top:190px; } +.layerswitcher-control .map-overlay { + width:250px; +} + .nudge-container { margin-top: 10px; } diff --git a/data/imagery.js b/data/imagery.js index fb6f3f020..6c232b626 100644 --- a/data/imagery.js +++ b/data/imagery.js @@ -42,55 +42,8 @@ iD.data.imagery = [ ] }, { - "name": "OSM - Tiger Edited Map", - "template": "http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/{z}/{x}/{y}.png", - "extent": [ - [ - -124.81, - 24.055 - ], - [ - -66.865, - 49.386 - ] - ] - }, - { - "name": "OSM - Tiger Edited Map", - "template": "http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/{z}/{x}/{y}.png", - "extent": [ - [ - -179.754, - 50.858 - ], - [ - -129.899, - 71.463 - ] - ] - }, - { - "name": "OSM - Tiger Edited Map", - "template": "http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/{z}/{x}/{y}.png", - "extent": [ - [ - -174.46, - 18.702 - ], - [ - -154.516, - 26.501 - ] - ] - }, - { - "name": "OSM US TIGER 2012 Roads Overlay", + "name": " TIGER 2012 Roads Overlay", "template": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", - "description": "Public domain road data from the US Government.", - "scaleExtent": [ - 0, - 17 - ], "subdomains": [ "a", "b", @@ -108,13 +61,8 @@ iD.data.imagery = [ ] }, { - "name": "OSM US TIGER 2012 Roads Overlay", + "name": " TIGER 2012 Roads Overlay", "template": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", - "description": "Public domain road data from the US Government.", - "scaleExtent": [ - 0, - 17 - ], "subdomains": [ "a", "b", @@ -132,13 +80,8 @@ iD.data.imagery = [ ] }, { - "name": "OSM US TIGER 2012 Roads Overlay", + "name": " TIGER 2012 Roads Overlay", "template": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", - "description": "Public domain road data from the US Government.", - "scaleExtent": [ - 0, - 17 - ], "subdomains": [ "a", "b", @@ -156,7 +99,7 @@ iD.data.imagery = [ ] }, { - "name": "OSM US USGS Topographic Maps", + "name": " USGS Topographic Maps", "template": "http://{t}.tile.openstreetmap.us/usgs_scanned_topos/{z}/{x}/{y}.png", "subdomains": [ "a", @@ -175,7 +118,7 @@ iD.data.imagery = [ ] }, { - "name": "OSM US USGS Topographic Maps", + "name": " USGS Topographic Maps", "template": "http://{t}.tile.openstreetmap.us/usgs_scanned_topos/{z}/{x}/{y}.png", "subdomains": [ "a", @@ -194,7 +137,7 @@ iD.data.imagery = [ ] }, { - "name": "OSM US USGS Topographic Maps", + "name": " USGS Topographic Maps", "template": "http://{t}.tile.openstreetmap.us/usgs_scanned_topos/{z}/{x}/{y}.png", "subdomains": [ "a", @@ -213,7 +156,7 @@ iD.data.imagery = [ ] }, { - "name": "OSM US USGS Large Scale Aerial Imagery", + "name": " USGS Large Scale Aerial Imagery", "template": "http://{t}.tile.openstreetmap.us/usgs_large_scale/{z}/{x}/{y}.jpg", "subdomains": [ "a", @@ -453,8 +396,9 @@ iD.data.imagery = [ "sourcetag": "Haiti streetnames" }, { - "name": "National Agriculture Imagery Program", + "name": "NAIP", "template": "http://cube.telascience.org/tilecache/tilecache.py/NAIP_ALL/{z}/{x}/{y}.png", + "description": "National Agriculture Imagery Program", "extent": [ [ -125.8, @@ -468,8 +412,9 @@ iD.data.imagery = [ "sourcetag": "NAIP" }, { - "name": "National Agriculture Imagery Program", + "name": "NAIP", "template": "http://cube.telascience.org/tilecache/tilecache.py/NAIP_ALL/{z}/{x}/{y}.png", + "description": "National Agriculture Imagery Program", "extent": [ [ -168.5, diff --git a/data/imagery.json b/data/imagery.json index e4497fc9d..ee9906d31 100644 --- a/data/imagery.json +++ b/data/imagery.json @@ -42,55 +42,8 @@ ] }, { - "name": "OSM - Tiger Edited Map", - "template": "http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/{z}/{x}/{y}.png", - "extent": [ - [ - -124.81, - 24.055 - ], - [ - -66.865, - 49.386 - ] - ] - }, - { - "name": "OSM - Tiger Edited Map", - "template": "http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/{z}/{x}/{y}.png", - "extent": [ - [ - -179.754, - 50.858 - ], - [ - -129.899, - 71.463 - ] - ] - }, - { - "name": "OSM - Tiger Edited Map", - "template": "http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/{z}/{x}/{y}.png", - "extent": [ - [ - -174.46, - 18.702 - ], - [ - -154.516, - 26.501 - ] - ] - }, - { - "name": "OSM US TIGER 2012 Roads Overlay", + "name": " TIGER 2012 Roads Overlay", "template": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", - "description": "Public domain road data from the US Government.", - "scaleExtent": [ - 0, - 17 - ], "subdomains": [ "a", "b", @@ -108,13 +61,8 @@ ] }, { - "name": "OSM US TIGER 2012 Roads Overlay", + "name": " TIGER 2012 Roads Overlay", "template": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", - "description": "Public domain road data from the US Government.", - "scaleExtent": [ - 0, - 17 - ], "subdomains": [ "a", "b", @@ -132,13 +80,8 @@ ] }, { - "name": "OSM US TIGER 2012 Roads Overlay", + "name": " TIGER 2012 Roads Overlay", "template": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", - "description": "Public domain road data from the US Government.", - "scaleExtent": [ - 0, - 17 - ], "subdomains": [ "a", "b", @@ -156,7 +99,7 @@ ] }, { - "name": "OSM US USGS Topographic Maps", + "name": " USGS Topographic Maps", "template": "http://{t}.tile.openstreetmap.us/usgs_scanned_topos/{z}/{x}/{y}.png", "subdomains": [ "a", @@ -175,7 +118,7 @@ ] }, { - "name": "OSM US USGS Topographic Maps", + "name": " USGS Topographic Maps", "template": "http://{t}.tile.openstreetmap.us/usgs_scanned_topos/{z}/{x}/{y}.png", "subdomains": [ "a", @@ -194,7 +137,7 @@ ] }, { - "name": "OSM US USGS Topographic Maps", + "name": " USGS Topographic Maps", "template": "http://{t}.tile.openstreetmap.us/usgs_scanned_topos/{z}/{x}/{y}.png", "subdomains": [ "a", @@ -213,7 +156,7 @@ ] }, { - "name": "OSM US USGS Large Scale Aerial Imagery", + "name": " USGS Large Scale Aerial Imagery", "template": "http://{t}.tile.openstreetmap.us/usgs_large_scale/{z}/{x}/{y}.jpg", "subdomains": [ "a", @@ -453,8 +396,9 @@ "sourcetag": "Haiti streetnames" }, { - "name": "National Agriculture Imagery Program", + "name": "NAIP", "template": "http://cube.telascience.org/tilecache/tilecache.py/NAIP_ALL/{z}/{x}/{y}.png", + "description": "National Agriculture Imagery Program", "extent": [ [ -125.8, @@ -468,8 +412,9 @@ "sourcetag": "NAIP" }, { - "name": "National Agriculture Imagery Program", + "name": "NAIP", "template": "http://cube.telascience.org/tilecache/tilecache.py/NAIP_ALL/{z}/{x}/{y}.png", + "description": "National Agriculture Imagery Program", "extent": [ [ -168.5, diff --git a/data/imagery_convert.js b/data/imagery_convert.js index 5dc36c502..abc5cbef6 100644 --- a/data/imagery_convert.js +++ b/data/imagery_convert.js @@ -14,14 +14,16 @@ var censor = { }; var replace = { - 'OSM - Mapnik': 'OpenStreetMap' + 'OSM - Mapnik': 'OpenStreetMap', + 'National Agriculture Imagery Program': 'NAIP' }; var description = { 'MapBox Satellite': 'Satellite and aerial imagery', 'OpenStreetMap': 'The default OpenStreetMap layer.', 'OSM US TIGER 2012 Roads Overlay': 'Public domain road data from the US Government.', - 'Bing aerial imagery': 'Satellite imagery.' + 'Bing aerial imagery': 'Satellite imagery.', + 'NAIP': 'National Agriculture Imagery Program' }; var scaleExtent = { @@ -39,8 +41,12 @@ $('set').each(function(i) { template: $(this).find('url').first().text() }; + // no luck with mapquest servers currently... + if (im.template.match(/mapquest/g)) return; if (censor[im.name]) return; + im.name = im.name.replace('OSM US', ''); + if (replace[im.name]) im.name = replace[im.name]; if (description[im.name]) im.description = description[im.name]; diff --git a/js/id/renderer/layers.js b/js/id/renderer/layers.js index 773e8011e..966e16bb8 100644 --- a/js/id/renderer/layers.js +++ b/js/id/renderer/layers.js @@ -4,6 +4,7 @@ iD.layers.push((function() { function custom() { var template = window.prompt('Enter a tile template. Valid tokens are {z}, {x}, {y} for Z/X/Y scheme and {u} for quadtile scheme.'); if (!template) return null; + if (template.match(/google/g)) return null; return iD.BackgroundSource.template({ template: template, name: 'Custom (customized)' diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index 88ee45f49..f788322b1 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -122,10 +122,16 @@ iD.ui.layerswitcher = function(context) { .text(function(d) { return d.data.name; }) - .call(bootstrap.tooltip().placement('right')) + .each(function(d) { + // only set tooltips for layers with tooltips + if (d.data.description) { + d3.select(this).call(bootstrap.tooltip().placement('right')); + } + }) .on('click.set-source', clickSetSource) .insert('span') .attr('class','icon toggle'); + selectLayer(context.background().source()); } context.map().on('move.layerswitcher-update', _.debounce(update, 1000)); From f6e726bcd68dd2f0a43d14e407794eed0b213978 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 6 Feb 2013 14:31:11 -0800 Subject: [PATCH 137/332] Join should run Reverse where necessary (fixes #652) --- js/id/actions/join.js | 6 ++++-- test/spec/actions/join.js | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/js/id/actions/join.js b/js/id/actions/join.js index 7bba4783c..267b6c75a 100644 --- a/js/id/actions/join.js +++ b/js/id/actions/join.js @@ -24,7 +24,8 @@ iD.actions.Join = function(ids) { // a <-- b ==> c // Expected result: // a <-- b <-- c - nodes = b.nodes.slice().reverse().concat(a.nodes.slice(1)); + b = iD.actions.Reverse(idB)(graph).entity(idB); + nodes = b.nodes.slice().concat(a.nodes.slice(1)); } else if (a.first() === b.last()) { // a <-- b <== c @@ -42,7 +43,8 @@ iD.actions.Join = function(ids) { // a --> b <== c // Expected result: // a --> b --> c - nodes = a.nodes.concat(b.nodes.slice().reverse().slice(1)); + b = iD.actions.Reverse(idB)(graph).entity(idB); + nodes = a.nodes.concat(b.nodes.slice().slice(1)); } graph.parentRelations(b).forEach(function (parent) { diff --git a/test/spec/actions/join.js b/test/spec/actions/join.js index 11a95bc2d..6951013d3 100644 --- a/test/spec/actions/join.js +++ b/test/spec/actions/join.js @@ -112,13 +112,14 @@ describe("iD.actions.Join", function () { 'b': iD.Node({id: 'b'}), 'c': iD.Node({id: 'c'}), '-': iD.Way({id: '-', nodes: ['b', 'a']}), - '=': iD.Way({id: '=', nodes: ['b', 'c']}) + '=': iD.Way({id: '=', nodes: ['b', 'c'], tags: {'lanes:forward': 2}}) }); graph = iD.actions.Join(['-', '='])(graph); expect(graph.entity('-').nodes).to.eql(['c', 'b', 'a']); expect(graph.entity('=')).to.be.undefined; + expect(graph.entity('-').tags).to.eql({'lanes:backward': 2}); }); it("joins a --> b <== c", function () { @@ -130,13 +131,14 @@ describe("iD.actions.Join", function () { 'b': iD.Node({id: 'b'}), 'c': iD.Node({id: 'c'}), '-': iD.Way({id: '-', nodes: ['a', 'b']}), - '=': iD.Way({id: '=', nodes: ['c', 'b']}) + '=': iD.Way({id: '=', nodes: ['c', 'b'], tags: {'lanes:forward': 2}}) }); graph = iD.actions.Join(['-', '='])(graph); expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c']); expect(graph.entity('=')).to.be.undefined; + expect(graph.entity('-').tags).to.eql({'lanes:backward': 2}); }); it("merges tags", function () { From 4b5dcd054e13667a3a456a75a722087791fcd6fc Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Wed, 6 Feb 2013 17:34:41 -0500 Subject: [PATCH 138/332] Fix sourcetag regression --- css/app.css | 1 - js/id/ui.js | 5 +++-- js/id/ui/layerswitcher.js | 24 ++++++++++++++++++------ 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/css/app.css b/css/app.css index ef42bb28c..65f98dfd6 100644 --- a/css/app.css +++ b/css/app.css @@ -93,7 +93,6 @@ a:hover { color:#597be7; } - textarea, input[type=text] { background-color: white; diff --git a/js/id/ui.js b/js/id/ui.js index ace66703d..747bccb70 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -161,8 +161,9 @@ iD.ui = function(context) { var imagery = linkList.append('li').attr('id', 'attribution'); imagery.append('span').text('imagery'); - imagery.append('a').attr('target', '_blank') - .attr('href', 'http://opengeodata.org/microsoft-imagery-details').text(' provided by bing'); + imagery + .append('span') + .attr('class', 'provided-by'); linkList.append('li').attr('class', 'source-switch').append('a').attr('href', '#') .text('dev') diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index f788322b1..b73ede9d3 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -38,7 +38,7 @@ iD.ui.layerswitcher = function(context) { selection.on('click.layerswitcher-inside', function() { return d3.event.stopPropagation(); }); - d3.select('body').on('click.layerswitcher-outside', hide); + context.container().on('click.layerswitcher-outside', hide); } var opa = content @@ -76,16 +76,28 @@ iD.ui.layerswitcher = function(context) { .style('opacity', String); // Make sure there is an active selection by default - d3.select('.opacity-options li:nth-child(2)').classed('selected', true); + opa.select('.opacity-options li:nth-child(2)').classed('selected', true); function selectLayer(d) { + content.selectAll('a.layer') .classed('selected', function(d) { return d === context.background().source(); }); - d3.select('#attribution a') - .attr('href', d.data.link) - .text('provided by ' + d.data.name); + + var provided_by = context.container().select('#attribution .provided-by') + .html(''); + + if (d.data.terms_url) { + provided_by.append('a') + .attr('href', (d.data.terms_url || '')) + .classed('disabled', !d.data.terms_url) + .text(' provided by ' + (d.data.sourcetag || d.data.name)); + } else { + provided_by + .text(' provided by ' + (d.data.sourcetag || d.data.name)); + } + } function clickSetSource(d) { @@ -96,7 +108,7 @@ iD.ui.layerswitcher = function(context) { d = configured; } context.background().source(d); - context.history().imagery_used(d.name); + context.history().imagery_used(d.data.sourcetag || d.data.name); context.redraw(); selectLayer(d); } From 2ca4387f741164d2b205d87b0751aef65579c210 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 6 Feb 2013 17:42:51 -0500 Subject: [PATCH 139/332] Fix snapping nodes to areas In the future it may be good to use shadow paths, but at this point there isn't really a reason to add thousands of additional paths. --- js/id/behavior/drag_node.js | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/js/id/behavior/drag_node.js b/js/id/behavior/drag_node.js index acba7919d..e3028953d 100644 --- a/js/id/behavior/drag_node.js +++ b/js/id/behavior/drag_node.js @@ -84,7 +84,11 @@ iD.behavior.DragNode = function(context) { if (d.type === 'node' && d.id !== entity.id) { loc = d.loc; } else if (d.type === 'way') { - loc = iD.geo.chooseIndex(d, d3.mouse(context.surface().node()), context).loc; + var point = d3.mouse(context.surface().node()), + index = iD.geo.chooseIndex(d, point, context); + if (iD.geo.dist(point, context.projection(index.loc)) < 10) { + loc = index.loc; + } } context.replace(iD.actions.MoveNode(entity.id, loc)); @@ -100,13 +104,18 @@ iD.behavior.DragNode = function(context) { var d = datum(); if (d.type === 'way') { - var choice = iD.geo.chooseIndex(d, d3.mouse(context.surface().node()), context); - context.replace( - iD.actions.MoveNode(entity.id, choice.loc), - iD.actions.AddVertex(d.id, entity.id, choice.index), - connectAnnotation(d)); + var point = d3.mouse(context.surface().node()), + choice = iD.geo.chooseIndex(d, point, context); + if (iD.geo.dist(point, context.projection(choice.loc)) < 10) { + context.replace( + iD.actions.MoveNode(entity.id, choice.loc), + iD.actions.AddVertex(d.id, entity.id, choice.index), + connectAnnotation(d)); + return; + } + } - } else if (d.type === 'node' && d.id !== entity.id) { + if (d.type === 'node' && d.id !== entity.id) { context.replace( iD.actions.Connect([entity.id, d.id]), connectAnnotation(d)); From ce53a9233a09dd506fdced44c2ec0b430901a25d Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Wed, 6 Feb 2013 18:11:50 -0500 Subject: [PATCH 140/332] Remove unscoped d3.select --- js/id/ui/save.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/ui/save.js b/js/id/ui/save.js index 905a43b11..38bba8518 100644 --- a/js/id/ui/save.js +++ b/js/id/ui/save.js @@ -30,7 +30,7 @@ iD.ui.save = function(context) { function click() { function commit(e) { - d3.select('.shaded').remove(); + context.container().select('.shaded').remove(); var l = iD.ui.loading(context.container(), t('uploading_changes'), true); connection.putChangeset(history.changes(), From 246481ad9262ea2a71316fa55ebd04cf24bc071b Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 6 Feb 2013 18:53:46 -0500 Subject: [PATCH 141/332] Switch back to mouseup, and block following click --- js/id/behavior/draw.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/js/id/behavior/draw.js b/js/id/behavior/draw.js index b0da771a4..6b1b7ff0a 100644 --- a/js/id/behavior/draw.js +++ b/js/id/behavior/draw.js @@ -27,13 +27,19 @@ iD.behavior.Draw = function(context) { target.on('mousemove.draw', null); - d3.select(window).on('click.draw', function() { + d3.select(window).on('mouseup.draw', function() { target.on('mousemove.draw', mousemove); if (iD.geo.dist(pos, point()) < closeTolerance || (iD.geo.dist(pos, point()) < tolerance && (+new Date() - time) < 500)) { click(); } + if (target.node() === d3.event.target) { + d3.select(window).on('click.draw', function() { + d3.select(window).on('click.draw', null); + d3.event.stopPropagation(); + }); + } }); } @@ -110,7 +116,7 @@ iD.behavior.Draw = function(context) { .on('mousedown.draw', null) .on('mousemove.draw', null); - d3.select(window).on('click.draw', null); + d3.select(window).on('mouseup.draw', null); d3.select(document) .call(keybinding.off) From 0af51a0ef6f542ea1b4443a3809bfb14a792e966 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 6 Feb 2013 15:58:13 -0800 Subject: [PATCH 142/332] Improvements to Split * Split a closed way at selected and antipode point (fixes #651) * Split an area into a multipolygon (fixes #572) --- js/id/actions/split.js | 69 +++++++++++++++++++++++++++----------- test/spec/actions/split.js | 60 +++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 19 deletions(-) diff --git a/js/id/actions/split.js b/js/id/actions/split.js index 24f2b4318..ea2100238 100644 --- a/js/id/actions/split.js +++ b/js/id/actions/split.js @@ -15,37 +15,55 @@ iD.actions.Split = function(nodeId, newWayId) { parents = graph.parentWays(node); return parents.filter(function (parent) { - return parent.first() !== nodeId && - parent.last() !== nodeId; + return parent.isClosed() || + (parent.first() !== nodeId && + parent.last() !== nodeId); }); } var action = function(graph) { - if (!action.enabled(graph)) - return graph; + var wayA = candidateWays(graph)[0], + wayB = iD.Way({id: newWayId, tags: wayA.tags}), + nodesA, + nodesB, + isArea = wayA.isArea(); - var way = candidateWays(graph)[0], - idx = _.indexOf(way.nodes, nodeId); + if (wayA.isClosed()) { + var nodes = wayA.nodes.slice(0, -1), + idxA = _.indexOf(nodes, nodeId), + idxB = idxA + Math.floor(nodes.length / 2); - // Create a 'b' way that contains all of the tags in the second - // half of this way - var newWay = iD.Way({id: newWayId, tags: way.tags, nodes: way.nodes.slice(idx)}); - graph = graph.replace(newWay); + if (idxB >= nodes.length) { + idxB %= nodes.length; + nodesA = nodes.slice(idxA).concat(nodes.slice(0, idxB + 1)); + nodesB = nodes.slice(idxB, idxA + 1); + } else { + nodesA = nodes.slice(idxA, idxB + 1); + nodesB = nodes.slice(idxB).concat(nodes.slice(0, idxA + 1)); + } + } else { + var idx = _.indexOf(wayA.nodes, nodeId); + nodesA = wayA.nodes.slice(0, idx + 1); + nodesB = wayA.nodes.slice(idx); + } - // Reduce the original way to only contain the first set of nodes - graph = graph.replace(way.update({nodes: way.nodes.slice(0, idx + 1)})); + wayA = wayA.update({nodes: nodesA}); + wayB = wayB.update({nodes: nodesB}); - graph.parentRelations(way).forEach(function(relation) { + graph = graph.replace(wayA); + graph = graph.replace(wayB); + + graph.parentRelations(wayA).forEach(function(relation) { if (relation.isRestriction()) { var via = relation.memberByRole('via'); - if (via && newWay.contains(via.id)) { - relation = relation.updateMember({id: newWay.id}, relation.memberById(way.id).index); + if (via && wayB.contains(via.id)) { + relation = relation.updateMember({id: wayB.id}, relation.memberById(wayA.id).index); graph = graph.replace(relation); } } else { - var role = relation.memberById(way.id).role, - last = newWay.last(), - i = relation.memberById(way.id).index, + var role = relation.memberById(wayA.id).role, + last = wayB.last(), + i = relation.memberById(wayA.id).index, j; for (j = 0; j < relation.members.length; j++) { @@ -55,11 +73,24 @@ iD.actions.Split = function(nodeId, newWayId) { } } - relation = relation.addMember({id: newWay.id, type: 'way', role: role}, i <= j ? i + 1 : i); + relation = relation.addMember({id: wayB.id, type: 'wayA', role: role}, i <= j ? i + 1 : i); graph = graph.replace(relation); } }); + if (isArea) { + var multipolygon = iD.Relation({ + tags: _.extend({}, wayA.tags, {type: 'multipolygon'}), + members: [ + {id: wayA.id, role: 'outer', type: 'way'}, + {id: wayB.id, role: 'outer', type: 'way'} + ]}); + + graph = graph.replace(multipolygon); + graph = graph.replace(wayA.update({tags: {}})); + graph = graph.replace(wayB.update({tags: {}})); + } + return graph; }; diff --git a/test/spec/actions/split.js b/test/spec/actions/split.js index 5ee68e67e..417525941 100644 --- a/test/spec/actions/split.js +++ b/test/spec/actions/split.js @@ -99,6 +99,66 @@ describe("iD.actions.Split", function () { expect(graph.entity('|').nodes).to.eql(['d', 'b']); }); + it("splits a closed way at the given point and its antipode", function () { + // Situation: + // a ---- b + // | | + // d ---- c + // + // Split at a. + // + // Expected result: + // a ---- b + // || | + // d ==== c + // + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + 'd': iD.Node({id: 'd'}), + '-': iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']}) + }); + + var g1 = iD.actions.Split('a', '=')(graph); + expect(g1.entity('-').nodes).to.eql(['a', 'b', 'c']); + expect(g1.entity('=').nodes).to.eql(['c', 'd', 'a']); + + var g2 = iD.actions.Split('b', '=')(graph); + expect(g2.entity('-').nodes).to.eql(['b', 'c', 'd']); + expect(g2.entity('=').nodes).to.eql(['d', 'a', 'b']); + + var g3 = iD.actions.Split('c', '=')(graph); + expect(g3.entity('-').nodes).to.eql(['c', 'd', 'a']); + expect(g3.entity('=').nodes).to.eql(['a', 'b', 'c']); + + var g4 = iD.actions.Split('d', '=')(graph); + expect(g4.entity('-').nodes).to.eql(['d', 'a', 'b']); + expect(g4.entity('=').nodes).to.eql(['b', 'c', 'd']); + }); + + it("splits an area by converting it to a multipolygon", function () { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + 'd': iD.Node({id: 'd'}), + '-': iD.Way({id: '-', tags: {building: 'yes'}, nodes: ['a', 'b', 'c', 'd', 'a']}) + }); + + graph = iD.actions.Split('a', '=')(graph); + expect(graph.entity('-').tags).to.eql({}); + expect(graph.entity('=').tags).to.eql({}); + expect(graph.parentRelations(graph.entity('-'))).to.have.length(1); + + var relation = graph.parentRelations(graph.entity('-'))[0]; + expect(relation.tags).to.eql({type: 'multipolygon', building: 'yes'}); + expect(relation.members).to.eql([ + {id: '-', role: 'outer', type: 'way'}, + {id: '=', role: 'outer', type: 'way'} + ]); + }); + it("adds the new way to parent relations (no connections)", function () { // Situation: // a ---- b ---- c From 4f6637d58ba85a677eafa245b9cc8af559f9e6c9 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 6 Feb 2013 19:11:28 -0500 Subject: [PATCH 143/332] Fix add_point tests (switch back to mouseup) --- test/spec/modes/add_point.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/spec/modes/add_point.js b/test/spec/modes/add_point.js index 5807a3c0b..62291f8ee 100644 --- a/test/spec/modes/add_point.js +++ b/test/spec/modes/add_point.js @@ -17,13 +17,13 @@ describe("iD.modes.AddPoint", function() { describe("clicking the map", function () { it("adds a node", function() { happen.mousedown(context.surface().node(), {}); - happen.click(window, {}); + happen.mouseup(window, {}); expect(context.changes().created).to.have.length(1); }); it("selects the node", function() { happen.mousedown(context.surface().node(), {}); - happen.click(window, {}); + happen.mouseup(window, {}); expect(context.mode().id).to.equal('select'); expect(context.mode().selection()).to.eql([context.changes().created[0].id]); }); From ec152716fba81566d1a0e28647d3cd30955d2c6a Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 7 Feb 2013 01:16:51 -0500 Subject: [PATCH 144/332] Fix calls to ui.flash --- js/id/renderer/map.js | 2 +- js/id/ui/geocoder.js | 2 +- js/id/ui/inspector.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 5074abd92..946b29b70 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -106,7 +106,7 @@ iD.Map = function(context) { } if (Math.log(d3.event.scale / Math.LN2 - 8) < minzoom + 1) { - iD.ui.flash() + iD.ui.flash(context.container()) .select('.content') .text('Cannot zoom out further in current mode.'); return map.zoom(16); diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index ffb735305..8883dc934 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -12,7 +12,7 @@ iD.ui.geocoder = function() { if (err) return hide(); hide(); if (!resp.length) { - return iD.ui.flash() + return iD.ui.flash(context.container()) .select('.content') .append('h3') .text('No location found for "' + searchVal + '"'); diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js index bf6099823..35e03d231 100644 --- a/js/id/ui/inspector.js +++ b/js/id/ui/inspector.js @@ -163,7 +163,7 @@ iD.ui.inspector = function() { }) .call(iD.keyReference(context)); } else { - iD.ui.flash() + iD.ui.flash(context.container()) .select('.content') .append('h3') .text(t('inspector.no_documentation_key')); From cf96055781e08033e94e920c0466b1e385343c30 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 7 Feb 2013 14:08:07 -0500 Subject: [PATCH 145/332] Fix scale calculation in orthogonalize --- js/id/actions/orthogonalize.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/actions/orthogonalize.js b/js/id/actions/orthogonalize.js index 068db55bd..b57b8ad0a 100644 --- a/js/id/actions/orthogonalize.js +++ b/js/id/actions/orthogonalize.js @@ -62,7 +62,7 @@ iD.actions.Orthogonalize = function(wayId, projection) { p = subtractPoints(a, b), q = subtractPoints(c, b); - var scale = p.length + q.length; + var scale = iD.geo.dist(p, [0, 0]) + iD.geo.dist(q, [0, 0]); p = normalizePoint(p, 1.0); q = normalizePoint(q, 1.0); From 9a2bafac1be3e5e2b8cee00bca226545445e2efb Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 7 Feb 2013 15:30:44 -0500 Subject: [PATCH 146/332] Power through less square iterations --- js/id/actions/orthogonalize.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/js/id/actions/orthogonalize.js b/js/id/actions/orthogonalize.js index b57b8ad0a..09711364f 100644 --- a/js/id/actions/orthogonalize.js +++ b/js/id/actions/orthogonalize.js @@ -1,9 +1,14 @@ +/* + * Based on https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/potlatch2/tools/Quadrilateralise.as + */ + iD.actions.Orthogonalize = function(wayId, projection) { var action = function(graph) { var way = graph.entity(wayId), nodes = graph.childNodes(way), points = nodes.map(function(n) { return projection(n.loc); }), - quad_nodes = [], i, j; + quad_nodes = [], + best, i, j; var score = squareness(); for (i = 0; i < 1000; i++) { @@ -12,14 +17,15 @@ iD.actions.Orthogonalize = function(wayId, projection) { points[j] = addPoints(points[j],motions[j]); } var newScore = squareness(); - if (newScore > score) { - return graph; + if (newScore < score) { + best = _.clone(points); + score = newScore; } - score = newScore; if (score < 1.0e-8) { break; } } + points = best; for (i = 0; i < points.length - 1; i++) { quad_nodes.push(iD.Node({ loc: projection.invert(points[i]) })); From b9d80538b1fa413914e94cbf04a02f4d6f39b029 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Feb 2013 15:41:14 -0500 Subject: [PATCH 147/332] jshinting --- js/id/actions/merge.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/js/id/actions/merge.js b/js/id/actions/merge.js index 8ba2a2318..5b515bcc5 100644 --- a/js/id/actions/merge.js +++ b/js/id/actions/merge.js @@ -6,8 +6,8 @@ iD.actions.Merge = function(ids) { var action = function(graph) { var geometries = groupEntitiesByGeometry(graph), - area = geometries['area'][0], - points = geometries['point']; + area = geometries.area[0], + points = geometries.point; points.forEach(function (point) { area = area.mergeTags(point.tags); @@ -26,9 +26,9 @@ iD.actions.Merge = function(ids) { action.enabled = function(graph) { var geometries = groupEntitiesByGeometry(graph); - return geometries['area'].length === 1 && - geometries['point'].length > 0 && - (geometries['area'].length + geometries['point'].length) === ids.length; + return geometries.area.length === 1 && + geometries.point.length > 0 && + (geometries.area.length + geometries.point.length) === ids.length; }; return action; From 02d4b8f1aaa3ad67eaa3843a6fc21652a2cf93a6 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 7 Feb 2013 15:48:32 -0500 Subject: [PATCH 148/332] Remove unnecessary code from orthogonalize --- js/id/actions/orthogonalize.js | 31 +------------------------------ 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/js/id/actions/orthogonalize.js b/js/id/actions/orthogonalize.js index 09711364f..ada281a95 100644 --- a/js/id/actions/orthogonalize.js +++ b/js/id/actions/orthogonalize.js @@ -28,36 +28,7 @@ iD.actions.Orthogonalize = function(wayId, projection) { points = best; for (i = 0; i < points.length - 1; i++) { - quad_nodes.push(iD.Node({ loc: projection.invert(points[i]) })); - } - - for (i = 0; i < nodes.length; i++) { - if (graph.parentWays(nodes[i]).length > 1) { - var closest, closest_dist = Infinity, dist; - for (j = 0; j < quad_nodes.length; j++) { - dist = iD.geo.dist(quad_nodes[j].loc, nodes[i].loc); - if (dist < closest_dist) { - closest_dist = dist; - closest = j; - } - } - quad_nodes.splice(closest, 1, nodes[i]); - } - } - - for (i = 0; i < quad_nodes.length; i++) { - graph = graph.replace(quad_nodes[i]); - } - - var ids = _.pluck(quad_nodes, 'id'), - difference = _.difference(_.uniq(way.nodes), ids); - - ids.push(ids[0]); - - graph = graph.replace(way.update({nodes: ids})); - - for (i = 0; i < difference.length; i++) { - graph = iD.actions.DeleteNode(difference[i])(graph); + graph = graph.replace(graph.entity(nodes[i].id).move(projection.invert(points[i]))); } return graph; From f0b761b97923cf4dc3100443ce03a64b24f66c83 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Feb 2013 16:21:25 -0500 Subject: [PATCH 149/332] Lasso action --- css/app.css | 11 ++++--- index.html | 2 ++ js/id/behavior/lasso.js | 57 +++++++++++++++++++++++++++++++++++++ js/id/modes/browse.js | 1 + js/id/ui/lasso.js | 63 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 130 insertions(+), 4 deletions(-) create mode 100644 js/id/behavior/lasso.js create mode 100644 js/id/ui/lasso.js diff --git a/css/app.css b/css/app.css index 65f98dfd6..99904d953 100644 --- a/css/app.css +++ b/css/app.css @@ -1398,6 +1398,13 @@ a.success-action { border-radius: 4px; } +.lasso-box { + fill-opacity:0.2; + fill: #bde5aa; + stroke: #000; + stroke-width: 1; +} + /* Media Queries ------------------------------------------------------- */ @@ -1408,10 +1415,6 @@ a.success-action { .save .label, .apply .label, .cancel .label { display: block;} } - - - - div.combobox { width:155px; z-index: 9999; diff --git a/index.html b/index.html index 9f4c23757..31f06870a 100644 --- a/index.html +++ b/index.html @@ -75,6 +75,7 @@ + @@ -102,6 +103,7 @@ + diff --git a/js/id/behavior/lasso.js b/js/id/behavior/lasso.js new file mode 100644 index 000000000..08dda8cd3 --- /dev/null +++ b/js/id/behavior/lasso.js @@ -0,0 +1,57 @@ +iD.behavior.Lasso = function(context) { + + var behavior = function(selection) { + + var timeout = null, + // the position of the first mousedown + pos = null, + lasso; + + function mousedown() { + if (d3.event.shiftKey === true) { + + lasso = iD.ui.lasso().a(d3.mouse(context.surface().node())); + + context.surface().call(lasso); + + selection + .on('mousemove.lasso', mousemove) + .on('mouseup.lasso', mouseup); + + d3.event.stopPropagation(); + d3.event.preventDefault(); + + } + } + + function mousemove() { + lasso.b(d3.mouse(context.surface().node())); + } + + function mouseup() { + var extent = iD.geo.Extent( + context.projection.invert(lasso.a()), + context.projection.invert(lasso.b())); + + lasso.close(); + + var selected = context.graph().intersects(extent); + + if (selected.length) { + context.enter(iD.modes.Select(context, _.pluck(selected, 'id'))); + } + + selection + .on('mousemove.lasso', null); + } + + selection + .on('mousedown.select', mousedown); + }; + + behavior.off = function(selection) { + selection.on('mousedown.lasso', null); + }; + + return behavior; +}; diff --git a/js/id/modes/browse.js b/js/id/modes/browse.js index 97f0d6935..5df94d69d 100644 --- a/js/id/modes/browse.js +++ b/js/id/modes/browse.js @@ -10,6 +10,7 @@ iD.modes.Browse = function(context) { var behaviors = [ iD.behavior.Hover(), iD.behavior.Select(context), + iD.behavior.Lasso(context), iD.behavior.DragNode(context)]; mode.enter = function() { diff --git a/js/id/ui/lasso.js b/js/id/ui/lasso.js new file mode 100644 index 000000000..1ca079fda --- /dev/null +++ b/js/id/ui/lasso.js @@ -0,0 +1,63 @@ +iD.ui.lasso = function() { + + var center, box, + group, + a = [0, 0], + b = [0, 0]; + + function lasso(selection) { + + group = selection.append('g') + .attr('class', 'lasso') + .attr('opacity', 0); + + box = group.append('rect') + .attr('class', 'lasso-box'); + + group.transition() + .style('opacity', 1); + + } + + // top-left + function topLeft(d) { + return 'translate(' + + [Math.min(d[0][0], d[1][0]), Math.min(d[0][1], d[1][1])] + ')'; + } + + function width(d) { return Math.abs(d[0][0] - d[1][0]); } + function height(d) { return Math.abs(d[0][1] - d[1][1]); } + + function draw() { + if (box) { + box.data([[a, b]]) + .attr('transform', topLeft) + .attr('width', width) + .attr('height', height); + } + } + + lasso.a = function(_) { + if (!arguments.length) return a; + a = _; + draw(); + return lasso; + }; + + lasso.b = function(_) { + if (!arguments.length) return b; + b = _; + draw(); + return lasso; + }; + + lasso.close = function(selection) { + if (group) { + group.transition() + .attr('opacity', 0) + .remove(); + } + }; + + return lasso; +}; From 843926009de92595a0ac996927fd47aea3c549b6 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Feb 2013 16:32:03 -0500 Subject: [PATCH 150/332] Normalize coordinates so that lasso can be dragged in any direction --- js/id/behavior/lasso.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/js/id/behavior/lasso.js b/js/id/behavior/lasso.js index 08dda8cd3..3986b5cf2 100644 --- a/js/id/behavior/lasso.js +++ b/js/id/behavior/lasso.js @@ -28,25 +28,32 @@ iD.behavior.Lasso = function(context) { lasso.b(d3.mouse(context.surface().node())); } + function normalize(a, b) { + return [ + [Math.min(a[0], b[0]), Math.min(a[1], b[1])], + [Math.max(a[0], b[0]), Math.max(a[1], b[1])]]; + } + function mouseup() { var extent = iD.geo.Extent( - context.projection.invert(lasso.a()), - context.projection.invert(lasso.b())); + normalize(context.projection.invert(lasso.a()), + context.projection.invert(lasso.b()))); lasso.close(); var selected = context.graph().intersects(extent); + selection + .on('mousemove.lasso', null) + .on('mouseup.lasso', null); + if (selected.length) { context.enter(iD.modes.Select(context, _.pluck(selected, 'id'))); } - - selection - .on('mousemove.lasso', null); } selection - .on('mousedown.select', mousedown); + .on('mousedown.lasso', mousedown); }; behavior.off = function(selection) { From edc424367344ebb5f0c21628929c02c660a494ce Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Feb 2013 16:36:07 -0500 Subject: [PATCH 151/332] Add includes to test, fixes tests --- test/index.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/index.html b/test/index.html index 7b90f7c09..1e5975595 100644 --- a/test/index.html +++ b/test/index.html @@ -67,6 +67,7 @@ + @@ -101,6 +102,7 @@ + From 5cad057212a2a93d76b3f144ef8370c289577255 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 7 Feb 2013 13:36:19 -0800 Subject: [PATCH 152/332] Clear selection on esc (fixes #643) --- js/id/modes/select.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 0bcfa8c0b..95eb3e78a 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -42,6 +42,10 @@ iD.modes.Select = function(context, selection, initial) { .filter(function(o) { return o.available(); }); operations.unshift(iD.operations.Delete(selection, context)); + keybinding.on('⎋', function() { + context.enter(iD.modes.Browse(context)); + }); + operations.forEach(function(operation) { keybinding.on(operation.key, function() { if (operation.enabled()) { From e1bc78871b04aa5a5a6800f2204b808908c1331c Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 7 Feb 2013 13:46:06 -0800 Subject: [PATCH 153/332] Handle entities that are already deleted (fixes #672) --- js/id/actions/delete_multiple.js | 5 ++++- test/spec/actions/delete_multiple.js | 9 +++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/js/id/actions/delete_multiple.js b/js/id/actions/delete_multiple.js index 839c1d780..a33dba4de 100644 --- a/js/id/actions/delete_multiple.js +++ b/js/id/actions/delete_multiple.js @@ -7,7 +7,10 @@ iD.actions.DeleteMultiple = function(ids) { }; ids.forEach(function (id) { - graph = actions[graph.entity(id).type](id)(graph); + var entity = graph.entity(id); + if (entity) { // It may have been deleted aready. + graph = actions[entity.type](id)(graph); + } }); return graph; diff --git a/test/spec/actions/delete_multiple.js b/test/spec/actions/delete_multiple.js index 3a70adc38..85329b7a8 100644 --- a/test/spec/actions/delete_multiple.js +++ b/test/spec/actions/delete_multiple.js @@ -9,4 +9,13 @@ describe("iD.actions.DeleteMultiple", function () { expect(graph.entity(w.id)).to.be.undefined; expect(graph.entity(r.id)).to.be.undefined; }); + + it("deletes a way and one of its nodes", function () { + var n = iD.Node(), + w = iD.Way({nodes: [n.id]}), + action = iD.actions.DeleteMultiple([w.id, n.id]), + graph = action(iD.Graph([n, w])); + expect(graph.entity(w.id)).to.be.undefined; + expect(graph.entity(n.id)).to.be.undefined; + }); }); From 425138e21db39acfae9a2b978b775cdb30ed417a Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Feb 2013 16:55:06 -0500 Subject: [PATCH 154/332] Remove discard-tags include --- index.html | 1 - 1 file changed, 1 deletion(-) diff --git a/index.html b/index.html index 8eca2fae7..37ff61404 100644 --- a/index.html +++ b/index.html @@ -98,7 +98,6 @@ - From 5e328435e6ad036a91e99196b95570e2d41a1bf2 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Feb 2013 16:58:38 -0500 Subject: [PATCH 155/332] jshint --- js/id/actions/join.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/id/actions/join.js b/js/id/actions/join.js index 267b6c75a..feccfebaf 100644 --- a/js/id/actions/join.js +++ b/js/id/actions/join.js @@ -51,7 +51,7 @@ iD.actions.Join = function(ids) { graph = graph.replace(parent.replaceMember(b, a)); }); - graph = graph.replace(a.mergeTags(b.tags).update({nodes: nodes})); + graph = graph.replace(a.mergeTags(b.tags).update({ nodes: nodes })); graph = iD.actions.DeleteWay(idB)(graph); return graph; @@ -60,7 +60,7 @@ iD.actions.Join = function(ids) { action.enabled = function(graph) { var geometries = groupEntitiesByGeometry(graph); - if (ids.length !== 2 || ids.length !== geometries['line'].length) + if (ids.length !== 2 || ids.length !== geometries.line.length) return false; var a = graph.entity(idA), From 3626534a704132ba39d03b00cce276127cef5d3d Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Feb 2013 17:10:31 -0500 Subject: [PATCH 156/332] Remove deprecate tags test --- test/index.html | 1 - test/spec/actions/deprecate_tags.js | 47 ----------------------------- 2 files changed, 48 deletions(-) delete mode 100644 test/spec/actions/deprecate_tags.js diff --git a/test/index.html b/test/index.html index 1e5975595..7f5d2a878 100644 --- a/test/index.html +++ b/test/index.html @@ -91,7 +91,6 @@ - diff --git a/test/spec/actions/deprecate_tags.js b/test/spec/actions/deprecate_tags.js deleted file mode 100644 index 61d1da4fb..000000000 --- a/test/spec/actions/deprecate_tags.js +++ /dev/null @@ -1,47 +0,0 @@ -describe('iD.actions.DeprecateTags', function () { - it('deprecates tags', function () { - var entity = iD.Entity({ tags: { barrier: 'wire_fence' } }), - graph = iD.actions.DeprecateTags(entity.id)(iD.Graph([entity])), - undeprecated = { - barrier: 'fence', - fence_type: 'chain' - }; - expect(graph.entity(entity.id).tags).to.eql(undeprecated); - }); - - it('does not overwrite explicit tags', function () { - var entity = iD.Entity({ tags: { barrier: 'wire_fence', fence_type: 'foo' } }), - graph = iD.actions.DeprecateTags(entity.id)(iD.Graph([entity])), - undeprecated = { - barrier: 'fence', - fence_type: 'foo' - }; - expect(graph.entity(entity.id).tags).to.eql(undeprecated); - }); - - it('leaves other tags alone', function () { - var entity = iD.Entity({ tags: { highway: 'ford', name: 'Foo' } }), - graph = iD.actions.DeprecateTags(entity.id)(iD.Graph([entity])), - undeprecated = { - ford: 'yes', - name: 'Foo' - }; - expect(graph.entity(entity.id).tags).to.eql(undeprecated); - }); - - it('wipes out tags that should be entirely removed', function () { - var entity = iD.Entity({ tags: { 'tiger:source': 'foo' } }), - graph = iD.actions.DeprecateTags(entity.id)(iD.Graph([entity])), - undeprecated = { }; - expect(graph.entity(entity.id).tags).to.eql(undeprecated); - }); - - it('replaces keys', function () { - var entity = iD.Entity({ tags: { power_rating: '1 billion volts' } }), - graph = iD.actions.DeprecateTags(entity.id)(iD.Graph([entity])), - undeprecated = { - 'generator:output': '1 billion volts' - }; - expect(graph.entity(entity.id).tags).to.eql(undeprecated); - }); -}); From 57493016b6ca60a737220bcfd5d1ca2662c8562b Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Feb 2013 17:17:24 -0500 Subject: [PATCH 157/332] Remove ref to 404 test, do not discard tags from empty tags --- js/id/graph/history.js | 10 +++++++--- test/index.html | 1 - 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/js/id/graph/history.js b/js/id/graph/history.js index 04d52f6ab..5f648b434 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -134,9 +134,13 @@ iD.History = function(context) { var difference = history.difference(); function discardTags(entity) { - return entity.update({ - tags: _.omit(entity.tags, iD.data.discarded) - }); + if (_.isEmpty(entity.tags)) { + return entity; + } else { + return entity.update({ + tags: _.omit(entity.tags, iD.data.discarded) + }); + } } return { diff --git a/test/index.html b/test/index.html index 7f5d2a878..8fb7da652 100644 --- a/test/index.html +++ b/test/index.html @@ -159,7 +159,6 @@ - From ec065dca4c8e05c790d0a58a0dbc9d48df86742d Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 7 Feb 2013 17:18:44 -0500 Subject: [PATCH 158/332] Fix flashing, reset transform on mouseup --- js/id/renderer/map.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 946b29b70..5bdeebce4 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -42,6 +42,9 @@ iD.Map = function(context) { d3.event.stopPropagation(); } }, true) + .on('mouseup.zoom', function() { + if (resetTransform) redraw(); + }) .attr('id', 'surface') .call(iD.svg.Surface()); From 887151c9febd31f91f776c72dde08238a0797000 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Feb 2013 17:32:26 -0500 Subject: [PATCH 159/332] Make all non-dashed keys into keys --- locale/en.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/locale/en.js b/locale/en.js index b92dc75ee..6c4f8bc2b 100644 --- a/locale/en.js +++ b/locale/en.js @@ -140,13 +140,13 @@ locale.en = { deprecated_tags: "Deprecated tags: {tags}" }, - "save": "Save", - "save_help": "Save changes to OpenStreetMap, making them visible to other users", - "no_changes": "You don't have any changes to save.", - "save_error": "An error occurred while trying to save", - "uploading_changes": "Uploading changes to OpenStreetMap.", - "just_edited": "You Just Edited OpenStreetMap!", - "okay": "Okay", + save: "Save", + save_help: "Save changes to OpenStreetMap, making them visible to other users", + no_changes: "You don't have any changes to save.", + save_error: "An error occurred while trying to save", + uploading_changes: "Uploading changes to OpenStreetMap.", + just_edited: "You Just Edited OpenStreetMap!", + okay: "Okay", "zoom-in": "Zoom In", "zoom-out": "Zoom Out", @@ -154,7 +154,7 @@ locale.en = { nothing_to_undo: "Nothing to undo.", nothing_to_redo: "Nothing to redo.", - "browser_notice": "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", + browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", inspector: { no_documentation_combination: "This is no documentation available for this tag combination", @@ -162,20 +162,20 @@ locale.en = { new_tag: "New Tag" }, - "view_on_osm": "View on OSM", + view_on_osm: "View on OSM", - "zoom_in_edit": "zoom in to edit the map", + zoom_in_edit: "zoom in to edit the map", - "edit_tags": "Edit tags", + edit_tags: "Edit tags", geocoder: { "find_location": "Find A Location", "find_a_place": "find a place" }, - "description": "Description", + description: "Description", - "logout": "logout", + logout: "logout", layerswitcher: { title: "Background", From 28c541067bc74baed7bc0689540dba265e047863 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 7 Feb 2013 17:58:28 -0500 Subject: [PATCH 160/332] Fix #680, fix use of imagery scaleExtents --- js/id/renderer/background.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/renderer/background.js b/js/id/renderer/background.js index 1f0834ed1..badafa18d 100644 --- a/js/id/renderer/background.js +++ b/js/id/renderer/background.js @@ -63,7 +63,7 @@ iD.Background = function() { var sel = this, tiles = tile .scale(projection.scale()) - .scaleExtent(source.scaleExtent || [1, 17]) + .scaleExtent(source.data.scaleExtent || [1, 17]) .translate(projection.translate())(), requests = [], scaleExtent = tile.scaleExtent(), From ad5c1df2285510fc211c9015df1e9d968f656ffb Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Feb 2013 17:59:31 -0500 Subject: [PATCH 161/332] Detect support as well as os and language --- js/id/id.js | 33 +++++++++++++++++++++++---------- js/id/ui.js | 2 +- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/js/id/id.js b/js/id/id.js index e83c442c4..e6396b491 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -102,16 +102,29 @@ window.iD = function () { iD.version = '0.0.0-alpha1'; -iD.supported = function() { - if (navigator.appName !== 'Microsoft Internet Explorer') { - return true; +iD.detect = function() { + var browser = {}; + + var ua = navigator.userAgent, + msie = new RegExp("MSIE ([0-9]{1,}[\\.0-9]{0,})"); + + if (msie.exec(ua) !== null) { + var rv = parseFloat(RegExp.$1); + browser.support = !(rv && rv < 9); } else { - var ua = navigator.userAgent; - var re = new RegExp("MSIE ([0-9]{1,}[\\.0-9]{0,})"); - if (re.exec(ua) !== null) { - rv = parseFloat( RegExp.$1 ); - } - if (rv && rv < 9) return false; - else return true; + browser.support = true; } + + browser.locale = navigator.language; + + function nav(x) { + return navigator.userAgent.indexOf(x) !== -1; + } + + if (nav('Win')) browser.os = 'win'; + else if (nav('Mac')) browser.os = 'mac'; + else if (nav('X11')) browser.os = 'linux'; + else if (nav('Linux')) browser.os = 'linux'; + + return browser; }; diff --git a/js/id/ui.js b/js/id/ui.js index 768d95813..37874b25f 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -6,7 +6,7 @@ iD.ui = function(context) { history = context.history(), map = context.map(); - if (!iD.supported()) { + if (!iD.detect().support) { container.html('This editor is supported in Firefox, Chrome, Safari, Opera, ' + 'and Internet Explorer 9 and above. Please upgrade your browser ' + 'or use Potlatch 2 to edit the map.') From 645dcebbfe242850a1d1b761b25481ce8e2ff692 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Feb 2013 18:09:28 -0500 Subject: [PATCH 162/332] Use platform-specific mod keys, refs #676 --- js/id/id.js | 1 + js/id/ui.js | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/js/id/id.js b/js/id/id.js index e6396b491..f1d7d853d 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -125,6 +125,7 @@ iD.detect = function() { else if (nav('Mac')) browser.os = 'mac'; else if (nav('X11')) browser.os = 'linux'; else if (nav('Linux')) browser.os = 'linux'; + else browser.os = 'win'; return browser; }; diff --git a/js/id/ui.js b/js/id/ui.js index 37874b25f..f18e0ef0c 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -210,14 +210,20 @@ iD.ui = function(context) { } } + var mod = { + 'mac': '⌘', + 'win': 'Ctrl', + 'linux': 'Ctrl' + }[iD.detect().os]; + limiter.select('#undo') .classed('disabled', !undo) - .attr('data-original-title', hintprefix('⌘ + Z', undo || t('nothing_to_undo'))) + .attr('data-original-title', hintprefix(mod + ' + Z', undo || t('nothing_to_undo'))) .call(refreshTooltip); limiter.select('#redo') .classed('disabled', !redo) - .attr('data-original-title', hintprefix('⌘ + ⇧ + Z', redo || t('nothing_to_redo'))) + .attr('data-original-title', hintprefix(mod + ' + ⇧ + Z', redo || t('nothing_to_redo'))) .call(refreshTooltip); }); From 27e7df888d7bcaa67da6a589e79e26dc53a40887 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Feb 2013 18:24:01 -0500 Subject: [PATCH 163/332] Support locales in taginfo. Fixes #678 --- js/id/ui/inspector.js | 109 ++++++++++++++++++++++++++---------------- 1 file changed, 68 insertions(+), 41 deletions(-) diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js index 35e03d231..fd29e08a5 100644 --- a/js/id/ui/inspector.js +++ b/js/id/ui/inspector.js @@ -119,6 +119,72 @@ iD.ui.inspector = function() { removeBtn.append('span') .attr('class', 'icon delete'); + function findLocal(docs) { + var locale = iD.detect().locale.toLowerCase(), + localized; + + localized = _.find(docs, function(d) { + return d.lang.toLowerCase() === locale; + }); + if (localized) return localized; + + // try the non-regional version of a language, like + // 'en' if the language is 'en-US' + if (locale.indexOf('-') !== -1) { + var first = locale.split('-')[0]; + localized = _.find(docs, function(d) { + return d.lang.toLowerCase() === first; + }); + if (localized) return localized; + } + + // finally fall back to english + return _.find(docs, function(d) { + return d.lang.toLowerCase() === 'en'; + }); + } + + function keyValueReference(err, docs) { + var local; + if (!err && docs) { + local = findLocal(docs); + } + if (local) { + var types = []; + if (local.on_area) types.push('area'); + if (local.on_node) types.push('point'); + if (local.on_way) types.push('line'); + local.types = types; + iD.ui.modal(context.container()) + .select('.content') + .datum(local) + .call(iD.ui.tagReference); + } else { + iD.ui.flash(context.container()) + .select('.content') + .append('h3') + .text(t('inspector.no_documentation_combination')); + } + } + + function keyReference(err, values) { + if (!err && values.data.length) { + iD.ui.modal(context.container()) + .select('.content') + .datum({ + data: values.data, + title: 'Key:' + params.key, + geometry: params.geometry + }) + .call(iD.keyReference(context)); + } else { + iD.ui.flash(context.container()) + .select('.content') + .append('h3') + .text(t('inspector.no_documentation_key')); + } + } + var helpBtn = row.append('button') .attr('tabindex', -1) .attr('class', 'tag-help minor') @@ -127,48 +193,9 @@ iD.ui.inspector = function() { geometry: entity.geometry(context.graph()) }); if (d.key && d.value) { - taginfo.docs(params, function(err, docs) { - var en; - if (!err && docs) { - 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; - iD.ui.modal(context.container()) - .select('.content') - .datum(en) - .call(iD.ui.tagReference); - } else { - iD.ui.flash(context.container()) - .select('.content') - .append('h3') - .text(t('inspector.no_documentation_combination')); - } - }); + taginfo.docs(params, keyValueReference); } else if (d.key) { - taginfo.values(params, function(err, values) { - if (!err && values.data.length) { - iD.ui.modal(context.container()) - .select('.content') - .datum({ - data: values.data, - title: 'Key:' + params.key, - geometry: params.geometry - }) - .call(iD.keyReference(context)); - } else { - iD.ui.flash(context.container()) - .select('.content') - .append('h3') - .text(t('inspector.no_documentation_key')); - } - }); + taginfo.values(params, keyReference); } }); From 01f2463ed5483adb7c6d751749ad718d64c75236 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Feb 2013 18:31:05 -0500 Subject: [PATCH 164/332] Support other locales based on browser language --- index.html | 5 ++++- locale/locale.js | 10 ++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/index.html b/index.html index 37ff61404..4dc3b373f 100644 --- a/index.html +++ b/index.html @@ -147,7 +147,10 @@
+ diff --git a/test/index_packaged.html b/test/index_packaged.html index 778231edd..1102336d9 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -26,6 +26,7 @@ + diff --git a/test/spec/actions/circularize.js b/test/spec/actions/circularize.js new file mode 100644 index 000000000..8215c2253 --- /dev/null +++ b/test/spec/actions/circularize.js @@ -0,0 +1,61 @@ +describe("iD.actions.Circularize", function () { + var projection = d3.geo.mercator(); + + it("creates a circle of 12 nodes", function () { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a', loc: [0, 0]}), + 'b': iD.Node({id: 'b', loc: [2, 0]}), + 'c': iD.Node({id: 'c', loc: [2, 2]}), + 'd': iD.Node({id: 'd', loc: [0, 2]}), + '-': iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']}) + }); + + graph = iD.actions.Circularize('-', projection)(graph); + + expect(graph.entity('-').nodes).to.have.length(13); + }); + + it("reuses existing nodes", function () { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a', loc: [0, 0]}), + 'b': iD.Node({id: 'b', loc: [2, 0]}), + 'c': iD.Node({id: 'c', loc: [2, 2]}), + 'd': iD.Node({id: 'd', loc: [0, 2]}), + '-': iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']}) + }); + + graph = iD.actions.Circularize('-', projection)(graph); + + expect(graph.entity('-').nodes.slice(0, 4)).to.eql(['c', 'b', 'a', 'd']); + }); + + it("deletes unused nodes that are not members of other ways", function () { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a', loc: [0, 0]}), + 'b': iD.Node({id: 'b', loc: [2, 0]}), + 'c': iD.Node({id: 'c', loc: [2, 2]}), + 'd': iD.Node({id: 'd', loc: [0, 2]}), + '-': iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']}) + }); + + graph = iD.actions.Circularize('-', projection, 3)(graph); + + expect(graph.entity('d')).to.be.undefined; + }); + + it("reconnects unused nodes that are members of other ways", function () { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a', loc: [0, 0]}), + 'b': iD.Node({id: 'b', loc: [2, 0]}), + 'c': iD.Node({id: 'c', loc: [2, 2]}), + 'd': iD.Node({id: 'd', loc: [0, 2]}), + '-': iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']}), + '=': iD.Way({id: '=', nodes: ['d']}) + }); + + graph = iD.actions.Circularize('-', projection, 3)(graph); + + expect(graph.entity('d')).to.be.undefined; + expect(graph.entity('=').nodes).to.eql(['c']); + }); +}); From 123c700853d4a0ee10015010d9042398aa54eb32 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Fri, 8 Feb 2013 16:49:09 -0500 Subject: [PATCH 185/332] Fix midpoint dragging --- js/id/behavior/select.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/behavior/select.js b/js/id/behavior/select.js index 32e246e98..3f7961974 100644 --- a/js/id/behavior/select.js +++ b/js/id/behavior/select.js @@ -22,7 +22,7 @@ iD.behavior.Select = function(context) { function mousedown() { var datum = d3.event.target.__data__; - if (datum instanceof iD.Entity) { + if (datum instanceof iD.Entity || (datum && datum.type === 'midpoint')) { pos = [d3.event.x, d3.event.y]; selection .on('mousemove.select', mousemove) From ba47d3183dc9744314a39c0ddd822dacddb7c174 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Fri, 8 Feb 2013 17:46:37 -0500 Subject: [PATCH 186/332] Fix lasso and add it to select --- js/id/behavior/select.js | 8 +++++--- js/id/modes/select.js | 1 + test/spec/behavior/select.js | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/js/id/behavior/select.js b/js/id/behavior/select.js index 3f7961974..9d30236bb 100644 --- a/js/id/behavior/select.js +++ b/js/id/behavior/select.js @@ -22,8 +22,8 @@ iD.behavior.Select = function(context) { function mousedown() { var datum = d3.event.target.__data__; + pos = [d3.event.x, d3.event.y]; if (datum instanceof iD.Entity || (datum && datum.type === 'midpoint')) { - pos = [d3.event.x, d3.event.y]; selection .on('mousemove.select', mousemove) .on('touchmove.select', mousemove); @@ -46,8 +46,6 @@ iD.behavior.Select = function(context) { // save the event for the click handler })(d3.event), 200); } - } else { - context.enter(iD.modes.Browse(context)); } } @@ -61,6 +59,10 @@ iD.behavior.Select = function(context) { function mouseup() { selection.on('mousemove.select', null); + if (d3.event.x === pos[0] && d3.event.y === pos[1] && + !(d3.event.target.__data__ instanceof iD.Entity)) { + context.enter(iD.modes.Browse(context)); + } } selection diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 95eb3e78a..5732b58df 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -9,6 +9,7 @@ iD.modes.Select = function(context, selection, initial) { behaviors = [ iD.behavior.Hover(), iD.behavior.Select(context), + iD.behavior.Lasso(context), iD.behavior.DragNode(context)], radialMenu; diff --git a/test/spec/behavior/select.js b/test/spec/behavior/select.js index cfb7b95b1..5e9bf9ea3 100644 --- a/test/spec/behavior/select.js +++ b/test/spec/behavior/select.js @@ -42,6 +42,7 @@ describe("iD.behavior.Select", function() { context.enter(iD.modes.Select(context, [a.id])); happen.click(context.surface().node()); happen.mousedown(context.surface().node()); + happen.mouseup(context.surface().node()); window.setTimeout(function() { expect(context.selection()).to.eql([]); done(); From 5b646b75c322124f122a2508a246ceec9ac7944a Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Fri, 8 Feb 2013 17:53:43 -0500 Subject: [PATCH 187/332] Only lasso if mouse has moved --- js/id/behavior/lasso.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/js/id/behavior/lasso.js b/js/id/behavior/lasso.js index 3986b5cf2..e99b1bf14 100644 --- a/js/id/behavior/lasso.js +++ b/js/id/behavior/lasso.js @@ -10,6 +10,8 @@ iD.behavior.Lasso = function(context) { function mousedown() { if (d3.event.shiftKey === true) { + pos = [d3.event.x, d3.event.y]; + lasso = iD.ui.lasso().a(d3.mouse(context.surface().node())); context.surface().call(lasso); @@ -35,20 +37,23 @@ iD.behavior.Lasso = function(context) { } function mouseup() { + var extent = iD.geo.Extent( normalize(context.projection.invert(lasso.a()), context.projection.invert(lasso.b()))); lasso.close(); - var selected = context.graph().intersects(extent); - selection .on('mousemove.lasso', null) .on('mouseup.lasso', null); - if (selected.length) { - context.enter(iD.modes.Select(context, _.pluck(selected, 'id'))); + if (d3.event.x !== pos[0] || d3.event.y !== pos[1]) { + var selected = context.graph().intersects(extent); + + if (selected.length) { + context.enter(iD.modes.Select(context, _.pluck(selected, 'id'))); + } } } From 334cb9f93b660a204b826eb6d1b3f0244793dc84 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Fri, 8 Feb 2013 18:01:45 -0500 Subject: [PATCH 188/332] Fix error, pos could be null --- js/id/behavior/select.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/behavior/select.js b/js/id/behavior/select.js index 9d30236bb..4d0bef2de 100644 --- a/js/id/behavior/select.js +++ b/js/id/behavior/select.js @@ -59,7 +59,7 @@ iD.behavior.Select = function(context) { function mouseup() { selection.on('mousemove.select', null); - if (d3.event.x === pos[0] && d3.event.y === pos[1] && + if (pos && d3.event.x === pos[0] && d3.event.y === pos[1] && !(d3.event.target.__data__ instanceof iD.Entity)) { context.enter(iD.modes.Browse(context)); } From f4a6edb224edf0b43a70b524b59ccd11dfeeaaa7 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Fri, 8 Feb 2013 18:25:39 -0500 Subject: [PATCH 189/332] Localize labels --- js/id/svg/labels.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/js/id/svg/labels.js b/js/id/svg/labels.js index dd26072f8..507f4cbdd 100644 --- a/js/id/svg/labels.js +++ b/js/id/svg/labels.js @@ -82,7 +82,7 @@ iD.svg.Labels = function(projection) { 'startOffset': '50%', 'xlink:href': function(d, i) { return '#halo-' + d.id; } }) - .text(function(d, i) { return d.tags.name; }); + .text(function(d, i) { return name(d); }); texts.exit().remove(); @@ -119,14 +119,14 @@ iD.svg.Labels = function(projection) { 'x': function(d, i) { var x = labels[i].x - 2; if (labels[i].textAnchor === 'middle') { - x -= textWidth(d.tags.name, labels[i].height) / 2; + x -= textWidth(name(d), labels[i].height) / 2; } return x; }, 'y': function(d, i) { return labels[i].y - labels[i].height + 1 - 2; }, 'rx': 3, 'ry': 3, - 'width': function(d, i) { return textWidth(d.tags.name, labels[i].height) + 4; }, + 'width': function(d, i) { return textWidth(name(d), labels[i].height) + 4; }, 'height': function(d, i) { return labels[i].height + 4; }, 'fill': 'white' }); @@ -149,8 +149,8 @@ iD.svg.Labels = function(projection) { .attr('y', get(labels, 'y')) .attr('transform', get(labels, 'transform')) .style('text-anchor', get(labels, 'textAnchor')) - .text(function(d) { return d.tags.name; }) - .each(function(d, i) { textWidth(d.tags.name, labels[i].height, this); }); + .text(function(d) { return name(d); }) + .each(function(d, i) { textWidth(name(d), labels[i].height, this); }); texts.exit().remove(); return texts; @@ -233,8 +233,13 @@ iD.svg.Labels = function(projection) { .property('_opacity', 0); } + function name(d) { + return d.tags[lang] || d.tags.name; + } + var rtree = new RTree(), rectangles = {}, + lang = 'name:' + iD.detect().locale.toLowerCase().split('-')[0], mousePosition, cacheDimensions; return function drawLabels(surface, graph, entities, filter, dimensions, fullRedraw) { @@ -264,7 +269,7 @@ iD.svg.Labels = function(projection) { // Split entities into groups specified by label_stack for (i = 0; i < entities.length; i++) { entity = entities[i]; - if (!entity.tags.name) continue; + if (!name(entity)) continue; if (hidePoints && entity.geometry(graph) === 'point') continue; for (k = 0; k < label_stack.length; k ++) { if (entity.geometry(graph) === label_stack[k][0] && @@ -292,7 +297,7 @@ iD.svg.Labels = function(projection) { var font_size = font_sizes[k]; for (i = 0; i < labelable[k].length; i ++) { entity = labelable[k][i]; - var width = textWidth(entity.tags.name, font_size), + var width = textWidth(name(entity), font_size), p; if (entity.geometry(graph) === 'point') { p = getPointLabel(entity, width, font_size); From 4a9d41d3fbd7b854dfbb61d9d2e92a2923a561f1 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 8 Feb 2013 15:05:24 -0800 Subject: [PATCH 190/332] Start on architecture docs --- ARCHITECTURE.md | 203 +++++++++++++++++++++++++++++++++++++++++++++ img/modes.png | Bin 0 -> 13614 bytes img/operations.png | Bin 0 -> 99538 bytes 3 files changed, 203 insertions(+) create mode 100644 ARCHITECTURE.md create mode 100644 img/modes.png create mode 100644 img/operations.png diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 000000000..c0ff6b217 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,203 @@ +## d3 + +[d3](http://d3js.org/) is the primary library used by iD. It is used for +rendering the map data as well as many sorts of general DOM manipulation tasks +for which jQuery would often be used. + +Notable features of d3 that are used by iD include +[d3.xhr](https://github.com/mbostock/d3/wiki/Requests#wiki-d3_xhr), which is +used to make the API requests to download data from openstreetmap.org and save +changes; +[d3.dispatch](https://github.com/mbostock/d3/wiki/Internals#wiki-d3_dispatch), +which provides a callback-based [Observer +pattern](http://en.wikipedia.org/wiki/Observer_pattern) between different +parts of iD; +[d3.geo.path](https://github.com/mbostock/d3/wiki/Geo-Paths#wiki-path), which +generates SVG paths for lines and areas; and +[d3.behavior.zoom](https://github.com/mbostock/d3/wiki/Zoom-Behavior#wiki-zoom), +which implements map panning and zooming. + +## Core + +The iD *core* implements the OSM data types, a graph of OSM object's +relationships to each other, and an undo/redo history for changes made during +editing. It aims to be generic enough to be used by other JavaScript-based +tools for OpenStreetMap. + +Briefly, the OSM data model includes three basic data types: nodes, ways, and +relations. A _node_ is a point type, having a single geographic coordinate. A +_way_ is an ordered list of nodes. And a _relation_ groups together nodes, +ways, and other relations to provide free-form higher-level structures. Each +of these three types has _tags_: an associative array of key-value pairs which +describe the object. + +In iD, these three types are implemented by `iD.Node`, `iD.Way` and +`iD.Relation`. These three classes inherit from a common base, `iD.Entity` +(the only use of classical inheritance in iD). Generically, we refer to a +node, way or relation as an _entity_. + +Every entity has an _ID_ either assigned by the OSM database, or, for an +entity that is newly created, constructed as a proxy consisting of a negative +numeral. IDs from the OSM database as treated as opaque strings; no +[assumptions](http://lists.openstreetmap.org/pipermail/dev/2013-February/026495.html) +are made of them other than that they can be compared for identity and do not +begin with a minus sign (and thus will not conflict with proxy IDs). In fact, +in the OSM database the three types of entities have separate ID spaces; a +node can have the same ID as a way, for instance. Because it is useful to +store heterogeneous entities in the same datastructure, iD ensures that every +entity has a fully-unique ID by prefixing each OSM ID with the first letter of +the entity type. For example, a way with OSM ID 123456 is represented as +'w123456' within iD. + +iD entities are *immutable*: once constructed, an `Entity` object cannot +change. Tags cannot be updated; nodes cannot be added or removed from ways, +and so on. Immutability makes it easier to reason about the behavior of an +entity: if your code has a reference to one, it is safe to store it and use it +later, knowing that it cannot have been changed outside of your control. It +also makes it possible to implement the entity graph (described below) as an +efficient [persistent data +structure](http://en.wikipedia.org/wiki/Persistent_data_structure). But +obviously, iD is an editor, and must allow entities to change somehow. The +solution is that all edits produce new copies of anything that changes. At the +entity level, this takes the form of methods such as `iD.Node#move`, which +returns a new node object that has the same ID and tags as the original, but a +different coordinate. More generically, `iD.Entity#update` returns a new +entity of the same type and ID as the original but with specified properties +such as `nodes`, `tags`, or `members` replaced. + +Entities are related to one another: ways have many nodes and relations have +many members. In order to render a map of a certain area, iD needs a +datastructure to hold all the entities in that area and traverse these +relationships. `iD.Graph` provides this functionality. The core of a graph is +a map between IDs and the associated entities; given an ID, the graph can give +you the entity. Like entities, a graph is immutable: adding, replacing, or +removing an entity produces a new graph, and the original is unchanged. +Because entities are immutable, the original and new graphs can share +references to entities that have not changed, keeping memory use to a minimum. +If you are familiar with how git works internally, this persistent data +structure approach is very similar. + +The final component of the core is comprised of `iD.History` and +`iD.Difference`, which track the changes made in an editing session and +provide undo/redo capabilities. Here, the immutable nature of the core types +really pays off: the history is a simple stack of graphs, each representing +the state of the data at a particular point in editing. The graph at the top +of the stack is the current state, off which all rendering is based. To undo +the last change, this graph is popped off the stack, and the map is +re-rendered based on the new top of the stack. Contrast this to a mutable +graph as used in JOSM and Potlatch: every command that changes the graph must +implement an equal and opposite undo command that restores the graph to the +previous state. + +## Actions + +In iD, an _action_ is a function that accepts a graph as input and returns a +modified graph as output. Actions typically need other inputs as well; for +example, `iD.actions.DeleteNode` also requires the ID of a node to delete. The +additional input is passed to the action's constructor: + +``` var action = iD.actions.DeleteNode('n123456'); // construct the action var +newGraph = action(oldGraph); // apply the action ``` + +iD provides actions for all the typical things an editor needs to do: add a +new entity, split a way in two, connect the vertices of two ways together, and +so on. In addition to performing the basic work needed to accomplish these +things, an action typically contains a significant amount of logic for keeping +the relationships between entities logical and consistent. For example, an +action as apparently simple as `DeleteNode`, in addition to removing the node +from the graph, needs to do two other things: remove the node from any ways in +which it is a member (which in turn requires deleting parent ways that are +left with just a single node), and removing it from any relations of which it +is a member. + +As you can imagine, implementing all these details requires an expert +knowledge of the OpenStreetMap data model. It is our hope that JavaScript +based tools for OpenStreetMap can reuse the implementations provided by iD in +other contexts, significantly reducing the work necessary to create a robust +tool. + +## Modes + +With _modes_, we shift gears from abstract data types and algorithms to the +parts of the architecture that implement the user interface for iD. Modes are +manifested in the interface by the four buttons at the top left: + +![Mode buttons](img/modes.png) + +The modality of existing OSM editors runs the gamut from Potlatch 2, which is +almost entirely modeless, to JOSM, which sports half a dozen modes out of the +box and has many more provided by plugins. iD seeks a middle ground: too few +modes can leave new users unsure where to start, while too many can be +overwhelming. + +iD's user-facing modes consist of a base "Browse" mode, in which you can move +around the map and select and edit entities, and three geometrically-oriented +drawing modes: Point, Line, and Area. In the code, these are broken down a +little bit more. There are separate modes for when an entity is selected +(`iD.modes.Select`) versus when nothing is selected (`iD.modes.Browse`), and +each of the geometric modes is split into one mode for starting to draw an +object and one mode for continuing an existing object (with the exception of +`iD.modes.AddPoint`, which is a single-step operation for obvious reasons). + +The code interface for each mode consists of a pair of methods: `enter` and +`exit`. In the `enter` method, a mode sets up all the behavior that should be +present when that mode is active. This typically means binding callbacks to +DOM events that will be triggered on map elements, installing keybindings, and +showing certain parts of the interface like the inspector in `Select` mode. +The `exit` mode does the opposite, removing the behavior installed by the +`enter` method. Together the two methods ensure that modes are self-contained +and exclusive: each mode knows exactly the behavior that is specific to that +mode, and exactly one mode's behavior is active at any time. + +## Behavior + +Certain behaviors are common to more than one mode. For example, iD indicates +interactive map elements by drawing a halo around them when you hover over +them, and this behavior is common to both the browse and draw modes. Instead +of duplicating the code to implement this behavior in all these modes, we +extract it to `iD.behavior.Hover`. + +_Behaviors_ take their inspiration from [d3's +behaviors](https://github.com/mbostock/d3/wiki/Behaviors). Like d3's `zoom` +and `drag`, each iD behavior is a function that takes as input a d3 selection +(assumed to consist of a single element) and installs the DOM event bindings +necessary to implement the behavior. The `Hover` behavior, for example, +installs bindings for the `mouseover` and `mouseout` events that add and +remove a `hover` class from map elements. + +Because certain behaviors are appropriate to some but not all modes, we need +the ability to remove a behavior when entering a mode where it is not +appropriate. (This is functionality [not yet +provided](https://github.com/mbostock/d3/issues/894) by d3's own behaviors.) +Each behavior implements an `off` function that "uninstalls" the behavior. +This is very similar to the `exit` method of a mode, and in fact many modes do +little else but uninstall behaviors in their `exit` methods. + +## Operations + +_Operations_ wrap actions, providing their user-interface: tooltips, key +bindings, and the logic that determines whether an action can be validly +performed given the current map state and selection. Each operation is +constructed with the list of IDs which are currently selected and a `context` +object which provides access to the history and other important parts of iD's +internal state. After being constructed, an operation can be queried as to +whether or not it should be made available (i.e., show up in the context menu) +and if so, if it should be enabled. + +![Operations menu](img/operations.png) + +We make a distinction between availability and enabled state for the sake of +learnability: most operations are available so long as an entity of the +appropriate type is selected. Even if it remains disabled for other reasons +(e.g. because you can't split a way on its start or end vertex), a new user +can still learn that "this is something I can do to this type of thing", and a +tooltip can provide an explanation of what that operation does and the +conditions under which it is enabled. + +To execute an operation, call it as a function, with no arguments. The typical +operation will perform the appropriate action, creating a new undo state in +the history, and then enter the appropriate mode. For example, +`iD.operations.Split` performs `iD.actions.Split`, then enters +`iD.modes.Select` with the resulting ways selected. + +## Rendering and other UI diff --git a/img/modes.png b/img/modes.png new file mode 100644 index 0000000000000000000000000000000000000000..6b5ec860348b7217977845221eb19091f842502a GIT binary patch literal 13614 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 z zZQaUwpd1wgw6o873glfYFw^3pV_;33wMngJI`Z}lM z{EcrYYockXp<#N8fK$ysRNOnTdEvv=bvGLzWUYStKax$slVYVVj&1r3n`!RreAR86%oqm6QBC*aPzfS4{x}&-m(^z zuf~w>nwP5viN83yg9nG$Mb8y za{KUd*5T;%H^2Me4exlzI|@PY@nfs|qVS_TwhWv2rr2tm+c8YR@#Ru9Gp6*54M$&~ zXWVNj=^N?E{6L`^v?xJAo6J8@*8fj|YBGQVPUWFePWN8Q5_!^sv+?n^Mye5|Ym9MQ zku>Uxx8q^Hs}T7hD(}Kf^m%I=ur2 z4h*0FyD!o$XU!8l)Rx>;c_DTVI`ykB_PG9hcJyO`*J9Dr71vD8S_(8L0q8uX-!h8|m*J>0E@1^MXh5=+GQ1`*_#M-X->&an)|G=<>Xh ze6Cp4k%+_YGTEryIvIz3RbQ>3)N_izPQ@p9n^|V?`@jGDSxGDf9s4#WWDns$SMl`h4?g;lVMF&4 z0!-HKL1$5 z)4WnpAHT&wv=IZwXg!<+YFE-$439HJO(Txn4i5j@Uwm@7=(5XGSD$%e-*6ERX#U2} z5fK)tFV6y#xk4*bP~s0jmhY|U7o65lO7Rxl%en!zfrb|Fy09iLV7qZrsta}x_9VVnkk{|! zY^3#$siDz}aecq^Y4}+hk=I{!#dOpUbiH=|()FT)9ZXOI6Cf}Q#y-NvcSb|eB|a@K_Uw&@^RzKUq0OZrCKLaDaB8KBD#^3QH3?U3!%ONS5t^=F1_ z{`J2c?7HLgBga*^l>mS5~Yuzz^)!3Se=zT;;P zR-5bFnRq=zgX5rj11slUCNf8UpFnM659BL1?i#jWzizW(>+di>^+zrn^TB6b5I29q zow0YJ{Kk<~LtGq#+96-+U$bBNFBvLLQ3MlL{-bd55>KAqP?tWDh5Df*Gg!Y;Z-s?; zc%y#%sbohlhD?Ki#~^Iz=SKQ!LL5mH1yD!=m_@ymttU<#9*+QOnN^RE#sIlL^Ywd&Cyx4hN9F3+P9qmfaiWJC{`tkj zwLf*i@PB{ff#G>xM%~F_hUa)k?D(dY!|UI3)$sLCJUu+{r_WKag~y`oa>w9K&JX|g z&pyT1K`%}3&GMS8xu{;E<#B4;uHmUi_eEp9;;B>NEg<>x(z5nEGTyEdRZv0-KxhW< zB~cE?FTCpN;ULKJ`DEu8#m$qJ&=fc2Zy9E78Q1Fm(MKPN?j&rZ6PqBfCg5sp;~mQT zg5@TM>@;#`Y3!_4E3w-=kg)8)=dO@x#ISdtj!8E=7=j~}R$f~mO4MekI-pGkYw086 z3aibE8=lmcc;GDdnvK(h2_ma|)sesemrb?Mv-T%11{+Q?B#$GF-YZh~V_oCA(~?}+ zOq}%@eYUtDY(K)JEXkld8l6pfR-?$PLkOIVT0wn!Q$rdJLSr%XUQO@WD4;@FL4~)n zR~NDbsnvzmLyChz*s9Fc+b}MUt>JeL#JH;Ta;84YAd5~(Xm?H{agm>Aai|Ur#nwVT z?Lwx913ncKIaQ{R7T$O*e(I7=CeF_5qHQk!^7xbWI|jJpM}>noG2$vnUtIm^pZe+H zV;}!m-ugAR2c4U1T=wtZKkVAIYjimB?*y@5RnTG1SA8FIiaQmuA$tAmUOPOy^TOeI zKE>jEQ*Vjf(sUlh+2ivYTYq`~gTuTPrG<_oEh$J}{crXRm%U*pPxDyjux7>ZTOatq zIm4(W9_P+2o*l0HrR| zS+TLHwQDaPZn|-Pxc`CsW6*IlIvcfU0TIF@yb&`7Q_4DxnxsVMR+JXyi@rbk<~K7B zSMTPDOCI^I~r}m00EB1f$7skrAdKqb#Wh@}{pCho?qm8=yia5k4Xk*}UZbTeK$KDY9D-}GQ z1*YUAXEK8^vU}D~>7;ZbI<&46us7V+ed&7iGLbR!8a(^pMtyjlaoPZpe*!|$LcyC z4LnXr?Gj8&O9Kz(ZB;^5a@W1oi(k;u(ZG+mRiwPxYUUO`#OrGpzFuLP?>m)q!ax8@ zA%jCt4$Dc);z3}=SB{S}>G>){Hx+_M)yq1vw-7}J2DyxXAuex%$j*qnkY1fOu%1Wh} z;mZrF&#W1qeCnyZXQr%8<-slf95ll7$Cidej~ySrc-OavQ-@9u_dI#e@blwyuv0dzJ8OOAOwKTqKw-(uE_@0>$EY`2zQ1E7;!U-=~ z>SxY!P(_3tm-~wB+McvcPrMBnq1VNl3MfRQBO1$`Jm@qILpog#>Q3U?FEZP!>-9&6 zAtjl~Q(u)AlGYQP4M#a8K#;*9Yj6QCPhNKnqLFt6JiE;D6&iRSp(GD^LxbN~xbq^u zDeb2+5X*| z38X~{C_)^wv-N~V=hBs8VLOTH>d(yg$eO}I$H&sy3ZwBNf8}uM&0Nqkj-K$WY%0W6 z%c9W%MWM>e;e@e@Pr0G66f`i>3Q_Q>It$+Sx#Z1!6`Jw|h-De;&GK+n_Q72-{O#vI zpU3Mxhx)T(=F`IIjX6wp&Z>F=@?Tg*BOirRK6Ny2eB&FUU#SRb;g0hW#5iGx;`rd+ z;mBF?6|C}HbIrBGZ~Vruhvs|U^Pb_8fBenJTJlOf45Td7#lqpG;Zy(lk>T2Ft{MLD z5B`Xr!0F*PfAcqohaP%Jqyd)?^Sfrrjm0c$E2Qsj8E=C@mmsUieI4c&hJcpqlTS_@ zPD7M3QfBR_{fADANnAOQ7R^a}{UMzDRaag~IqD26bY04Wwq>!E-Yi0RqRi}7Zr-vv zy#(iKEC=6t(?%VFi-VjD56=!KdEJG|5=Y}#ChDmtl@xhv@;HSRrQ}MwveI-aJuCE! z1S7fh7rFU~<4A=qIbbGvqDwG}PDv~uUv^D=BE42F(``Jt1W)8|I812(g|>hLlN9lB z5iNFQ86#6Bh_o0GJlEQ58tph+=t(#9bT&EyGH4WTE722t0Wv)R#)a8F!6!Tvl50^? zX~FH28$V}ZzI5d%pw5#Dx8l3t&S8h#+4W3IVXF-;?;~Y%cJk}i6MYWDieu&TZR}KR z1S~ozmx5m3cuKbqI2B&bZ5d;j8y1}R&|s+4_9T4g!{Sv@={jSK_Pmr!CzwPPj3w3t zcNtj8q;`8)kyXl0Vv9_gUG7{pv4lq5{tQ>*9PH)%>Bm2w?JhbdC$iJynl7~DGaDJ} zA2nHs#lxF^CxD6{=d}QzJJk}76a|@(d zQCF~dPGekR*yn=+|6avAVHaI=5tHe6$Nr}Esj(J2ji*qU{FJ?_n{qV&>}P*w*v@yc zua1>!9+Q182v$#-#@X zV+J=fcjDIVGIcJ>u%iiOWJXiJU({pE-9(mfmJT~bxuk?e0L^1fgeQBP(5x_;cF70=eFcP-X zfLK0>Lho`@ofm!m#Y)=u;5M%l!hSMn$(LL0HGp&z-!%7$*gAM{vfcGsdDuW&DkwU%G3>Ts zS!K=>U1eGTR!XzQnzm`Mf?7&d7Q`t^ohr@ph`u3iWkJZ$=y;Xg{A9AnD_uVvkUZQ^ zoI>H%K_$P+;=do-m-ww)){8I{Ja7sPqrw;rN)kykrQ~{xn3hlLYrxOPOVtX-mp)e05U-(InDXoJt?-hcmi6ed zqcNJ=@JX5AU)lK6IXb_S*Bp?;UX&HYLQ89;+PL;GULJB!DM?FZ>WF;YZb*esf#{Y^ zFXeYb3J^cOIeQ^*PG83(S=R%p&m+7!FWv75=)(nmdoRCyc-?DW%dqF_3@KC5p`XGg zpR#DcozKu$C+I^yKL3R;g!dSl`H1uQ#y7t?+=BBu$cvWmd*AzVmIu=k`UzZJ$EG={ z`*JF`Z~WUkI&_G)!yC??UmJMMt6zl!uVn-^2Ow=AG+vDu-qY%dpH|)mW$)g-JjvP< zJ5Vmm-#p4xxF^?_Uv?RKymt7}H@#`N^wLY?Tni|W!EI)F3!Qc?E^%5Bj`N1TXUkW| zCp{lD$ey8kA2=O#36F8CX6A4L*~V6ftsRrh_vp9L+wpags=Q@Cg@0_+!K*a#6g|Yg z>^YQ;NxVqNDU86McuHSXT5y|gNFLIi3)8td^3D#@Y;f9#-HQEAyBU18`F+mF(kdp8&;xi4cSFPC{l_hXH8p3;X@F zEJi?ub~}lU@UwCS8>C|#dgCP~IOWH3v!brT>DXh+-Z?6r6erG|zj7n5v&(Kds?kA{ zp@MuL;oTW&`XobsRjiKDH5oc2p)}-hX~D@W`J{8>jW^~H=D>l&=_!e$>*9h7E&vi% z;VrFUuvOtJZ$3){Hj+s%yj7`nsMYmHGVO(&8jqQRG7+vN`Nccl=8meviT!-S7`;f} zka8iTWtb;5biuU^9q~gCJ~X`Z_FtgYABJPk=nO=P6cRCnjJ+!>IaYTH$Ywx}Gy77K};+Jc$Q;@$WpUjMm_{R4h--wKi zbcCQ#?P*VLIIM?X8K^zoFOvPPaQ8_k#&D0ss@7u7^`j!=UHnRVqU4>kCIL zdaWc#__|m>s_!m3+rV}(zG)n%m~!ilfREuE3xP1svovT1mvHx3F`?is1%IVg^Mrt) zLP%M>;V?yJd&G*N;dt*+E{5y^z_B3xH*@F?-`_VOm96D_Hq0jtMG5fvbx zt`5&v-d&hf7pEdu_@eD)g$>c8%(}^6)`I{4Y{CEg|IQ!Xee#R<{Kx;}fA(8>S4W8ar0=B<{TmH1AxraA zt`=V2jWgUFw}$23y>ulfG`YrZI`s2+gMK{S!-seVzd7?8lUB`mk{+aAc=6j?Tq=|L z@X?YfogCT5wCQdQhL?oBQ2W|a3Dq|uAdyU7*ZsS?8}w=~6z5zXJn#@h_o=Uaq}LXC zhbtuLJUCr#<46`A1^9C4B=@RCdS}Sx8c(82lyIC=x@OEJe7iZ=<4?27}{exW%&ha^CMV~z_x`}2q8eEnl?W$MZh7KN94}Cg1ark&ed2reue^j#b;ESq3 zJ5Q}j&yXI@0LO;@g$U%J_bswvg|{_k(@7?{4fBnIb)cfbE% z|8%f_{a?29Lcb%a6;ra^7cWlta`lzO>8>07y>Yk`1;W*)PW5qW)MQf~;E%|VTZzyV*;aPYshW~x2f@e1$ymCu#kc14jv+4W0fyWzKdC9#v0 zE$uXJ0*q|1)Rh@2{gW4j>?b}I#+Hd=I0PPM0;WP;0t1Z@;xI7VlOcwS0a{~`U>Sbp74eDjEY(P=ovKp<9)n#gsf z$`9GAdU|~tKr1$SKMzgO*1uOp6DMj&AIhgoe|;n|V8adWXCL^=aY(1JhXEtjAmTlq z9-TN2={Udg6gixikq?gST0VGBXYe3=N;-JEVZ(2S?rrb<4BD&z(1A{o)zL5fRKQ=( ztW$h>d0(Vw-00L(f!zbM97}IVqHcpndJ`~0c-*6?5Ifg50|Y4MsR#ic8)K3#m!}!J z9xA)~rwbXYOy~x|L(CB|1BJ%5*6|*LcWoPj!%?}L=cH(DoFBoSu3Jtn!D#Kk2lLuI z=ANUh{wYF2<}6Q74r0gGd;R>1c(mG2mcX^t?1}#V-Q^*1wI4aZjjj- zSNf`f&a;TS(ktWuK^G2fhsGn|CG7rzpXiP}IP!$NI+M%8BC6N5v@d(|bRs>Gkf&%V zajm^N{HCnxfM>^_x7@{ldaK_--Dh#_dv{;`#k0Hr=zsLj?*909|CPIc{J;30%`toN z@;`08n{5$62d)ptq3xgilYesezy1&Y&yDk(Df%bl`L8~7|GduuU-(e1wY-eH3~;o; zlm2lwWsas2W8}(pJ^tPPpS0*r#B1&8;b;nWM|$XpHu!d^ro^;Kbb@#Hl zIhu&~Qwj}qZxOG`J3n2Z18-DS54PR@?KsCI4D1FQhS#8uKL_tOINj_$2&?n(qPx}o z^5%fhX^VoxC%Ei-NL394;=g~8XVw1v}+dQH4{#bE@uk`Ayccn}_2&4AV=g2I`zSW2bOx0TLZjh)$n0@TM~t9haQ-QJZHqeaI{f9EgN{37HYH&^TC)T{ zr(Hp;Tr?SvH>gTx&5u0a@rrgi@bId{c^7~52Y;|B0u~mtji23E{;t3J<+Hng_ka5D z-u=m+{E54F|L7n6qr3lZkLcgXBHsLa|Ng&s_b>nPzdYbp(ff!0@E;B@l`&LuwW-?^ zxa^>4a_@vPEm^?)Zul4ELI2hX^gy>ioBi{S&*zoV(j5SO> zfsLwo6Hg$1;j6sM5R&eGZnl%I1J5CyYRV-;K4bJ$AJ&eV8$SYbBLG0ebPS%~V2sz5 zklt-BY;~^K%>v6fK8K$TuSY{XW!5$k6pjSUzZQr7&NuNz;nRxAay z(wx>sf0V%h`fa>0XnfTN&xfruyYM#{jti5k@3hGAp)!oXxyFo6q_*;Qf;)cmLqO|A$lf|HJ?MpRdmGy-b_T2+_BJINx^f z?qB=6e`ktI$*X=kMAH^-$NM>i3cH6?N=_x=u`@DBb`gDe-Pv0C8!HwGlpi~x0gvfy z2@G_GK72Uw<7kc}qT2_dQy2WsE{d`3NlN;){Kv$B|ACI-d3eUrY*+vhWrFi}-_#Jy z*phGM;H4pfECW1qpE#`JXb+mGFH44dXzSv(ohLgW!2#}$=Xw`PmX1Fh?+FRP919M%gQ}qVe#(@55xiNwRKnNUd(h&Ls&TMi`LeW)^?>UwX)6OA?xQyR8|Deyg zY>}=idsJvUh0lNwMX1u!Mng}UzI)xW3f)k*35HtkwPh^#;F#2Ag57a+ryhunJ~-oPGCNVm;W6jnej3UT zPq!HEp1_b#@@Le#P;_I$39q27g7FRSg01^}ZN~5n-rMHbZ~RyO4eiR^3Xlt^?v0g< zb22T@DweT3>GTFeySFD#%B6RsD_MEuF~jbN-XpJIM%xw1Em~EseOjZ!wu_csvVn{m zZFSODIf4_4EpKULwt?pi7IEsrGKZaZ^=1oc6=ISI;}L}`)9Kn(YxfzN&%iK3 z4&-nBt-p2mN$H;%bH@b%h= zFID*F)r;QVhVNReKy6U7dMGYF5#ebFSS3D(UtNN|TUO-ah0OoefBYZ4;q&2r*xYb} z8(Cg>x&Om{qE=4iN;@>55sU%Zo7R8)M}KsYHN{iGcpnevI62JDrQwsPbNq^TwoTeB zkB#!epU#|dpVLaBZS~_*d?RCcUAnR<3GqxF@{#d{;}t=`7`^`Nk#YB%J`Ma;gZ+3c z`h^>FTPvS!uv_(b!s?#c+&&eg@E%e$PX`s#2j$2Rt>LE*`Y}h7%tS2|&tQHCzHvJ6 z;1@R&{Zx2p#bbN*%QJw>yt6kA)kWu=-_8EePfgaS-*lC}v4M4eg3I0o3exxVPd%Nh zlaHfnZ^II$RXm4A&gHA_URTKTC>{F7CUhajIbO9K7z@F; z5z}vD1&}$U4hW#f077o?j;HKmFm9}<1fCvczLa2$0W4yiL+WDIuo|M21(W5&>sx5r z`68J&=?p@?mCPyh8JCP~f={SNvHIWnM%oAKVr<9inv;!jS|EZj)!&j2zxFa?e0cnX z^0anv4$WMi^KreWQ!0^{*D)SF%;}~;VEMiH{EJ0ZUUYq<^f$vA!FXKv^mUKclo<`^ zj^__N`Ab>9Y&NtyZ>xo zBalbQG9G=JQh_7Jd|M8O@pK^Gw&AC;($8T~?*6sE^LOw5)*t;bA&0}!5%*Whv2v8s zro{c82haFiYnb5Ejz+ul8AA8T@#u#J#<<~eMsS&YLl}J9=b!y%Wk*Z6*hDPHT1Mvi z2R>s8H#9S>3&y3NjvSka-Cza-HgQ!UP4Fi0mA09)HDF$!Q3lLyJ-o2U`s)WOQRc{Z zMn5JWeu_Tbg9Du4t}z*)uE}&=mS~?d=+5OhI$KqEE?Tig+BQ4|!;|3$4Siq{r_GzW zHK$gh`t(Fz6+O!jm%&Fn-j+(Dqq_3$J?iPGAO-&g0vp>bXXrM_+&_?lIe78!=4=je zgv+lmh+!cc;w#4+L&!EoEAJ|%*v`7OF@(x49|p-YqR&zKKtre-6lqkd~ z{HNaKuS{ZFp5q|9*Y6l8!PIR?nXR0*r%V|bGB5XK(gldbJu2K}&^mJDA#Qs?CHRTvVC{@T@(GwHu^ z8VFo=h`(&W2EPkOQI*IGYuQ+u21>?<3#`4hC^4;x~EYPk(Z&Y zLI|`U=iy$(!%@ZYG2y3p?bdvuh}YmTq`*SkQH;7KYiY6J^60P)Ww|M|Z->X^a#(=>hYu&0#sP2ekwK3C!Tj5Q`d_>IyG1p+77PUM z7m0=iMYKf)&Qg~Q_Y9PB7-aT+dA@CIWv{PPeg-#P)POy_d!GCr)ty}3x-!*2=N~wY z3dp+B%AM{Ny+4iF)TKJ#M3j|@3s+k@8oE?qPJ_(s;B#{7detXjE@!@c@%yz|dgFCZ zUpu+~;?}K8mJc6zSdf!U7#FUbmUDQ`StT#g0H%yH_*$J?LbM~^s&kE(39|9%b=?+U z+@NFZhuv_BhvxEfSnyR>xE&a5xf;llUPUTi6t6f5b2d~tKk9LlSGRWUwTJr~V@VM1GKv2S%u?iTlVh0IlkcO!JRfeEtQmNUk2tV&* zKoSBGbAyNJV3pi)K;iqQu05qW$#2r+eE?QKslU*x?3~scyddF5o3y3xiX(~mmtGDn zye^Ni4Nq>a`rwB#c;y`@IAZ7I4AIc0}>@sWKQQ--WmJ)?$e+A_(UA#^)&F{l+xV>&@pN1K!_QzZkPd33T52s z-5lQ)-EgW}E%kG_qqhtAX>~6?j%VfhH~+yu*lPg)oB!Sae%*q}J?0$QjSKNX{gH(Q zFzJMzINJ2j|J(nqe|vBk%8Lp-e|~5eT`GU&1&B_NfpZSW`9nCyBjX*P^}gK7-0&C= z{ah>8#zCP^BLJ83%gicyd}Tv7z5@-pfZHf3zq^S4l~*4o-*A4I&1I!!Q_W_vqW*%YiuB)JZP# z;>YEb4Dzpg*}cgwxiNsh`3HY+_uu=)FM5mn|8n=A{}=!HqAoNaselKcTRk`0wXSsH zUul)SzxQwa8;dOP&LH~X*BP(EsAH5vZFC)(rMx2J8WQ;7wXP7pK6_R~7GbM12d_Kg zbFPQwCqEiF06h4M7ECwA!Lq6%9BG{KW!!j#7aUELcO}K~Y~$fBySqxtF-I4E)2TCd zq%7L5Xl`JD2moHqZUDo(8e!j2-%F2(S+-@Y4#&e&JX@4dH+}N1eqzKAD3U0a51$gu zD25~)Sc+F=j0=*heVOyWj&(r+m~fKL0ishR6g+j|1Cu!Kmq`Qa(5TGD zZ>pD`9_Otxs8x`DRd_l!yBY>V2R)OrjAy4{ysI#oLS`T%d+@4QwGd6IuL@lIg>XQY z5iBjBJI*hoI60T))Q7Qcm@%r(t>s6^Lh&2SldpQ^eUnzbU7y{X;pf!^Z|!<({fiCE zo~H(!&G1Bk^6+Xuigasjx+NMRJYUN69cR3N-#oix09ES=}++$#^#WZ@6oD z(MjeMp7Zh6Jj3%zW!+#6-*_4ngZ6*+FaE_IYk&072cuKOqkgYPOrIdcT4B&(7&^9( zZR(as*WwIFxayjLUGiLx67h3)S04N`XNuI7A@8CJO0fnwy{Mx79CuEJIjw>0Sh2{9 z#^aPBvscg(toJ<#uHwUTUun5kgv5uKR1qN`$EHL2|5eArBS3SYmn9b!7&KOKN%6D3a zgT!iCxeyD9DN1>!WMc}(lmS8vB}C+jiqZ~p16rAr@#I6;r9h4oN@*89ot*nuo$>^8 zEy0DhTM+gCibJm0xS1C52gO7;-}M6P8nYT^4h2c7J~P=2T+b zSyQ<}!LrX85m-O<94D#2et3oCtA2GtS8mFB3{JH5^6t-DhxbwALO*IXzdh=mVsSPp z6Z~z;=svW;O`Yf_WY;$x1yvs?Dk0ubyjGG=)L_BE6g+Jb%9^=l5FHLMSeJ z&@IC+OJf?3`J!34x;yapBy3Kg$RS8~_gWDB{(Vc87{NHSiRwhSV55Ax1G#O72lD#q zo*Hns&4KLuvG#$r_85su_e(s;v z*3JILwoZo1jfY)g+_{x5RaS;INXmD`xXE(5y1eD?Z+g;^Z0MN#s|a8G=*0~u5d$YL z$K`0eD@7O_5y$&7zQyE&3rKV!7}NU*G_lsUw8~09x}CmE9?&|Cz<8nVU>~P*VFt$8 zN84e_Nh^6z2bBpGL`!dT9{Ha)?>?OM4{!)JXpS%YrnNR&ZuX=5Xdd1ZN zjP30*1mpWXAM{w7(ry4PBxEHJ&cJ=x+|YdEY`_!QoX7vC5Bz;|TNqrG3>FyKZt#wB zNJr%5WDmhdIVso_;#U7!kkTmMF#w9o*|kX2H?5;`|NAX{pxfg`Mhi22f6qK1RUGA) zUzHsy8x}WsODt`7DC%%L{JZ+o<5SA0-@a~Tf-~XF0J4@&**OG}TaF2>CY5nxBCrkM zmJ5DbU3fF%b%la^!)o{749PGUt}YjCb%lP786>@u0gi5@`BC_#MtEF$MN_=b_QhwP z2cu8$CR_C@Pw}@*2>9ijHdi@*iH5H6iVJ4hZg>s3)guXhb1a?E8<{K^|MHOt;y4(( z!KqGR+VRp4^QwFLr(iHngRe0~C%AN=Md z<$2Xib&(AW!ITD<(*U13Z`BO;oXx_u0jO++=GB9TXh!bojS8=V^RNNlR;D*}FNvZ; z?l2Pqc6n@&4xbUO)jMAD0GZ+3GEUhL-!MpQ05mnC@J8Td<}##b(CXI zAGlL)c+8nX<_Ku;U-xKSw=ZyK(JOUW*!pVS%THj}UTKCn{8P;19AP70H{n*D24}Ab zi9u~py$lOO@$`ll;i%6Bf0OwB{LlVuPrfWuDN-AsQWT2Jz~!4GMN`V!|KS7y`~rJn zd-zQGOB>@W%Q>9<$6<&FuzFi`;ni_IQt&?*@s?wvCeGp0=@vDAV(sb_9ZZf4&uxdN zhi{`*Vojv?o0{u&+u&pq_4qqp(+yyw_0r8n zrB!}xN!frX zH3V*0j>&8F%E0>~Q`DvaLUQw%a+O=qa@+ZuT4&wogca94`t6VE&oX19)4Q6*EU@1?7r_v~+)dqk0%fFZq zf83KR?^N|>l#>r09gH#y7@9=^GFpQ(PH>Dl%7i*L#^Fd|!T;%~lgMm({9!?x% z`c^lq?vi;(iqHAw+}DcYe{~##I?f+fZ}`#usJl5zcpm=gy3zxA&AaiG>AP>94)=FB znPARgcO++Y;kEH0-O$OvRDr5@j;_g~<7st|p4^DsL0yw&j%@tD-8@Ek#`yM12!LQt z58!)@&^*18w>g40+-`I%e`VJ*7@a5gapt#jD;SSs@|-?qGy3F@hu>N`@R&T8gS3rz0nF!-6yTtlVs=y2$M~ zWjsFD=U9ZCg>bIu9N)v|xi2b~53cGxJaBu?MoZ~NiVK?!Tw8{}IKcx~K2GybsFD0sUUb1)+!mwsBJBg8Sa`(}ZfZMpEAAPsn?VAo?p0;U{L zdeL$Zl->S1M-U^=I7>pLEYF{xwVKV37{9_lBfccEUQX*I)dy zr7_z+?284K%@pm->5rCfgbzNW>_no@DLTWTa5+5lMmVzJK-?K^y!AWI;Y3)+0hE>z z9;nB-wMyj-&U2v$?mM47afM|l# z^)A!5IT>&n+;Pg~q7FCSJ*=B>WaIh9lj)5mI^}(+Wh^hbJo_$Ps^|uvGa%da5jfY? zLH-;(eeyI*nlh?#uJyinx2R%HE+XZC|H;0~YtVSFA9RC`u%K)M9M9t?}b&Px*XYa1oBDZT+6quU`q$2C#IUDq-HZ;wh6}c5o-c~mvH9D(dlzMWa+&_FFIQk zzQU`|vb*S9WEdukY_J0|3SBz|h3Jr-CqGK-{tWA82*-OVf9{`hD^=Nu#fT%9t`FmZ zmYopjI!5t?Q2fh@|yX?bGty@lP7#_@u7^w3NjgzD5%| z)H9BBXAJ5h;3uS)7LUVg-<2wVSZ>f!`VG`zQefq%@XCYX93SAEGyn3-l(F!D=I=aaC+VJhn80Hu<{*-tHI^T9X!ezf;duQpMmGl@!ppINbQDM;+ zJ;5=!&8dl!I+@epTt|l^dCC_&atG&D-IJVw9qDR$-yBfh%&O~V^MtWJc!C90XW^u{_!I$td+b{3Cf#Ewo6>D9OUCzJYzBc*Z!jI4nIC81+jIngO_ zFuyNyyZ?<@!$S7+Nt3+^9S_ODHokT|jnr9Hkztwjt6NB(A_(;2dj&fuucM@3+>l&e zy!XMiYTV;ygV)~`$$H}E#lMXsFqraiI`E>qWVnc?dgdO{h*KT$e|R3jZMuNvWetX# za_~cgs3ey7Uun3ui`1Rtdky++n>6FwN!VNeZ}gb1YwObj4mhX6d4JKw#=rc_UnbkW zXwp=UciwL*Lkql%TIw{e;p$n!wjSVRKO%I^(bJTzIQRIfH@6@sP(*?caDD9SrP*%M2NhUf=yD1vMw;;h+8a-KRb! z($gA?obN@=^7!p9oJQ_6=J4Uj>qK^lT%!|AhNpgMmu%@TzWDz2!|-+60Oy>ufy|t< zsR6zUIXLl6q{7jDS8Iw-zPbS(o`U6ka=wM*%4rzlUJBZ-(ox!Z>BopTk;5yC&09xX zm*ZJ==&sILN9%~b9n^J=MM&D3M!}f#RY!~wtsQ;Hd8U`X66^Caa6;^!M1?Ow(qugC zzr&n`TW7mf8lU$Iwx0(CE1}$EkfTVZ`+!_|@aCJkkmiX6v8e!$?|$zWzq6-wx-R(l zd7nu7?AI+pTmJVy=&9?6Pu6{X($^g#ap3WEoXg21q0OM?a9%R{hAiIgDW{L??Iq=8 z#i^6GsUjL0(+yC?>1=Xeb%Cv+n|_uD-+gKIi{^-Z*=+dJjg3mY!HDX@mo;3`ZEo2^ ztpuQ$JOzmOOSvw`QJE>b>!poZM`0++U(YuI;OnZp#Gz_TrZjIw9S99BIO`jR&#C`b z7Z?#Kk91HvZ9nyv3)KB~+7s80@vtQ$Hc(71%v{w8`Zrx&L)O?6@X%H3?_JA z^h9Ki25uRNc^^}}?p+S&S^aHeFOP0BLcu?Lh|!giUtZ~9zI&X_jDS(0QGuIc%X7L2 zQFkj76?+;7MpB((p;-}WzCZm}fAy=oU;gD^Vf1+56S$&BkI@}DhH)#yy&?%1U6bf) zCWEbbxj2OOomUBC*G;;*NQ*v7+jaM?{qwA5_j%`Ah(17;n0y|6&$X2CAH7Frr;ZcJ zR-p|I{bq-ex8+`G`EDKg(^1jqi`Mt~uJH{|tG@PKv$_T2Yj{tOlYOUa42&^xKzq@% zM0&RQQp@W_*S?O>7ZUN|7~JV1DC2E!PZVE1N-wH=^_O!@3g4r7hobp66i7z|dA7T| z93%6hH3Az4)twq%@yAP@B0hZh=!19rR{Brw{_v0faNT8NI3E2!i5)MV^e+*nc|k6!CyCSgo03M>N5uReGuUHIUmyT>N1=5*BaylBUR2v0!w zIih%DyfTFQOJC73GU(@xk{U%b;5D!!`md9NA>HfK!yEGvwPi!kYK+J0pw<1Obo~!0 z%D3}`a5<1;`0z?`LWIK1Xv^cCGVcBL{M{-|o^u?=C}=PHo9%KMT0rn!Us6(bp?D`3 zd`32sSLX#LfaReGT1A`xi~xSp)eCQT_N&N6_sViE2Fm3ZpDx!~PX(PkHsyQrP9G+# zwKEA#`lDL-4Z>D#{cwY83>qELU*eb!udTEXhwe@JZ^D#>|JO8ptpR*TihKQBXCjKq zTnf0I`d3-JzTFxh75V$}er_9Z8L;Dn7wStsVB`s@1D1oEM;QAERKP#mf--+N$i z*OJxO2&Xeh$Vt3N;a>MSNQ+e6&jGx&mdp1qGl+PSV|(0y@#9)tUUP%%(M7&T&e6~k z1vdC?BgRhVOqZ9U937gYBAa%wbbA=d7kyfov%###)%m0~Rm}drmZbQ$Z<>8mnD42M zcjde>4kj5ba>*Iv$NTSou-7%dtXtsYh`M(i&$<*tZkWJkd`}!D>}veOLygI03rHGS zM?8aYx?aj9yX58moG)FDCiVH+h$zESB|rb2pYNl0@4f#{QFDeHotxt_J$&cx)5h$6 z_ji8QS2;iE^S|kA`Wt$OpTVDAaA+m#*1YTmQR9es#fBiD7vM`6>Zel=(idGiIE)5b zMaOLL?1xT0BEts8BMlSio$OOT!A8@bXp>j1m#3B@qQ)~BqMgECd<&!tsny6}ZI`2Y z+oPV3RsaA%07*naRM|+E$F9s2Cpzb~9T9cg+-GEKSwi-~gVS|#`Ufg_Du%v$@pZ3=^oaIVikI6gQ%B!Y>Z+J-RFEUt*f}S;_dIolN@<CEGYy-}ZI;#kSS+xf#UyANC7nazDN??#HuU&j>tT@0>M!}YrIe+@*AYcSr8 z-WJV?SL7q-fwsmC1L-^wJZ@;=iACIQ*jGQ@qtmZmK3ZgMb~8OLlQu1Wq!oc!381>a zuGDZZy_?78kNsCmxF;t#w;RAFYq;-IE*R|R@s*ZGeb@E|*ewA=2fS=tJAKK*|1w*z|vg3?f&CvI+TRipdJ1t|; z&c<#mx0)kvBJRumzpGxAK?rUXl_0iGEy?Z_-BMRU=I4s>n z4urGuju{%je4bPGNE^-a(mVG*ZVJIqf9i?ek7_(JLOjaoo;BcYjt@Qa<<0kt&O}NK z6793v;KHfH9JJ)qMLu$v9K)5Y89%J$?QiED&L&OSc%oaR5x5#L8OY;aa$>YzVR#l_ zzmFf!D|4^0!6)y1c=xD?ECNcXo_Pn_dTJUU)u@q9RNlmg8+;}^+1l2fFI74am$SL@ zQp>lu!7t6Y7Crpl)Qt$C<8*qptEc^Wj>aSK@6(gL8^w@@(+v8CLC|Le6+kp)X2<~8 zoxkmDuDsIbKyF|T!8c|v?I;=Vc)xGW$gAuznDL^T4|P>@>MI4@{f47f19MNl-E%($ z>2UJSX=8JGjvG|J1)O zKqZZV-1Ak%;GGtxmOSJvJmg7=R#NwCj>8jyl=(z==0BX}Jm*kS5C(}vDtG+!z2;REXp?Y+bULGo@D&d0Hy#>A4tbV=iI*~J^4(672t zGakea-1+y{34c54n754}+i?&u{_S(u7=O@HxsVW?A@FWnmkJ9P>mOi|LUv^C*_;) zq8o<7`9d)*T_p3#r=MiZFMAXJH`AGqKWPb&oVU3^#Zz~$pXEq1EX@qVq;%{9E@ZWa z0YCbKD^&d-?`~J{;85piD(fD48MnvKR%bK->j~!3zMBn|53CDcaLUZ{l+LYc0PP2t zDkyvDo=UYFCsJlO4c3aS>#Tu&zQ#!2(t}}}ZFb6jz$M2yn(*{gO9LrtdC@r6vz%cW z_7Fn>4sksaWvcX{!Z(UWGzJi=<5k)E*jTEsa9D!EzN4bh36Tle3gnok(qMz z>lp9ODSl~;#3R}{nc#awE6sS>{hVr*H&^7lV0l%z6BjSuVuc6fjb&iYlSa|7M`%4_@!5_Hm!vJ>mttRZb@3+%WxIA46EDQ-o7W{Ad`%%++Y}Ri+1@KFGqQeYZ%<-K!5eCU)}xLpZ!0>_q`dLce5U4bU3U$=IzNw zb%Y?MP0>tOkFzs);~cD4t!}GbiZX1W8 zoAdN(G~@{6-QaEaIT(*`@yet%2JB6L`sb*tv*@IHMG+69>tQYR_eF^K_0(LK&f%du z9l|-N98E9mk`bMG)l)a0eqO^x9t=61Qztb;n*Vr(2mFoWNv#p+;z4|oe{)$hZAB=L zbdig?bY--2oGsfSH_K^!Py_Wr4T`A+Tf4bN=3$NMyg8yz;}}wA$HPp14D+;vhr!Nmmq9{iPh9eOjDr$yjz=(T4wrD2aAJZ^ zk8n=6;?%JPmeVKRShyc>-&R4dKppp-dI7;Ygpd5Ex3h&LzN!sk~h0sfU96XS>%td~M@wKePWCpPodmI9Xu4Po5 zv2u?xQnP`*Pxh#X2Mpc(k39yj97n_1J#HNA(;xqM-It?Fp8A;lc9zu8AbH;o{d5cN z6%U=$V60J!GkAkY_vw!@y3iPH?n#dU1YF*;N7)*X_jeGk@i#^s38jl_=!{JuVVN5AG;GHjghYzI4lZ-lZO+KCT1f0n0 z2$W2QoD97iO~HED;QxIOB68@WG2Nv1o01*pqwvAk$onB0Jh@|9Tr#=t z^Upt9Yi&j;oZxaIm{UeQ$_}S#%dMqbNSh+Zy!V>qrHivKP^MV6L*Guvbc`q4;{Z+((OdA$NC~0&hV6d%Sq9!(IY%Rkp%voG;}lvC#U%H7SiIJjB$8b(4e;|><_$a z6OLODs+S@e%-y%SnUvG|%SNy8hR#`I7{2SymcE(P-D9{{q&k96<=I2Z$wMdyCul`W z)xEi%OtqRRxUl`+_nrpE_sWPQ=P1%;jt!-pfk@~{V`Wbkd9~VKUEgnRb8i?5=+8Ix z0KZPdb!2p467&_tdZ-{GAX`X*5c;uIh`k*Iis&Me?k@~vN6x5 z%$Jk6l(?MRu5=g9S5fD0ADtg30>ic;siMEWA7_R!$2h^U$K*LW`ELLMa@|AtT5+u@ zn5vjlJozu29T%!c55{P%zw>hAHVis=gF85B5@&)LS{hx4*Nn9mIu!c~X2#h)$}2K> zgD1rY%P-u=i-6*z`8k%ceActc7lpx;3cic%D7=teD2(0j_{_? z1l)0T?Wi0D8sO#SaJEu^_t7RwV`+mGUfL;i59=Gwa5`gX?q#f9Z#)EFa+n56|E0hA zH^vw1BpX9P4>(5(2i~-)=&K zbiLOsVBI-TpXDriHT>WKAwb)vmk=*ZN;0LScTZ}FGzxf{4dMSDOPjkR(hC+HINF)) zIKZyi>l%yXN2%!NtN4xX(W6B2+0!YqAjj&H_w*_R8M;v^7iXb@t4~~>H`@q(D`7;u zxqD79_1dod|No8@n%8%{Kn}544hW}WX%vgon4xw5f`>`Yu?qicu3oW<)5N?%xHvt;);Gl<3>Tm>S_*LFHSR05e zi@H}&_;O_4Zq_drey^Vkw+P@Eyny$jXvjDPqgEDe6LJDR_2UJ;bOS2oq1+soN6xPs zPl&DEIQ{$ewY&TQ2BkT8D!=YxJkZTte>%G13&-%#MDV;eu9Dwf5a|(OgqUH@B5u|$m7Omk;_@Z`>F+AyN z8O9u~H3O#P8gvsSzsqR8&53w1R3m~1uW~N0@Bg|nnr}BC zZ)uCK>XumQL8I{Adwu)8Zo$*1t)!TY$WQ~uVd9lui5b){Jw0n+>A`03;*c%OId-nK zBfXuSbwpE1XTP04&oRe%H7dLVbS#yD{9?XmE^oQ>8>eS2vyCBI(Y zQ~pscur3d{oY3C74iC!7uryWXRiJP!Z1@vgSdW2V2oJ}C(e(q}oefW+DxAPExzP9t z=d`ke!;xrFOgyYia5NW#+5Ku>je8-nNJZ-n-o@W*Ae9m~8ZpOXz>?FOG6g|>ltzf` zTv_?;8$&}w_b7NN>~KbkhIi{e8GFO0p`-48?!D~ASz$b$J`UyvJL8*{`P~mQzLfud z&P+E)i2hxM^L=HmchLfJU`>S%Ur$=B4}aT?VEh_m=H#Y7_#MB_q({b^!6!Q}VJc(0 zpMyW$$($4gY13UYK7$t=hNCZVe4W$TqGgSloraSmz1O|o`3bIB(RfHtjH{Kjx;YH= zppmSuxnCU5=4>&1*vWX~d)zN6o$S_L3y`C8PWX|qXcZJ=p?u!=bBcgA3qCR!Mge7`u4!vUy3-;m*M)SiuYNU?%_MN7pn5__)Tc;D+QW-lI)BrmqaW^5)FzX8Ek6N` zXLuK^YbRVAXY$fZf|Z%EtEY4vrYPW1uB#x;joYM@%_|-K>QlV9IbxqIs(BY9MWU5@Xj&=3+N`+{wc_3SyEV{UczT?&_ z3w2v_tgmaOAKq(FwK{yf?>ht4wf>=WV0FYD*Oz`s;WS2JI&h_*P@gPnNOFi9@DJzZ zFZ1-NYu%X9a-)f{&q1V1bii&Ij4k+7q@yb}M-feAbkC&D_%_E1k8s$`*q2|&k{(Z2 z7Ebdka1VKELxy|87XE7#lGzzIJ6-EEX?!QUqwC~JH`Q_MDG-Yi=FF1kqmTccy0CDLUp#}0J=>2lW`Tl=0oMf02+0O5YG34Qlslrk5u(d2gxEX#+c z!A2~+gJW955E<>=56*u0-WiFwezkRmR_w^EVpo*?5P_>MCNUhP4ht0j?G3}EjyWOq zwzHWbtm0ca_jei-BHkV!JBCvE)%-SM*kQh^xTncK6R5ha#a`{Y4TJ#>v9K zO|wTRg|}$B=th~-T8CplEh~6? Jc-G!6}%>;K8r6bs@AI$v)8c$V0A#N>E$Ajj% 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�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 LNkvXXu0mjfiQS)R literal 0 HcmV?d00001 From f1079a644ae56aabf55248be5e02398342e62834 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 8 Feb 2013 20:44:14 -0800 Subject: [PATCH 191/332] Fix relation role in Split (#694) --- js/id/actions/split.js | 2 +- test/spec/actions/split.js | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/js/id/actions/split.js b/js/id/actions/split.js index ea2100238..92f9296f7 100644 --- a/js/id/actions/split.js +++ b/js/id/actions/split.js @@ -73,7 +73,7 @@ iD.actions.Split = function(nodeId, newWayId) { } } - relation = relation.addMember({id: wayB.id, type: 'wayA', role: role}, i <= j ? i + 1 : i); + relation = relation.addMember({id: wayB.id, type: 'way', role: role}, i <= j ? i + 1 : i); graph = graph.replace(relation); } }); diff --git a/test/spec/actions/split.js b/test/spec/actions/split.js index 417525941..2e829fc93 100644 --- a/test/spec/actions/split.js +++ b/test/spec/actions/split.js @@ -175,12 +175,15 @@ describe("iD.actions.Split", function () { 'b': iD.Node({id: 'b'}), 'c': iD.Node({id: 'c'}), '-': iD.Way({id: '-', nodes: ['a', 'b', 'c']}), - 'r': iD.Relation({id: 'r', members: [{id: '-', type: 'way'}]}) + 'r': iD.Relation({id: 'r', members: [{id: '-', type: 'way', role: 'forward'}]}) }); graph = iD.actions.Split('b', '=')(graph); - expect(_.pluck(graph.entity('r').members, 'id')).to.eql(['-', '=']); + expect(graph.entity('r').members).to.eql([ + {id: '-', type: 'way', role: 'forward'}, + {id: '=', type: 'way', role: 'forward'} + ]); }); it("adds the new way to parent relations (forward order)", function () { From a60ed7f6e55c01b6a28803c00e5b43c131ddffc7 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Sat, 9 Feb 2013 00:04:29 -0500 Subject: [PATCH 192/332] Remove pointer-events for area being drawn --- js/id/behavior/draw_way.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 8c4c4a5cc..0a05c7e9d 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -47,6 +47,14 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { context.enter(iD.modes.Browse(context)); } + function lineActives(d) { + return d.id === segment.id || d.id === start.id || d.id === end.id; + } + + function areaActives(d) { + return d.id === wayId || d.id === end.id; + } + var drawWay = function(surface) { draw.on('move', move) .on('click', drawWay.add) @@ -63,7 +71,7 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { surface.call(draw) .selectAll('.way, .node') - .filter(function (d) { return d.id === segment.id || d.id === start.id || d.id === end.id; }) + .filter(isArea ? areaActives : lineActives) .classed('active', true); context.history() From 334e842926a522417b640d1ab372a90b397b718b Mon Sep 17 00:00:00 2001 From: Alex Barth Date: Sat, 9 Feb 2013 10:52:01 -0500 Subject: [PATCH 193/332] Title for installation instructions --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 8cf1daeeb..e185fbb4f 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ * [Read up on Contributing and the code style of iD](CONTRIBUTING.md) * See [open issues in the issue tracker](https://github.com/systemed/iD/issues?state=open) if you're looking for something to do +## Installation + To run the current development version, fork this project and serve it locally. If you have Python handy, just `cd` into the project root directory and run From 8a8a29013ed77dff22e53c5dbeeee8ed7dbc5ec2 Mon Sep 17 00:00:00 2001 From: Ian B Date: Sat, 9 Feb 2013 15:22:02 +0100 Subject: [PATCH 194/332] Search results area Display multiple search results --- css/app.css | 15 ++++++++++++++ js/id/ui/geocoder.js | 47 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/css/app.css b/css/app.css index 99904d953..35f7c3821 100644 --- a/css/app.css +++ b/css/app.css @@ -848,6 +848,21 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} margin: 4px; } +.geocode-control div { + top: 50px; + width: 340px; + margin: 4px; + padding: 5px; +} +.geocode-control div span { + display: inline-block; + border-bottom: 1px solid #333; +} + +.geocode-control div span:hover { + background-color: #333; +} + /* Geolocator */ .geolocate-control { diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index 8883dc934..52fcb1672 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -8,21 +8,54 @@ iD.ui.geocoder = function() { d3.event.preventDefault(); var searchVal = this.value; d3.json('http://nominatim.openstreetmap.org/search/' + - encodeURIComponent(searchVal) + '?limit=10&format=json', function(err, resp) { + encodeURIComponent(searchVal) + '?limit=10&format=json', function (err, resp) { if (err) return hide(); - hide(); if (!resp.length) { return iD.ui.flash(context.container()) .select('.content') .append('h3') .text('No location found for "' + searchVal + '"'); } - var bounds = resp[0].boundingbox; - map.extent(iD.geo.Extent([parseFloat(bounds[3]), parseFloat(bounds[0])], [parseFloat(bounds[2]), parseFloat(bounds[1])])); - if (map.zoom() > 19) map.zoom(19); + if(resp.length > 1) { + for (var i=0; i < resp.length; i++) { + var displayName, elementType, typeStr, span; + displayName = resp[i].display_name, + elementType = resp[i].type, + typeStr = elementType.charAt(0).toUpperCase() + elementType.slice(1) + ': ', + span = resultsList.append('span').text(typeStr); + if(displayName.length > 80) displayName = displayName.substr(0,80) + '...'; + span.append('a') + .attr('data-min-lon',resp[i].boundingbox[3]) + .attr('data-min-lat',resp[i].boundingbox[0]) + .attr('data-max-lon',resp[i].boundingbox[2]) + .attr('data-max-lat',resp[i].boundingbox[1]) + .text(displayName) + .on('click', clickResult); + } + resultsList.classed('hide',false); + } else { + var bounds = resp[0].boundingbox; + var extent = iD.geo.Extent([parseFloat(bounds[3]), parseFloat(bounds[0])], [parseFloat(bounds[2]), parseFloat(bounds[1])]); + applyBounds(extent); + } }); } + function clickResult() { + var result = d3.select(this); + var extent = iD.geo.Extent( + [parseFloat(result.attr('data-min-lon')), parseFloat(result.attr('data-min-lat'))], + [parseFloat(result.attr('data-max-lon')), parseFloat(result.attr('data-max-lat'))] + ); + applyBounds(extent); + } + + function applyBounds(extent) { + hide(); + map.extent(extent); + if (map.zoom() > 19) map.zoom(19); + } + function clickoutside(selection) { selection .on('click.geocoder-inside', function() { @@ -38,6 +71,7 @@ iD.ui.geocoder = function() { function setVisible(show) { button.classed('active', show); gcForm.classed('hide', !show); + if (!show) resultsList.classed('hide', !show); if (show) inputNode.node().focus(); else inputNode.node().blur(); } @@ -55,6 +89,9 @@ iD.ui.geocoder = function() { .attr({ type: 'text', placeholder: t('geocoder.find_a_place') }) .on('keydown', keydown); + var resultsList = selection.append('div') + .attr('class','content fillD map-overlay hide'); + selection.call(clickoutside); } From 78cdc3aec2a0d186f24bd8e8010f3a7fbf7f9197 Mon Sep 17 00:00:00 2001 From: Alex Barth Date: Sat, 9 Feb 2013 10:52:01 -0500 Subject: [PATCH 195/332] Title for installation instructions --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 8cf1daeeb..e185fbb4f 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ * [Read up on Contributing and the code style of iD](CONTRIBUTING.md) * See [open issues in the issue tracker](https://github.com/systemed/iD/issues?state=open) if you're looking for something to do +## Installation + To run the current development version, fork this project and serve it locally. If you have Python handy, just `cd` into the project root directory and run From cc51fdc4beba0ddf3f59f9ea84f0fae96faabf37 Mon Sep 17 00:00:00 2001 From: Ian B Date: Sat, 9 Feb 2013 22:27:19 +0100 Subject: [PATCH 196/332] Refactored geocoder to use data joins --- js/id/ui/geocoder.js | 66 +++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index 52fcb1672..22c897653 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -9,44 +9,40 @@ iD.ui.geocoder = function() { var searchVal = this.value; d3.json('http://nominatim.openstreetmap.org/search/' + encodeURIComponent(searchVal) + '?limit=10&format=json', function (err, resp) { - if (err) return hide(); - if (!resp.length) { - return iD.ui.flash(context.container()) - .select('.content') - .append('h3') - .text('No location found for "' + searchVal + '"'); - } - if(resp.length > 1) { - for (var i=0; i < resp.length; i++) { - var displayName, elementType, typeStr, span; - displayName = resp[i].display_name, - elementType = resp[i].type, - typeStr = elementType.charAt(0).toUpperCase() + elementType.slice(1) + ': ', - span = resultsList.append('span').text(typeStr); - if(displayName.length > 80) displayName = displayName.substr(0,80) + '...'; - span.append('a') - .attr('data-min-lon',resp[i].boundingbox[3]) - .attr('data-min-lat',resp[i].boundingbox[0]) - .attr('data-max-lon',resp[i].boundingbox[2]) - .attr('data-max-lat',resp[i].boundingbox[1]) - .text(displayName) - .on('click', clickResult); + if (err) return hide(); + if (!resp.length) { + return iD.ui.flash(context.container()) + .select('.content') + .append('h3') + .text('No location found for "' + searchVal + '"'); } - resultsList.classed('hide',false); - } else { - var bounds = resp[0].boundingbox; - var extent = iD.geo.Extent([parseFloat(bounds[3]), parseFloat(bounds[0])], [parseFloat(bounds[2]), parseFloat(bounds[1])]); - applyBounds(extent); - } - }); + if(resp.length > 1) { + var spans = resultsList.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') + .text(function(d) { + if(d.display_name > 80) return d.display_name.substr(0,80) + '...'; + return d.display_name; + }) + .on('click', clickResult); + spans.exit().remove(); + resultsList.classed('hide',false); + } else { + var bounds = resp[0].boundingbox; + var extent = iD.geo.Extent([parseFloat(bounds[3]), parseFloat(bounds[0])], [parseFloat(bounds[2]), parseFloat(bounds[1])]); + applyBounds(extent); + } + }); } - function clickResult() { - var result = d3.select(this); - var extent = iD.geo.Extent( - [parseFloat(result.attr('data-min-lon')), parseFloat(result.attr('data-min-lat'))], - [parseFloat(result.attr('data-max-lon')), parseFloat(result.attr('data-max-lat'))] - ); + function clickResult(data) { + var bounds = data.boundingbox; + var extent = iD.geo.Extent([parseFloat(bounds[3]), parseFloat(bounds[0])], [parseFloat(bounds[2]), parseFloat(bounds[1])]); applyBounds(extent); } From f56e27ad2a6d8a4dd8ba88b0c6796796071e5779 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Sat, 9 Feb 2013 17:21:28 -0500 Subject: [PATCH 197/332] Style tweaks and refactoring --- css/app.css | 4 ---- js/id/ui/geocoder.js | 33 +++++++++++++++++++-------------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/css/app.css b/css/app.css index 35f7c3821..355fd7262 100644 --- a/css/app.css +++ b/css/app.css @@ -859,10 +859,6 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} border-bottom: 1px solid #333; } -.geocode-control div span:hover { - background-color: #333; -} - /* Geolocator */ .geolocate-control { diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index 22c897653..c873e1c10 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -2,48 +2,53 @@ iD.ui.geocoder = function() { var map, context; + function resultExtent(bounds) { + return new iD.geo.Extent( + [parseFloat(bounds[3]), parseFloat(bounds[0])], + [parseFloat(bounds[2]), parseFloat(bounds[1])]); + } + function geocoder(selection) { function keydown() { if (d3.event.keyCode !== 13) return; d3.event.preventDefault(); var searchVal = this.value; d3.json('http://nominatim.openstreetmap.org/search/' + - encodeURIComponent(searchVal) + '?limit=10&format=json', function (err, resp) { + encodeURIComponent(searchVal) + '?limit=10&format=json', function(err, resp) { if (err) return hide(); if (!resp.length) { return iD.ui.flash(context.container()) .select('.content') .append('h3') .text('No location found for "' + searchVal + '"'); - } - if(resp.length > 1) { + } else if (resp.length > 1) { var spans = resultsList.selectAll('span') .data(resp, function (d) { return d.place_id; }); + spans.enter() .append('span') - .text(function(d) { + .text(function(d) { return d.type.charAt(0).toUpperCase() + d.type.slice(1) + ': '; }) .append('a') .text(function(d) { - if(d.display_name > 80) return d.display_name.substr(0,80) + '...'; - return d.display_name; + if (d.display_name > 80) { + return d.display_name.substr(0, 80) + '…'; + } else { + return d.display_name; + } }) .on('click', clickResult); spans.exit().remove(); - resultsList.classed('hide',false); + resultsList.classed('hide', false); } else { - var bounds = resp[0].boundingbox; - var extent = iD.geo.Extent([parseFloat(bounds[3]), parseFloat(bounds[0])], [parseFloat(bounds[2]), parseFloat(bounds[1])]); - applyBounds(extent); + applyBounds(resultExtent(resp[0].boundingbox)); } }); } - function clickResult(data) { - var bounds = data.boundingbox; - var extent = iD.geo.Extent([parseFloat(bounds[3]), parseFloat(bounds[0])], [parseFloat(bounds[2]), parseFloat(bounds[1])]); - applyBounds(extent); + function clickResult(d) { + applyBounds(resultExtent(d.boundingbox)); } function applyBounds(extent) { From d9b581653032a9d775a1b23c0bb03c8e26449965 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Sat, 9 Feb 2013 17:34:44 -0500 Subject: [PATCH 198/332] Store background setting in hash. Fixes #632 --- js/id/id.js | 17 +++++++++++++---- js/id/renderer/background.js | 13 +++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/js/id/id.js b/js/id/id.js index 5e39e6b0e..544c66a12 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -92,10 +92,19 @@ window.iD = function () { return context; }; - context.background() - .source(_.find(iD.layers, function(l) { - return l.data.name === 'Bing aerial imagery'; - })); + var q = iD.util.stringQs(location.hash.substring(1)); + if (q.layer) { + context.background() + .source(_.find(iD.layers, function(l) { + return l.data.sourcetag === q.layer; + })); + } + if (!context.background()) { + context.background() + .source(_.find(iD.layers, function(l) { + return l.data.name === 'Bing aerial imagery'; + })); + } return d3.rebind(context, dispatch, 'on'); }; diff --git a/js/id/renderer/background.js b/js/id/renderer/background.js index be4cc1927..8a14dc0f3 100644 --- a/js/id/renderer/background.js +++ b/js/id/renderer/background.js @@ -152,9 +152,22 @@ iD.Background = function() { return background; }; + function setPermalink(source) { + var tag = source.data.sourcetag; + var q = iD.util.stringQs(location.hash.substring(1)); + if (tag) { + location.replace('#' + iD.util.qsString(_.assign(q, { + layer: tag + }), true)); + } else { + location.replace('#' + iD.util.qsString(_.omit(q, 'layer'), true)); + } + } + background.source = function(_) { if (!arguments.length) return source; source = _; + setPermalink(source); return background; }; From d584eb2f9d0a123c46b272ef10736db585a4f46c Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Sat, 9 Feb 2013 17:56:22 -0500 Subject: [PATCH 199/332] Pan, fix initial loads --- js/id/behavior/hash.js | 6 ++---- js/id/id.js | 13 ++++++++----- js/id/ui.js | 16 +++++++++++++++- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/js/id/behavior/hash.js b/js/id/behavior/hash.js index ced648e45..62f7e51b2 100644 --- a/js/id/behavior/hash.js +++ b/js/id/behavior/hash.js @@ -66,11 +66,9 @@ iD.behavior.Hash = function(context) { if (location.hash) { var q = iD.util.stringQs(location.hash.substring(1)); - if (q.id) { - willselect(q.id); - } + if (q.id) willselect(q.id); hashchange(); - hash.hadHash = true; + if (q.map) hash.hadHash = true; } } diff --git a/js/id/id.js b/js/id/id.js index 544c66a12..665e0b0a1 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -92,14 +92,17 @@ window.iD = function () { return context; }; - var q = iD.util.stringQs(location.hash.substring(1)); + var q = iD.util.stringQs(location.hash.substring(1)), detected = false; if (q.layer) { context.background() - .source(_.find(iD.layers, function(l) { - return l.data.sourcetag === q.layer; - })); + .source(_.find(iD.layers, function(l) { + if (l.data.sourcetag === q.layer) { + return (detected = true); + } + })); } - if (!context.background()) { + + if (!detected) { context.background() .source(_.find(iD.layers, function(l) { return l.data.name === 'Bing aerial imagery'; diff --git a/js/id/ui.js b/js/id/ui.js index 359a39dcb..8681c9a50 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -232,12 +232,26 @@ iD.ui = function(context) { map.size(m.size()); }); + function pan(d) { + return function() { + map.pan(d); + map.redraw(); + }; + } + + // pan amount + var pa = 5; + var keybinding = d3.keybinding('main') .on('⌘+Z', function() { history.undo(); }) .on('⌃+Z', function() { history.undo(); }) .on('⌘+⇧+Z', function() { history.redo(); }) .on('⌃+⇧+Z', function() { history.redo(); }) - .on('⌫', function() { d3.event.preventDefault(); }); + .on('⌫', function() { d3.event.preventDefault(); }) + .on('←', pan([pa, 0])) + .on('↑', pan([0, pa])) + .on('→', pan([-pa, 0])) + .on('↓', pan([0, -pa])); modes.forEach(function(m) { keybinding.on(m.key, function() { if (map.editable()) context.enter(m); }); From 6faf7a27c4ddc82bfa0ac8c7d98263191b476c8a Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Sat, 9 Feb 2013 18:02:09 -0500 Subject: [PATCH 200/332] Support zooming with keyboard. Fixes #695 --- js/id/ui.js | 6 +++++- js/lib/d3.keybinding.js | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index 8681c9a50..8dccb8352 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -251,7 +251,11 @@ iD.ui = function(context) { .on('←', pan([pa, 0])) .on('↑', pan([0, pa])) .on('→', pan([-pa, 0])) - .on('↓', pan([0, -pa])); + .on('↓', pan([0, -pa])) + .on('⇧+=', function() { map.zoomIn(); }) + .on('+', function() { map.zoomIn(); }) + .on('-', function() { map.zoomOut(); }) + .on('dash', function() { map.zoomOut(); }); modes.forEach(function(m) { keybinding.on(m.key, function() { if (map.editable()) context.enter(m); }); diff --git a/js/lib/d3.keybinding.js b/js/lib/d3.keybinding.js index e16ade725..b1106c976 100644 --- a/js/lib/d3.keybinding.js +++ b/js/lib/d3.keybinding.js @@ -151,7 +151,7 @@ d3.keybinding = function(namespace) { '=': 187, 'equals': 187, // Comma, or , ',': 188, comma: 188, - //'-': 189, //??? + 'dash': 189, //??? // Period, or ., or full-stop '.': 190, period: 190, 'full-stop': 190, // Slash, or /, or forward-slash From f15191af2ccfc26bef0e2a6148e51a8080366732 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Sat, 9 Feb 2013 18:06:35 -0500 Subject: [PATCH 201/332] Update tests --- test/spec/behavior/hash.js | 2 ++ test/spec/renderer/background.js | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/test/spec/behavior/hash.js b/test/spec/behavior/hash.js index 7f9200ae1..c93e42d21 100644 --- a/test/spec/behavior/hash.js +++ b/test/spec/behavior/hash.js @@ -51,6 +51,8 @@ describe("iD.behavior.Hash", function () { }); it("stores the current zoom and coordinates in location.hash on map move events", function () { + location.hash = ""; + hash(); context.map().center([38.9, -77.0]); diff --git a/test/spec/renderer/background.js b/test/spec/renderer/background.js index 4321e861e..cdfb3239d 100644 --- a/test/spec/renderer/background.js +++ b/test/spec/renderer/background.js @@ -22,8 +22,8 @@ describe('iD.Background', function() { }); it('#source', function() { - expect(c.source(iD.BackgroundSource.Bing)).to.equal(c); - expect(c.source()).to.equal(iD.BackgroundSource.Bing); + expect(c.source(iD.layers[0])).to.equal(c); + expect(c.source()).to.equal(iD.layers[0]); }); }); From 574e072598e98e60e14b55fa1407f05c0395564c Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Sat, 9 Feb 2013 18:08:03 -0500 Subject: [PATCH 202/332] Update license --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5c991f29b..26728933b 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "editor", "openstreetmap" ], - "license": "BSD", + "license": "WTFPL", "devDependencies": { "uglify-js": "~2.2.2", "mocha-phantomjs": "~1.1.1" From c406413b67ac2ea3a728682ac9e29fe034089305 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 9 Feb 2013 15:09:02 -0800 Subject: [PATCH 203/332] Browse button should be active when moving (fixes #692) --- js/id/modes/move_way.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/id/modes/move_way.js b/js/id/modes/move_way.js index e21dded78..99a2ebb48 100644 --- a/js/id/modes/move_way.js +++ b/js/id/modes/move_way.js @@ -1,6 +1,7 @@ iD.modes.MoveWay = function(context, wayId) { var mode = { - id: 'move-way' + id: 'move-way', + button: 'browse' }; var keybinding = d3.keybinding('move-way'); From 71fc68ddfa712cddeb4dbac8017b989cfe567a09 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Sat, 9 Feb 2013 18:11:59 -0500 Subject: [PATCH 204/332] Go live by default. Fixes #653 --- index.html | 3 +-- js/id/ui.js | 7 +++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/index.html b/index.html index 4dc3b373f..e4b474f45 100644 --- a/index.html +++ b/index.html @@ -155,8 +155,7 @@ var id = iD(); id.connection() - .keys(keys) - .url('http://api06.dev.openstreetmap.org'); + .keys(keys); d3.select("#iD") .call(id.ui()) diff --git a/js/id/ui.js b/js/id/ui.js index 8dccb8352..81797b731 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -165,8 +165,11 @@ iD.ui = function(context) { .append('span') .attr('class', 'provided-by'); - linkList.append('li').attr('class', 'source-switch').append('a').attr('href', '#') - .text('dev') + linkList.append('li') + .attr('class', 'source-switch') + .append('a').attr('href', '#') + .text('live') + .classed('live', true) .on('click.editor', function() { d3.event.preventDefault(); if (d3.select(this).classed('live')) { From bd8847ba6a42bfadbba2090ca19878aafacf39d8 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Sat, 9 Feb 2013 18:18:30 -0500 Subject: [PATCH 205/332] Sub french and german translations --- index.html | 2 + locale/de.js | 187 +++++++++++++++++++++++++++++++++++++++++++++++++++ locale/fr.js | 187 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 376 insertions(+) create mode 100644 locale/de.js create mode 100644 locale/fr.js diff --git a/index.html b/index.html index e4b474f45..0701835f3 100644 --- a/index.html +++ b/index.html @@ -143,6 +143,8 @@ + + diff --git a/locale/de.js b/locale/de.js new file mode 100644 index 000000000..091ef9a07 --- /dev/null +++ b/locale/de.js @@ -0,0 +1,187 @@ +locale.de = { + modes: { + add_area: { + title: "Area", + description: "Add parks, buildings, lakes, or other areas to the map.", + tail: "Click on the map to start drawing an area, like a park, lake, or building.", + key: "A" + }, + add_line: { + title: "Line", + description: "Lines can be highways, streets, pedestrian paths, or even canals.", + tail: "Click on the map to start drawing an road, path, or route.", + key: "L" + }, + add_point: { + title: "Point", + description: "Restaurants, monuments, and postal boxes are points.", + tail: "Click on the map to add a point.", + key: "P" + }, + browse: { + title: "Browse", + description: "Pan and zoom the map.", + key: "B" + }, + draw_area: { + tail: "Click to add points to your area. Click the first point to finish the area." + }, + draw_line: { + tail: "Click to add more points to the line. Click on other lines to connect to them, and double-click to end the line." + } + }, + + operations: { + add: { + annotation: { + point: "Added a point.", + vertex: "Added a node to a way." + } + }, + start: { + annotation: { + line: "Started a line.", + area: "Started an area." + } + }, + 'continue': { + annotation: { + line: "Continued a line.", + area: "Continued an area." + } + }, + cancel_draw: { + annotation: "Cancelled drawing." + }, + change_tags: { + annotation: "Changed tags." + }, + circularize: { + title: "Circularize", + description: "Make this round.", + key: "O", + annotation: { + line: "Made a line circular.", + area: "Made an area circular." + } + }, + orthogonalize: { + title: "Orthogonalize", + description: "Square these corners.", + key: "Q", + annotation: { + line: "Squared the corners of a line.", + area: "Squared the corners of an area." + } + }, + 'delete': { + title: "Delete", + description: "Remove this from the map.", + key: "⌫", + annotation: { + point: "Deleted a point.", + vertex: "Deleted a node from a way.", + line: "Deleted a line.", + area: "Deleted an area.", + relation: "Deleted a relation.", + multiple: "Deleted {n} objects." + } + }, + connect: { + annotation: { + point: "Connected a way to a point.", + vertex: "Connected a way to another.", + line: "Connected a way to a line.", + area: "Connected a way to an area." + } + }, + disconnect: { + title: "Disconnect", + description: "Disconnect these ways from each other.", + key: "D", + annotation: "Disconnected ways." + }, + merge: { + title: "Merge", + description: "Merge these lines.", + key: "C", + annotation: "Merged {n} lines." + }, + move: { + title: "Move", + description: "Move this to a different location.", + key: "M", + annotation: { + point: "Moved a point.", + vertex: "Moved a node in a way.", + line: "Moved a line.", + area: "Moved an area." + } + }, + reverse: { + title: "Reverse", + description: "Make this line go in the opposite direction.", + key: "V", + annotation: "Reversed a line." + }, + split: { + title: "Split", + description: "Split this into two ways at this point.", + key: "X", + annotation: "Split a way." + } + }, + + validations: { + untagged_point: "Untagged point which is not part of a line or area", + untagged_line: "Untagged line", + untagged_area: "Untagged area", + tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area", + deprecated_tags: "Deprecated tags: {tags}" + }, + + save: "Save", + save_help: "Save changes to OpenStreetMap, making them visible to other users", + no_changes: "You don't have any changes to save.", + save_error: "An error occurred while trying to save", + uploading_changes: "Uploading changes to OpenStreetMap.", + just_edited: "You Just Edited OpenStreetMap!", + okay: "Okay", + + "zoom-in": "Zoom In", + "zoom-out": "Zoom Out", + + nothing_to_undo: "Nothing to undo.", + nothing_to_redo: "Nothing to redo.", + + browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", + + inspector: { + no_documentation_combination: "This is no documentation available for this tag combination", + no_documentation_key: "This is no documentation available for this key", + new_tag: "New Tag" + }, + + view_on_osm: "View on OSM", + + zoom_in_edit: "zoom in to edit the map", + + edit_tags: "Edit tags", + + geocoder: { + "find_location": "Find A Location", + "find_a_place": "find a place" + }, + + description: "Description", + + logout: "logout", + + layerswitcher: { + title: "Background", + description: "Background Settings", + percent_brightness: "{opacity}% brightness", + fix_misalignment: "Fix misalignment", + reset: "reset" + } +}; diff --git a/locale/fr.js b/locale/fr.js new file mode 100644 index 000000000..6c4f8bc2b --- /dev/null +++ b/locale/fr.js @@ -0,0 +1,187 @@ +locale.en = { + modes: { + add_area: { + title: "Area", + description: "Add parks, buildings, lakes, or other areas to the map.", + tail: "Click on the map to start drawing an area, like a park, lake, or building.", + key: "A" + }, + add_line: { + title: "Line", + description: "Lines can be highways, streets, pedestrian paths, or even canals.", + tail: "Click on the map to start drawing an road, path, or route.", + key: "L" + }, + add_point: { + title: "Point", + description: "Restaurants, monuments, and postal boxes are points.", + tail: "Click on the map to add a point.", + key: "P" + }, + browse: { + title: "Browse", + description: "Pan and zoom the map.", + key: "B" + }, + draw_area: { + tail: "Click to add points to your area. Click the first point to finish the area." + }, + draw_line: { + tail: "Click to add more points to the line. Click on other lines to connect to them, and double-click to end the line." + } + }, + + operations: { + add: { + annotation: { + point: "Added a point.", + vertex: "Added a node to a way." + } + }, + start: { + annotation: { + line: "Started a line.", + area: "Started an area." + } + }, + 'continue': { + annotation: { + line: "Continued a line.", + area: "Continued an area." + } + }, + cancel_draw: { + annotation: "Cancelled drawing." + }, + change_tags: { + annotation: "Changed tags." + }, + circularize: { + title: "Circularize", + description: "Make this round.", + key: "O", + annotation: { + line: "Made a line circular.", + area: "Made an area circular." + } + }, + orthogonalize: { + title: "Orthogonalize", + description: "Square these corners.", + key: "Q", + annotation: { + line: "Squared the corners of a line.", + area: "Squared the corners of an area." + } + }, + 'delete': { + title: "Delete", + description: "Remove this from the map.", + key: "⌫", + annotation: { + point: "Deleted a point.", + vertex: "Deleted a node from a way.", + line: "Deleted a line.", + area: "Deleted an area.", + relation: "Deleted a relation.", + multiple: "Deleted {n} objects." + } + }, + connect: { + annotation: { + point: "Connected a way to a point.", + vertex: "Connected a way to another.", + line: "Connected a way to a line.", + area: "Connected a way to an area." + } + }, + disconnect: { + title: "Disconnect", + description: "Disconnect these ways from each other.", + key: "D", + annotation: "Disconnected ways." + }, + merge: { + title: "Merge", + description: "Merge these lines.", + key: "C", + annotation: "Merged {n} lines." + }, + move: { + title: "Move", + description: "Move this to a different location.", + key: "M", + annotation: { + point: "Moved a point.", + vertex: "Moved a node in a way.", + line: "Moved a line.", + area: "Moved an area." + } + }, + reverse: { + title: "Reverse", + description: "Make this line go in the opposite direction.", + key: "V", + annotation: "Reversed a line." + }, + split: { + title: "Split", + description: "Split this into two ways at this point.", + key: "X", + annotation: "Split a way." + } + }, + + validations: { + untagged_point: "Untagged point which is not part of a line or area", + untagged_line: "Untagged line", + untagged_area: "Untagged area", + tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area", + deprecated_tags: "Deprecated tags: {tags}" + }, + + save: "Save", + save_help: "Save changes to OpenStreetMap, making them visible to other users", + no_changes: "You don't have any changes to save.", + save_error: "An error occurred while trying to save", + uploading_changes: "Uploading changes to OpenStreetMap.", + just_edited: "You Just Edited OpenStreetMap!", + okay: "Okay", + + "zoom-in": "Zoom In", + "zoom-out": "Zoom Out", + + nothing_to_undo: "Nothing to undo.", + nothing_to_redo: "Nothing to redo.", + + browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", + + inspector: { + no_documentation_combination: "This is no documentation available for this tag combination", + no_documentation_key: "This is no documentation available for this key", + new_tag: "New Tag" + }, + + view_on_osm: "View on OSM", + + zoom_in_edit: "zoom in to edit the map", + + edit_tags: "Edit tags", + + geocoder: { + "find_location": "Find A Location", + "find_a_place": "find a place" + }, + + description: "Description", + + logout: "logout", + + layerswitcher: { + title: "Background", + description: "Background Settings", + percent_brightness: "{opacity}% brightness", + fix_misalignment: "Fix misalignment", + reset: "reset" + } +}; From 843baa11c05f0fe29d07c43ef39cea03fe7e506f Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 9 Feb 2013 15:11:19 -0800 Subject: [PATCH 206/332] graph -> core This matches how it's described in ARCHITECTURE.md. --- Makefile | 2 +- combobox.html | 12 +++++------ index.html | 14 ++++++------- js/id/{graph => core}/difference.js | 0 js/id/{graph => core}/entity.js | 0 js/id/{graph => core}/graph.js | 0 js/id/{graph => core}/history.js | 0 js/id/{graph => core}/node.js | 0 js/id/{graph => core}/relation.js | 0 js/id/{graph => core}/way.js | 0 test/index.html | 28 ++++++++++++------------- test/index_packaged.html | 14 ++++++------- test/rendering.html | 12 +++++------ test/spec/{graph => core}/difference.js | 0 test/spec/{graph => core}/entity.js | 0 test/spec/{graph => core}/graph.js | 0 test/spec/{graph => core}/history.js | 0 test/spec/{graph => core}/node.js | 0 test/spec/{graph => core}/relation.js | 0 test/spec/{graph => core}/way.js | 0 20 files changed, 41 insertions(+), 41 deletions(-) rename js/id/{graph => core}/difference.js (100%) rename js/id/{graph => core}/entity.js (100%) rename js/id/{graph => core}/graph.js (100%) rename js/id/{graph => core}/history.js (100%) rename js/id/{graph => core}/node.js (100%) rename js/id/{graph => core}/relation.js (100%) rename js/id/{graph => core}/way.js (100%) rename test/spec/{graph => core}/difference.js (100%) rename test/spec/{graph => core}/entity.js (100%) rename test/spec/{graph => core}/graph.js (100%) rename test/spec/{graph => core}/history.js (100%) rename test/spec/{graph => core}/node.js (100%) rename test/spec/{graph => core}/relation.js (100%) rename test/spec/{graph => core}/way.js (100%) diff --git a/Makefile b/Makefile index 30c179741..826337109 100644 --- a/Makefile +++ b/Makefile @@ -44,7 +44,7 @@ all: \ js/id/modes/*.js \ js/id/operations.js \ js/id/operations/*.js \ - js/id/graph/*.js \ + js/id/core/*.js \ js/id/renderer/*.js \ js/id/svg.js \ js/id/svg/*.js \ diff --git a/combobox.html b/combobox.html index 92949ecfa..63a2c9757 100644 --- a/combobox.html +++ b/combobox.html @@ -114,12 +114,12 @@ - - - - - - + + + + + + diff --git a/index.html b/index.html index 0701835f3..40a424899 100644 --- a/index.html +++ b/index.html @@ -130,13 +130,13 @@ - - - - - - - + + + + + + + diff --git a/js/id/graph/difference.js b/js/id/core/difference.js similarity index 100% rename from js/id/graph/difference.js rename to js/id/core/difference.js diff --git a/js/id/graph/entity.js b/js/id/core/entity.js similarity index 100% rename from js/id/graph/entity.js rename to js/id/core/entity.js diff --git a/js/id/graph/graph.js b/js/id/core/graph.js similarity index 100% rename from js/id/graph/graph.js rename to js/id/core/graph.js diff --git a/js/id/graph/history.js b/js/id/core/history.js similarity index 100% rename from js/id/graph/history.js rename to js/id/core/history.js diff --git a/js/id/graph/node.js b/js/id/core/node.js similarity index 100% rename from js/id/graph/node.js rename to js/id/core/node.js diff --git a/js/id/graph/relation.js b/js/id/core/relation.js similarity index 100% rename from js/id/graph/relation.js rename to js/id/core/relation.js diff --git a/js/id/graph/way.js b/js/id/core/way.js similarity index 100% rename from js/id/graph/way.js rename to js/id/core/way.js diff --git a/test/index.html b/test/index.html index ca610d714..52e13f03d 100644 --- a/test/index.html +++ b/test/index.html @@ -124,13 +124,13 @@ - - - - - - - + + + + + + + @@ -163,13 +163,13 @@ - - - - - - - + + + + + + + diff --git a/test/index_packaged.html b/test/index_packaged.html index 1102336d9..cd3451357 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -43,13 +43,13 @@ - - - - - - - + + + + + + + diff --git a/test/rendering.html b/test/rendering.html index ff624305a..cd75a848b 100644 --- a/test/rendering.html +++ b/test/rendering.html @@ -26,12 +26,12 @@ - - - - - - + + + + + +
diff --git a/test/spec/graph/difference.js b/test/spec/core/difference.js similarity index 100% rename from test/spec/graph/difference.js rename to test/spec/core/difference.js diff --git a/test/spec/graph/entity.js b/test/spec/core/entity.js similarity index 100% rename from test/spec/graph/entity.js rename to test/spec/core/entity.js diff --git a/test/spec/graph/graph.js b/test/spec/core/graph.js similarity index 100% rename from test/spec/graph/graph.js rename to test/spec/core/graph.js diff --git a/test/spec/graph/history.js b/test/spec/core/history.js similarity index 100% rename from test/spec/graph/history.js rename to test/spec/core/history.js diff --git a/test/spec/graph/node.js b/test/spec/core/node.js similarity index 100% rename from test/spec/graph/node.js rename to test/spec/core/node.js diff --git a/test/spec/graph/relation.js b/test/spec/core/relation.js similarity index 100% rename from test/spec/graph/relation.js rename to test/spec/core/relation.js diff --git a/test/spec/graph/way.js b/test/spec/core/way.js similarity index 100% rename from test/spec/graph/way.js rename to test/spec/core/way.js From 5fb4fc8521f11f179819d693cacbe8b5e6825a51 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Sat, 9 Feb 2013 18:23:07 -0500 Subject: [PATCH 207/332] Add locale readme --- locale/README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 locale/README.md diff --git a/locale/README.md b/locale/README.md new file mode 100644 index 000000000..1f97112ed --- /dev/null +++ b/locale/README.md @@ -0,0 +1,18 @@ +# Translations + +At this stage in its development, iD is using an extremely minimal, simple +system for translations. This directory contains languages according to +code (de: German, fr: French, etc). + +To contribute: + +If you're technically-minded, clone this repository and edit the necessary +file, and you can preview your changes in-place if your system language is +set. Check out [the contributing guide for submitting changes](https://github.com/systemed/iD/blob/master/CONTRIBUTING.md). + +If you aren't, you can still contribute! You'll still need a GitHub account, but +you can just browse to your language's file here, +click 'Edit', and edit each translated string. + +Contributions to translations are under the same liberal +license as iD itself, [wtfpl](http://www.wtfpl.net/). From cd470675e23d433b66faad8c9e435743f01ccd77 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Sat, 9 Feb 2013 18:27:18 -0500 Subject: [PATCH 208/332] Display name fix --- js/id/ui/geocoder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index c873e1c10..7f1a6ca4c 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -32,7 +32,7 @@ iD.ui.geocoder = function() { }) .append('a') .text(function(d) { - if (d.display_name > 80) { + if (d.display_name.length > 80) { return d.display_name.substr(0, 80) + '…'; } else { return d.display_name; From ccb5b81645ff2feb3b32e6ea116dd7c67d28c107 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 9 Feb 2013 15:35:25 -0800 Subject: [PATCH 209/332] Clean up geocoder Pass context directly; fix indentation; i18n. --- js/id/ui.js | 2 +- js/id/ui/geocoder.js | 45 +++++++++++++------------------------------- locale/en.js | 5 +++-- 3 files changed, 17 insertions(+), 35 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index 81797b731..3b13c2847 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -128,7 +128,7 @@ iD.ui = function(context) { } container.append('div').attr('class', 'geocode-control map-control') - .call(iD.ui.geocoder().map(map).context(context)); + .call(iD.ui.geocoder(context)); container.append('div').attr('class', 'map-control layerswitcher-control') .call(iD.ui.layerswitcher(context)); diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index 7f1a6ca4c..04f68f1ba 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -1,7 +1,4 @@ -iD.ui.geocoder = function() { - - var map, context; - +iD.ui.geocoder = function(context) { function resultExtent(bounds) { return new iD.geo.Extent( [parseFloat(bounds[3]), parseFloat(bounds[0])], @@ -20,7 +17,7 @@ iD.ui.geocoder = function() { return iD.ui.flash(context.container()) .select('.content') .append('h3') - .text('No location found for "' + searchVal + '"'); + .text(t('geocoder.no_results', {name: searchVal})); } else if (resp.length > 1) { var spans = resultsList.selectAll('span') .data(resp, function (d) { return d.place_id; }); @@ -53,19 +50,11 @@ iD.ui.geocoder = function() { function applyBounds(extent) { hide(); + var map = context.map(); map.extent(extent); if (map.zoom() > 19) map.zoom(19); } - function clickoutside(selection) { - selection - .on('click.geocoder-inside', function() { - return d3.event.stopPropagation(); - }); - context.container().on('click.geocoder-outside', hide); - } - - function show() { setVisible(true); } function hide() { setVisible(false); } function toggle() { setVisible(gcForm.classed('hide')); } @@ -79,34 +68,26 @@ iD.ui.geocoder = function() { var button = selection.append('button') .attr('tabindex', -1) - .attr('title', t('geocoder.find_location')) + .attr('title', t('geocoder.title')) .html('') .on('click', toggle); var gcForm = selection.append('form'); - var inputNode = gcForm.attr('class','content fillD map-overlay hide') + var inputNode = gcForm.attr('class', 'content fillD map-overlay hide') .append('input') - .attr({ type: 'text', placeholder: t('geocoder.find_a_place') }) - .on('keydown', keydown); + .attr({ type: 'text', placeholder: t('geocoder.placeholder') }) + .on('keydown', keydown); var resultsList = selection.append('div') - .attr('class','content fillD map-overlay hide'); + .attr('class', 'content fillD map-overlay hide'); - selection.call(clickoutside); + selection.on('click.geocoder-inside', function() { + return d3.event.stopPropagation(); + }); + + context.container().on('click.geocoder-outside', hide); } - geocoder.map = function(_) { - if (!arguments.length) return map; - map = _; - return geocoder; - }; - - geocoder.context = function(_) { - if (!arguments.length) return context; - context = _; - return geocoder; - }; - return geocoder; }; diff --git a/locale/en.js b/locale/en.js index 6c4f8bc2b..1f354013b 100644 --- a/locale/en.js +++ b/locale/en.js @@ -169,8 +169,9 @@ locale.en = { edit_tags: "Edit tags", geocoder: { - "find_location": "Find A Location", - "find_a_place": "find a place" + title: "Find A Place", + placeholder: "find a place", + no_results: "Couldn't locate a place named '{name}'" }, description: "Description", From 8c72ebc3cdef05bf8f527267053b879c84945dc6 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 9 Feb 2013 15:49:24 -0800 Subject: [PATCH 210/332] Fix fr locale --- locale/fr.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locale/fr.js b/locale/fr.js index 6c4f8bc2b..9bd37e688 100644 --- a/locale/fr.js +++ b/locale/fr.js @@ -1,4 +1,4 @@ -locale.en = { +locale.fr = { modes: { add_area: { title: "Area", From b9fb37e1e4e2ae0d454026de7de043ec315c320c Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 9 Feb 2013 15:59:59 -0800 Subject: [PATCH 211/332] i18n --- js/id/ui.js | 40 +++++++++++++++++++++++++--------------- locale/en.js | 5 +++++ 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index 3b13c2847..57063a8b9 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -7,9 +7,7 @@ iD.ui = function(context) { map = context.map(); if (!iD.detect().support) { - container.html('This editor is supported in Firefox, Chrome, Safari, Opera, ' + - 'and Internet Explorer 9 and above. Please upgrade your browser ' + - 'or use Potlatch 2 to edit the map.') + container.text(t('browser_notice')) .style('text-align:center;font-style:italic;'); return; } @@ -110,7 +108,7 @@ iD.ui = function(context) { var zoom = container.append('div') .attr('class', 'zoombuttons map-control') .selectAll('button') - .data([['zoom-in', '+', map.zoomIn, 'Zoom In'], ['zoom-out', '-', map.zoomOut, 'Zoom Out']]) + .data([['zoom-in', '+', map.zoomIn, t('zoom-in')], ['zoom-out', '-', map.zoomOut, t('zoom-out')]]) .enter() .append('button') .attr('tabindex', -1) @@ -154,21 +152,33 @@ iD.ui = function(context) { var linkList = aboutList.append('ul') .attr('id','about') .attr('class','pad1 fillD about-block link-list'); - linkList.append('li').append('a').attr('target', '_blank') - .attr('href', 'http://github.com/systemed/iD').text(iD.version); - linkList.append('li').append('a').attr('target', '_blank') - .attr('href', 'http://github.com/systemed/iD/issues').text('report a bug'); - var imagery = linkList.append('li').attr('id', 'attribution'); - imagery.append('span').text('imagery'); + linkList.append('li') + .append('a') + .attr('target', '_blank') + .attr('href', 'http://github.com/systemed/iD') + .text(iD.version); + + linkList.append('li') + .append('a') + .attr('target', '_blank') + .attr('href', 'http://github.com/systemed/iD/issues') + .text(t('report_a_bug')); + + var imagery = linkList.append('li') + .attr('id', 'attribution'); + + imagery.append('span') + .text('imagery'); + imagery .append('span') - .attr('class', 'provided-by'); + .attr('class', 'provided-by'); linkList.append('li') .attr('class', 'source-switch') .append('a').attr('href', '#') - .text('live') + .text(t('live')) .classed('live', true) .on('click.editor', function() { d3.event.preventDefault(); @@ -176,12 +186,12 @@ iD.ui = function(context) { map.flush(); context.connection() .url('http://api06.dev.openstreetmap.org'); - d3.select(this).text('dev').classed('live', false); + d3.select(this).text(t('dev')).classed('live', false); } else { map.flush(); context.connection() .url('http://www.openstreetmap.org'); - d3.select(this).text('live').classed('live', true); + d3.select(this).text(t('live')).classed('live', true); } }); @@ -198,7 +208,7 @@ iD.ui = function(context) { window.onbeforeunload = function() { history.save(); - if (history.hasChanges()) return 'You have unsaved changes'; + if (history.hasChanges()) return t('unsaved_changes'); }; history.on('change.editor', function() { diff --git a/locale/en.js b/locale/en.js index 1f354013b..ff6e0b8f2 100644 --- a/locale/en.js +++ b/locale/en.js @@ -141,6 +141,7 @@ locale.en = { }, save: "Save", + unsaved_changes: "You have unsaved changes", save_help: "Save changes to OpenStreetMap, making them visible to other users", no_changes: "You don't have any changes to save.", save_error: "An error occurred while trying to save", @@ -178,6 +179,10 @@ locale.en = { logout: "logout", + live: "live", + dev: "dev", + report_a_bug: "report a bug", + layerswitcher: { title: "Background", description: "Background Settings", From 5feb9dea5da859624cd3414f2ac30a8be8ee86fa Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 9 Feb 2013 16:52:08 -0800 Subject: [PATCH 212/332] Make contributors i18n-friendly --- js/id/ui.js | 18 ++++----------- js/id/ui/contributors.js | 47 ++++++++++++++++++++-------------------- locale/en.js | 5 +++++ 3 files changed, 33 insertions(+), 37 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index 57063a8b9..6c0b24ac2 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -62,10 +62,7 @@ iD.ui = function(context) { .message(false) .on('zoom', function() { map.zoom(16); }); - map.on('move.editor', _.debounce(function() { - disableTooHigh(); - contributors.call(iD.ui.contributors(context)); - }, 500)); + map.on('move.editor', _.debounce(disableTooHigh, 500)); buttons.append('span') .attr('class', function(d) { @@ -195,16 +192,9 @@ iD.ui = function(context) { } }); - var contributors = linkList.append('li') - .attr('id', 'user-list'); - contributors.append('span') - .attr('class', 'icon nearby icon-pre-text'); - contributors.append('span') - .text('Viewing contributions by '); - contributors.append('span') - .attr('class', 'contributor-list'); - contributors.append('span') - .attr('class', 'contributor-count'); + linkList.append('li') + .attr('id', 'user-list') + .call(iD.ui.contributors(context)); window.onbeforeunload = function() { history.save(); diff --git a/js/id/ui/contributors.js b/js/id/ui/contributors.js index e029bfd04..743235edf 100644 --- a/js/id/ui/contributors.js +++ b/js/id/ui/contributors.js @@ -1,7 +1,5 @@ iD.ui.contributors = function(context) { - - function contributors(selection) { - + function update(selection) { var users = {}, limit = 3, entities = context.graph().intersects(context.map().extent()); @@ -13,28 +11,25 @@ iD.ui.contributors = function(context) { var u = Object.keys(users), subset = u.slice(0, limit); - var l = selection - .select('.contributor-list') - .selectAll('a.user-link') - .data(subset, function(d) { return d; }); + selection.html('') + .append('span') + .attr('class', 'icon nearby icon-pre-text'); + var userList = d3.select(document.createElement('span')); - l.enter().append('a') + userList.selectAll() + .data(subset) + .enter() + .append('a') .attr('class', 'user-link') .attr('href', function(d) { return context.connection().userUrl(d); }) .attr('target', '_blank') .text(String); - l.exit().remove(); - - selection - .select('.contributor-count') - .html(''); - if (u.length > limit) { - selection - .select('.contributor-count') - .append('a') + var count = d3.select(document.createElement('span')); + + count.append('a') .attr('target', '_blank') .attr('href', function() { var ext = context.map().extent(); @@ -42,11 +37,13 @@ iD.ui.contributors = function(context) { ext[0][0], ext[0][1], ext[1][0], ext[1][1]]; }) - .text(' and ' + (u.length - limit) + ' others'); + .text(u.length - limit); + + selection.append('span') + .html(t('contributors.truncated_list', {users: userList.html(), count: count.html()})); } else { - selection - .select('.contributor-count') - .html(''); + selection.append('span') + .html(t('contributors.list', {users: userList.html()})); } if (!u.length) { @@ -54,9 +51,13 @@ iD.ui.contributors = function(context) { } else if (selection.style('opacity') === '0') { selection.transition().style('opacity', 1); } - } - return contributors; + return function(selection) { + update(selection); + context.map().on('move.contributors', _.debounce(function() { + update(selection); + }, 500)); + }; }; diff --git a/locale/en.js b/locale/en.js index ff6e0b8f2..0bad1b751 100644 --- a/locale/en.js +++ b/locale/en.js @@ -189,5 +189,10 @@ locale.en = { percent_brightness: "{opacity}% brightness", fix_misalignment: "Fix misalignment", reset: "reset" + }, + + contributors: { + list: "Viewing contributions by {users}", + truncated_list: "Viewing contributions by {users} and {count} others" } }; From c56879b29eb5e5c963f91db3ec0c2e0997c8e45c Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 9 Feb 2013 16:53:29 -0800 Subject: [PATCH 213/332] Update contributors on load (fixes #699) --- js/id/ui/contributors.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/js/id/ui/contributors.js b/js/id/ui/contributors.js index 743235edf..596c576ad 100644 --- a/js/id/ui/contributors.js +++ b/js/id/ui/contributors.js @@ -56,6 +56,10 @@ iD.ui.contributors = function(context) { return function(selection) { update(selection); + context.connection().on('load.contributors', function() { + update(selection); + }); + context.map().on('move.contributors', _.debounce(function() { update(selection); }, 500)); From 65ab71c3d0e67a8dffb7665b2097f702c54b3595 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 9 Feb 2013 17:04:39 -0800 Subject: [PATCH 214/332] Extract iD.ui.SourceSwitch --- index.html | 1 + js/id/ui.js | 18 +----------------- js/id/ui/source_switch.js | 25 +++++++++++++++++++++++++ locale/en.js | 7 +++++-- test/index.html | 1 + 5 files changed, 33 insertions(+), 19 deletions(-) create mode 100644 js/id/ui/source_switch.js diff --git a/index.html b/index.html index 40a424899..adbc28ef4 100644 --- a/index.html +++ b/index.html @@ -77,6 +77,7 @@ + diff --git a/js/id/ui.js b/js/id/ui.js index 6c0b24ac2..529d8bca5 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -174,23 +174,7 @@ iD.ui = function(context) { linkList.append('li') .attr('class', 'source-switch') - .append('a').attr('href', '#') - .text(t('live')) - .classed('live', true) - .on('click.editor', function() { - d3.event.preventDefault(); - if (d3.select(this).classed('live')) { - map.flush(); - context.connection() - .url('http://api06.dev.openstreetmap.org'); - d3.select(this).text(t('dev')).classed('live', false); - } else { - map.flush(); - context.connection() - .url('http://www.openstreetmap.org'); - d3.select(this).text(t('live')).classed('live', true); - } - }); + .call(iD.ui.SourceSwitch(context)); linkList.append('li') .attr('id', 'user-list') diff --git a/js/id/ui/source_switch.js b/js/id/ui/source_switch.js new file mode 100644 index 000000000..1b1bb9530 --- /dev/null +++ b/js/id/ui/source_switch.js @@ -0,0 +1,25 @@ +iD.ui.SourceSwitch = function(context) { + function click() { + d3.event.preventDefault(); + + var live = d3.select(this).classed('live'); + + context.map() + .flush(); + + context.connection() + .url(live ? 'http://api06.dev.openstreetmap.org' : 'http://www.openstreetmap.org'); + + d3.select(this) + .text(live ? t('source_switch.dev') : t('source_switch.live')) + .classed('live', !live); + } + + return function(selection) { + selection.append('a') + .attr('href', '#') + .text(t('source_switch.live')) + .classed('live', true) + .on('click', click); + } +}; diff --git a/locale/en.js b/locale/en.js index 0bad1b751..a2233551b 100644 --- a/locale/en.js +++ b/locale/en.js @@ -179,8 +179,6 @@ locale.en = { logout: "logout", - live: "live", - dev: "dev", report_a_bug: "report a bug", layerswitcher: { @@ -194,5 +192,10 @@ locale.en = { contributors: { list: "Viewing contributions by {users}", truncated_list: "Viewing contributions by {users} and {count} others" + }, + + source_switch: { + live: "live", + dev: "dev" } }; diff --git a/test/index.html b/test/index.html index 52e13f03d..056a0aff8 100644 --- a/test/index.html +++ b/test/index.html @@ -71,6 +71,7 @@ + From be387170f70cce0bcd9185325802168ae64cb816 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Sun, 10 Feb 2013 00:04:24 -0500 Subject: [PATCH 215/332] start Latvian translation --- index.html | 1 + locale/lv.js | 198 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+) create mode 100644 locale/lv.js diff --git a/index.html b/index.html index adbc28ef4..89ba50fe1 100644 --- a/index.html +++ b/index.html @@ -146,6 +146,7 @@ + diff --git a/locale/lv.js b/locale/lv.js new file mode 100644 index 000000000..8cd194d39 --- /dev/null +++ b/locale/lv.js @@ -0,0 +1,198 @@ +locale.lv = { + modes: { + add_area: { + title: "Apgabals", + description: "Pievieno parkus, ēkas, ezerus un citus apgabalus.", + tail: "Klikšķiniet uz kartes, lai sāktu zīmēt apgablalu, piemēram parku, ezeru, vai ēku.", + key: "A" + }, + add_line: { + title: "Līnija", + description: "Līnijas var būt ceļi, ielas, takas vai pat kanāli.", + tail: "Klikšķiniet uz kartes, lai sāktu zīmēt līniju, piemēram ceļu vai taku.", + key: "L" + }, + add_point: { + title: "Punkts", + description: "Kafejnīcas, pieminekļi, un veikali var būt punkti.", + tail: "Klikšķiniet uz kartes, lai pievienotu interešu punktu.", + key: "P" + }, + browse: { + title: "Pārlūkot", + description: "Pārlūko karti.", + key: "B" + }, + draw_area: { + tail: "Klikšķiniet, lai pievinotu mezglus apgabalam. Lai beigtu zīmēt apgabalu, klikšķiniet uz sākuma mezgla." + }, + draw_line: { + tail: "Klikšķiniet, lai pievienotu mezglus līnijai. Lai savienotu ar citām linijām, klikšķiniet uz tām. Dubultklikšķis nobeidz līniju." + } + }, + + operations: { + add: { + annotation: { + point: "Punkts pievienots.", + vertex: "Mezgls pievienots līnijai." + } + }, + start: { + annotation: { + line: "Līnija iesākta.", + area: "Apgabals iesākts." + } + }, + 'continue': { + annotation: { + line: "Linija turpināta.", + area: "Apgabals turpināts." + } + }, + cancel_draw: { + annotation: "Zīmēšana atcelta." + }, + change_tags: { + annotation: "Apzīmējumi mainīti." + }, + circularize: { + title: "Circularize", + description: "Make this round.", + key: "O", + annotation: { + line: "Made a line circular.", + area: "Made an area circular." + } + }, + orthogonalize: { + title: "Orthogonalize", + description: "Square these corners.", + key: "Q", + annotation: { + line: "Squared the corners of a line.", + area: "Squared the corners of an area." + } + }, + 'delete': { + title: "Dzēst", + description: "Noņemt no kartes.", + key: "⌫", + annotation: { + point: "Punkts dzēsts.", + vertex: "Mezgls dzests.", + line: "Līnija dzēsta.", + area: "Apgabals dzēsts.", + relation: "Relācija dzēsta.", + multiple: "{n} objekti dzēsti." + } + }, + connect: { + annotation: { + point: "Līnija savienota ar punktu.", + vertex: "Līnija savienota ar otru.", + line: "Līnija savienota ar līniju.", + area: "Līnija savienota ar apgabalu." + } + }, + disconnect: { + title: "Atvienot", + description: "Atvieno līnijas.", + key: "D", + annotation: "Līnijas atvienotas." + }, + merge: { + title: "Sapludināt", + description: "Sapludināt līnijas.", + key: "C", + annotation: "{n} līnijas sapludinātas." + }, + move: { + title: "Pārvietot", + description: "Pārvieto objektu.", + key: "M", + annotation: { + point: "Punkts pārvietots.", + vertex: "Mezgls pārvietots.", + line: "Līnija pārvietota.", + area: "Apgabals pārvietots." + } + }, + reverse: { + title: "Mainīt virzienu", + description: "Maini līnijas virzienu.", + key: "V", + annotation: "Līnijas virziens mainīts." + }, + split: { + title: "Sadalīt", + description: "Sadali līniju pie ši punkta.", + key: "X", + annotation: "Līnija sadalīta." + } + }, + + validations: { + untagged_point: "Neapzīmēts punkts", + untagged_line: "Neapzīmēta līnija", + untagged_area: "Neapzīmēts apgabals", + tag_suggests_area: "Apzīmējums {tag} parasti lietots apgabaliem, bet objekts nav apgabals", + deprecated_tags: "Novecojūši apzīmējumi: {tags}" + }, + + save: "Saglabāt", + unsaved_changes: "Jums ir nesaglabātas maiņas", + save_help: "Saglabā maiņas, padarot tās redzamas citiem", + no_changes: "Jums nav maiņas ko saglabāt", + save_error: "Kļūda. Nevarēja saglabāt maiņas", + uploading_changes: "Augšupielādē", + just_edited: "Jūs nupat rediģējāt OpenStreetMap", + okay: "Labi", + + "zoom-in": "Pietuvināt", + "zoom-out": "Attālināt", + + nothing_to_undo: "Nekas ko atcelt", + nothing_to_redo: "Nekas ko atatsaukt", + + browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", + + inspector: { + no_documentation_combination: "This is no documentation available for this tag combination", + no_documentation_key: "This is no documentation available for this key", + new_tag: "Jauns apzīmejums" + }, + + view_on_osm: "Apskatīt OSM lapu", + + zoom_in_edit: "zoom in to edit the map", + + edit_tags: "Rediģēt apzīmējumus", + + geocoder: { + title: "Atrast vietu", + placeholder: "atrod vietu", + no_results: "Nevarēja atrast vietu vārdā '{name}'" + }, + + description: "Apraksts", + + logout: "logout", + + live: "live", + dev: "dev", + report_a_bug: "ziņot par kļūdu", + + layerswitcher: { + title: "Fons", + description: "Fona iestatījumi", + percent_brightness: "{opacity}% gaišums", + fix_misalignment: "Labot fona nolīdzināšanu", + reset: "Pārstatīt" + }, + + contributors: { + list: "{users} papildinājumi redzāmi", + truncated_list: "{users} un {count} citu papildinājumi redzāmi" + } +}; From 72a29a14f44294ebe045c8d04e4d9e1f9083f31a Mon Sep 17 00:00:00 2001 From: Neogeografen Date: Sun, 10 Feb 2013 00:14:16 -0800 Subject: [PATCH 216/332] Started danish language --- locale/da.js | 201 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 locale/da.js diff --git a/locale/da.js b/locale/da.js new file mode 100644 index 000000000..217f597f6 --- /dev/null +++ b/locale/da.js @@ -0,0 +1,201 @@ +locale.da = { + modes: { + add_area: { + title: "Område", + description: "Tilføj parker, bygninger, søer, eller andre områder til kortet.", + tail: "Klik på kortet for at indtegne et område fx en park, sø eller bygning.", + key: "A" + }, + add_line: { + title: "Linje", + description: "Linjer kan være veje, gader eller stier selv kanaler kan være linjer.", + tail: "Klik på koret for at indtegne en vej, sti eller rute.", + key: "L" + }, + add_point: { + title: "Punkt", + description: "Restauranter, mindesmærker og postkasser er punkter.", + tail: "Klik på kortet for at tilføje et punkt.", + key: "P" + }, + browse: { + title: "Browse", + description: "Træk rundt og zoom på kortet.", + key: "B" + }, + draw_area: { + tail: "Klik her for at tilføje punkter til dit område. Click the first point to finish the area." + }, + draw_line: { + tail: "Click to add more points to the line. Click on other lines to connect to them, and double-click to end the line." + } + }, + + operations: { + add: { + annotation: { + point: "Added a point.", + vertex: "Added a node to a way." + } + }, + start: { + annotation: { + line: "Started a line.", + area: "Started an area." + } + }, + 'continue': { + annotation: { + line: "Continued a line.", + area: "Continued an area." + } + }, + cancel_draw: { + annotation: "Cancelled drawing." + }, + change_tags: { + annotation: "Changed tags." + }, + circularize: { + title: "Circularize", + description: "Make this round.", + key: "O", + annotation: { + line: "Made a line circular.", + area: "Made an area circular." + } + }, + orthogonalize: { + title: "Orthogonalize", + description: "Square these corners.", + key: "Q", + annotation: { + line: "Squared the corners of a line.", + area: "Squared the corners of an area." + } + }, + 'delete': { + title: "Delete", + description: "Remove this from the map.", + key: "⌫", + annotation: { + point: "Deleted a point.", + vertex: "Deleted a node from a way.", + line: "Deleted a line.", + area: "Deleted an area.", + relation: "Deleted a relation.", + multiple: "Deleted {n} objects." + } + }, + connect: { + annotation: { + point: "Connected a way to a point.", + vertex: "Connected a way to another.", + line: "Connected a way to a line.", + area: "Connected a way to an area." + } + }, + disconnect: { + title: "Disconnect", + description: "Disconnect these ways from each other.", + key: "D", + annotation: "Disconnected ways." + }, + merge: { + title: "Merge", + description: "Merge these lines.", + key: "C", + annotation: "Merged {n} lines." + }, + move: { + title: "Move", + description: "Move this to a different location.", + key: "M", + annotation: { + point: "Moved a point.", + vertex: "Moved a node in a way.", + line: "Moved a line.", + area: "Moved an area." + } + }, + reverse: { + title: "Reverse", + description: "Make this line go in the opposite direction.", + key: "V", + annotation: "Reversed a line." + }, + split: { + title: "Split", + description: "Split this into two ways at this point.", + key: "X", + annotation: "Split a way." + } + }, + + validations: { + untagged_point: "Untagged point which is not part of a line or area", + untagged_line: "Untagged line", + untagged_area: "Untagged area", + tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area", + deprecated_tags: "Deprecated tags: {tags}" + }, + + save: "Save", + unsaved_changes: "You have unsaved changes", + save_help: "Save changes to OpenStreetMap, making them visible to other users", + no_changes: "You don't have any changes to save.", + save_error: "An error occurred while trying to save", + uploading_changes: "Uploading changes to OpenStreetMap.", + just_edited: "You Just Edited OpenStreetMap!", + okay: "Okay", + + "zoom-in": "Zoom ind", + "zoom-out": "Zoom ud", + + nothing_to_undo: "Nothing to undo.", + nothing_to_redo: "Nothing to redo.", + + browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", + + inspector: { + no_documentation_combination: "This is no documentation available for this tag combination", + no_documentation_key: "This is no documentation available for this key", + new_tag: "Nyt Tag" + }, + + view_on_osm: "Vis på OSM", + + zoom_in_edit: "zoom ind for at rette kortet", + + edit_tags: "Ret tags", + + geocoder: { + title: "Find et sted", + placeholder: "find et sted", + no_results: "Kunne ikke finde '{name}'" + }, + + description: "Description", + + logout: "log ud", + + report_a_bug: "report a bug", + + layerswitcher: { + title: "Background", + description: "Background Settings", + percent_brightness: "{opacity}% brightness", + fix_misalignment: "Fix misalignment", + reset: "nulstill" + }, + + contributors: { + list: "Vis bidrag fra {users}", + truncated_list: "Vis bidrag fra {users} og {count} andre" + }, + + source_switch: { + live: "live", + dev: "dev" + } +}; From 0481cdaa1fd1bd33a8a59539b44d00058a7ce786 Mon Sep 17 00:00:00 2001 From: richlv Date: Sun, 10 Feb 2013 12:06:28 +0200 Subject: [PATCH 217/332] Update locale/lv.js minor typo & style fixes --- locale/lv.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/locale/lv.js b/locale/lv.js index 8cd194d39..2c28b400f 100644 --- a/locale/lv.js +++ b/locale/lv.js @@ -3,13 +3,13 @@ locale.lv = { add_area: { title: "Apgabals", description: "Pievieno parkus, ēkas, ezerus un citus apgabalus.", - tail: "Klikšķiniet uz kartes, lai sāktu zīmēt apgablalu, piemēram parku, ezeru, vai ēku.", + tail: "Klikšķiniet uz kartes, lai sāktu zīmēt apgabalu, piemēram parku, ezeru, vai ēku.", key: "A" }, add_line: { title: "Līnija", description: "Līnijas var būt ceļi, ielas, takas vai pat kanāli.", - tail: "Klikšķiniet uz kartes, lai sāktu zīmēt līniju, piemēram ceļu vai taku.", + tail: "Klikšķiniet uz kartes, lai sāktu zīmēt līniju, piemēram, ceļu vai taku.", key: "L" }, add_point: { @@ -120,13 +120,13 @@ locale.lv = { }, reverse: { title: "Mainīt virzienu", - description: "Maini līnijas virzienu.", + description: "Mainīt līnijas virzienu.", key: "V", annotation: "Līnijas virziens mainīts." }, split: { title: "Sadalīt", - description: "Sadali līniju pie ši punkta.", + description: "Sadalīt līniju pie šī punkta.", key: "X", annotation: "Līnija sadalīta." } @@ -136,14 +136,14 @@ locale.lv = { untagged_point: "Neapzīmēts punkts", untagged_line: "Neapzīmēta līnija", untagged_area: "Neapzīmēts apgabals", - tag_suggests_area: "Apzīmējums {tag} parasti lietots apgabaliem, bet objekts nav apgabals", - deprecated_tags: "Novecojūši apzīmējumi: {tags}" + tag_suggests_area: "Apzīmējums {tag} parasti tiek lietots apgabaliem, bet objekts nav apgabals", + deprecated_tags: "Novecojuši apzīmējumi: {tags}" }, save: "Saglabāt", - unsaved_changes: "Jums ir nesaglabātas maiņas", - save_help: "Saglabā maiņas, padarot tās redzamas citiem", - no_changes: "Jums nav maiņas ko saglabāt", + unsaved_changes: "Jums ir nesaglabātas izmaiņas", + save_help: "Saglabā izmaiņas, padarot tās redzamas citiem", + no_changes: "Jums nav izmaiņu, ko saglabāt", save_error: "Kļūda. Nevarēja saglabāt maiņas", uploading_changes: "Augšupielādē", just_edited: "Jūs nupat rediģējāt OpenStreetMap", @@ -152,8 +152,8 @@ locale.lv = { "zoom-in": "Pietuvināt", "zoom-out": "Attālināt", - nothing_to_undo: "Nekas ko atcelt", - nothing_to_redo: "Nekas ko atatsaukt", + nothing_to_undo: "Nav nekā, ko atcelt", + nothing_to_redo: "Nav nekā, ko atsaukt", browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", @@ -171,8 +171,8 @@ locale.lv = { geocoder: { title: "Atrast vietu", - placeholder: "atrod vietu", - no_results: "Nevarēja atrast vietu vārdā '{name}'" + placeholder: "meklē vietu", + no_results: "Nevarēja atrast vietu '{name}'" }, description: "Apraksts", @@ -192,7 +192,7 @@ locale.lv = { }, contributors: { - list: "{users} papildinājumi redzāmi", - truncated_list: "{users} un {count} citu papildinājumi redzāmi" + list: "{users} papildinājumi redzami", + truncated_list: "{users} un {count} citu papildinājumi redzami" } }; From b639de3f20798cc9c7be363fd6e1cb8729856414 Mon Sep 17 00:00:00 2001 From: metalalp Date: Sun, 10 Feb 2013 07:12:29 -0800 Subject: [PATCH 218/332] Creating Turkish translation --- locale/tr.js | 201 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 locale/tr.js diff --git a/locale/tr.js b/locale/tr.js new file mode 100644 index 000000000..2b84a8b0d --- /dev/null +++ b/locale/tr.js @@ -0,0 +1,201 @@ +locale.tr = { + modes: { + add_area: { + title: "Area", + description: "Add parks, buildings, lakes, or other areas to the map.", + tail: "Click on the map to start drawing an area, like a park, lake, or building.", + key: "A" + }, + add_line: { + title: "Line", + description: "Lines can be highways, streets, pedestrian paths, or even canals.", + tail: "Click on the map to start drawing an road, path, or route.", + key: "L" + }, + add_point: { + title: "Point", + description: "Restaurants, monuments, and postal boxes are points.", + tail: "Click on the map to add a point.", + key: "P" + }, + browse: { + title: "Browse", + description: "Pan and zoom the map.", + key: "B" + }, + draw_area: { + tail: "Click to add points to your area. Click the first point to finish the area." + }, + draw_line: { + tail: "Click to add more points to the line. Click on other lines to connect to them, and double-click to end the line." + } + }, + + operations: { + add: { + annotation: { + point: "Added a point.", + vertex: "Added a node to a way." + } + }, + start: { + annotation: { + line: "Started a line.", + area: "Started an area." + } + }, + 'continue': { + annotation: { + line: "Continued a line.", + area: "Continued an area." + } + }, + cancel_draw: { + annotation: "Cancelled drawing." + }, + change_tags: { + annotation: "Changed tags." + }, + circularize: { + title: "Circularize", + description: "Make this round.", + key: "O", + annotation: { + line: "Made a line circular.", + area: "Made an area circular." + } + }, + orthogonalize: { + title: "Orthogonalize", + description: "Square these corners.", + key: "Q", + annotation: { + line: "Squared the corners of a line.", + area: "Squared the corners of an area." + } + }, + 'delete': { + title: "Delete", + description: "Remove this from the map.", + key: "⌫", + annotation: { + point: "Deleted a point.", + vertex: "Deleted a node from a way.", + line: "Deleted a line.", + area: "Deleted an area.", + relation: "Deleted a relation.", + multiple: "Deleted {n} objects." + } + }, + connect: { + annotation: { + point: "Connected a way to a point.", + vertex: "Connected a way to another.", + line: "Connected a way to a line.", + area: "Connected a way to an area." + } + }, + disconnect: { + title: "Disconnect", + description: "Disconnect these ways from each other.", + key: "D", + annotation: "Disconnected ways." + }, + merge: { + title: "Merge", + description: "Merge these lines.", + key: "C", + annotation: "Merged {n} lines." + }, + move: { + title: "Move", + description: "Move this to a different location.", + key: "M", + annotation: { + point: "Moved a point.", + vertex: "Moved a node in a way.", + line: "Moved a line.", + area: "Moved an area." + } + }, + reverse: { + title: "Reverse", + description: "Make this line go in the opposite direction.", + key: "V", + annotation: "Reversed a line." + }, + split: { + title: "Split", + description: "Split this into two ways at this point.", + key: "X", + annotation: "Split a way." + } + }, + + validations: { + untagged_point: "Untagged point which is not part of a line or area", + untagged_line: "Untagged line", + untagged_area: "Untagged area", + tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area", + deprecated_tags: "Deprecated tags: {tags}" + }, + + save: "Save", + unsaved_changes: "You have unsaved changes", + save_help: "Save changes to OpenStreetMap, making them visible to other users", + no_changes: "You don't have any changes to save.", + save_error: "An error occurred while trying to save", + uploading_changes: "Uploading changes to OpenStreetMap.", + just_edited: "You Just Edited OpenStreetMap!", + okay: "Okay", + + "zoom-in": "Zoom In", + "zoom-out": "Zoom Out", + + nothing_to_undo: "Nothing to undo.", + nothing_to_redo: "Nothing to redo.", + + browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", + + inspector: { + no_documentation_combination: "This is no documentation available for this tag combination", + no_documentation_key: "This is no documentation available for this key", + new_tag: "New Tag" + }, + + view_on_osm: "View on OSM", + + zoom_in_edit: "zoom in to edit the map", + + edit_tags: "Edit tags", + + geocoder: { + title: "Find A Place", + placeholder: "find a place", + no_results: "Couldn't locate a place named '{name}'" + }, + + description: "Description", + + logout: "logout", + + report_a_bug: "report a bug", + + layerswitcher: { + title: "Background", + description: "Background Settings", + percent_brightness: "{opacity}% brightness", + fix_misalignment: "Fix misalignment", + reset: "reset" + }, + + contributors: { + list: "Viewing contributions by {users}", + truncated_list: "Viewing contributions by {users} and {count} others" + }, + + source_switch: { + live: "live", + dev: "dev" + } +}; From bed7ec109ebd4befd3574de6b6583b9ef4fe030f Mon Sep 17 00:00:00 2001 From: Christian Mayer Date: Sun, 10 Feb 2013 18:01:41 +0100 Subject: [PATCH 219/332] Initial translation to German --- locale/de.js | 180 +++++++++++++++++++++++++-------------------------- 1 file changed, 90 insertions(+), 90 deletions(-) diff --git a/locale/de.js b/locale/de.js index 091ef9a07..a2e3f6e7d 100644 --- a/locale/de.js +++ b/locale/de.js @@ -1,187 +1,187 @@ locale.de = { modes: { add_area: { - title: "Area", - description: "Add parks, buildings, lakes, or other areas to the map.", - tail: "Click on the map to start drawing an area, like a park, lake, or building.", + title: "Fläche", + description: "Füge Parks, Gebäude, Seen oder andere Flächen zur Karte hinzu.", + tail: "Klicke in die Karte um das Zeichnen einer Fläche wie einen Park, einen See oder Gebäude zu starten.", key: "A" }, add_line: { - title: "Line", - description: "Lines can be highways, streets, pedestrian paths, or even canals.", - tail: "Click on the map to start drawing an road, path, or route.", + title: "Linie", + description: "Linien können Autobahnen, Straßen, Fußwege oder sogar Kanäle sein.", + tail: "Klicke in die Karte um das Zeichnen einer Straße eines Pfades oder einer Route zu starten.", key: "L" }, add_point: { - title: "Point", - description: "Restaurants, monuments, and postal boxes are points.", - tail: "Click on the map to add a point.", + title: "Punkt", + description: "Restaurants, Denkmäler und Briefkästen sind Punkte", + tail: "Klicke in die Karte, um einen Punkt hinzuzufügen.", key: "P" }, browse: { - title: "Browse", - description: "Pan and zoom the map.", + title: "Navigation", + description: "Verschieben und Vergrößern/Verkleinern des Kartenausschnitts.", key: "B" }, draw_area: { - tail: "Click to add points to your area. Click the first point to finish the area." + tail: "Klicke, um Punkte zur Fläche hinzuzufügen. Klicke den ersten Punkt, um die Fläche abzuschließen." }, draw_line: { - tail: "Click to add more points to the line. Click on other lines to connect to them, and double-click to end the line." + tail: "Klicke, um mehr Punkte zur Linie hizuzufügen. Klicke auf eine andere Linie um die Linien zu verbinden, und klicke doppelt, um die Linie zu beenden." } }, operations: { add: { annotation: { - point: "Added a point.", - vertex: "Added a node to a way." + point: "Punkt hinzugefügt.", + vertex: "Stützpunkt einem Weg hinzugefügt." } }, start: { annotation: { - line: "Started a line.", - area: "Started an area." + line: "Linie begonnen.", + area: "Fläche begonnen." } }, 'continue': { annotation: { - line: "Continued a line.", - area: "Continued an area." + line: "Linie fortgesetzt.", + area: "Fläche fortgesetzt." } }, cancel_draw: { - annotation: "Cancelled drawing." + annotation: "Zeichnen abgebrochen." }, change_tags: { - annotation: "Changed tags." + annotation: "Tags verändert." }, circularize: { - title: "Circularize", - description: "Make this round.", + title: "Abrunden", + description: "Runde dies ab.", key: "O", annotation: { - line: "Made a line circular.", - area: "Made an area circular." + line: "Runde eine Linie ab.", + area: "Runde eine Fläche ab." } }, orthogonalize: { - title: "Orthogonalize", - description: "Square these corners.", + title: "Rechtwinkligkeit herstellen", + description: "Diese Ecken rechtwinklig ausrichten.", key: "Q", annotation: { - line: "Squared the corners of a line.", - area: "Squared the corners of an area." + line: "Die Ecken einer Linie rechtwinklig ausgerichtet.", + area: "Die Ecken einer Fläche rechtwinklig ausgerichtet." } }, 'delete': { - title: "Delete", - description: "Remove this from the map.", + title: "Löschen", + description: "Lösche dies aus der Karte.", key: "⌫", annotation: { - point: "Deleted a point.", - vertex: "Deleted a node from a way.", - line: "Deleted a line.", - area: "Deleted an area.", - relation: "Deleted a relation.", - multiple: "Deleted {n} objects." + point: "Punkt gelöscht.Deleted a point.", + vertex: "Stützpunkt aus einem Weg gelöscht.", + line: "Linie gelöscht.", + area: "Fläche gelöscht.", + relation: "Relation gelöscht.", + multiple: "{n} Objekte gelöscht." } }, connect: { annotation: { - point: "Connected a way to a point.", - vertex: "Connected a way to another.", - line: "Connected a way to a line.", - area: "Connected a way to an area." + point: "Weg mit einem Punkt verbunden.", + vertex: "Weg mit einem anderem Weg verbunden.", + line: "Weg mit einer Linie verbunden.", + area: "Weg mit einer Fläche verbunden." } }, disconnect: { - title: "Disconnect", - description: "Disconnect these ways from each other.", + title: "Trennen", + description: "Trenne diese Wege voneinander.", key: "D", - annotation: "Disconnected ways." + annotation: "Wege getrennt." }, merge: { - title: "Merge", - description: "Merge these lines.", + title: "Vereinigen", + description: "Vereinige diese Linien.", key: "C", - annotation: "Merged {n} lines." + annotation: "{n} Linien vereinigt." }, move: { - title: "Move", - description: "Move this to a different location.", + title: "Verschieben", + description: "Verschiebe dieses Objekt an einen anderen Ort.", key: "M", annotation: { - point: "Moved a point.", - vertex: "Moved a node in a way.", - line: "Moved a line.", - area: "Moved an area." + point: "Punkt verschoben.", + vertex: "Stützpunkt in einen Weg veschoben.", + line: "Linie verschoben.", + area: "Fläche verschoben." } }, reverse: { - title: "Reverse", - description: "Make this line go in the opposite direction.", + title: "Umkehren", + description: "Ändere die Richtung diese Linie.", key: "V", - annotation: "Reversed a line." + annotation: "Linienrichtung umgekehrt." }, split: { - title: "Split", - description: "Split this into two ways at this point.", + title: "Teilen", + description: "Teile dies in zwei Wege an diesem Punkt.", key: "X", - annotation: "Split a way." + annotation: "Weg geteilt." } }, validations: { - untagged_point: "Untagged point which is not part of a line or area", - untagged_line: "Untagged line", - untagged_area: "Untagged area", - tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area", - deprecated_tags: "Deprecated tags: {tags}" + untagged_point: "Punkt, der kein Teil einer Linie oder Fläche ist entmarkiert", + untagged_line: "Linie entmarkiert", + untagged_area: "Fläche entmarkiert", + tag_suggests_area: "Die Markierung {tag} suggeriert eine Fläche, ist aber keine Fläche", + deprecated_tags: "Abgelehnte Markierungen: {tags}" }, - save: "Save", - save_help: "Save changes to OpenStreetMap, making them visible to other users", - no_changes: "You don't have any changes to save.", - save_error: "An error occurred while trying to save", - uploading_changes: "Uploading changes to OpenStreetMap.", - just_edited: "You Just Edited OpenStreetMap!", + save: "Speichern", + save_help: "Speichere Änderungen zu OpenStreetMap, so dass sie für andere Nutzer sichtbar werden", + no_changes: "Sie haben keine Änderungen zum Speichern.", + save_error: "Es ist ein Fehler aufgetreten beim Versuch des Speicherns", + uploading_changes: "Lade Änderungen zu OpenStreetMap.", + just_edited: "Sie haben gerade OpenStreetMap editiert!", okay: "Okay", - "zoom-in": "Zoom In", - "zoom-out": "Zoom Out", + "zoom-in": "Hineinzoomen", + "zoom-out": "Herauszoomen", - nothing_to_undo: "Nothing to undo.", - nothing_to_redo: "Nothing to redo.", + nothing_to_undo: "Nichts zum Rückgängigmachen.", + nothing_to_redo: "Nichts zum Wiederherstellen.", - browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", + browser_notice: "Dieser Editor wird in Firefox, Chrome, Safari, Opera, und Internet Explorer 9 und höher unterstzützt. Bitte aktualisieren Sie Ihren Browser oder nutzen Sie Potlatch 2, um die Karte zu modifizieren.", inspector: { - no_documentation_combination: "This is no documentation available for this tag combination", - no_documentation_key: "This is no documentation available for this key", - new_tag: "New Tag" + no_documentation_combination: "Es ist keine Dokumentation verfügbar für diese Markierungskombination.", + no_documentation_key: "Es ist keine Dokumentation verfügbar für dieses Schlüsselwort", + new_tag: "Neue Markierung" }, - view_on_osm: "View on OSM", + view_on_osm: "Bei OSM anschauen", - zoom_in_edit: "zoom in to edit the map", + zoom_in_edit: "Hineinzoomen, um die Karte zu editieren", - edit_tags: "Edit tags", + edit_tags: "Markierungen bearbeiten", geocoder: { - "find_location": "Find A Location", - "find_a_place": "find a place" + "find_location": "Finde einen Ort", + "find_a_place": "Finde einen Platz" }, - description: "Description", + description: "Beschreibung", - logout: "logout", + logout: "Abmelden", layerswitcher: { - title: "Background", - description: "Background Settings", - percent_brightness: "{opacity}% brightness", - fix_misalignment: "Fix misalignment", - reset: "reset" + title: "Hintergrund", + description: "Hintergrundeinstellungen", + percent_brightness: "{opacity}% Helligkeit", + fix_misalignment: "Fehlerhafte Ausrichtung reparieren", + reset: "Zurücksetzen" } }; From 6ce987f50fce7cb353ed64344bbe58b80b80d0c6 Mon Sep 17 00:00:00 2001 From: Christian Mayer Date: Sun, 10 Feb 2013 18:11:08 +0100 Subject: [PATCH 220/332] Corrected typos --- locale/de.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/locale/de.js b/locale/de.js index a2e3f6e7d..3c24f8f46 100644 --- a/locale/de.js +++ b/locale/de.js @@ -3,13 +3,13 @@ locale.de = { add_area: { title: "Fläche", description: "Füge Parks, Gebäude, Seen oder andere Flächen zur Karte hinzu.", - tail: "Klicke in die Karte um das Zeichnen einer Fläche wie einen Park, einen See oder Gebäude zu starten.", + tail: "Klicke in die Karte, um das Zeichnen einer Fläche wie einen Park, einen See oder Gebäude zu starten.", key: "A" }, add_line: { title: "Linie", description: "Linien können Autobahnen, Straßen, Fußwege oder sogar Kanäle sein.", - tail: "Klicke in die Karte um das Zeichnen einer Straße eines Pfades oder einer Route zu starten.", + tail: "Klicke in die Karte, um das Zeichnen einer Straße eines Pfades oder einer Route zu starten.", key: "L" }, add_point: { @@ -24,10 +24,10 @@ locale.de = { key: "B" }, draw_area: { - tail: "Klicke, um Punkte zur Fläche hinzuzufügen. Klicke den ersten Punkt, um die Fläche abzuschließen." + tail: "Klicke, um Punkte zur Fläche hinzuzufügen. Klicke auf den ersten Punkt, um die Fläche abzuschließen." }, draw_line: { - tail: "Klicke, um mehr Punkte zur Linie hizuzufügen. Klicke auf eine andere Linie um die Linien zu verbinden, und klicke doppelt, um die Linie zu beenden." + tail: "Klicke, um mehr Punkte zur Linie hizuzufügen. Klicke auf eine andere Linie um die Linien zu verbinden und klicke doppelt, um die Linie zu beenden." } }, @@ -79,11 +79,11 @@ locale.de = { description: "Lösche dies aus der Karte.", key: "⌫", annotation: { - point: "Punkt gelöscht.Deleted a point.", + point: "Punkt gelöscht.", vertex: "Stützpunkt aus einem Weg gelöscht.", line: "Linie gelöscht.", area: "Fläche gelöscht.", - relation: "Relation gelöscht.", + relation: "Verbindung gelöscht.", multiple: "{n} Objekte gelöscht." } }, @@ -120,7 +120,7 @@ locale.de = { }, reverse: { title: "Umkehren", - description: "Ändere die Richtung diese Linie.", + description: "Ändere die Richtung dieser Linie.", key: "V", annotation: "Linienrichtung umgekehrt." }, @@ -133,7 +133,7 @@ locale.de = { }, validations: { - untagged_point: "Punkt, der kein Teil einer Linie oder Fläche ist entmarkiert", + untagged_point: "Punkt, der kein Teil einer Linie oder Fläche ist, entmarkiert", untagged_line: "Linie entmarkiert", untagged_area: "Fläche entmarkiert", tag_suggests_area: "Die Markierung {tag} suggeriert eine Fläche, ist aber keine Fläche", From 00313ebc4696cc221ccc0a4ffbdae46370dd006d Mon Sep 17 00:00:00 2001 From: metalalp Date: Sun, 10 Feb 2013 21:40:03 +0200 Subject: [PATCH 221/332] Updated some Turkish translations. --- locale/tr.js | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/locale/tr.js b/locale/tr.js index 2b84a8b0d..1315eb05a 100644 --- a/locale/tr.js +++ b/locale/tr.js @@ -1,60 +1,60 @@ locale.tr = { modes: { add_area: { - title: "Area", - description: "Add parks, buildings, lakes, or other areas to the map.", - tail: "Click on the map to start drawing an area, like a park, lake, or building.", + title: "Alan", + description: "Park, bina, göl ve benzeri alanları haritaya ekle.", + tail: "Park, göl ya da bina gibi alanları çizmek için haritaya tıklayın.", key: "A" }, add_line: { - title: "Line", - description: "Lines can be highways, streets, pedestrian paths, or even canals.", - tail: "Click on the map to start drawing an road, path, or route.", + title: "Çizgi", + description: "Yollar, sokaklar, patikalar ya da kanallar çizgi ile çizilebilir.", + tail: "Yol, patika yada rota çizmek için haritaya tıklayın.", key: "L" }, add_point: { - title: "Point", - description: "Restaurants, monuments, and postal boxes are points.", - tail: "Click on the map to add a point.", + title: "Nokta", + description: "Restoranlar, anıtlar ya da posta kutuları nokta ile gösterilebilir.", + tail: "Nokta eklemek için haritaya tıklayın.", key: "P" }, browse: { - title: "Browse", - description: "Pan and zoom the map.", + title: "Tara", + description: "Harita üzerinde dolan ve yaklaş.", key: "B" }, draw_area: { - tail: "Click to add points to your area. Click the first point to finish the area." + tail: "Alanınıza nokta eklemek için tıklayınız. İlk noktaya tıklayarak alan çizimini bitirebilirsiniz." }, draw_line: { - tail: "Click to add more points to the line. Click on other lines to connect to them, and double-click to end the line." + tail: "Çizgiye daha fazla nokta eklemek için tıklayınız. Diğer çizgilerle bağlamak için üstlerine tıklyınız ve bitirmek için de son noktada çift tıklayınız." } }, operations: { add: { annotation: { - point: "Added a point.", - vertex: "Added a node to a way." + point: "Nokta eklendi.", + vertex: "Çizgiye bir nod eklendi." } }, start: { annotation: { - line: "Started a line.", - area: "Started an area." + line: "Çizgi çizimi başlatıldı.", + area: "Alan çizimi başlatıldı." } }, 'continue': { annotation: { - line: "Continued a line.", - area: "Continued an area." + line: "Çizgiye devam edildi.", + area: "Alana devam edildi." } }, cancel_draw: { - annotation: "Cancelled drawing." + annotation: "Çizim iptal edildi." }, change_tags: { - annotation: "Changed tags." + annotation: "Etiketler değiştirildi." }, circularize: { title: "Circularize", From 3e7b0b0d98f3518fc8475d78c9508c2b01ce4765 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 9 Feb 2013 20:48:25 -0800 Subject: [PATCH 222/332] Optimize iD.Connection The big win here is using direct property accessors on node attributes rather than iteration. The rest is just micro-optimization. --- js/id/connection.js | 135 +++++++++++++++++++++------------------ test/bench/node-xml.html | 99 ++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+), 61 deletions(-) create mode 100644 test/bench/node-xml.html diff --git a/js/id/connection.js b/js/id/connection.js index 5f5f17482..df9c318ca 100644 --- a/js/id/connection.js +++ b/js/id/connection.js @@ -7,7 +7,13 @@ iD.Connection = function(context) { keys, inflight = {}, loadedTiles = {}, - oauth = iD.OAuth(context).url(url); + oauth = iD.OAuth(context).url(url), + ndStr = 'nd', + tagStr = 'tag', + memberStr = 'member', + nodeStr = 'node', + wayStr = 'way', + relationStr = 'relation'; function changesetUrl(changesetId) { return url + '/browse/changeset/' + changesetId; @@ -34,92 +40,99 @@ iD.Connection = function(context) { } function getNodes(obj) { - var nelems = obj.getElementsByTagName('nd'), nodes = new Array(nelems.length); - for (var i = 0, l = nelems.length; i < l; i++) { - nodes[i] = 'n' + nelems[i].attributes.ref.nodeValue; + var elems = obj.getElementsByTagName(ndStr), + nodes = new Array(elems.length); + for (var i = 0, l = elems.length; i < l; i++) { + nodes[i] = 'n' + elems[i].attributes.ref.nodeValue; } return nodes; } function getTags(obj) { - var tags = {}, tagelems = obj.getElementsByTagName('tag'); - for (var i = 0, l = tagelems.length; i < l; i++) { - var item = tagelems[i]; - tags[item.attributes.k.nodeValue] = item.attributes.v.nodeValue; + var elems = obj.getElementsByTagName(tagStr), + tags = {}; + for (var i = 0, l = elems.length; i < l; i++) { + var attrs = elems[i].attributes; + tags[attrs.k.nodeValue] = attrs.v.nodeValue; } return tags; } function getMembers(obj) { - var elems = obj.getElementsByTagName('member'), + var elems = obj.getElementsByTagName(memberStr), members = new Array(elems.length); - for (var i = 0, l = elems.length; i < l; i++) { + var attrs = elems[i].attributes; members[i] = { - id: elems[i].attributes.type.nodeValue[0] + elems[i].attributes.ref.nodeValue, - type: elems[i].attributes.type.nodeValue, - role: elems[i].attributes.role.nodeValue + id: attrs.type.nodeValue[0] + attrs.ref.nodeValue, + type: attrs.type.nodeValue, + role: attrs.role.nodeValue }; } return members; } - function nodeData(obj) { - var o = { type: 'node', tags: getTags(obj) }; - for (var i = 0, l = obj.attributes.length; i < l; i++) { - o[obj.attributes[i].nodeName] = obj.attributes[i].nodeValue; - } - if (o.lon && o.lat) { - o.loc = [parseFloat(o.lon), parseFloat(o.lat)]; - delete o.lon; delete o.lat; - } - o.id = iD.Entity.id.fromOSM('node', o.id); - return new iD.Node(o); - } + var parsers = { + node: function nodeData(obj) { + var attrs = obj.attributes; + return new iD.Node({ + id: iD.Entity.id.fromOSM(nodeStr, attrs.id.nodeValue), + loc: [parseFloat(attrs.lon.nodeValue), parseFloat(attrs.lat.nodeValue)], + version: attrs.version.nodeValue, + changeset: attrs.changeset.nodeValue, + user: attrs.user.nodeValue, + uid: attrs.uid.nodeValue, + visible: attrs.visible.nodeValue, + timestamp: attrs.timestamp.nodeValue, + tags: getTags(obj) + }); + }, - function wayData(obj) { - var o = { type: 'way', nodes: getNodes(obj), - tags: getTags(obj) - }; - for (var i = 0, l = obj.attributes.length; i < l; i++) { - o[obj.attributes[i].nodeName] = obj.attributes[i].nodeValue; - } - o.id = iD.Entity.id.fromOSM('way', o.id); - return new iD.Way(o); - } + way: function wayData(obj) { + var attrs = obj.attributes; + return new iD.Way({ + id: iD.Entity.id.fromOSM(wayStr, attrs.id.nodeValue), + version: attrs.version.nodeValue, + changeset: attrs.changeset.nodeValue, + user: attrs.user.nodeValue, + uid: attrs.uid.nodeValue, + visible: attrs.visible.nodeValue, + timestamp: attrs.timestamp.nodeValue, + tags: getTags(obj), + nodes: getNodes(obj) + }); + }, - function relationData(obj) { - var o = { - type: 'relation', members: getMembers(obj), - tags: getTags(obj) - }; - for (var i = 0, l = obj.attributes.length; i < l; i++) { - o[obj.attributes[i].nodeName] = obj.attributes[i].nodeValue; + relation: function relationData(obj) { + var attrs = obj.attributes; + return new iD.Relation({ + id: iD.Entity.id.fromOSM(relationStr, attrs.id.nodeValue), + version: attrs.version.nodeValue, + changeset: attrs.changeset.nodeValue, + user: attrs.user.nodeValue, + uid: attrs.uid.nodeValue, + visible: attrs.visible.nodeValue, + timestamp: attrs.timestamp.nodeValue, + tags: getTags(obj), + members: getMembers(obj) + }); } - o.id = iD.Entity.id.fromOSM('relation', o.id); - return new iD.Relation(o); - } + }; function parse(dom) { if (!dom || !dom.childNodes) return new Error('Bad request'); - var root = dom.childNodes[0]; - var entities = {}; + + var root = dom.childNodes[0], + children = root.childNodes, + entities = {}; var i, o, l; - for (i = 0, l = root.childNodes.length; i < l; i++) { - switch(root.childNodes[i].nodeName) { - case 'node': - o = nodeData(root.childNodes[i]); - entities[o.id] = o; - break; - case 'way': - o = wayData(root.childNodes[i]); - entities[o.id] = o; - break; - case 'relation': - o = relationData(root.childNodes[i]); - entities[o.id] = o; - break; + for (i = 0, l = children.length; i < l; i++) { + var child = children[i], + parser = parsers[child.nodeName]; + if (parser) { + o = parser(child); + entities[o.id] = o; } } diff --git a/test/bench/node-xml.html b/test/bench/node-xml.html new file mode 100644 index 000000000..174738350 --- /dev/null +++ b/test/bench/node-xml.html @@ -0,0 +1,99 @@ + + + + Node XML + + + +
+
+
+

+        
+    
+    
+

From 3f47dbe65eff37780353b9775b6261b61f61248a Mon Sep 17 00:00:00 2001
From: Ansis Brammanis 
Date: Sun, 10 Feb 2013 15:36:03 -0500
Subject: [PATCH 223/332] Typo fix via @Richlv

---
 locale/en.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/locale/en.js b/locale/en.js
index a2233551b..1e45312de 100644
--- a/locale/en.js
+++ b/locale/en.js
@@ -158,8 +158,8 @@ locale.en = {
     browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.",
 
     inspector: {
-        no_documentation_combination:  "This is no documentation available for this tag combination",
-        no_documentation_key: "This is no documentation available for this key",
+        no_documentation_combination:  "There is no documentation available for this tag combination",
+        no_documentation_key: "There is no documentation available for this key",
         new_tag: "New Tag"
     },
 

From 14e1ad71476c4db1791d8166878acd408be47fe7 Mon Sep 17 00:00:00 2001
From: Ansis Brammanis 
Date: Sun, 10 Feb 2013 15:48:47 -0500
Subject: [PATCH 224/332] Add turkish to index.html

---
 index.html | 1 +
 1 file changed, 1 insertion(+)

diff --git a/index.html b/index.html
index 89ba50fe1..34906c477 100644
--- a/index.html
+++ b/index.html
@@ -147,6 +147,7 @@
         
         
         
+        
 
     
     

From a3fca51af140c261654b40686bd58fb233dcf397 Mon Sep 17 00:00:00 2001
From: Van De Casteele 
Date: Sun, 10 Feb 2013 17:19:46 -0430
Subject: [PATCH 225/332] Update locale/fr.js

French translation
---
 locale/fr.js | 168 +++++++++++++++++++++++++--------------------------
 1 file changed, 84 insertions(+), 84 deletions(-)

diff --git a/locale/fr.js b/locale/fr.js
index 9bd37e688..9b4017b31 100644
--- a/locale/fr.js
+++ b/locale/fr.js
@@ -1,185 +1,185 @@
 locale.fr = {
     modes: {
         add_area: {
-            title: "Area",
-            description: "Add parks, buildings, lakes, or other areas to the map.",
-            tail: "Click on the map to start drawing an area, like a park, lake, or building.",
+            title: "Polygone",
+            description: "Les polygones peuvent être des parcs, des batîments, des lacs ou tout autre objet surfacique.",
+            tail: "Cliquez sur la carte pour ajouter un polygone tel qu'un parc, un lac ou un bâtiment.",
             key: "A"
         },
         add_line: {
-            title: "Line",
-            description: "Lines can be highways, streets, pedestrian paths, or even canals.",
-            tail: "Click on the map to start drawing an road, path, or route.",
+            title: "Ligne",
+            description: "Les lignes peuvent être des autoroutes, des routes, des chemins ou encore des caneaux.",
+            tail: "Cliquez sur la carte pour ajouter une nouvelle ligne telle qu'une route ou un nouveau chemin.",
             key: "L"
         },
         add_point: {
             title: "Point",
-            description: "Restaurants, monuments, and postal boxes are points.",
-            tail: "Click on the map to add a point.",
+            description: "Les points peuvent être des restaurants, des monuments, ou encore des boites aux lettres.",
+            tail: "Cliquez sur la carte pour ajouter un point tel qu'un restaurant ou un monument.",
             key: "P"
         },
         browse: {
-            title: "Browse",
-            description: "Pan and zoom the map.",
+            title: "Navigation",
+            description: "Naviguer ou zoomer sur la carte.",
             key: "B"
         },
         draw_area: {
-            tail: "Click to add points to your area. Click the first point to finish the area."
+            tail: "Cliquez pour ajouter un point à la zone. Cliquez sur le dernier point pour fermer la zone."
         },
         draw_line: {
-            tail: "Click to add more points to the line. Click on other lines to connect to them, and double-click to end the line."
+            tail: "Cliquez pour ajouter un point à la ligne. Cliquez sur une autre ligne pour les connecter, puis faîtes un double-clique pour terminer la ligne."
         }
     },
 
     operations: {
         add: {
             annotation: {
-                point: "Added a point.",
-                vertex: "Added a node to a way."
+                point: "Ajouter un point.",
+                vertex: "Ajouter un noeud à une ligne."
             }
         },
         start: {
             annotation: {
-                line: "Started a line.",
-                area: "Started an area."
+                line: "Commencer une nouvelle ligne.",
+                area: "Commencer un polygone."
             }
         },
         'continue': {
             annotation: {
-                line: "Continued a line.",
-                area: "Continued an area."
+                line: "Continuer une ligne.",
+                area: "Continuer un polygone."
             }
         },
         cancel_draw: {
-            annotation: "Cancelled drawing."
+            annotation: "Annuler un ajout."
         },
         change_tags: {
-            annotation: "Changed tags."
+            annotation: "Modifier les tags."
         },
         circularize: {
             title: "Circularize",
-            description: "Make this round.",
+            description: "Créer un cercle.",
             key: "O",
             annotation: {
-                line: "Made a line circular.",
-                area: "Made an area circular."
+                line: "Créer un cercle linéaire.",
+                area: "Créer un cercle surfacique (disque)."
             }
         },
         orthogonalize: {
-            title: "Orthogonalize",
-            description: "Square these corners.",
+            title: "Orthogonaliser",
+            description: "Rendre une forme orthogonale.",
             key: "Q",
             annotation: {
-                line: "Squared the corners of a line.",
-                area: "Squared the corners of an area."
+                line: "Orthogonaliser une ligne orthogonale.",
+                area: "Orthogonaliser un polygone orthogonale."
             }
         },
         'delete': {
-            title: "Delete",
-            description: "Remove this from the map.",
+            title: "Supprimer",
+            description: "Supprime l'élément de la carte.",
             key: "⌫",
             annotation: {
-                point: "Deleted a point.",
-                vertex: "Deleted a node from a way.",
-                line: "Deleted a line.",
-                area: "Deleted an area.",
-                relation: "Deleted a relation.",
-                multiple: "Deleted {n} objects."
+                point: "Supprime un point.",
+                vertex: "Supprime le noeud d'une ligne.",
+                line: "Supprime une ligne.",
+                area: "Supprime un polygone.",
+                relation: "Supprime une relation.",
+                multiple: "Supprime {n} objets."
             }
         },
         connect: {
             annotation: {
-                point: "Connected a way to a point.",
-                vertex: "Connected a way to another.",
-                line: "Connected a way to a line.",
-                area: "Connected a way to an area."
+                point: "Joindre une ligne à un point.",
+                vertex: "Joindre les noeuds à une ligne.",
+                line: "Joindre les chemins ensemble.",
+                area: "Joindre une ligne à un polygone."
             }
         },
         disconnect: {
-            title: "Disconnect",
-            description: "Disconnect these ways from each other.",
+            title: "Séparer",
+            description: "Sépare les lignes l'une de l'autre.",
             key: "D",
-            annotation: "Disconnected ways."
+            annotation: "Sépare les lignes."
         },
         merge: {
-            title: "Merge",
-            description: "Merge these lines.",
+            title: "Fusionner",
+            description: "Fusionne les lignes.",
             key: "C",
-            annotation: "Merged {n} lines."
+            annotation: "Fusionne les {n} ligne."
         },
         move: {
-            title: "Move",
-            description: "Move this to a different location.",
+            title: "Déplacer",
+            description: "Déplace l'élément à un autre endroit.",
             key: "M",
             annotation: {
-                point: "Moved a point.",
-                vertex: "Moved a node in a way.",
-                line: "Moved a line.",
-                area: "Moved an area."
+                point: "Déplace un point.",
+                vertex: "Déplace le noeud d'une ligne.",
+                line: "Déplace une ligne.",
+                area: "Déplace un polygone."
             }
         },
         reverse: {
-            title: "Reverse",
-            description: "Make this line go in the opposite direction.",
+            title: "Inverser",
+            description: "Inverse le sens d'une ligne.",
             key: "V",
-            annotation: "Reversed a line."
+            annotation: "Inverse le sens d'une ligne."
         },
         split: {
-            title: "Split",
-            description: "Split this into two ways at this point.",
+            title: "Couper",
+            description: "Coupe une ligne en deux par rapport au point sélectionné.",
             key: "X",
-            annotation: "Split a way."
+            annotation: "Coupe une ligne."
         }
     },
 
     validations: {
-        untagged_point: "Untagged point which is not part of a line or area",
-        untagged_line: "Untagged line",
-        untagged_area: "Untagged area",
-        tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area",
-        deprecated_tags: "Deprecated tags: {tags}"
+        untagged_point: "Point sans aucun tag ne faisant partie ni d'une ligne, ni d'un polygone",
+        untagged_line: "Ligne sans aucun tag",
+        untagged_area: "Polygone sans aucun tag",
+        tag_suggests_area: "Ce tag {tag} suppose que cette ligne devrait être un polygone, or ce n'est pas le cas",
+        deprecated_tags: "Tags obsolètes : {tags}"
     },
 
-    save: "Save",
-    save_help: "Save changes to OpenStreetMap, making them visible to other users",
-    no_changes: "You don't have any changes to save.",
-    save_error: "An error occurred while trying to save",
-    uploading_changes: "Uploading changes to OpenStreetMap.",
-    just_edited: "You Just Edited OpenStreetMap!",
+    save: "Sauvegarder",
+    save_help: "Envoie des modifications au serveyr OpenStreetMap afin qu'elles soient visibles par les autres contributeurs.",
+    no_changes: "Vous n'avez aucune modification à enregistrer.",
+    save_error: "Une erreur est survenue lors de l'enregistrement des données",
+    uploading_changes: "Envoie des modifications vers OpenStreetMap.",
+    just_edited: "Vous venez de participer à OpenStreetMap!",
     okay: "Okay",
 
-    "zoom-in": "Zoom In",
-    "zoom-out": "Zoom Out",
+    "zoom-in": "Zoomer",
+    "zoom-out": "Dézoomer",
 
-    nothing_to_undo: "Nothing to undo.",
-    nothing_to_redo: "Nothing to redo.",
+    nothing_to_undo: "Rien à annuler.",
+    nothing_to_redo: "Rien à refaire.",
 
-    browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.",
+    browser_notice: "Les navigateurs supportés par cet éditeur sont : Firefox, Chrome, Safari, Opera et Internet Explorer (version 9 et supérieures). Pour éditer la carte, veuillez mettre à jour votre navigateur ou utiliser Potlatch 2.",
 
     inspector: {
-        no_documentation_combination:  "This is no documentation available for this tag combination",
-        no_documentation_key: "This is no documentation available for this key",
-        new_tag: "New Tag"
+        no_documentation_combination:  "Aucune documentation n'est disponible pour cette combinaison de tag,
+        no_documentation_key: "Aucune documentation n'est disponible pour cette clé",
+        new_tag: "Nouveau tag"
     },
 
-    view_on_osm: "View on OSM",
+    view_on_osm: "Consulter dans OSM",
 
-    zoom_in_edit: "zoom in to edit the map",
+    zoom_in_edit: "Zoomer pour modifier la carte
 
-    edit_tags: "Edit tags",
+    edit_tags: "Editer les tags
 
     geocoder: {
-        "find_location": "Find A Location",
-        "find_a_place": "find a place"
+        "find_location": "Trouver un emplacement",
+        "find_a_place": "Trouver un endroit"
     },
 
     description: "Description",
 
-    logout: "logout",
+    logout: "Déconnexion",
 
     layerswitcher: {
-        title: "Background",
-        description: "Background Settings",
+        title: "Fond de carte",
+        description: "Paramètres du fond de carte",
         percent_brightness: "{opacity}% brightness",
         fix_misalignment: "Fix misalignment",
         reset: "reset"

From 39e99aa9e1c67972b8344843f0ba0aefd746852d Mon Sep 17 00:00:00 2001
From: richlv 
Date: Sun, 10 Feb 2013 23:56:33 +0200
Subject: [PATCH 226/332] Update locale/lv.js

translate additional strings;
fix more typos
---
 locale/lv.js | 38 +++++++++++++++++++-------------------
 1 file changed, 19 insertions(+), 19 deletions(-)

diff --git a/locale/lv.js b/locale/lv.js
index 2c28b400f..fa32bef5c 100644
--- a/locale/lv.js
+++ b/locale/lv.js
@@ -3,7 +3,7 @@ locale.lv = {
         add_area: {
             title: "Apgabals",
             description: "Pievieno parkus, ēkas, ezerus un citus apgabalus.",
-            tail: "Klikšķiniet uz kartes, lai sāktu zīmēt apgabalu, piemēram parku, ezeru, vai ēku.",
+            tail: "Klikšķiniet uz kartes, lai sāktu zīmēt apgabalu, piemēram, parku, ezeru, vai ēku.",
             key: "A"
         },
         add_line: {
@@ -46,7 +46,7 @@ locale.lv = {
         },
         'continue': {
             annotation: {
-                line: "Linija turpināta.",
+                line: "Līnija turpināta.",
                 area: "Apgabals turpināts."
             }
         },
@@ -57,26 +57,26 @@ locale.lv = {
             annotation: "Apzīmējumi mainīti."
         },
         circularize: {
-            title: "Circularize",
-            description: "Make this round.",
+            title: "Pārveidot par apļveida",
+            description: "Pārveidot šo objektu par apļveida.",
             key: "O",
             annotation: {
-                line: "Made a line circular.",
-                area: "Made an area circular."
+                line: "Līnija pārveidota par apļveida.",
+                area: "Apgabals pārveidots par apļveida."
             }
         },
         orthogonalize: {
-            title: "Orthogonalize",
-            description: "Square these corners.",
+            title: "Ortogonalizēt",
+            description: "Pārveidot, lai visi leņķi būtu tasnleņķi.",
             key: "Q",
             annotation: {
-                line: "Squared the corners of a line.",
-                area: "Squared the corners of an area."
+                line: "Līnijas leņķi pārvedoti par taisnleņķiem.",
+                area: "Apgabala leņķi pārvedoti par taisnleņķiem."
             }
         },
         'delete': {
             title: "Dzēst",
-            description: "Noņemt no kartes.",
+            description: "Izdzēst no kartes.",
             key: "⌫",
             annotation: {
                 point: "Punkts dzēsts.",
@@ -90,7 +90,7 @@ locale.lv = {
         connect: {
             annotation: {
                 point: "Līnija savienota ar punktu.",
-                vertex: "Līnija savienota ar otru.",
+                vertex: "Līnija savienota ar citu.",
                 line: "Līnija savienota ar līniju.",
                 area: "Līnija savienota ar apgabalu."
             }
@@ -155,29 +155,29 @@ locale.lv = {
     nothing_to_undo: "Nav nekā, ko atcelt",
     nothing_to_redo: "Nav nekā, ko atsaukt",
 
-    browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.",
+    browser_notice: "Šis redaktors tiek atbalstīts ar Firefox, Chrome, Safari, Opera, un Internet Explorer 9 un jaunāku. Lūdzu, atjauniniet savu pārlūkprogrammu vai izmantojiet Potlatch 2 to kartes rediģēšanai,
 
     inspector: {
-        no_documentation_combination:  "This is no documentation available for this tag combination",
-        no_documentation_key: "This is no documentation available for this key",
-        new_tag: "Jauns apzīmejums"
+        no_documentation_combination: "Šai apzīmējumu kombinācijai nav piejama dokumetācija",
+        no_documentation_key: "There is no documentation available for this key",
+        new_tag: "Jauns apzīmējums"
     },
 
     view_on_osm: "Apskatīt OSM lapu",
 
-    zoom_in_edit: "zoom in to edit the map",
+    zoom_in_edit: "pietuviniet, lai rediģētu karti,
 
     edit_tags: "Rediģēt apzīmējumus",
 
     geocoder: {
         title: "Atrast vietu",
         placeholder: "meklē vietu",
-        no_results: "Nevarēja atrast vietu '{name}'"
+        no_results: "Nevar atrast vietu '{name}'"
     },
 
     description: "Apraksts",
 
-    logout: "logout",
+    logout: "atslēgties",
 
     live: "live",
     dev: "dev",

From 937fd97900213886360f3d3e22f52a4c44ba03a2 Mon Sep 17 00:00:00 2001
From: richlv 
Date: Sun, 10 Feb 2013 23:57:52 +0200
Subject: [PATCH 227/332] Update locale/lv.js

add missing doublequote
---
 locale/lv.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/locale/lv.js b/locale/lv.js
index fa32bef5c..2d45b1dd9 100644
--- a/locale/lv.js
+++ b/locale/lv.js
@@ -155,7 +155,7 @@ locale.lv = {
     nothing_to_undo: "Nav nekā, ko atcelt",
     nothing_to_redo: "Nav nekā, ko atsaukt",
 
-    browser_notice: "Šis redaktors tiek atbalstīts ar Firefox, Chrome, Safari, Opera, un Internet Explorer 9 un jaunāku. Lūdzu, atjauniniet savu pārlūkprogrammu vai izmantojiet Potlatch 2 to kartes rediģēšanai,
+    browser_notice: "Šis redaktors tiek atbalstīts ar Firefox, Chrome, Safari, Opera, un Internet Explorer 9 un jaunāku. Lūdzu, atjauniniet savu pārlūkprogrammu vai izmantojiet Potlatch 2 to kartes rediģēšanai",
 
     inspector: {
         no_documentation_combination: "Šai apzīmējumu kombinācijai nav piejama dokumetācija",

From f75111bd51a645ef8b792c6d38ac6a0382158847 Mon Sep 17 00:00:00 2001
From: richlv 
Date: Sun, 10 Feb 2013 23:58:32 +0200
Subject: [PATCH 228/332] Update locale/lv.js

add another missing doublequote
---
 locale/lv.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/locale/lv.js b/locale/lv.js
index 2d45b1dd9..b7ba5504f 100644
--- a/locale/lv.js
+++ b/locale/lv.js
@@ -165,7 +165,7 @@ locale.lv = {
 
     view_on_osm: "Apskatīt OSM lapu",
 
-    zoom_in_edit: "pietuviniet, lai rediģētu karti,
+    zoom_in_edit: "pietuviniet, lai rediģētu karti",
 
     edit_tags: "Rediģēt apzīmējumus",
 

From a162a92adcf252e3c033f399d70283f8c04f30ed Mon Sep 17 00:00:00 2001
From: richlv 
Date: Mon, 11 Feb 2013 00:19:43 +0200
Subject: [PATCH 229/332] Update locale/en.js

- as this apparently uses american english, change "Cancelled" -> "Canceled"
- remove extra space (yay)
---
 locale/en.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/locale/en.js b/locale/en.js
index 1e45312de..efd3c1d90 100644
--- a/locale/en.js
+++ b/locale/en.js
@@ -51,7 +51,7 @@ locale.en = {
             }
         },
         cancel_draw: {
-            annotation: "Cancelled drawing."
+            annotation: "Canceled drawing."
         },
         change_tags: {
             annotation: "Changed tags."
@@ -158,7 +158,7 @@ locale.en = {
     browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.",
 
     inspector: {
-        no_documentation_combination:  "There is no documentation available for this tag combination",
+        no_documentation_combination: "There is no documentation available for this tag combination",
         no_documentation_key: "There is no documentation available for this key",
         new_tag: "New Tag"
     },

From db820e2d294ce8216f3f4f69b1f03ca71c0c9194 Mon Sep 17 00:00:00 2001
From: Tom MacWright 
Date: Sun, 10 Feb 2013 18:51:48 -0500
Subject: [PATCH 230/332] Fix js in french translation

---
 locale/fr.js | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/locale/fr.js b/locale/fr.js
index 9b4017b31..904d5cb9e 100644
--- a/locale/fr.js
+++ b/locale/fr.js
@@ -157,16 +157,16 @@ locale.fr = {
     browser_notice: "Les navigateurs supportés par cet éditeur sont : Firefox, Chrome, Safari, Opera et Internet Explorer (version 9 et supérieures). Pour éditer la carte, veuillez mettre à jour votre navigateur ou utiliser Potlatch 2.",
 
     inspector: {
-        no_documentation_combination:  "Aucune documentation n'est disponible pour cette combinaison de tag,
+        no_documentation_combination:  "Aucune documentation n'est disponible pour cette combinaison de tag",
         no_documentation_key: "Aucune documentation n'est disponible pour cette clé",
         new_tag: "Nouveau tag"
     },
 
     view_on_osm: "Consulter dans OSM",
 
-    zoom_in_edit: "Zoomer pour modifier la carte
+    zoom_in_edit: "Zoomer pour modifier la carte",
 
-    edit_tags: "Editer les tags
+    edit_tags: "Editer les tags",
 
     geocoder: {
         "find_location": "Trouver un emplacement",

From e60fd8665863b02d8b0c5b9a342631ec98de8a71 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Iv=C3=A1n=20Perdomo?= 
Date: Mon, 11 Feb 2013 08:42:34 +0100
Subject: [PATCH 231/332] Add Spanish to available locales

---
 locale/es.js | 201 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 201 insertions(+)
 create mode 100644 locale/es.js

diff --git a/locale/es.js b/locale/es.js
new file mode 100644
index 000000000..c2296b745
--- /dev/null
+++ b/locale/es.js
@@ -0,0 +1,201 @@
+locale.es = {
+    modes: {
+        add_area: {
+            title: "Zona", //"Area",
+            description: "Agregar parques, edificios, lagos u otras zonas en el mapa", //"Add parks, buildings, lakes, or other areas to the map.",
+            tail: "Hacer click en el mapa para empezar a dibujar una zona como un parque, lago o edificio", //"Click on the map to start drawing an area, like a park, lake, or building.",
+            key: "Z", //"A"
+        },
+        add_line: {
+            title: "Línea", //"Line",
+            description: "Las líneas pueden ser autopistas, calles, pasos peatonales o canales.", //"Lines can be highways, streets, pedestrian paths, or even canals.",
+            tail: "Hace clic para dibujar en el mapa, una calle, camino o ruta.", //"Click on the map to start drawing an road, path, or route.",
+            key: "L"
+        },
+        add_point: {
+            title: "Punto", //"Point",
+            description: "Son puntos los restaurantes, monumentos y buzones", //"Restaurants, monuments, and postal boxes are points.",
+            tail: "Hacer clic para agregar un punto en el mapa", //"Click on the map to add a point.",
+            key: "P"
+        },
+        browse: {
+            title: "Navegar", //"Browse",
+            description: "Aumentar y navegar el mapa", //"Pan and zoom the map.",
+            key: "N" //"B"
+        },
+        draw_area: {
+            tail: "Hacer clic para agregar puntos en tu zona. Hacer hacer click en el primer punto para finalizar la zona." //"Click to add points to your area. Click the first point to finish the area."
+        },
+        draw_line: {
+            tail: "Hacer clic para agregar más puntos a la línea. Hacer clic en otras líneas para conectarlas, y doble clic para finalizar." //"Click to add more points to the line. Click on other lines to connect to them, and double-click to end the line."
+        }
+    },
+
+    operations: {
+        add: {
+            annotation: {
+                point: "Punto agregado", //"Added a point.",
+                vertex: "Nodo agregado a una ruta" //"Added a node to a way."
+            }
+        },
+        start: {
+            annotation: {
+                line: "Línea iniciada", //"Started a line.",
+                area: "Zona iniciada" //"Started an area."
+            }
+        },
+        'continue': {
+            annotation: {
+                line: "Línea continuada.", //"Continued a line.",
+                area: "Zona continuada." //"Continued an area."
+            }
+        },
+        cancel_draw: {
+            annotation: "Dibujo cancelado." //"Cancelled drawing."
+        },
+        change_tags: {
+            annotation: "Etiquetas cambiadas." //"Changed tags."
+        },
+        circularize: {
+            title: "Redondear", //"Circularize",
+            description: "Hacer esto redondo.", //"Make this round.",
+            key: "O",
+            annotation: {
+                line: "Redondear una línea.", //"Made a line circular.",
+                area: "Redondear una zona." //"Made an area circular."
+            }
+        },
+        orthogonalize: {
+            title: "Escuadrar", //"Orthogonalize",
+            description: "Escuadrar estas esquinas.", //"Square these corners.",
+            key: "E", //"Q",
+            annotation: {
+                line: "Esquinas de la línea escuadrados.", //"Squared the corners of a line.",
+                area: "Esquinas de la zona escuadrados." //"Squared the corners of an area."
+            }
+        },
+        'delete': {
+            title: "Eliminar", //"Delete",
+            description: "Eliminar esto del mapa.", //"Remove this from the map.",
+            key: "⌫",
+            annotation: {
+                point: "Punto eliminado.", //"Deleted a point.",
+                vertex: "Nodo elimnado de una ruta.", //"Deleted a node from a way.",
+                line: "Línea eliminada.", //"Deleted a line.",
+                area: "Zona eliminada.", //"Deleted an area.",
+                relation: "Relación eliminada.", //"Deleted a relation.",
+                multiple: "{n} objetos eliminados." //"Deleted {n} objects."
+            }
+        },
+        connect: {
+            annotation: {
+                point: "Punto conectado a una ruta.", //"Connected a way to a point.",
+                vertex: "Ruta conectada a otra.", //"Connected a way to another.",
+                line: "Ruta conectada a una línea.", //"Connected a way to a line.",
+                area: "Ruta conectada a una zona." //"Connected a way to an area."
+            }
+        },
+        disconnect: {
+            title: "Desconectar", //"Disconnect",
+            description: "Desconectar estas rutas.", //"Disconnect these ways from each other.",
+            key: "D",
+            annotation: "Rutas desconectadas." //"Disconnected ways."
+        },
+        merge: {
+            title: "Combinar", //"Merge",
+            description: "Combinar estas líneas.", //"Merge these lines.",
+            key: "C",
+            annotation: "{n} líneas combinadas" //"Merged {n} lines."
+        },
+        move: {
+            title: "Mover", //"Move",
+            description: "Mover esto a una ubicación diferente.", //"Move this to a different location.",
+            key: "M",
+            annotation: {
+                point: "Punto movido", //"Moved a point.",
+                vertex: "Nodo movido a una ruta", //"Moved a node in a way.",
+                line: "Línea movida", //"Moved a line.",
+                area: "Zona movida" //"Moved an area."
+            }
+        },
+        reverse: {
+            title: "Invertir", //"Reverse",
+            description: "Hacer que esta línea vaya en sentido inverso.", //"Make this line go in the opposite direction.",
+            key: "I", //"V",
+            annotation: "Línea invertida" //"Reversed a line."
+        },
+        split: {
+            title: "Dividir", //"Split",
+            description: "Dividir en dos rutas en éste punto.", //"Split this into two ways at this point.",
+            key: "D", //"X",
+            annotation: "Dividir una ruta." //"Split a way."
+        }
+    },
+
+    validations: {
+        untagged_point: "Punto sin etiquetar que no es parte de una línea ni zona.", //"Untagged point which is not part of a line or area",
+        untagged_line: "Línea sin etiquetar", //"Untagged line",
+        untagged_area: "Zona sin etiquetar", //"Untagged area",
+        tag_suggests_area: "La etiqueta {tag} sugiere que esta línea debería ser una zona, pero no lo es.", //"The tag {tag} suggests line should be area, but it is not an area",
+        deprecated_tags: "Etiquetas obsoletas: {tags}" //"Deprecated tags: {tags}"
+    },
+
+    save: "Guardar", //"Save",
+    unsaved_changes: "Tienes cambios sin guardar", //"You have unsaved changes",
+    save_help: "Guardar los cambios en OpenStreetMap haciéndolos visibles a otros usuarios", //"Save changes to OpenStreetMap, making them visible to other users",
+    no_changes: "No tienes cambios sin guardar", //"You don't have any changes to save.",
+    save_error: "Ha ocurrido un error tratando de guardar", //"An error occurred while trying to save",
+    uploading_changes: "Subiendo cambios a OpenStreetMap", //"Uploading changes to OpenStreetMap.",
+    just_edited: "Acabas de editar OpenStreetMap!", //"You Just Edited OpenStreetMap!",
+    okay: "OK", //"Okay",
+
+    "zoom-in": "Aumentar", // "Zoom In",
+    "zoom-out": "Alejar", //"Zoom Out",
+
+    nothing_to_undo: "Nada para deshacer", //"Nothing to undo.",
+    nothing_to_redo: "Nada para rehacer", //"Nothing to redo.",
+
+    browser_notice: "Este editor soporta Firefox, Chrome, Safari, Opera e Internet Explorer 9 o superior. Por favor actualiza tu navegador o utiliza Potlatch 2 para editar el mapa.", //"This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.",
+
+    inspector: {
+        no_documentation_combination: "No hay documentación disponible para esta combinación de etiquetas", //"This is no documentation available for this tag combination",
+        no_documentation_key: "No hay documentación disponible para esta tecla", //"This is no documentation available for this key",
+        new_tag: "Nueve etiqueta" //"New Tag"
+    },
+
+    view_on_osm: "Ver en OSM", //"View on OSM",
+
+    zoom_in_edit: "acercar para editar el mapa", //"zoom in to edit the map",
+
+    edit_tags: "Editar etiquetas", //"Edit tags",
+
+    geocoder: {
+        title: "Encontrar un lugar", //"Find A Place",
+        placeholder: "encontrar un lugar", //"find a place",
+        no_results: "No se pudo encontrar el lugar llamado '{name}'" //"Couldn't locate a place named '{name}'"
+    },
+
+    description: "Descripción", //"Description",
+
+    logout: "cerrar sesión", //"logout",
+
+    report_a_bug: "reportar un error", //"report a bug",
+
+    layerswitcher: {
+        title: "Fondo", //"Background",
+        description: "Configuración de fondo", //"Background Settings",
+        percent_brightness: "{opacity}% brillo", //"{opacity}% brightness",
+        fix_misalignment: "Arreglar alineamiento", //"Fix misalignment",
+        reset: "reiniciar" //"reset"
+    },
+
+    contributors: {
+        list: "Viendo las contribuciones de usuarios {users}", //"Viewing contributions by {users}",
+        truncated_list: "Viendo las contribuciones de {users} y {count} más" //"Viewing contributions by {users} and {count} others"
+    },
+
+    source_switch: {
+        live: "en vivo", //"live",
+        dev: "dev"
+    }
+};

From 19ccada7847d91e4bdef69cdeed71a08c8f53022 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Iv=C3=A1n=20Perdomo?= 
Date: Mon, 11 Feb 2013 08:44:56 +0100
Subject: [PATCH 232/332] Removes extra comma

---
 locale/es.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/locale/es.js b/locale/es.js
index c2296b745..47fb7427d 100644
--- a/locale/es.js
+++ b/locale/es.js
@@ -4,7 +4,7 @@ locale.es = {
             title: "Zona", //"Area",
             description: "Agregar parques, edificios, lagos u otras zonas en el mapa", //"Add parks, buildings, lakes, or other areas to the map.",
             tail: "Hacer click en el mapa para empezar a dibujar una zona como un parque, lago o edificio", //"Click on the map to start drawing an area, like a park, lake, or building.",
-            key: "Z", //"A"
+            key: "Z" //"A"
         },
         add_line: {
             title: "Línea", //"Line",

From ae0de28c070a8a0cfc0c3d71793326854b7206bd Mon Sep 17 00:00:00 2001
From: Martin Raifer 
Date: Mon, 11 Feb 2013 11:58:57 +0100
Subject: [PATCH 233/332] Update locale/de.js

rephrased (the very uncommon) usage of "entmarkiert" to "ohne Attribute".
---
 locale/de.js | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/locale/de.js b/locale/de.js
index 3c24f8f46..a1ad84e9e 100644
--- a/locale/de.js
+++ b/locale/de.js
@@ -133,11 +133,11 @@ locale.de = {
     },
 
     validations: {
-        untagged_point: "Punkt, der kein Teil einer Linie oder Fläche ist, entmarkiert",
-        untagged_line: "Linie entmarkiert",
-        untagged_area: "Fläche entmarkiert",
-        tag_suggests_area: "Die Markierung {tag} suggeriert eine Fläche, ist aber keine Fläche",
-        deprecated_tags: "Abgelehnte Markierungen: {tags}"
+        untagged_point: "Punkt ohne Attribute, der kein Teil einer Linie oder Fläche ist",
+        untagged_line: "Linie ohne Attribute",
+        untagged_area: "Fläche ohne Attribute",
+        tag_suggests_area: "Das Attribut {tag} suggeriert eine Fläche, ist aber keine Fläche",
+        deprecated_tags: "Veralterte Attribute: {tags}"
     },
 
     save: "Speichern",

From c0a1eb9e1922e0abea3867bd10b90812cafa8249 Mon Sep 17 00:00:00 2001
From: Martin Raifer 
Date: Mon, 11 Feb 2013 12:19:09 +0100
Subject: [PATCH 234/332] Update locale/de.js

more "Markierung" -> "Attribut"
(see usage in the OSM wiki: http://wiki.openstreetmap.org/wiki/DE:Tagging)
---
 locale/de.js | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/locale/de.js b/locale/de.js
index a1ad84e9e..4ff4d3660 100644
--- a/locale/de.js
+++ b/locale/de.js
@@ -157,16 +157,16 @@ locale.de = {
     browser_notice: "Dieser Editor wird in Firefox, Chrome, Safari, Opera, und Internet Explorer 9 und höher unterstzützt. Bitte aktualisieren Sie Ihren Browser oder nutzen Sie Potlatch 2, um die Karte zu modifizieren.",
 
     inspector: {
-        no_documentation_combination:  "Es ist keine Dokumentation verfügbar für diese Markierungskombination.",
-        no_documentation_key: "Es ist keine Dokumentation verfügbar für dieses Schlüsselwort",
-        new_tag: "Neue Markierung"
+        no_documentation_combination:  "Für dieses Attribut ist keine Dokumentation verfügbar.",
+        no_documentation_key: "Für dises Schlüsselwort ist keine Dokumentation verfügbar",
+        new_tag: "Neues Attribut"
     },
 
     view_on_osm: "Bei OSM anschauen",
 
     zoom_in_edit: "Hineinzoomen, um die Karte zu editieren",
 
-    edit_tags: "Markierungen bearbeiten",
+    edit_tags: "Attribute bearbeiten",
 
     geocoder: {
         "find_location": "Finde einen Ort",

From 1bfdb0715ff12ffd92c097a20807b36e68a21fff Mon Sep 17 00:00:00 2001
From: Martin Raifer 
Date: Mon, 11 Feb 2013 12:21:47 +0100
Subject: [PATCH 235/332] Update locale/de.js

updated geocoder localization
---
 locale/de.js | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/locale/de.js b/locale/de.js
index 4ff4d3660..40a916bf7 100644
--- a/locale/de.js
+++ b/locale/de.js
@@ -169,8 +169,9 @@ locale.de = {
     edit_tags: "Attribute bearbeiten",
 
     geocoder: {
-        "find_location": "Finde einen Ort",
-        "find_a_place": "Finde einen Platz"
+        title: "Suche einen Ort",
+        placeholder: "suche einen Ort",
+        no_results: "Der Ort '{name}' konnte nicht gefunden werden"
     },
 
     description: "Beschreibung",

From ec8cd00a801eb9f376e861701728c3c3a6d44cc8 Mon Sep 17 00:00:00 2001
From: Martin Raifer 
Date: Mon, 11 Feb 2013 12:26:09 +0100
Subject: [PATCH 236/332] Update locale/de.js

Rephrased more unusual formulations.
---
 locale/de.js | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/locale/de.js b/locale/de.js
index 40a916bf7..0c7231f25 100644
--- a/locale/de.js
+++ b/locale/de.js
@@ -141,12 +141,12 @@ locale.de = {
     },
 
     save: "Speichern",
-    save_help: "Speichere Änderungen zu OpenStreetMap, so dass sie für andere Nutzer sichtbar werden",
+    save_help: "Speichere Änderungen auf OpenStreetMap, um diese für andere Nutzer sichtbar zu machen",
     no_changes: "Sie haben keine Änderungen zum Speichern.",
-    save_error: "Es ist ein Fehler aufgetreten beim Versuch des Speicherns",
-    uploading_changes: "Lade Änderungen zu OpenStreetMap.",
+    save_error: "Beim Speichern ist ein Fehler aufgetreten",
+    uploading_changes: "Änderungen werden zu OpenStreetMap hochgeladen.",
     just_edited: "Sie haben gerade OpenStreetMap editiert!",
-    okay: "Okay",
+    okay: "OK",
 
     "zoom-in": "Hineinzoomen",
     "zoom-out": "Herauszoomen",
@@ -154,7 +154,7 @@ locale.de = {
     nothing_to_undo: "Nichts zum Rückgängigmachen.",
     nothing_to_redo: "Nichts zum Wiederherstellen.",
 
-    browser_notice: "Dieser Editor wird in Firefox, Chrome, Safari, Opera, und Internet Explorer 9 und höher unterstzützt. Bitte aktualisieren Sie Ihren Browser oder nutzen Sie Potlatch 2, um die Karte zu modifizieren.",
+    browser_notice: "Dieser Editor wird von Firefox, Chrome, Safari, Opera, und Internet Explorer (Version 9 und höher) unterstzützt. Bitte aktualisieren Sie Ihren Browser oder nutzen Sie Potlatch 2, um die Karte zu modifizieren.",
 
     inspector: {
         no_documentation_combination:  "Für dieses Attribut ist keine Dokumentation verfügbar.",
@@ -162,9 +162,9 @@ locale.de = {
         new_tag: "Neues Attribut"
     },
 
-    view_on_osm: "Bei OSM anschauen",
+    view_on_osm: "Auf OSM anschauen",
 
-    zoom_in_edit: "Hineinzoomen, um die Karte zu editieren",
+    zoom_in_edit: "Hineinzoomen, um die Karte zu bearbeiten",
 
     edit_tags: "Attribute bearbeiten",
 

From 9b784cb76e7826f853383b38ae9b46013935f160 Mon Sep 17 00:00:00 2001
From: Tom MacWright 
Date: Mon, 11 Feb 2013 10:00:46 -0500
Subject: [PATCH 237/332] Include spanish translation in index

---
 index.html | 1 +
 1 file changed, 1 insertion(+)

diff --git a/index.html b/index.html
index 34906c477..5d43a890c 100644
--- a/index.html
+++ b/index.html
@@ -148,6 +148,7 @@
         
         
         
+        
 
     
     

From 38b327fefeb436ad06444c42abe3620c2f391a44 Mon Sep 17 00:00:00 2001
From: Tom MacWright 
Date: Mon, 11 Feb 2013 10:42:49 -0500
Subject: [PATCH 238/332] jshint fixes, fix global leak

---
 js/id/behavior/add_way.js | 2 +-
 js/id/core/history.js     | 2 +-
 js/id/svg.js              | 2 +-
 js/id/ui/source_switch.js | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/js/id/behavior/add_way.js b/js/id/behavior/add_way.js
index 782d7f183..19f9437cd 100644
--- a/js/id/behavior/add_way.js
+++ b/js/id/behavior/add_way.js
@@ -1,5 +1,5 @@
 iD.behavior.AddWay = function(context) {
-    var event = d3.dispatch('start', 'startFromWay', 'startFromNode')
+    var event = d3.dispatch('start', 'startFromWay', 'startFromNode'),
         draw = iD.behavior.Draw(context);
 
     var addWay = function(surface) {
diff --git a/js/id/core/history.js b/js/id/core/history.js
index caefc45d2..1bedaeb60 100644
--- a/js/id/core/history.js
+++ b/js/id/core/history.js
@@ -187,7 +187,7 @@ iD.History = function(context) {
                     annotation: i.annotation,
                     imagery_used: i.imagery_used,
                     entities: i.graph.entities
-                }
+                };
             }));
 
             context.storage(getKey('history'), json);
diff --git a/js/id/svg.js b/js/id/svg.js
index bbd22fb78..dda91ec99 100644
--- a/js/id/svg.js
+++ b/js/id/svg.js
@@ -41,6 +41,6 @@ iD.svg = {
                 }
             });
             return tags;
-        }
+        };
     }
 };
diff --git a/js/id/ui/source_switch.js b/js/id/ui/source_switch.js
index 1b1bb9530..624e26af4 100644
--- a/js/id/ui/source_switch.js
+++ b/js/id/ui/source_switch.js
@@ -21,5 +21,5 @@ iD.ui.SourceSwitch = function(context) {
             .text(t('source_switch.live'))
             .classed('live', true)
             .on('click', click);
-    }
+    };
 };

From 97ab739f1be4a82e84b2e998804e6cb12f288592 Mon Sep 17 00:00:00 2001
From: Ansis Brammanis 
Date: Mon, 11 Feb 2013 11:00:45 -0500
Subject: [PATCH 239/332] Update bound data for all elements of points,
 vertices

---
 js/id/svg/points.js   | 1 +
 js/id/svg/vertices.js | 3 +--
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/js/id/svg/points.js b/js/id/svg/points.js
index a7fdb10fe..838faf2bb 100644
--- a/js/id/svg/points.js
+++ b/js/id/svg/points.js
@@ -52,6 +52,7 @@ iD.svg.Points = function(projection) {
         // sets the data (point entity) on the element
         groups.select('image')
             .attr('xlink:href', imageHref);
+        groups.select('.shadow, .stroke');
 
         groups.exit()
             .remove();
diff --git a/js/id/svg/vertices.js b/js/id/svg/vertices.js
index e9769a374..339c0752a 100644
--- a/js/id/svg/vertices.js
+++ b/js/id/svg/vertices.js
@@ -40,8 +40,7 @@ iD.svg.Vertices = function(projection) {
 
         // Selecting the following implicitly
         // sets the data (vertix entity) on the elements
-        groups.select('circle.fill');
-        groups.select('circle.stroke');
+        groups.select('circle.fill, circle.stroke, circle.shadow');
 
         groups.exit()
             .remove();

From d34863bfc29f7bafac3cf1540a33541830733cb6 Mon Sep 17 00:00:00 2001
From: Tom MacWright 
Date: Mon, 11 Feb 2013 11:02:58 -0500
Subject: [PATCH 240/332] Toggle ui elements gracefully. Refs #449

---
 index.html                |  1 +
 js/id/ui/geocoder.js      | 16 +++++++++++-----
 js/id/ui/inspector.js     |  4 +++-
 js/id/ui/layerswitcher.js | 10 +++++++---
 js/id/ui/toggle.js        | 15 +++++++++++++++
 5 files changed, 37 insertions(+), 9 deletions(-)
 create mode 100644 js/id/ui/toggle.js

diff --git a/index.html b/index.html
index 5d43a890c..08cd1c9db 100644
--- a/index.html
+++ b/index.html
@@ -78,6 +78,7 @@
         
         
         
+        
 
         
         
diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js
index 04f68f1ba..0643c41d1 100644
--- a/js/id/ui/geocoder.js
+++ b/js/id/ui/geocoder.js
@@ -6,6 +6,9 @@ iD.ui.geocoder = function(context) {
     }
 
     function geocoder(selection) {
+
+        var shown = false;
+
         function keydown() {
             if (d3.event.keyCode !== 13) return;
             d3.event.preventDefault();
@@ -59,11 +62,14 @@ iD.ui.geocoder = function(context) {
         function toggle() { setVisible(gcForm.classed('hide')); }
 
         function setVisible(show) {
-            button.classed('active', show);
-            gcForm.classed('hide', !show);
-            if (!show) resultsList.classed('hide', !show);
-            if (show) inputNode.node().focus();
-            else inputNode.node().blur();
+            if (show !== shown) {
+                button.classed('active', show);
+                gcForm.call(iD.ui.toggle(show));
+                if (!show) resultsList.classed('hide', !show);
+                if (show) inputNode.node().focus();
+                else inputNode.node().blur();
+                shown = show;
+            }
         }
 
         var button = selection.append('button')
diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js
index fd29e08a5..836f73cff 100644
--- a/js/id/ui/inspector.js
+++ b/js/id/ui/inspector.js
@@ -9,7 +9,7 @@ iD.ui.inspector = function() {
         var entity = selection.datum();
 
         var inspector = selection.append('div')
-            .attr('class','inspector content');
+            .attr('class','inspector content hide');
 
         inspector.append('div')
             .attr('class', 'head inspector-inner fillL')
@@ -42,6 +42,8 @@ iD.ui.inspector = function() {
         inspectorbody.append('div')
             .attr('class', 'inspector-buttons pad1 fillD')
             .call(drawButtons);
+
+        inspector.call(iD.ui.toggle(true));
     }
 
     function drawHead(selection) {
diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js
index 2007b45fa..9ae4f3fbe 100644
--- a/js/id/ui/layerswitcher.js
+++ b/js/id/ui/layerswitcher.js
@@ -15,7 +15,8 @@ iD.ui.layerswitcher = function(context) {
     function layerswitcher(selection) {
 
         var content = selection
-            .append('div').attr('class', 'content fillD map-overlay hide');
+            .append('div').attr('class', 'content fillD map-overlay hide'),
+            shown = false;
 
         var button = selection
             .append('button')
@@ -30,8 +31,11 @@ iD.ui.layerswitcher = function(context) {
         function toggle() { setVisible(content.classed('hide')); }
 
         function setVisible(show) {
-            button.classed('active', show);
-            content.classed('hide', !show);
+            if (show !== shown) {
+                button.classed('active', show);
+                content.call(iD.ui.toggle(show));
+                shown = show;
+            }
         }
 
         function clickoutside(selection) {
diff --git a/js/id/ui/toggle.js b/js/id/ui/toggle.js
new file mode 100644
index 000000000..96c6af645
--- /dev/null
+++ b/js/id/ui/toggle.js
@@ -0,0 +1,15 @@
+// toggles the visibility of ui elements, using a combination of the
+// hide class, which sets display=none, and a d3 transition for opacity.
+// this will cause blinking when called repeatedly, so check that the
+// value actually changes between calls.
+iD.ui.toggle = function(show) {
+    return function(selection) {
+        selection.style('opacity', show ? 0 : 1)
+            .classed('hide', false)
+            .transition()
+            .style('opacity', show ? 1 : 0)
+            .each('end', function() {
+                d3.select(this).classed('hide', !show);
+            });
+    };
+};

From f1237a952059e2469a744da672a982a13837b70b Mon Sep 17 00:00:00 2001
From: Tom MacWright 
Date: Mon, 11 Feb 2013 11:10:44 -0500
Subject: [PATCH 241/332] Add toggle to tests

---
 test/index.html | 1 +
 1 file changed, 1 insertion(+)

diff --git a/test/index.html b/test/index.html
index 056a0aff8..fe81a4ec5 100644
--- a/test/index.html
+++ b/test/index.html
@@ -72,6 +72,7 @@
     
     
     
+    
 
     
     

From fe9cf436e589897635e0f13e62e61f4996655634 Mon Sep 17 00:00:00 2001
From: Ansis Brammanis 
Date: Mon, 11 Feb 2013 11:19:10 -0500
Subject: [PATCH 242/332] Update translation key

---
 locale/lv.js | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/locale/lv.js b/locale/lv.js
index b7ba5504f..f341f7fed 100644
--- a/locale/lv.js
+++ b/locale/lv.js
@@ -179,8 +179,6 @@ locale.lv = {
 
     logout: "atslēgties",
 
-    live: "live",
-    dev: "dev",
     report_a_bug: "ziņot par kļūdu",
 
     layerswitcher: {
@@ -194,5 +192,10 @@ locale.lv = {
     contributors: {
         list: "{users} papildinājumi redzami",
         truncated_list: "{users} un {count} citu papildinājumi redzami"
+    },
+
+    source_switch: {
+        live: "live",
+        dev: "dev"
     }
 };

From 0e871bb9574da0349d5c81cfe559f6d400a83ed8 Mon Sep 17 00:00:00 2001
From: Ansis Brammanis 
Date: Mon, 11 Feb 2013 11:53:26 -0500
Subject: [PATCH 243/332] Fix shift-click on points in ff

---
 css/map.css | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/css/map.css b/css/map.css
index 73da09b55..885c8195c 100644
--- a/css/map.css
+++ b/css/map.css
@@ -32,6 +32,10 @@ g.point circle {
     fill:#fff;
 }
 
+g.point image {
+  pointer-events: none;
+}
+
 g.point .shadow {
     fill: none;
     pointer-events: all;

From fc6cb352d855be62958a661b57974b83adfd9f4f Mon Sep 17 00:00:00 2001
From: Ansis Brammanis 
Date: Mon, 11 Feb 2013 12:12:18 -0500
Subject: [PATCH 244/332] use compatible mouse position properties

---
 js/id/behavior/lasso.js  | 4 ++--
 js/id/behavior/select.js | 6 +++---
 js/id/modes/select.js    | 4 ++--
 3 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/js/id/behavior/lasso.js b/js/id/behavior/lasso.js
index e99b1bf14..0a26f311d 100644
--- a/js/id/behavior/lasso.js
+++ b/js/id/behavior/lasso.js
@@ -10,7 +10,7 @@ iD.behavior.Lasso = function(context) {
         function mousedown() {
             if (d3.event.shiftKey === true) {
 
-                pos = [d3.event.x, d3.event.y];
+                pos = [d3.event.clientX, d3.event.clientY];
 
                 lasso = iD.ui.lasso().a(d3.mouse(context.surface().node()));
 
@@ -48,7 +48,7 @@ iD.behavior.Lasso = function(context) {
                 .on('mousemove.lasso', null)
                 .on('mouseup.lasso', null);
 
-            if (d3.event.x !== pos[0] || d3.event.y !== pos[1]) {
+            if (d3.event.clientX !== pos[0] || d3.event.clientY !== pos[1]) {
                 var selected = context.graph().intersects(extent);
 
                 if (selected.length) {
diff --git a/js/id/behavior/select.js b/js/id/behavior/select.js
index 4d0bef2de..7369af97c 100644
--- a/js/id/behavior/select.js
+++ b/js/id/behavior/select.js
@@ -22,7 +22,7 @@ iD.behavior.Select = function(context) {
 
         function mousedown() {
             var datum = d3.event.target.__data__;
-            pos = [d3.event.x, d3.event.y];
+            pos = [d3.event.clientX, d3.event.clientY];
             if (datum instanceof iD.Entity || (datum && datum.type === 'midpoint')) {
                 selection
                     .on('mousemove.select', mousemove)
@@ -51,7 +51,7 @@ iD.behavior.Select = function(context) {
 
         // allow mousemoves to cancel the click
         function mousemove() {
-            if (iD.geo.dist([d3.event.x, d3.event.y], pos) > 4) {
+            if (iD.geo.dist([d3.event.clientX, d3.event.clientY], pos) > 4) {
                 window.clearTimeout(timeout);
                 timeout = null;
             }
@@ -59,7 +59,7 @@ iD.behavior.Select = function(context) {
 
         function mouseup() {
             selection.on('mousemove.select', null);
-            if (pos && d3.event.x === pos[0] && d3.event.y === pos[1] &&
+            if (pos && d3.event.clientX === pos[0] && d3.event.clientY === pos[1] &&
                 !(d3.event.target.__data__ instanceof iD.Entity)) {
                 context.enter(iD.modes.Browse(context));
             }
diff --git a/js/id/modes/select.js b/js/id/modes/select.js
index 5732b58df..9b12c8134 100644
--- a/js/id/modes/select.js
+++ b/js/id/modes/select.js
@@ -76,10 +76,10 @@ iD.modes.Select = function(context, selection, initial) {
                 var inspector_size = context.container().select('.inspector-wrap').size(),
                     map_size = context.map().size(),
                     offset = 50,
-                    shift_left = d3.event.x - map_size[0] + inspector_size[0] + offset,
+                    shift_left = d3.event.clientX - map_size[0] + inspector_size[0] + offset,
                     center = (map_size[0] / 2) + shift_left + offset;
 
-                if (shift_left > 0 && inspector_size[1] > d3.event.y) {
+                if (shift_left > 0 && inspector_size[1] > d3.event.clientY) {
                     context.map().centerEase(context.projection.invert([center, map_size[1]/2]));
                 }
             }

From fe32ca9d03ca27ed0b7d5d22400a3b374470f700 Mon Sep 17 00:00:00 2001
From: Ansis Brammanis 
Date: Mon, 11 Feb 2013 12:36:21 -0500
Subject: [PATCH 245/332] Yep, detecting opera.

Goal can be achieved with two different svg styles,
but Opera only implements one, Firefox the other. Can't
apply both because Chrome implements both.
---
 css/map.css | 6 ++++++
 js/id/id.js | 2 ++
 js/id/ui.js | 2 ++
 3 files changed, 10 insertions(+)

diff --git a/css/map.css b/css/map.css
index 885c8195c..bea6edde3 100644
--- a/css/map.css
+++ b/css/map.css
@@ -648,7 +648,13 @@ text.pointlabel {
 }
 
 .pathlabel .textpath {
+  dominant-baseline: middle;
+}
+
+/* Opera doesn't support dominant-baseline */
+.opera .pathlabel .textpath {
   baseline-shift: -33%;
+  dominant-baseline: auto;
 }
 
 .pointlabel-halo,
diff --git a/js/id/id.js b/js/id/id.js
index 665e0b0a1..df986bc5f 100644
--- a/js/id/id.js
+++ b/js/id/id.js
@@ -127,6 +127,8 @@ iD.detect = function() {
         browser.support = true;
     }
 
+    browser.opera = ua.indexOf('Opera') >= 0;
+
     browser.locale = navigator.language;
 
     function nav(x) {
diff --git a/js/id/ui.js b/js/id/ui.js
index 529d8bca5..ecab88470 100644
--- a/js/id/ui.js
+++ b/js/id/ui.js
@@ -12,6 +12,8 @@ iD.ui = function(context) {
             return;
         }
 
+        if (iD.detect().opera) container.classed('opera', true);
+
         function hintprefix(x, y) {
             return '' + y + '' + '
' + x + '
'; } From d458a3707f2271bbc8ee69e37ac9233f2e052c86 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 11 Feb 2013 12:42:02 -0500 Subject: [PATCH 246/332] Add comments about opera detection --- css/map.css | 2 +- js/id/id.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/css/map.css b/css/map.css index bea6edde3..d8f8ab220 100644 --- a/css/map.css +++ b/css/map.css @@ -651,7 +651,7 @@ text.pointlabel { dominant-baseline: middle; } -/* Opera doesn't support dominant-baseline */ +/* Opera doesn't support dominant-baseline. See #715 */ .opera .pathlabel .textpath { baseline-shift: -33%; dominant-baseline: auto; diff --git a/js/id/id.js b/js/id/id.js index df986bc5f..a7d7a44ac 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -127,6 +127,7 @@ iD.detect = function() { browser.support = true; } + // Added due to incomplete svg style support. See #715 browser.opera = ua.indexOf('Opera') >= 0; browser.locale = navigator.language; From c77f23a809ee5cd14f73e5418e04b81f7d0d7fc4 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 11 Feb 2013 12:47:26 -0500 Subject: [PATCH 247/332] Added lint to locale, update a french and german with strings to translate --- locale/de.js | 17 ++++ locale/fr.js | 21 ++++- locale/lint.js | 237 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 273 insertions(+), 2 deletions(-) create mode 100644 locale/lint.js diff --git a/locale/de.js b/locale/de.js index 0c7231f25..6087c7c71 100644 --- a/locale/de.js +++ b/locale/de.js @@ -141,6 +141,8 @@ locale.de = { }, save: "Speichern", + // TODO + unsaved_changes: "You have unsaved changes", save_help: "Speichere Änderungen auf OpenStreetMap, um diese für andere Nutzer sichtbar zu machen", no_changes: "Sie haben keine Änderungen zum Speichern.", save_error: "Beim Speichern ist ein Fehler aufgetreten", @@ -176,13 +178,28 @@ locale.de = { description: "Beschreibung", + // TODO + report_a_bug: "report a bug", + logout: "Abmelden", + // TODO + contributors: { + list: "Viewing contributions by {users}", + truncated_list: "Viewing contributions by {users} and {count} others" + }, + layerswitcher: { title: "Hintergrund", description: "Hintergrundeinstellungen", percent_brightness: "{opacity}% Helligkeit", fix_misalignment: "Fehlerhafte Ausrichtung reparieren", reset: "Zurücksetzen" + }, + + // TODO + source_switch: { + live: "live", + dev: "dev" } }; diff --git a/locale/fr.js b/locale/fr.js index 904d5cb9e..eeb280beb 100644 --- a/locale/fr.js +++ b/locale/fr.js @@ -141,6 +141,8 @@ locale.fr = { }, save: "Sauvegarder", + // TODO + unsaved_changes: "You have unsaved changes", save_help: "Envoie des modifications au serveyr OpenStreetMap afin qu'elles soient visibles par les autres contributeurs.", no_changes: "Vous n'avez aucune modification à enregistrer.", save_error: "Une erreur est survenue lors de l'enregistrement des données", @@ -169,19 +171,34 @@ locale.fr = { edit_tags: "Editer les tags", geocoder: { - "find_location": "Trouver un emplacement", - "find_a_place": "Trouver un endroit" + title: "Trouver un emplacement", + placeholder: "Trouver un endroit", + // TODO + no_results: "Couldn't locate a place named '{name}'" }, description: "Description", logout: "Déconnexion", + // TODO + report_a_bug: "report a bug", + + contributors: { + list: "Viewing contributions by {users}", + truncated_list: "Viewing contributions by {users} and {count} others" + }, + layerswitcher: { title: "Fond de carte", description: "Paramètres du fond de carte", percent_brightness: "{opacity}% brightness", fix_misalignment: "Fix misalignment", reset: "reset" + }, + + source_switch: { + live: "live", + dev: "dev" } }; diff --git a/locale/lint.js b/locale/lint.js new file mode 100644 index 000000000..44c5139e7 --- /dev/null +++ b/locale/lint.js @@ -0,0 +1,237 @@ +/** + * Fragment used to represent a string fragment in the diff. + */ +var Fragment = function (string) { + this.content = string; + this.equiv = false; +}; + +/** + * Wrap in given tag or return the clean value. + */ +Fragment.prototype.toString = function (tag) { + if (this.equiv || !tag) { + return this.content; + } + else { + return '<' + tag + '>' + this.content + ''; + } +}; + +var moveToEnd = function (a, i, k) { + if (!a.equiv && (!k[i-1] || k[i-1].equiv)) { + // Find next item equiv item. + for (var j = i+1; k[j] && !k[j].equiv; j++); + if (k[j] && k[j].content === a.content) { + k[i] = k[j]; + k[j] = a; + } + } +}; + +var aggregate = function (a, i, k) { + if (!a.equiv && k[i+1] && !k[i+1].equiv) { + k[i+1].content = a.content + k[i+1].content; + delete k[i]; + } +}; + +var join = function (what, t) { + return what.map(function (a) { + if (a) return a.toString(t); + }).join(''); +}; + +var clone = function(source) { + if (typeof source === 'object' && source !== null) { + var target = Array.isArray(source) ? [] : {}; + for (var key in source) target[key] = clone(source[key]); + return target; + } + return source; +}; + +var WordDiff = { + nonWord: /(&.+?;|[\u0000-\u0040\u005B-\u0060\u007B-\u00A9\u00AB-\u00B4\u00B6-\u00B9\u00BB-\u00BF\u00D7\u00F7\u02C2-\u02C5\u02D2-\u02DF\u02E5-\u02EB\u02ED\u02EF-\u036F\u0375\u037E\u0384\u0385\u0387\u03F6\u0482-\u0489\u055A-\u055F\u0589\u058A\u0591-\u05C7\u05F3\u05F4\u0600-\u0603\u0606-\u061B\u061E\u061F\u064B-\u065E\u0660-\u066D\u0670\u06D4\u06D6-\u06E4\u06EA-\u06ED\u06F0-\u06F9\u06FD\u06FE\u0700-\u070D\u070F\u0711\u0730-\u074A\u07A6-\u07B0\u07C0-\u07C9\u07EB-\u07F3\u07F6-\u07F9\u0901-\u0903\u093C\u093E-\u094D\u0951-\u0954\u09E2\u0962-\u0970\u06E7-\u06E9\u0981-\u0983\u09BC\u09BE-\u09C4\u09C7\u09C8\u09CB-\u09CD\u09D7\u09E3\u09E6-\u09EF\u09F2-\u09FA\u0A01-\u0A03\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A66-\u0A71\u0A75\u0A81-\u0A83\u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AE2\u0AE3\u0AE6-\u0AEF\u0AF1\u0B01-\u0B03\u0B3C\u0B3E-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B62\u0B63\u0B66-\u0B70\u0B82\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0BE6-\u0BFA\u0C01-\u0C03\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C66-\u0C6F\u0C78-\u0C7F\u0C82\u0C83\u0CBC\u0CBE-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CE2\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D02\u0D03\u0D3E-\u0D44\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D62\u0D63\u0D66-\u0D75\u0D79\u0D82\u0D83\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2-\u0DF4\u0E31\u0E34-\u0E3A\u0E3F\u0E47-\u0E5B\u0EB1\u0EB4-\u0EB9\u0EBB\u0EBC\u0EC8-\u0ECD\u0ED0-\u0ED9\u0F01-\u0F3F\u0F71-\u0F87\u0F90-\u0F97\u0F99-\u0FBC\u0FBE-\u0FCC\u0FCE-\u0FD4\u102B-\u103E\u1040-\u104F\u1056-\u1059\u105E-\u1060\u1062-\u1064\u1067-\u106D\u1071-\u1074\u1082-\u108D\u108F-\u1099\u109E\u109F\u10FB\u135F-\u137C\u1390-\u1399\u166D\u166E\u1680\u169B\u169C\u16EB-\u16F0\u1712-\u1714\u1732-\u1736\u1752\u1753\u1772\u1773\u17B4-\u17D6\u17D8-\u17DB\u17DD\u17E0-\u17E9\u17F0-\u17F9\u1800-\u180E\u1810-\u1819\u18A9\u1920-\u192B\u1930-\u193B\u1940\u1944-\u194F\u19B0-\u19C0\u19C8\u19C9\u19D0-\u19D9\u19DE-\u19FF\u1A17-\u1A1B\u1A1E\u1A1F\u1B00-\u1B04\u1B34-\u1B44\u1B50-\u1B7C\u1B80-\u1B82\u1BA1-\u1BAA\u1BB0-\u1BB9\u1C24-\u1C37\u1C3B-\u1C49\u1C50-\u1C59\u1C7E\u1C7F\u1DC0-\u1DE6\u1DFE\u1DFF\u1FBD\u1FBF-\u1FC1\u1FCD-\u1FCF\u1FDD-\u1FDF\u1FED-\u1FEF\u1FFD\u1FFE\u2000-\u2064\u206A-\u2070\u2074-\u207E\u2080-\u208E\u20A0-\u20B5\u20D0-\u20F0\u2100\u2101\u2103-\u2106\u2108\u2109\u2114\u2116-\u2118\u211E-\u2123\u2125\u2127\u2129\u212E\u213A\u213B\u2140-\u2144\u214A-\u214D\u214F\u2153-\u2182\u2185-\u2188\u2190-\u23E7\u2400-\u2426\u2440-\u244A\u2460-\u269D\u26A0-\u26BC\u26C0-\u26C3\u2701-\u2704\u2706-\u2709\u270C-\u2727\u2729-\u274B\u274D\u274F-\u2752\u2756\u2758-\u275E\u2761-\u2794\u2798-\u27AF\u27B1-\u27BE\u27C0-\u27CA\u27CC\u27D0-\u2B4C\u2B50-\u2B54\u2CE5-\u2CEA\u2CF9-\u2CFF\u2DE0-\u2E2E\u2E30\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u2FFB\u3000-\u3004\u3007-\u3030\u3036-\u303A\u303D-\u303F\u3099-\u309C\u30A0\u30FB\u3190-\u319F\u31C0-\u31E3\u3200-\u321E\u3220-\u3243\u3250-\u32FE\u3300-\u33FF\u4DC0-\u4DFF\uA490-\uA4C6\uA60D-\uA60F\uA620-\uA629\uA66F-\uA673\uA67C-\uA67E\uA700-\uA716\uA720\uA721\uA789\uA78A\uA802\uA806\uA80B\uA823-\uA82B\uA874-\uA877\uA880\uA881\uA8B4-\uA8C4\uA8CE-\uA8D9\uA900-\uA909\uA926-\uA92F\uA947-\uA953\uA95F\uAA29-\uAA36\uAA43\uAA4C\uAA4D\uAA50-\uAA59\uAA5C-\uAA5F\uD800\uDB7F\uDB80\uDBFF\uDC00\uDFFF\uE000\uF8FF\uFB1E\uFB29\uFD3E\uFD3F\uFDFC\uFDFD\uFE00-\uFE19\uFE20-\uFE26\uFE30-\uFE52\uFE54-\uFE66\uFE68-\uFE6B\uFEFF\uFF01-\uFF20\uFF3B-\uFF40\uFF5B-\uFF65\uFFE0-\uFFE6\uFFE8-\uFFEE\uFFF9-\uFFFD])/, + + tokenize: function (args) { + // Split on non-word characters. + for (var type in args) { + args[type] = args[type].split(WordDiff.nonWord).filter(function (s) { + return s.length; + }); + } + + // Calculate the indexes and offsets for common suffixes and prefixes. + var i = -1, j = args.del.length, k = args.ins.length; + while (args.del[++i] === args.ins[i] && i <= j); + while (j >= i && k >= i && args.del[--j] === args.ins[--k]); + + args.prefix = args.del.slice(0, i).join(''); + args.suffix = args.del.slice(j + 1).join(''); + args.del = args.del.slice(i, ++j); + args.ins = args.ins.slice(i, ++k); + }, + + lcs: function (args) { + var matrix = []; + + for (var i = 0; i < args.del.length; i++) { + matrix[i] = []; + for (var j = 0; j < args.ins.length; j++) { + if (args.del[i] === args.ins[j]) { + matrix[i][j] = (matrix[i - 1] && matrix[i - 1][j - 1] || 0) + args.del[i].length; + } + else { + matrix[i][j] = Math.max(matrix[i][j - 1] || 0, matrix[i - 1] && matrix[i - 1][j] || 0); + } + } + } + + return matrix; + }, + + changeset: function (args, matrix) { + var result = {}; + + ['del', 'ins'].forEach(function (type) { + result[type] = args[type].map(function (a) { return new Fragment(a); }); + }); + + // Backtrack through the matrix. + for (var i = result.del.length - 1, j = result.ins.length - 1; i >= 0; i--, j--) { + if (j < 0 || result.del[i].content !== result.ins[j].content) { + if (j < 0 || (j > 0 && matrix[i - 1] && (matrix[i][j - 1] < matrix[i - 1][j]))) { + j++; + } + else { + i++; + } + } + else { + result.del[i] = result.ins[j]; + result.del[i].equiv = true; + } + } + + // Fill up gaps. + for (var i = 0; i < result.del.length; i++) { + if (result.del[i].equiv && result.del[i].content.length < 3) { + var j = result.ins.indexOf(result.del[i]); + if (result.del[i-1] && result.del[i+1] && result.ins[j-1] && result.ins[j+1] && !result.del[i-1].equiv && !result.del[i+1].equiv && !result.ins[j-1].equiv && !result.ins[j+1].equiv){ + result.del[i].equiv = false; + result.ins[j] = clone(result.del[i]); + } + } + } + + ['del', 'ins'].forEach(function (type) { + // Try to move changes to the end. + for (var i = 0; i < result[type].length; i++) + moveToEnd(result[type][i], i, result[type]); + + // Aggregate subsequent changes to minimize ins/del tags. + for (var i = 0; i < result[type].length; i++) + aggregate(result[type][i], i, result[type]); + }); + + return result; + }, + + htmlRender: function (args, result) { + var diff = { + del: args.prefix + join(result.del, 'del') + args.suffix, + ins: args.prefix + join(result.ins, 'ins') + args.suffix + }; + + return diff; + }, + + htmlDiff: function (del, ins) { + var args = { 'del': del, 'ins': ins }; + + WordDiff.tokenize(args); + var matrix = WordDiff.lcs(args); + var result = WordDiff.changeset(args, matrix); + return WordDiff.htmlRender(args, result); + }, + + render: function (args, result) { + var join = function (what, type) { + return what.map(function (a) { + if (!a) return; + if (a.equiv) return a.content; + if (type == 'del') return '\033[31;4m' + a.content + '\033[0m'; + if (type == 'ins') return '\033[32;4m' + a.content + '\033[0m'; + }).join(''); + }; + + return { + del: args.prefix + join(result.del, 'del') + args.suffix, + ins: args.prefix + join(result.ins, 'ins') + args.suffix + }; + }, + + diff: function(del, ins) { + var args = { 'del': del, 'ins': ins }; + + WordDiff.tokenize(args); + var matrix = WordDiff.lcs(args); + var result = WordDiff.changeset(args, matrix); + return WordDiff.render(args, result); + } +}; + + + + +var fs = require('fs'), _ = require('lodash'); + +function getKeys(lang, keys, prefix) { + keys = keys || []; + prefix = prefix || ''; + for (var i in lang) { + keys.push(prefix + i); + if (typeof lang[i] === 'object') { + getKeys(lang[i], keys, i + '.'); + } + } + return keys; +} + +var languages = ['de', 'en', 'es', 'fr', 'lv', 'tr']; +var langkeys = {}; + +eval(fs.readFileSync('./locale.js', 'utf8')); +for (var i = 0; i < languages.length; i++) { + eval(fs.readFileSync('./' + languages[i] + '.js', 'utf8')); + langkeys[languages[i]] = getKeys(locale[languages[i]]).sort(); +} + +// for (var i = 1; i < languages.length - 1; i++) { +// +// var changes = WordDiff.diff( +// langkeys[languages[i]].join(','), +// langkeys[languages[i + 1]].join(',')); +// +// +// console.warn('actual:' + '\n' + changes.del); +// console.warn('expected:' + '\n' + changes.ins); +// } + +var allkeys = []; +_.forEach(langkeys, function(l) { + allkeys = _.union(allkeys, l); +}); + +// console.warn('\n\n------------------------------------------'); +// console.warn('all keys ---------------------------------\n\n'); + +// console.log(allkeys.join(',')); + +_.forEach(langkeys, function(l, k) { + var missing = _.difference(allkeys, l); + if (missing.length) { + console.log('\n', k, 'is missing\n\n', _.difference(allkeys, l).join(',')); + } +}); From e8efb981506014cba4a9dac0a34a66859f0bfeae Mon Sep 17 00:00:00 2001 From: nyampire Date: Tue, 12 Feb 2013 02:53:52 +0900 Subject: [PATCH 248/332] Add Japanese Translation --- locale/ja.js | 201 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 locale/ja.js diff --git a/locale/ja.js b/locale/ja.js new file mode 100644 index 000000000..bb44b8739 --- /dev/null +++ b/locale/ja.js @@ -0,0 +1,201 @@ +locale.en = { + modes: { + add_area: { + title: "エリア", + description: "公園や建物、湖沼、をマップに追加します", + tail: "マップをクリックすると、公園や湖沼、建物などのエリアの描画が開始されます。", + key: "A" + }, + add_line: { + title: "ライン", + description: "ラインは車両用の道路や歩道、用水路を表すことができます", + tail: "マップをクリックすると、道路や歩道、流水経路の描画が始まります", + key: "L" + }, + add_point: { + title: "ポイント", + description: "レストランや記念碑、郵便ボックスはポイントで表現します", + tail: "マップをクリックするとポイントを追加できます", + key: "P" + }, + browse: { + title: "ブラウズ", + description: "マップを拡大縮小します", + key: "B" + }, + draw_area: { + tail: "クリックするとエリアにポイントを追加できます。起点となっているポイントをクリックするとエリアが作成されます" + }, + draw_line: { + tail: "クリックするとラインにポイントを追加できます。クリックすることで他のラインと接続することが可能です。ライン描画を終了するにはダブルクリックしてください" + } + }, + + operations: { + add: { + annotation: { + point: "ポイントを追加しました", + vertex: "ウェイにノードを追加しました" + } + }, + start: { + annotation: { + line: "ラインの描画を開始しました", + area: "エリアの描画を開始しました" + } + }, + 'continue': { + annotation: { + line: "ライン描画を継続中", + area: "エリア描画を継続中" + } + }, + cancel_draw: { + annotation: "描画をキャンセルしました" + }, + change_tags: { + annotation: "タグを変更しました" + }, + circularize: { + title: "円状に並べる", + description: "この地物を円状に配置します", + key: "O", + annotation: { + line: "ラインを円状にしました", + area: "エリアを円状にしました" + } + }, + orthogonalize: { + title: "角の直交化Orthogonalize", + description: "角を90度に配置します", + key: "Q", + annotation: { + line: "ラインの角を90度にしました", + area: "エリアの角を90度にしました" + } + }, + 'delete': { + title: "削除", + description: "この地物をマップから削除します", + key: "⌫", + annotation: { + point: "ポイント削除しました", + vertex: "ウェイ上のノードを削除しました", + line: "ライン削除しました", + area: "エリア削除しました", + relation: "リレーション削除しました", + multiple: "{n} 個のオブジェクトを削除しました" + } + }, + connect: { + annotation: { + point: "ウェイをポイントに接続しました", + vertex: "ウェイを他のウェイト接続しました", + line: "ウェイとラインを接続しました", + area: "ウェイとエリアを接続しました" + } + }, + disconnect: { + title: "接続解除", + description: "ウェイの接続を解除して切り離します", + key: "D", + annotation: "ウェイの接続を解除しました" + }, + merge: { + title: "結合", + description: "複数のラインを結合します", + key: "C", + annotation: "{n} 本のラインを結合しました" + }, + move: { + title: "移動", + description: "この地物を別の位置に移動させます", + key: "M", + annotation: { + point: "ポイントを移動しました", + vertex: "ウェイ上のノードを移動しました", + line: "ラインを移動しました", + area: "エリアを移動しました" + } + }, + reverse: { + title: "方向反転", + description: "ラインの向きを反転させます", + key: "V", + annotation: "ラインの向きを反転しました" + }, + split: { + title: "分割", + description: "このポイントを境目としてウェイを2つに分割します", + key: "X", + annotation: "ウェイを分割しました" + } + }, + + validations: { + untagged_point: "ポイントにタグが付与されておらず、ラインやエリアの一部でもありません", + untagged_line: "ラインにタグが付与されていません", + untagged_area: "エリアにタグが付与されていません", + tag_suggests_area: "ラインに {tag} タグが付与されています。エリアで描かれるべきです", + deprecated_tags: "タグの重複: {tags}" + }, + + save: "Save", + unsaved_changes: "変更が保存されていません", + save_help: "変更点をOpenStreetMapに保存し、他ユーザが確認できるようにします", + no_changes: "変更点がありません", + save_error: "データ保存中にエラーが発生しました", + uploading_changes: "変更点をOpenStreetMapへアップロードしています", + just_edited: "OpenStreetMap編集完了!", + okay: "OK", + + "zoom-in": "ズームイン", + "zoom-out": "ズームアウト", + + nothing_to_undo: "やり直す変更点がありません", + nothing_to_redo: "やり直した変更点がありません", + + browser_notice: "このエディタは Firefox, Chrome, Safari, Opera, および Internet Explorer 9 以上をサポートしています。ブラウザのバージョンを更新するか、Potlatch 2を使用して編集してください", + + inspector: { + no_documentation_combination: "このタグの組み合わせに関する説明文はありません", + no_documentation_key: "このキーに対する説明文はありません", + new_tag: "新規タグ" + }, + + view_on_osm: "OSMで確認", + + zoom_in_edit: "編集するにはさらに地図を拡大してください", + + edit_tags: "タグを編集", + + geocoder: { + title: "特定地点を検索", + placeholder: "地点を検索", + no_results: "'{name}' という名称の地点が見つかりません" + }, + + description: "説明", + + logout: "ログアウト", + + report_a_bug: "バグを報告", + + layerswitcher: { + title: "背景画像", + description: "背景画像の設定", + percent_brightness: "{opacity}% 輝度", + fix_misalignment: "値の調整", + reset: "設定リセット" + }, + + contributors: { + list: "{users} による編集履歴を確認", + truncated_list: "{users} とその他 {count} 人による編集履歴を表示" + }, + + source_switch: { + live: "本番サーバ", + dev: "開発サーバ" + } +}; From 16bbc88cdf68172e5aa4e4bb7088d7717371cb27 Mon Sep 17 00:00:00 2001 From: Martin Raifer Date: Mon, 11 Feb 2013 20:06:45 +0100 Subject: [PATCH 249/332] Update locale/de.js. Fixes #719 --- locale/de.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/locale/de.js b/locale/de.js index 6087c7c71..f74c1a3fc 100644 --- a/locale/de.js +++ b/locale/de.js @@ -141,10 +141,9 @@ locale.de = { }, save: "Speichern", - // TODO - unsaved_changes: "You have unsaved changes", + unsaved_changes: "Ungespeicherte Änderugen vorhanden", save_help: "Speichere Änderungen auf OpenStreetMap, um diese für andere Nutzer sichtbar zu machen", - no_changes: "Sie haben keine Änderungen zum Speichern.", + no_changes: "Keine Änderungen zum Speichern vorhanden.", save_error: "Beim Speichern ist ein Fehler aufgetreten", uploading_changes: "Änderungen werden zu OpenStreetMap hochgeladen.", just_edited: "Sie haben gerade OpenStreetMap editiert!", @@ -178,15 +177,13 @@ locale.de = { description: "Beschreibung", - // TODO - report_a_bug: "report a bug", + report_a_bug: "Programmfehler melden", logout: "Abmelden", - // TODO contributors: { - list: "Viewing contributions by {users}", - truncated_list: "Viewing contributions by {users} and {count} others" + list: "Diese Kartenansicht enthält Beiträge von:", + truncated_list: "Diese Kartenansicht enthält Beiträge von: {users} und {count} Anderen" }, layerswitcher: { @@ -197,7 +194,6 @@ locale.de = { reset: "Zurücksetzen" }, - // TODO source_switch: { live: "live", dev: "dev" From 66afcd992340216d15b8710b20dba080a7c14174 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 11 Feb 2013 11:07:29 -0800 Subject: [PATCH 250/332] Platform-specific keybindings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Specifying keybindings that use a command key (⌘ on Mac, Ctrl on Win/Linux) using the appropriate Mac binding, e.g. '⌘Z'. Use iD.ui.cmd to translate it to an appropriate key string for other platforms, e.g. 'Ctrl+Z'. --- css/app.css | 1 - index.html | 1 + js/id/ui.js | 18 +++++------------- js/id/ui/cmd.js | 22 ++++++++++++++++++++++ js/lib/d3.keybinding.js | 2 +- 5 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 js/id/ui/cmd.js diff --git a/css/app.css b/css/app.css index 355fd7262..44d7d7dbf 100644 --- a/css/app.css +++ b/css/app.css @@ -1333,7 +1333,6 @@ a.success-action { color: #222; font-size: 10px; padding: 0px 7px; - text-transform: uppercase; font-weight: bold; display: inline-block; border-radius: 2px; diff --git a/index.html b/index.html index 08cd1c9db..8c084cf77 100644 --- a/index.html +++ b/index.html @@ -60,6 +60,7 @@ + diff --git a/js/id/ui.js b/js/id/ui.js index ecab88470..7214628b4 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -200,20 +200,14 @@ iD.ui = function(context) { } } - var mod = { - 'mac': '⌘', - 'win': 'Ctrl', - 'linux': 'Ctrl' - }[iD.detect().os]; - limiter.select('#undo') .classed('disabled', !undo) - .attr('data-original-title', hintprefix(mod + ' + Z', undo || t('nothing_to_undo'))) + .attr('data-original-title', hintprefix(iD.ui.cmd('⌘Z'), undo || t('nothing_to_undo'))) .call(refreshTooltip); limiter.select('#redo') .classed('disabled', !redo) - .attr('data-original-title', hintprefix(mod + ' + ⇧ + Z', redo || t('nothing_to_redo'))) + .attr('data-original-title', hintprefix(iD.ui.cmd('⌘⇧Z'), redo || t('nothing_to_redo'))) .call(refreshTooltip); }); @@ -232,16 +226,14 @@ iD.ui = function(context) { var pa = 5; var keybinding = d3.keybinding('main') - .on('⌘+Z', function() { history.undo(); }) - .on('⌃+Z', function() { history.undo(); }) - .on('⌘+⇧+Z', function() { history.redo(); }) - .on('⌃+⇧+Z', function() { history.redo(); }) + .on(iD.ui.cmd('⌘Z'), function() { history.undo(); }) + .on(iD.ui.cmd('⌘⇧Z'), function() { history.redo(); }) .on('⌫', function() { d3.event.preventDefault(); }) .on('←', pan([pa, 0])) .on('↑', pan([0, pa])) .on('→', pan([-pa, 0])) .on('↓', pan([0, -pa])) - .on('⇧+=', function() { map.zoomIn(); }) + .on('⇧=', function() { map.zoomIn(); }) .on('+', function() { map.zoomIn(); }) .on('-', function() { map.zoomOut(); }) .on('dash', function() { map.zoomOut(); }); diff --git a/js/id/ui/cmd.js b/js/id/ui/cmd.js new file mode 100644 index 000000000..9876da236 --- /dev/null +++ b/js/id/ui/cmd.js @@ -0,0 +1,22 @@ +// Translate a MacOS key command into the appropriate Windows/Linux equivalent. +// For example, ⌘Z -> Ctrl+Z +iD.ui.cmd = function(code) { + if (iD.detect().os === 'mac') + return code; + + var modifiers = { + '⌘': 'Ctrl', + '⇧': 'Shift', + '⌥': 'Alt' + }, keys = []; + + for (var i = 0; i < code.length; i++) { + if (code[i] in modifiers) { + keys.push(modifiers[code[i]]); + } else { + keys.push(code[i]); + } + } + + return keys.join('+'); +}; diff --git a/js/lib/d3.keybinding.js b/js/lib/d3.keybinding.js index b1106c976..6aa39fb39 100644 --- a/js/lib/d3.keybinding.js +++ b/js/lib/d3.keybinding.js @@ -63,7 +63,7 @@ d3.keybinding = function(namespace) { callback: callback }; - code = code.toLowerCase().match(/(?:(?:[^+])+|\+\+|^\+$)/g); + code = code.toLowerCase().match(/(?:(?:[^+⇧⌃⌥⌘])+|[⇧⌃⌥⌘]|\+\+|^\+$)/g); for (var i = 0; i < code.length; i++) { // Normalise matching errors From 1298fe195dc5be44855161f8d1106306e3ceb996 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 11 Feb 2013 11:15:23 -0800 Subject: [PATCH 251/332] Remove keybinding coupling from iD.behavior.drag --- js/id/behavior/drag.js | 10 ---------- js/id/behavior/drag_node.js | 26 ++++++++++++++++++++------ 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/js/id/behavior/drag.js b/js/id/behavior/drag.js index cd6f607bf..012e08d34 100644 --- a/js/id/behavior/drag.js +++ b/js/id/behavior/drag.js @@ -24,7 +24,6 @@ iD.behavior.drag = function() { origin = null, selector = '', filter = null, - keybinding = d3.keybinding('drag'), event_, target; event.of = function(thiz, argumentz) { @@ -137,9 +136,6 @@ iD.behavior.drag = function() { drag.off = function(selection) { selection.on("mousedown.drag" + selector, null) .on("touchstart.drag" + selector, null); - keybinding - .on('⌘+Z', null) - .on('⌃+Z', null); }; drag.delegate = function(_) { @@ -174,11 +170,5 @@ iD.behavior.drag = function() { return drag; }; - keybinding - .on('⌘+Z', drag.cancel) - .on('⌃+Z', drag.cancel); - - d3.select(document).call(keybinding); - return d3.rebind(drag, event, "on"); }; diff --git a/js/id/behavior/drag_node.js b/js/id/behavior/drag_node.js index e3028953d..3e652794d 100644 --- a/js/id/behavior/drag_node.js +++ b/js/id/behavior/drag_node.js @@ -36,6 +36,8 @@ iD.behavior.DragNode = function(context) { } function start(entity) { + context.history() + .on('undone.drag-node', cancel); wasMidpoint = entity.type === 'midpoint'; if (wasMidpoint) { @@ -95,12 +97,7 @@ iD.behavior.DragNode = function(context) { } function end(entity) { - context.surface() - .classed('behavior-drag-node', false) - .selectAll('.active') - .classed('active', false); - - stopNudge(); + off(); var d = datum(); if (d.type === 'way') { @@ -132,6 +129,23 @@ iD.behavior.DragNode = function(context) { } } + function off() { + context.history() + .on('undone.drag_node', null); + + context.surface() + .classed('behavior-drag-node', false) + .selectAll('.active') + .classed('active', false); + + stopNudge(); + } + + function cancel() { + off(); + behavior.cancel(); + } + var behavior = iD.behavior.drag() .delegate("g.node, g.midpoint") .origin(origin) From c97084555314725805187198c1bdd64626710d98 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 11 Feb 2013 14:52:30 -0500 Subject: [PATCH 252/332] Fix #717 --- css/app.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/css/app.css b/css/app.css index 44d7d7dbf..2d0b91bc4 100644 --- a/css/app.css +++ b/css/app.css @@ -903,6 +903,10 @@ img.tile { -o-transform-origin:0 0; } +#surface { + position: static; +} + #tile-g { opacity: 0.5; } From 7401bc431c9003a486555ce5f82fa8d4d6dc421b Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 11 Feb 2013 15:38:39 -0500 Subject: [PATCH 253/332] Fade lasso in and out --- js/id/ui/lasso.js | 15 ++++++--------- js/id/ui/toggle.js | 3 ++- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/js/id/ui/lasso.js b/js/id/ui/lasso.js index 338fa2f2d..511df2a7e 100644 --- a/js/id/ui/lasso.js +++ b/js/id/ui/lasso.js @@ -8,21 +8,18 @@ iD.ui.lasso = function() { function lasso(selection) { group = selection.append('g') - .attr('class', 'lasso') - .attr('opacity', 0); + .attr('class', 'lasso hide'); box = group.append('rect') .attr('class', 'lasso-box'); - group.transition() - .style('opacity', 1); + group.call(iD.ui.toggle(true)); } // top-left function topLeft(d) { - return 'translate(' + - [Math.min(d[0][0], d[1][0]), Math.min(d[0][1], d[1][1])].join(',') + ')'; + return 'translate(' + Math.min(d[0][0], d[1][0]) + ',' + Math.min(d[0][1], d[1][1]) + ')'; } function width(d) { return Math.abs(d[0][0] - d[1][0]); } @@ -53,9 +50,9 @@ iD.ui.lasso = function() { lasso.close = function(selection) { if (group) { - group.transition() - .attr('opacity', 0) - .remove(); + group.call(iD.ui.toggle(false, function() { + d3.select(this).remove(); + })); } }; diff --git a/js/id/ui/toggle.js b/js/id/ui/toggle.js index 96c6af645..3a7500023 100644 --- a/js/id/ui/toggle.js +++ b/js/id/ui/toggle.js @@ -2,7 +2,7 @@ // hide class, which sets display=none, and a d3 transition for opacity. // this will cause blinking when called repeatedly, so check that the // value actually changes between calls. -iD.ui.toggle = function(show) { +iD.ui.toggle = function(show, callback) { return function(selection) { selection.style('opacity', show ? 0 : 1) .classed('hide', false) @@ -10,6 +10,7 @@ iD.ui.toggle = function(show) { .style('opacity', show ? 1 : 0) .each('end', function() { d3.select(this).classed('hide', !show); + if (callback) callback.apply(this); }); }; }; From 4d976013f93ca4ab54d44afccf514c1df223fdd1 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 11 Feb 2013 12:44:04 -0800 Subject: [PATCH 254/332] Connect via drag for points (fixes #725) --- css/map.css | 4 ++++ js/id/behavior/drag_node.js | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/css/map.css b/css/map.css index d8f8ab220..43db827fe 100644 --- a/css/map.css +++ b/css/map.css @@ -52,6 +52,10 @@ g.point.selected .shadow { fill-opacity: 0.7; } +g.point.active, g.point.active * { + pointer-events: none; +} + /* vertices */ g.vertex .fill { diff --git a/js/id/behavior/drag_node.js b/js/id/behavior/drag_node.js index 3e652794d..1d1afcc2c 100644 --- a/js/id/behavior/drag_node.js +++ b/js/id/behavior/drag_node.js @@ -147,7 +147,7 @@ iD.behavior.DragNode = function(context) { } var behavior = iD.behavior.drag() - .delegate("g.node, g.midpoint") + .delegate("g.node, g.point, g.midpoint") .origin(origin) .on('start', start) .on('move', move) From b0bd041f2ff934066ba7bed02f0433a55c82d9a3 Mon Sep 17 00:00:00 2001 From: nyampire Date: Tue, 12 Feb 2013 06:00:07 +0900 Subject: [PATCH 255/332] Fix minor expression --- locale/ja.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/locale/ja.js b/locale/ja.js index bb44b8739..3f6455554 100644 --- a/locale/ja.js +++ b/locale/ja.js @@ -24,10 +24,10 @@ locale.en = { key: "B" }, draw_area: { - tail: "クリックするとエリアにポイントを追加できます。起点となっているポイントをクリックするとエリアが作成されます" + tail: "クリックするとエリア上にポイントを追加できます。起点となっているポイントをクリックするとエリアが作成されます" }, draw_line: { - tail: "クリックするとラインにポイントを追加できます。クリックすることで他のラインと接続することが可能です。ライン描画を終了するにはダブルクリックしてください" + tail: "クリックするとライン上にポイントを追加できます。クリックすることで他のラインと接続することが可能です。ライン描画を終了するにはダブルクリックしてください" } }, @@ -183,9 +183,9 @@ locale.en = { layerswitcher: { title: "背景画像", - description: "背景画像の設定", + description: "背景画像設定", percent_brightness: "{opacity}% 輝度", - fix_misalignment: "値の調整", + fix_misalignment: "背景画像を移動", reset: "設定リセット" }, From 8bd62b92372774bedf481340538a60d4b1e6a7f7 Mon Sep 17 00:00:00 2001 From: Alper Dincer Date: Tue, 12 Feb 2013 11:09:31 +0200 Subject: [PATCH 256/332] Finished translation to Turkish "Way" is translated to "Taraf", but there should be a better translation. I'm still looking for a better word and change them when I find it. --- locale/tr.js | 154 +++++++++++++++++++++++++-------------------------- 1 file changed, 77 insertions(+), 77 deletions(-) diff --git a/locale/tr.js b/locale/tr.js index 1315eb05a..904bccee6 100644 --- a/locale/tr.js +++ b/locale/tr.js @@ -57,145 +57,145 @@ locale.tr = { annotation: "Etiketler değiştirildi." }, circularize: { - title: "Circularize", - description: "Make this round.", + title: "Daireleştir", + description: "Yuvarlak hale getir", key: "O", annotation: { - line: "Made a line circular.", - area: "Made an area circular." + line: "Çizgiyi daireleştirin.", + area: "Alanı daireleştirin." } }, orthogonalize: { - title: "Orthogonalize", - description: "Square these corners.", + title: "Doğrultmak", + description: "Köşeleri doğrultun.", key: "Q", annotation: { - line: "Squared the corners of a line.", - area: "Squared the corners of an area." + line: "Çizginin köşeleri doğrultuldu.", + area: "Alanın köşeleri doğrultuldu." } }, 'delete': { - title: "Delete", - description: "Remove this from the map.", + title: "Sil", + description: "Haritan bunu sil.", key: "⌫", annotation: { - point: "Deleted a point.", - vertex: "Deleted a node from a way.", - line: "Deleted a line.", - area: "Deleted an area.", - relation: "Deleted a relation.", - multiple: "Deleted {n} objects." + point: "Bir nokta silindi.", + vertex: "Yoldan bir nod silindi.", + line: "Bir çizgi silindi.", + area: "Bir alan silindi.", + relation: "Bir ilişki silindi.", + multiple: "{n} adet obje silindi." } }, connect: { annotation: { - point: "Connected a way to a point.", - vertex: "Connected a way to another.", - line: "Connected a way to a line.", - area: "Connected a way to an area." + point: "Taraf bir noktaya bağlandı.", + vertex: "Bir taraf diğerine bağlandı.", + line: "Taraf bir çizgiye bağlandı.", + area: "Taraf bir alana bağlandı." } }, disconnect: { - title: "Disconnect", - description: "Disconnect these ways from each other.", + title: "Birbirinden Ayır", + description: "Her iki tarafı da ayır.", key: "D", - annotation: "Disconnected ways." + annotation: "Taraflar birbirinden ayrıldı." }, merge: { - title: "Merge", - description: "Merge these lines.", + title: "Birleştir", + description: "Bu çizgileri birleştir.", key: "C", - annotation: "Merged {n} lines." + annotation: "{n} adet çizgi birleştirildi." }, move: { - title: "Move", - description: "Move this to a different location.", + title: "Taşı", + description: "Bunu farklı bir konuma taşı.", key: "M", annotation: { - point: "Moved a point.", - vertex: "Moved a node in a way.", - line: "Moved a line.", - area: "Moved an area." + point: "Bir nokta taşındı.", + vertex: "Yoldan bir nokta taşındı.", + line: "Bir çizgi taşındı.", + area: "Bir alan taşındı." } }, reverse: { - title: "Reverse", - description: "Make this line go in the opposite direction.", + title: "Ters çevir", + description: "Bu çizgiyi ters yönde çevir.", key: "V", - annotation: "Reversed a line." + annotation: "Çizgi ters çevrildi." }, split: { - title: "Split", - description: "Split this into two ways at this point.", + title: "Ayır", + description: "Bu yolu bu noktadan ikiye ayır.", key: "X", - annotation: "Split a way." + annotation: "Yolu ayır." } }, validations: { - untagged_point: "Untagged point which is not part of a line or area", - untagged_line: "Untagged line", - untagged_area: "Untagged area", - tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area", - deprecated_tags: "Deprecated tags: {tags}" + untagged_point: "Herhangi bir çizgi ya da alana bağlantısı olmayan ve etiketlenmemiş bir nokta.", + untagged_line: "Etiketlenmemiş çizgi", + untagged_area: "Etiketlenmemiş alan", + tag_suggests_area: "{tag} etiketi buranın alan olmasını tavsiye ediyor ama alan değil.", + deprecated_tags: "Kullanımdan kaldırılmış etiket : {tags}" }, - save: "Save", - unsaved_changes: "You have unsaved changes", - save_help: "Save changes to OpenStreetMap, making them visible to other users", - no_changes: "You don't have any changes to save.", - save_error: "An error occurred while trying to save", - uploading_changes: "Uploading changes to OpenStreetMap.", - just_edited: "You Just Edited OpenStreetMap!", - okay: "Okay", + save: "Kaydet", + unsaved_changes: "Kaydedilmemiş değişiklikleriniz var", + save_help: "Diğer kullanıcıların yaptığınız değişiklikleri görmesi için OpenStreetMap'e kaydediniz", + no_changes: "Kaydedecek hiçbir değişikliğiniz yok", + save_error: "Kaydederken bir hata oluştu", + uploading_changes: "Değişiklikleriniz OpenStreetMap'e gönderiliyor.", + just_edited: "Şu an OpenStreetMap'de bir değişiklik yaptınız!", + okay: "Tamam", - "zoom-in": "Zoom In", - "zoom-out": "Zoom Out", + "zoom-in": "Yaklaş", + "zoom-out": "Uzaklaş", - nothing_to_undo: "Nothing to undo.", - nothing_to_redo: "Nothing to redo.", + nothing_to_undo: "Geri alınacak birşey yok.", + nothing_to_redo: "Tekrar yapılacak birşey yok.", - browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", + browser_notice: "Bu editör sadece Firefox, Chrome, Safari, Opera ile Internet Explorer 9 ve üstü tarayıcılarda çalışmaktadır. Lütfen tarayınıcı güncelleyin ya da Potlatch 2'yi kullanarak haritada güncelleme yapınız.", inspector: { - no_documentation_combination: "This is no documentation available for this tag combination", - no_documentation_key: "This is no documentation available for this key", - new_tag: "New Tag" + no_documentation_combination: "Bu etiket kombinasyonu için dökümantasyon bulunmamaktadır.", + no_documentation_key: "Bu anahtar için dökümantasyon bulunmamaktadır.", + new_tag: "Yeni Etiket" }, - view_on_osm: "View on OSM", + view_on_osm: "OSM üstünde Gör", - zoom_in_edit: "zoom in to edit the map", + zoom_in_edit: "Güncelleme yapmak için haritada yakınlaşmalısınız", - edit_tags: "Edit tags", + edit_tags: "Etiketleri güncelle", geocoder: { - title: "Find A Place", - placeholder: "find a place", - no_results: "Couldn't locate a place named '{name}'" + title: "Bir Yer Bul", + placeholder: "bir yer bul", + no_results: "'{name}' ismindeki yer bulunamadı" }, - description: "Description", + description: "Açıklama", - logout: "logout", + logout: "Çıkış", - report_a_bug: "report a bug", + report_a_bug: "Hata rapor et", layerswitcher: { - title: "Background", - description: "Background Settings", - percent_brightness: "{opacity}% brightness", - fix_misalignment: "Fix misalignment", - reset: "reset" + title: "Arkaplan", + description: "Arkaplan Ayarları", + percent_brightness: "{opacity}% parlaklık", + fix_misalignment: "Yanlış hizalamayı düzelt", + reset: "Sıfırla" }, contributors: { - list: "Viewing contributions by {users}", - truncated_list: "Viewing contributions by {users} and {count} others" + list: "{users} tarafından yapılan katkılar görünmektedir", + truncated_list: "{users} ve diğer {count} tarafından yapılan katkılar görünmektedir" }, source_switch: { - live: "live", - dev: "dev" + live: "canlı", + dev: "geliştirme" } }; From 2062fe9a67846cf019fe4586bd756bd66ebbdfbc Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Tue, 12 Feb 2013 11:29:21 -0500 Subject: [PATCH 257/332] Don't end drag when parent removed --- js/id/behavior/drag.js | 11 ++++++++--- js/id/behavior/drag_node.js | 1 + 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/js/id/behavior/drag.js b/js/id/behavior/drag.js index 012e08d34..c1d877d1c 100644 --- a/js/id/behavior/drag.js +++ b/js/id/behavior/drag.js @@ -24,7 +24,7 @@ iD.behavior.drag = function() { origin = null, selector = '', filter = null, - event_, target; + event_, target, surface; event.of = function(thiz, argumentz) { return function(e1) { @@ -62,14 +62,13 @@ iD.behavior.drag = function() { if (touchId === null) d3_eventCancel(); function point() { - var p = target.parentNode; + var p = target.parentNode || surface; return touchId !== null ? d3.touches(p).filter(function(p) { return p.identifier === touchId; })[0] : d3.mouse(p); } function dragmove() { - if (!target.parentNode) return dragend(); var p = point(), dx = p[0] - origin_[0], @@ -170,5 +169,11 @@ iD.behavior.drag = function() { return drag; }; + drag.surface = function() { + if (!arguments.length) return surface; + surface = arguments[0]; + return drag; + }; + return d3.rebind(drag, event, "on"); }; diff --git a/js/id/behavior/drag_node.js b/js/id/behavior/drag_node.js index 1d1afcc2c..0cf732b4d 100644 --- a/js/id/behavior/drag_node.js +++ b/js/id/behavior/drag_node.js @@ -148,6 +148,7 @@ iD.behavior.DragNode = function(context) { var behavior = iD.behavior.drag() .delegate("g.node, g.point, g.midpoint") + .surface(context.surface().node()) .origin(origin) .on('start', start) .on('move', move) From 502f35869c7cf97ffd27337fc41485a156c537b1 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 10:40:41 -0800 Subject: [PATCH 258/332] Fall back to en strings (fixes #738) --- locale/locale.js | 11 ++++++++--- test/index.html | 1 + test/index_packaged.html | 1 + test/spec/lib/locale.js | 26 ++++++++++++++++++++++++++ 4 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 test/spec/lib/locale.js diff --git a/locale/locale.js b/locale/locale.js index c3dd5bcc4..8582df03e 100644 --- a/locale/locale.js +++ b/locale/locale.js @@ -6,9 +6,11 @@ locale.current = function(_) { return locale; }; -function t(s, o) { +function t(s, o, loc) { + loc = loc || locale._current; + var path = s.split(".").reverse(), - rep = locale[locale._current]; + rep = locale[loc]; while (rep !== undefined && path.length) rep = rep[path.pop()]; @@ -16,6 +18,9 @@ function t(s, o) { if (o) for (var k in o) rep = rep.replace('{' + k + '}', o[k]); return rep; } else { - if (console) console.error('key ' + s + ' not found'); + var missing = 'Missing translation: ' + s; + if (console) console.error(missing); + if (loc !== 'en') return t(s, o, 'en'); + return missing; } } diff --git a/test/index.html b/test/index.html index fe81a4ec5..3ba4e3bd8 100644 --- a/test/index.html +++ b/test/index.html @@ -144,6 +144,7 @@ + diff --git a/test/index_packaged.html b/test/index_packaged.html index cd3451357..874639094 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -22,6 +22,7 @@ + diff --git a/test/spec/lib/locale.js b/test/spec/lib/locale.js new file mode 100644 index 000000000..3228aa99b --- /dev/null +++ b/test/spec/lib/locale.js @@ -0,0 +1,26 @@ +describe("locale", function() { + var saved, error; + + beforeEach(function() { + saved = locale; + error = console.error; + console.error = function () {}; + locale = { _current: 'en', en: {test: 'test', foo: 'bar'}, __: {}} + }); + + afterEach(function() { + locale = saved; + console.error = error; + }); + + describe("t", function() { + it("defaults to locale._current", function() { + expect(t('test')).to.equal('test'); + }); + + it("falls back to en", function() { + locale._current = '__'; + expect(t('test')).to.equal('test'); + }); + }); +}); From cca9c30b32c40bec01ba8100ed6606dbf1ee8bbe Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 10:51:19 -0800 Subject: [PATCH 259/332] Fix midpoint hover (fixes #728) --- js/id/svg/midpoints.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/id/svg/midpoints.js b/js/id/svg/midpoints.js index 5403e4b45..174012cf3 100644 --- a/js/id/svg/midpoints.js +++ b/js/id/svg/midpoints.js @@ -51,7 +51,9 @@ iD.svg.Midpoints = function(projection) { groups.attr('transform', iD.svg.PointTransform(projection)); - groups.select('circle'); + // Propagate data bindings. + groups.select('circle.shadow'); + groups.select('circle.fill'); groups.exit() .remove(); From 6df64dbf37e3ebeb92f1e9a09e575157e5342d65 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 11:00:11 -0800 Subject: [PATCH 260/332] Switch to number keys 1-4 for modes (fixes #731) --- js/id/modes/add_area.js | 2 +- js/id/modes/add_line.js | 2 +- js/id/modes/add_point.js | 2 +- js/id/modes/browse.js | 2 +- locale/de.js | 12 ++++-------- locale/en.js | 12 ++++-------- locale/es.js | 12 ++++-------- locale/fr.js | 12 ++++-------- locale/ja.js | 12 ++++-------- locale/lv.js | 12 ++++-------- locale/tr.js | 12 ++++-------- 11 files changed, 32 insertions(+), 60 deletions(-) diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js index 4cb98a3bc..1614a3997 100644 --- a/js/id/modes/add_area.js +++ b/js/id/modes/add_area.js @@ -4,7 +4,7 @@ iD.modes.AddArea = function(context) { button: 'area', title: t('modes.add_area.title'), description: t('modes.add_area.description'), - key: t('modes.add_area.key') + key: '4' }; var behavior = iD.behavior.AddWay(context) diff --git a/js/id/modes/add_line.js b/js/id/modes/add_line.js index 76e31e83b..ae1d03cef 100644 --- a/js/id/modes/add_line.js +++ b/js/id/modes/add_line.js @@ -4,7 +4,7 @@ iD.modes.AddLine = function(context) { button: 'line', title: t('modes.add_line.title'), description: t('modes.add_line.description'), - key: t('modes.add_line.key') + key: '3' }; var behavior = iD.behavior.AddWay(context) diff --git a/js/id/modes/add_point.js b/js/id/modes/add_point.js index 457ed87a4..47bdafae8 100644 --- a/js/id/modes/add_point.js +++ b/js/id/modes/add_point.js @@ -3,7 +3,7 @@ iD.modes.AddPoint = function(context) { id: 'add-point', title: t('modes.add_point.title'), description: t('modes.add_point.description'), - key: t('modes.add_point.key') + key: '2' }; var behavior = iD.behavior.Draw(context) diff --git a/js/id/modes/browse.js b/js/id/modes/browse.js index 5df94d69d..d26ea4a55 100644 --- a/js/id/modes/browse.js +++ b/js/id/modes/browse.js @@ -4,7 +4,7 @@ iD.modes.Browse = function(context) { id: 'browse', title: t('modes.browse.title'), description: t('modes.browse.description'), - key: t('modes.browse.key') + key: '1' }; var behaviors = [ diff --git a/locale/de.js b/locale/de.js index f74c1a3fc..543eaab2d 100644 --- a/locale/de.js +++ b/locale/de.js @@ -3,25 +3,21 @@ locale.de = { add_area: { title: "Fläche", description: "Füge Parks, Gebäude, Seen oder andere Flächen zur Karte hinzu.", - tail: "Klicke in die Karte, um das Zeichnen einer Fläche wie einen Park, einen See oder Gebäude zu starten.", - key: "A" + tail: "Klicke in die Karte, um das Zeichnen einer Fläche wie einen Park, einen See oder Gebäude zu starten." }, add_line: { title: "Linie", description: "Linien können Autobahnen, Straßen, Fußwege oder sogar Kanäle sein.", - tail: "Klicke in die Karte, um das Zeichnen einer Straße eines Pfades oder einer Route zu starten.", - key: "L" + tail: "Klicke in die Karte, um das Zeichnen einer Straße eines Pfades oder einer Route zu starten." }, add_point: { title: "Punkt", description: "Restaurants, Denkmäler und Briefkästen sind Punkte", - tail: "Klicke in die Karte, um einen Punkt hinzuzufügen.", - key: "P" + tail: "Klicke in die Karte, um einen Punkt hinzuzufügen." }, browse: { title: "Navigation", - description: "Verschieben und Vergrößern/Verkleinern des Kartenausschnitts.", - key: "B" + description: "Verschieben und Vergrößern/Verkleinern des Kartenausschnitts." }, draw_area: { tail: "Klicke, um Punkte zur Fläche hinzuzufügen. Klicke auf den ersten Punkt, um die Fläche abzuschließen." diff --git a/locale/en.js b/locale/en.js index efd3c1d90..007c8e5be 100644 --- a/locale/en.js +++ b/locale/en.js @@ -3,25 +3,21 @@ locale.en = { add_area: { title: "Area", description: "Add parks, buildings, lakes, or other areas to the map.", - tail: "Click on the map to start drawing an area, like a park, lake, or building.", - key: "A" + tail: "Click on the map to start drawing an area, like a park, lake, or building." }, add_line: { title: "Line", description: "Lines can be highways, streets, pedestrian paths, or even canals.", - tail: "Click on the map to start drawing an road, path, or route.", - key: "L" + tail: "Click on the map to start drawing an road, path, or route." }, add_point: { title: "Point", description: "Restaurants, monuments, and postal boxes are points.", - tail: "Click on the map to add a point.", - key: "P" + tail: "Click on the map to add a point." }, browse: { title: "Browse", - description: "Pan and zoom the map.", - key: "B" + description: "Pan and zoom the map." }, draw_area: { tail: "Click to add points to your area. Click the first point to finish the area." diff --git a/locale/es.js b/locale/es.js index 47fb7427d..4bb5f3959 100644 --- a/locale/es.js +++ b/locale/es.js @@ -3,25 +3,21 @@ locale.es = { add_area: { title: "Zona", //"Area", description: "Agregar parques, edificios, lagos u otras zonas en el mapa", //"Add parks, buildings, lakes, or other areas to the map.", - tail: "Hacer click en el mapa para empezar a dibujar una zona como un parque, lago o edificio", //"Click on the map to start drawing an area, like a park, lake, or building.", - key: "Z" //"A" + tail: "Hacer click en el mapa para empezar a dibujar una zona como un parque, lago o edificio" //"Click on the map to start drawing an area, like a park, lake, or building." }, add_line: { title: "Línea", //"Line", description: "Las líneas pueden ser autopistas, calles, pasos peatonales o canales.", //"Lines can be highways, streets, pedestrian paths, or even canals.", - tail: "Hace clic para dibujar en el mapa, una calle, camino o ruta.", //"Click on the map to start drawing an road, path, or route.", - key: "L" + tail: "Hace clic para dibujar en el mapa, una calle, camino o ruta." //"Click on the map to start drawing an road, path, or route.", }, add_point: { title: "Punto", //"Point", description: "Son puntos los restaurantes, monumentos y buzones", //"Restaurants, monuments, and postal boxes are points.", - tail: "Hacer clic para agregar un punto en el mapa", //"Click on the map to add a point.", - key: "P" + tail: "Hacer clic para agregar un punto en el mapa" //"Click on the map to add a point.", }, browse: { title: "Navegar", //"Browse", - description: "Aumentar y navegar el mapa", //"Pan and zoom the map.", - key: "N" //"B" + description: "Aumentar y navegar el mapa" //"Pan and zoom the map.", }, draw_area: { tail: "Hacer clic para agregar puntos en tu zona. Hacer hacer click en el primer punto para finalizar la zona." //"Click to add points to your area. Click the first point to finish the area." diff --git a/locale/fr.js b/locale/fr.js index eeb280beb..85a704271 100644 --- a/locale/fr.js +++ b/locale/fr.js @@ -3,25 +3,21 @@ locale.fr = { add_area: { title: "Polygone", description: "Les polygones peuvent être des parcs, des batîments, des lacs ou tout autre objet surfacique.", - tail: "Cliquez sur la carte pour ajouter un polygone tel qu'un parc, un lac ou un bâtiment.", - key: "A" + tail: "Cliquez sur la carte pour ajouter un polygone tel qu'un parc, un lac ou un bâtiment." }, add_line: { title: "Ligne", description: "Les lignes peuvent être des autoroutes, des routes, des chemins ou encore des caneaux.", - tail: "Cliquez sur la carte pour ajouter une nouvelle ligne telle qu'une route ou un nouveau chemin.", - key: "L" + tail: "Cliquez sur la carte pour ajouter une nouvelle ligne telle qu'une route ou un nouveau chemin." }, add_point: { title: "Point", description: "Les points peuvent être des restaurants, des monuments, ou encore des boites aux lettres.", - tail: "Cliquez sur la carte pour ajouter un point tel qu'un restaurant ou un monument.", - key: "P" + tail: "Cliquez sur la carte pour ajouter un point tel qu'un restaurant ou un monument." }, browse: { title: "Navigation", - description: "Naviguer ou zoomer sur la carte.", - key: "B" + description: "Naviguer ou zoomer sur la carte." }, draw_area: { tail: "Cliquez pour ajouter un point à la zone. Cliquez sur le dernier point pour fermer la zone." diff --git a/locale/ja.js b/locale/ja.js index 3f6455554..ade489c51 100644 --- a/locale/ja.js +++ b/locale/ja.js @@ -3,25 +3,21 @@ locale.en = { add_area: { title: "エリア", description: "公園や建物、湖沼、をマップに追加します", - tail: "マップをクリックすると、公園や湖沼、建物などのエリアの描画が開始されます。", - key: "A" + tail: "マップをクリックすると、公園や湖沼、建物などのエリアの描画が開始されます。" }, add_line: { title: "ライン", description: "ラインは車両用の道路や歩道、用水路を表すことができます", - tail: "マップをクリックすると、道路や歩道、流水経路の描画が始まります", - key: "L" + tail: "マップをクリックすると、道路や歩道、流水経路の描画が始まります" }, add_point: { title: "ポイント", description: "レストランや記念碑、郵便ボックスはポイントで表現します", - tail: "マップをクリックするとポイントを追加できます", - key: "P" + tail: "マップをクリックするとポイントを追加できます" }, browse: { title: "ブラウズ", - description: "マップを拡大縮小します", - key: "B" + description: "マップを拡大縮小します" }, draw_area: { tail: "クリックするとエリア上にポイントを追加できます。起点となっているポイントをクリックするとエリアが作成されます" diff --git a/locale/lv.js b/locale/lv.js index f341f7fed..1d55450f8 100644 --- a/locale/lv.js +++ b/locale/lv.js @@ -3,25 +3,21 @@ locale.lv = { add_area: { title: "Apgabals", description: "Pievieno parkus, ēkas, ezerus un citus apgabalus.", - tail: "Klikšķiniet uz kartes, lai sāktu zīmēt apgabalu, piemēram, parku, ezeru, vai ēku.", - key: "A" + tail: "Klikšķiniet uz kartes, lai sāktu zīmēt apgabalu, piemēram, parku, ezeru, vai ēku." }, add_line: { title: "Līnija", description: "Līnijas var būt ceļi, ielas, takas vai pat kanāli.", - tail: "Klikšķiniet uz kartes, lai sāktu zīmēt līniju, piemēram, ceļu vai taku.", - key: "L" + tail: "Klikšķiniet uz kartes, lai sāktu zīmēt līniju, piemēram, ceļu vai taku." }, add_point: { title: "Punkts", description: "Kafejnīcas, pieminekļi, un veikali var būt punkti.", - tail: "Klikšķiniet uz kartes, lai pievienotu interešu punktu.", - key: "P" + tail: "Klikšķiniet uz kartes, lai pievienotu interešu punktu." }, browse: { title: "Pārlūkot", - description: "Pārlūko karti.", - key: "B" + description: "Pārlūko karti." }, draw_area: { tail: "Klikšķiniet, lai pievinotu mezglus apgabalam. Lai beigtu zīmēt apgabalu, klikšķiniet uz sākuma mezgla." diff --git a/locale/tr.js b/locale/tr.js index 904bccee6..d14f08ecb 100644 --- a/locale/tr.js +++ b/locale/tr.js @@ -3,25 +3,21 @@ locale.tr = { add_area: { title: "Alan", description: "Park, bina, göl ve benzeri alanları haritaya ekle.", - tail: "Park, göl ya da bina gibi alanları çizmek için haritaya tıklayın.", - key: "A" + tail: "Park, göl ya da bina gibi alanları çizmek için haritaya tıklayın." }, add_line: { title: "Çizgi", description: "Yollar, sokaklar, patikalar ya da kanallar çizgi ile çizilebilir.", - tail: "Yol, patika yada rota çizmek için haritaya tıklayın.", - key: "L" + tail: "Yol, patika yada rota çizmek için haritaya tıklayın." }, add_point: { title: "Nokta", description: "Restoranlar, anıtlar ya da posta kutuları nokta ile gösterilebilir.", - tail: "Nokta eklemek için haritaya tıklayın.", - key: "P" + tail: "Nokta eklemek için haritaya tıklayın." }, browse: { title: "Tara", - description: "Harita üzerinde dolan ve yaklaş.", - key: "B" + description: "Harita üzerinde dolan ve yaklaş." }, draw_area: { tail: "Alanınıza nokta eklemek için tıklayınız. İlk noktaya tıklayarak alan çizimini bitirebilirsiniz." From 4d6778e1594eb83f3ff493cb58c551d04788ef75 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Tue, 12 Feb 2013 15:21:32 -0500 Subject: [PATCH 261/332] Update and fix ja translation --- index.html | 1 + locale/ja.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/index.html b/index.html index 8c084cf77..018d6d008 100644 --- a/index.html +++ b/index.html @@ -146,6 +146,7 @@ + diff --git a/locale/ja.js b/locale/ja.js index 3f6455554..39f80122e 100644 --- a/locale/ja.js +++ b/locale/ja.js @@ -1,4 +1,4 @@ -locale.en = { +locale.ja = { modes: { add_area: { title: "エリア", From 7554c7445dd997e3dfa3ef77980f0abfb73ec0c5 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Tue, 12 Feb 2013 15:34:26 -0500 Subject: [PATCH 262/332] Fix typo triggering too many redraws --- js/id/renderer/map.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index ef101dbc7..af828b49d 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -43,7 +43,7 @@ iD.Map = function(context) { } }, true) .on('mouseup.zoom', function() { - if (resetTransform) redraw(); + if (resetTransform()) redraw(); }) .attr('id', 'surface') .call(iD.svg.Surface()); From e92d991677b0b313478f7f10757e1081b8835b22 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 11:05:53 -0800 Subject: [PATCH 263/332] Remove dead code --- js/id/ui.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index 7214628b4..9fec273cd 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -188,14 +188,11 @@ iD.ui = function(context) { }; history.on('change.editor', function() { - var undo = history.undoAnnotation(), redo = history.redoAnnotation(); function refreshTooltip(selection) { - if (selection.property('disabled')) { - selection.call(undo_tooltip.hide); - } else if (selection.property('tooltipVisible')) { + if (selection.property('tooltipVisible')) { selection.call(undo_tooltip.show); } } From 50f5a43efaa712941d49c95163a3ffec01854e6a Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 11:31:07 -0800 Subject: [PATCH 264/332] Avoid data-original-title hack --- js/id/ui.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index 9fec273cd..60b241b7e 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -43,10 +43,12 @@ iD.ui = function(context) { .enter().append('button') .attr('tabindex', -1) .attr('class', function(mode) { return mode.title + ' add-button col3'; }) - .call(bootstrap.tooltip().placement('bottom').html(true)) - .attr('data-original-title', function(mode) { - return hintprefix(mode.key, mode.description); - }) + .call(bootstrap.tooltip() + .placement('bottom') + .html(true) + .title(function(mode) { + return hintprefix(mode.key, mode.description); + })) .on('click.editor', function(mode) { context.enter(mode); }); function disableTooHigh() { From 5761c31d298b6cf10669ce409d4f26196f44babf Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 11:45:32 -0800 Subject: [PATCH 265/332] Extract iD.ui.Modes --- index.html | 1 + js/id/ui.js | 73 ++++++----------------------------------------- js/id/ui/modes.js | 68 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 64 deletions(-) create mode 100644 js/id/ui/modes.js diff --git a/index.html b/index.html index 018d6d008..cdf6b38e4 100644 --- a/index.html +++ b/index.html @@ -67,6 +67,7 @@ + diff --git a/js/id/ui.js b/js/id/ui.js index 60b241b7e..564c07983 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -14,10 +14,6 @@ iD.ui = function(context) { if (iD.detect().opera) container.classed('opera', true); - function hintprefix(x, y) { - return '' + y + '' + '
' + x + '
'; - } - var m = container.append('div') .attr('id', 'map') .call(map); @@ -29,60 +25,9 @@ iD.ui = function(context) { var limiter = bar.append('div') .attr('class', 'limiter'); - var buttons_joined = limiter.append('div') - .attr('class', 'button-wrap joined col4'); - - var modes = [ - iD.modes.Browse(context), - iD.modes.AddPoint(context), - iD.modes.AddLine(context), - iD.modes.AddArea(context)]; - - var buttons = buttons_joined.selectAll('button.add-button') - .data(modes) - .enter().append('button') - .attr('tabindex', -1) - .attr('class', function(mode) { return mode.title + ' add-button col3'; }) - .call(bootstrap.tooltip() - .placement('bottom') - .html(true) - .title(function(mode) { - return hintprefix(mode.key, mode.description); - })) - .on('click.editor', function(mode) { context.enter(mode); }); - - function disableTooHigh() { - if (map.editable()) { - notice.message(false); - buttons.attr('disabled', null); - } else { - buttons.attr('disabled', 'disabled'); - notice.message(true); - context.enter(iD.modes.Browse(context)); - } - } - - var notice = iD.ui.notice(limiter) - .message(false) - .on('zoom', function() { map.zoom(16); }); - - map.on('move.editor', _.debounce(disableTooHigh, 500)); - - buttons.append('span') - .attr('class', function(d) { - return d.id + ' icon icon-pre-text'; - }); - - buttons.append('span').attr('class', 'label').text(function(mode) { return mode.title; }); - - context.on('enter.editor', function(entered) { - buttons.classed('active', function(mode) { return entered.button === mode.button; }); - container.classed("mode-" + entered.id, true); - }); - - context.on('exit.editor', function(exited) { - container.classed("mode-" + exited.id, false); - }); + limiter.append('div') + .attr('class', 'button-wrap joined col4') + .call(iD.ui.Modes(context), limiter); var undo_buttons = limiter.append('div') .attr('class', 'button-wrap joined col1'), @@ -201,12 +146,12 @@ iD.ui = function(context) { limiter.select('#undo') .classed('disabled', !undo) - .attr('data-original-title', hintprefix(iD.ui.cmd('⌘Z'), undo || t('nothing_to_undo'))) + .attr('data-original-title', iD.ui.tooltipHtml(undo || t('nothing_to_undo'), iD.ui.cmd('⌘Z'))) .call(refreshTooltip); limiter.select('#redo') .classed('disabled', !redo) - .attr('data-original-title', hintprefix(iD.ui.cmd('⌘⇧Z'), redo || t('nothing_to_redo'))) + .attr('data-original-title', iD.ui.tooltipHtml(redo || t('nothing_to_redo'), iD.ui.cmd('⌘⇧Z'))) .call(refreshTooltip); }); @@ -237,10 +182,6 @@ iD.ui = function(context) { .on('-', function() { map.zoomOut(); }) .on('dash', function() { map.zoomOut(); }); - modes.forEach(function(m) { - keybinding.on(m.key, function() { if (map.editable()) context.enter(m); }); - }); - d3.select(document) .call(keybinding); @@ -269,3 +210,7 @@ iD.ui = function(context) { }; }; + +iD.ui.tooltipHtml = function(text, key) { + return '' + text + '' + '
' + key + '
'; +}; diff --git a/js/id/ui/modes.js b/js/id/ui/modes.js new file mode 100644 index 000000000..a08b2b13b --- /dev/null +++ b/js/id/ui/modes.js @@ -0,0 +1,68 @@ +iD.ui.Modes = function(context) { + var modes = [ + iD.modes.Browse(context), + iD.modes.AddPoint(context), + iD.modes.AddLine(context), + iD.modes.AddArea(context)]; + + return function(selection, limiter) { + var buttons = selection.selectAll('button.add-button') + .data(modes); + + buttons.enter().append('button') + .attr('tabindex', -1) + .attr('class', function(mode) { return mode.title + ' add-button col3'; }) + .on('click.mode-buttons', function(mode) { context.enter(mode); }) + .call(bootstrap.tooltip() + .placement('bottom') + .html(true) + .title(function(mode) { + return iD.ui.tooltipHtml(mode.description, mode.key); + })); + + var notice = iD.ui.notice(limiter) + .message(false) + .on('zoom', function() { context.map().zoom(16); }); + + function disableTooHigh() { + if (context.map().editable()) { + notice.message(false); + buttons.attr('disabled', null); + } else { + buttons.attr('disabled', 'disabled'); + notice.message(true); + context.enter(iD.modes.Browse(context)); + } + } + + context.map() + .on('move.mode-buttons', _.debounce(disableTooHigh, 500)); + + buttons.append('span') + .attr('class', function(mode) { return mode.id + ' icon icon-pre-text'; }); + + buttons.append('span') + .attr('class', 'label') + .text(function(mode) { return mode.title; }); + + context.on('enter.editor', function(entered) { + buttons.classed('active', function(mode) { return entered.button === mode.button; }); + context.container() + .classed("mode-" + entered.id, true); + }); + + context.on('exit.editor', function(exited) { + context.container() + .classed("mode-" + exited.id, false); + }); + + var keybinding = d3.keybinding('mode-buttons'); + + modes.forEach(function(m) { + keybinding.on(m.key, function() { if (context.map().editable()) context.enter(m); }); + }); + + d3.select(document) + .call(keybinding); + } +}; From e73c19a4e8858b9be7d08b49fede70387b55baa9 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 11:55:38 -0800 Subject: [PATCH 266/332] Extract iD.ui.UndoRedo --- index.html | 1 + js/id/id.js | 2 +- js/id/ui.js | 43 +++------------------------------------ js/id/ui/undo_redo.js | 47 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 41 deletions(-) create mode 100644 js/id/ui/undo_redo.js diff --git a/index.html b/index.html index cdf6b38e4..4ea9d9721 100644 --- a/index.html +++ b/index.html @@ -81,6 +81,7 @@ + diff --git a/js/id/id.js b/js/id/id.js index a7d7a44ac..f0747ad02 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -34,7 +34,7 @@ window.iD = function () { context.replace = history.replace; context.pop = history.pop; context.undo = history.undo; - context.redo = history.undo; + context.redo = history.redo; context.changes = history.changes; /* Graph */ diff --git a/js/id/ui.js b/js/id/ui.js index 564c07983..8515d3dfd 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -29,23 +29,9 @@ iD.ui = function(context) { .attr('class', 'button-wrap joined col4') .call(iD.ui.Modes(context), limiter); - var undo_buttons = limiter.append('div') - .attr('class', 'button-wrap joined col1'), - undo_tooltip = bootstrap.tooltip().placement('bottom').html(true); - - undo_buttons.append('button') - .attr({ id: 'undo', 'class': 'col6' }) - .classed('disabled', true) - .html("") - .on('click.editor', history.undo) - .call(undo_tooltip); - - undo_buttons.append('button') - .attr({ id: 'redo', 'class': 'col6' }) - .classed('disabled', true) - .html("") - .on('click.editor', history.redo) - .call(undo_tooltip); + limiter.append('div') + .attr('class', 'button-wrap joined col1') + .call(iD.ui.UndoRedo(context)); limiter.append('div').attr('class','button-wrap col1').append('button') .attr('class', 'save col12') @@ -134,27 +120,6 @@ iD.ui = function(context) { if (history.hasChanges()) return t('unsaved_changes'); }; - history.on('change.editor', function() { - var undo = history.undoAnnotation(), - redo = history.redoAnnotation(); - - function refreshTooltip(selection) { - if (selection.property('tooltipVisible')) { - selection.call(undo_tooltip.show); - } - } - - limiter.select('#undo') - .classed('disabled', !undo) - .attr('data-original-title', iD.ui.tooltipHtml(undo || t('nothing_to_undo'), iD.ui.cmd('⌘Z'))) - .call(refreshTooltip); - - limiter.select('#redo') - .classed('disabled', !redo) - .attr('data-original-title', iD.ui.tooltipHtml(redo || t('nothing_to_redo'), iD.ui.cmd('⌘⇧Z'))) - .call(refreshTooltip); - }); - d3.select(window).on('resize.editor', function() { map.size(m.size()); }); @@ -170,8 +135,6 @@ iD.ui = function(context) { var pa = 5; var keybinding = d3.keybinding('main') - .on(iD.ui.cmd('⌘Z'), function() { history.undo(); }) - .on(iD.ui.cmd('⌘⇧Z'), function() { history.redo(); }) .on('⌫', function() { d3.event.preventDefault(); }) .on('←', pan([pa, 0])) .on('↑', pan([0, pa])) diff --git a/js/id/ui/undo_redo.js b/js/id/ui/undo_redo.js new file mode 100644 index 000000000..f38066af4 --- /dev/null +++ b/js/id/ui/undo_redo.js @@ -0,0 +1,47 @@ +iD.ui.UndoRedo = function(context) { + return function(selection) { + var tooltip = bootstrap.tooltip() + .placement('bottom') + .html(true); + + var undoButton = selection.append('button') + .attr('class', 'col6 disabled') + .html('') + .on('click', context.undo) + .call(tooltip); + + var redoButton = selection.append('button') + .attr('class', 'col6 disabled') + .html('') + .on('click', context.redo) + .call(tooltip); + + var keybinding = d3.keybinding('undo') + .on(iD.ui.cmd('⌘Z'), context.undo) + .on(iD.ui.cmd('⌘⇧Z'), context.redo); + + d3.select(document) + .call(keybinding); + + context.history().on('change.editor', function() { + var undo = context.history().undoAnnotation(), + redo = context.history().redoAnnotation(); + + function refreshTooltip(selection) { + if (selection.property('tooltipVisible')) { + selection.call(tooltip.show); + } + } + + undoButton + .classed('disabled', !undo) + .attr('data-original-title', iD.ui.tooltipHtml(undo || t('nothing_to_undo'), iD.ui.cmd('⌘Z'))) + .call(refreshTooltip); + + redoButton + .classed('disabled', !redo) + .attr('data-original-title', iD.ui.tooltipHtml(redo || t('nothing_to_redo'), iD.ui.cmd('⌘⇧Z'))) + .call(refreshTooltip); + }); + } +}; From 50edf5870308c7d98a8795d8a5bf2719c7fa82f5 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 12:33:00 -0800 Subject: [PATCH 267/332] Clean up iD.ui.Save, give it a shortcut (fixes #739) --- js/id/ui.js | 6 +- js/id/ui/save.js | 169 +++++++++++++++++++++++++---------------------- 2 files changed, 93 insertions(+), 82 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index 8515d3dfd..535c825eb 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -33,9 +33,9 @@ iD.ui = function(context) { .attr('class', 'button-wrap joined col1') .call(iD.ui.UndoRedo(context)); - limiter.append('div').attr('class','button-wrap col1').append('button') - .attr('class', 'save col12') - .call(iD.ui.save(context)); + limiter.append('div') + .attr('class', 'button-wrap col1') + .call(iD.ui.Save(context)); var zoom = container.append('div') .attr('class', 'zoombuttons map-control') diff --git a/js/id/ui/save.js b/js/id/ui/save.js index 38bba8518..e19097ec9 100644 --- a/js/id/ui/save.js +++ b/js/id/ui/save.js @@ -1,99 +1,110 @@ -iD.ui.save = function(context) { - return function (selection) { - var map = context.map(), - history = context.history(), - connection = context.connection(), - tooltip = bootstrap.tooltip() - .placement('bottom'); +iD.ui.Save = function(context) { + var map = context.map(), + history = context.history(), + connection = context.connection(), + key = iD.ui.cmd('⌘S'), + modal; - function success(e, changeset_id) { + function save() { + d3.event.preventDefault(); + + if (!history.hasChanges()) return; + + connection.authenticate(function (err) { var modal = iD.ui.modal(context.container()); + var changes = history.changes(); + changes.connection = connection; modal.select('.content') - .classed('success-modal', true) - .datum({ - id: changeset_id, - comment: e.comment - }) - .call(iD.ui.success(connection) - .on('cancel', function() { + .classed('commit-modal', true) + .datum(changes) + .call(iD.ui.commit(context) + .on('cancel', function () { modal.remove(); - })); - } + }) + .on('fix', clickFix) + .on('save', commit)); + }); + } - function clickFix(d) { - map.extent(d.entity.extent(context.graph())); - if (map.zoom() > 19) map.zoom(19); - context.enter(iD.modes.Select(context, [d.entity.id])); - modal.remove(); - } + function commit(e) { + context.container().select('.shaded') + .remove(); - function click() { + var loading = iD.ui.loading(context.container(), t('uploading_changes'), true); - function commit(e) { - context.container().select('.shaded').remove(); - var l = iD.ui.loading(context.container(), t('uploading_changes'), true); + connection.putChangeset( + history.changes(), + e.comment, + history.imagery_used(), + function(err, changeset_id) { + loading.remove(); + history.reset(); + map.flush().redraw(); + if (err) { + var desc = iD.ui.confirm() + .select('.description'); + desc.append('h2') + .text(t('save_error')); + desc.append('p').text(err.responseText); + } else { + success(e, changeset_id); + } + }); + } - connection.putChangeset(history.changes(), - e.comment, - history.imagery_used(), function(err, changeset_id) { - l.remove(); - history.reset(); - map.flush().redraw(); - if (err) { - var desc = iD.ui.confirm() - .select('.description'); - desc.append('h2') - .text(t('save_error')); - desc.append('p').text(err.responseText); - } else { - success(e, changeset_id); - } - }); - } + function success(e, changeset_id) { + modal = iD.ui.modal(context.container()); + modal.select('.content') + .classed('success-modal', true) + .datum({ + id: changeset_id, + comment: e.comment + }) + .call(iD.ui.success(connection) + .on('cancel', function() { + modal.remove(); + })); + } - if (history.hasChanges()) { - connection.authenticate(function(err) { - var modal = iD.ui.modal(context.container()); - var changes = history.changes(); - changes.connection = connection; - modal.select('.content') - .classed('commit-modal', true) - .datum(changes) - .call(iD.ui.commit(context) - .on('cancel', function() { - modal.remove(); - }) - .on('fix', clickFix) - .on('save', commit)); - }); - } else { - iD.ui.confirm().select('.description') - .append('h3').text(t('no_changes')); - } - } + function clickFix(d) { + map.extent(d.entity.extent(context.graph())); + if (map.zoom() > 19) map.zoom(19); + context.enter(iD.modes.Select(context, [d.entity.id])); + modal.remove(); + } - selection.html("" + t('save') + "") - .attr('title', t('save_help')) + return function (selection) { + var button = selection.append('button') + .attr('class', 'save col12 disabled') .attr('tabindex', -1) - .property('disabled', true) - .call(tooltip) - .on('click', click); + .on('click', save) + .call(bootstrap.tooltip() + .placement('bottom') + .html(true) + .title(iD.ui.tooltipHtml(t('save_help'), key))); - selection.append('span') + button.append('span') + .attr('class', 'label') + .text(t('save')); + + button.append('span') .attr('class', 'count'); - history.on('change.save-button', function() { + var keybinding = d3.keybinding('undo-redo') + .on(key, save); + + d3.select(document) + .call(keybinding); + + context.history().on('change.save', function() { var hasChanges = history.hasChanges(); - selection - .property('disabled', !hasChanges) - .classed('has-count', hasChanges) - .select('span.count') - .text(history.numChanges()); + button + .classed('disabled', !hasChanges) + .classed('has-count', hasChanges); - if (!hasChanges) { - selection.call(tooltip.hide); - } + button.select('span.count') + .text(history.numChanges()); }); }; }; From ceb8d5cf8168b173948c20a70f54d41ab35ed1b3 Mon Sep 17 00:00:00 2001 From: Van De Casteele Date: Tue, 12 Feb 2013 16:24:50 -0430 Subject: [PATCH 268/332] Update locale/fr.js Translate some updates. Do I have to translate also the words in brackets like {users} or {count} ? Also, does brightness relates to luminosity or opacity? --- locale/fr.js | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/locale/fr.js b/locale/fr.js index 85a704271..adf4a2d26 100644 --- a/locale/fr.js +++ b/locale/fr.js @@ -136,9 +136,8 @@ locale.fr = { deprecated_tags: "Tags obsolètes : {tags}" }, - save: "Sauvegarder", - // TODO - unsaved_changes: "You have unsaved changes", + save: "Sauvegarder", + unsaved_changes: "Vous avez des modifications non enregistrées", save_help: "Envoie des modifications au serveyr OpenStreetMap afin qu'elles soient visibles par les autres contributeurs.", no_changes: "Vous n'avez aucune modification à enregistrer.", save_error: "Une erreur est survenue lors de l'enregistrement des données", @@ -168,21 +167,19 @@ locale.fr = { geocoder: { title: "Trouver un emplacement", - placeholder: "Trouver un endroit", - // TODO - no_results: "Couldn't locate a place named '{name}'" + placeholder: "Trouver un endroit", + no_results: "Impossible de localiser l'endroit nommé '{name}'" }, - description: "Description", + description: "Déscription", logout: "Déconnexion", - // TODO - report_a_bug: "report a bug", + report_a_bug: "Signaler un bug", contributors: { - list: "Viewing contributions by {users}", - truncated_list: "Viewing contributions by {users} and {count} others" + list: "Consulter les contributions de {users}", + truncated_list: "Consulter les contributions de {users} et {count} les autres" }, layerswitcher: { From 293784fbe9e9b8a2cf29fb548827c308a1a62c27 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Tue, 12 Feb 2013 15:57:30 -0500 Subject: [PATCH 269/332] Note about translation and tokens --- locale/README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/locale/README.md b/locale/README.md index 1f97112ed..b1610b311 100644 --- a/locale/README.md +++ b/locale/README.md @@ -14,5 +14,23 @@ If you aren't, you can still contribute! You'll still need a GitHub account, but you can just browse to your language's file here, click 'Edit', and edit each translated string. +## Translating Strings + +Let's look at an example line from `en.js`: + +```javascript +no_results: "Couldn't locate a place named '{name}'" +``` + +The word in brackets, `{name}`, should **not** be translated into a new +language: it's replaced with a place name when iD presents the text. So +a French translation would look like + +```javascript +no_results: "Impossible de localiser l'endroit nommé '{name}'" +``` + +## License + Contributions to translations are under the same liberal license as iD itself, [wtfpl](http://www.wtfpl.net/). From d9d05f3e3a4abf45fc93f6c60c67a9bc2ae57ccd Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Tue, 12 Feb 2013 16:20:39 -0500 Subject: [PATCH 270/332] Test translations --- locale/lint.js | 237 --------------------------------------- test/index.html | 8 +- test/spec/translation.js | 35 ++++++ 3 files changed, 42 insertions(+), 238 deletions(-) delete mode 100644 locale/lint.js create mode 100644 test/spec/translation.js diff --git a/locale/lint.js b/locale/lint.js deleted file mode 100644 index 44c5139e7..000000000 --- a/locale/lint.js +++ /dev/null @@ -1,237 +0,0 @@ -/** - * Fragment used to represent a string fragment in the diff. - */ -var Fragment = function (string) { - this.content = string; - this.equiv = false; -}; - -/** - * Wrap in given tag or return the clean value. - */ -Fragment.prototype.toString = function (tag) { - if (this.equiv || !tag) { - return this.content; - } - else { - return '<' + tag + '>' + this.content + ''; - } -}; - -var moveToEnd = function (a, i, k) { - if (!a.equiv && (!k[i-1] || k[i-1].equiv)) { - // Find next item equiv item. - for (var j = i+1; k[j] && !k[j].equiv; j++); - if (k[j] && k[j].content === a.content) { - k[i] = k[j]; - k[j] = a; - } - } -}; - -var aggregate = function (a, i, k) { - if (!a.equiv && k[i+1] && !k[i+1].equiv) { - k[i+1].content = a.content + k[i+1].content; - delete k[i]; - } -}; - -var join = function (what, t) { - return what.map(function (a) { - if (a) return a.toString(t); - }).join(''); -}; - -var clone = function(source) { - if (typeof source === 'object' && source !== null) { - var target = Array.isArray(source) ? [] : {}; - for (var key in source) target[key] = clone(source[key]); - return target; - } - return source; -}; - -var WordDiff = { - nonWord: /(&.+?;|[\u0000-\u0040\u005B-\u0060\u007B-\u00A9\u00AB-\u00B4\u00B6-\u00B9\u00BB-\u00BF\u00D7\u00F7\u02C2-\u02C5\u02D2-\u02DF\u02E5-\u02EB\u02ED\u02EF-\u036F\u0375\u037E\u0384\u0385\u0387\u03F6\u0482-\u0489\u055A-\u055F\u0589\u058A\u0591-\u05C7\u05F3\u05F4\u0600-\u0603\u0606-\u061B\u061E\u061F\u064B-\u065E\u0660-\u066D\u0670\u06D4\u06D6-\u06E4\u06EA-\u06ED\u06F0-\u06F9\u06FD\u06FE\u0700-\u070D\u070F\u0711\u0730-\u074A\u07A6-\u07B0\u07C0-\u07C9\u07EB-\u07F3\u07F6-\u07F9\u0901-\u0903\u093C\u093E-\u094D\u0951-\u0954\u09E2\u0962-\u0970\u06E7-\u06E9\u0981-\u0983\u09BC\u09BE-\u09C4\u09C7\u09C8\u09CB-\u09CD\u09D7\u09E3\u09E6-\u09EF\u09F2-\u09FA\u0A01-\u0A03\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A66-\u0A71\u0A75\u0A81-\u0A83\u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AE2\u0AE3\u0AE6-\u0AEF\u0AF1\u0B01-\u0B03\u0B3C\u0B3E-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B62\u0B63\u0B66-\u0B70\u0B82\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0BE6-\u0BFA\u0C01-\u0C03\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C66-\u0C6F\u0C78-\u0C7F\u0C82\u0C83\u0CBC\u0CBE-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CE2\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D02\u0D03\u0D3E-\u0D44\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D62\u0D63\u0D66-\u0D75\u0D79\u0D82\u0D83\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2-\u0DF4\u0E31\u0E34-\u0E3A\u0E3F\u0E47-\u0E5B\u0EB1\u0EB4-\u0EB9\u0EBB\u0EBC\u0EC8-\u0ECD\u0ED0-\u0ED9\u0F01-\u0F3F\u0F71-\u0F87\u0F90-\u0F97\u0F99-\u0FBC\u0FBE-\u0FCC\u0FCE-\u0FD4\u102B-\u103E\u1040-\u104F\u1056-\u1059\u105E-\u1060\u1062-\u1064\u1067-\u106D\u1071-\u1074\u1082-\u108D\u108F-\u1099\u109E\u109F\u10FB\u135F-\u137C\u1390-\u1399\u166D\u166E\u1680\u169B\u169C\u16EB-\u16F0\u1712-\u1714\u1732-\u1736\u1752\u1753\u1772\u1773\u17B4-\u17D6\u17D8-\u17DB\u17DD\u17E0-\u17E9\u17F0-\u17F9\u1800-\u180E\u1810-\u1819\u18A9\u1920-\u192B\u1930-\u193B\u1940\u1944-\u194F\u19B0-\u19C0\u19C8\u19C9\u19D0-\u19D9\u19DE-\u19FF\u1A17-\u1A1B\u1A1E\u1A1F\u1B00-\u1B04\u1B34-\u1B44\u1B50-\u1B7C\u1B80-\u1B82\u1BA1-\u1BAA\u1BB0-\u1BB9\u1C24-\u1C37\u1C3B-\u1C49\u1C50-\u1C59\u1C7E\u1C7F\u1DC0-\u1DE6\u1DFE\u1DFF\u1FBD\u1FBF-\u1FC1\u1FCD-\u1FCF\u1FDD-\u1FDF\u1FED-\u1FEF\u1FFD\u1FFE\u2000-\u2064\u206A-\u2070\u2074-\u207E\u2080-\u208E\u20A0-\u20B5\u20D0-\u20F0\u2100\u2101\u2103-\u2106\u2108\u2109\u2114\u2116-\u2118\u211E-\u2123\u2125\u2127\u2129\u212E\u213A\u213B\u2140-\u2144\u214A-\u214D\u214F\u2153-\u2182\u2185-\u2188\u2190-\u23E7\u2400-\u2426\u2440-\u244A\u2460-\u269D\u26A0-\u26BC\u26C0-\u26C3\u2701-\u2704\u2706-\u2709\u270C-\u2727\u2729-\u274B\u274D\u274F-\u2752\u2756\u2758-\u275E\u2761-\u2794\u2798-\u27AF\u27B1-\u27BE\u27C0-\u27CA\u27CC\u27D0-\u2B4C\u2B50-\u2B54\u2CE5-\u2CEA\u2CF9-\u2CFF\u2DE0-\u2E2E\u2E30\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u2FFB\u3000-\u3004\u3007-\u3030\u3036-\u303A\u303D-\u303F\u3099-\u309C\u30A0\u30FB\u3190-\u319F\u31C0-\u31E3\u3200-\u321E\u3220-\u3243\u3250-\u32FE\u3300-\u33FF\u4DC0-\u4DFF\uA490-\uA4C6\uA60D-\uA60F\uA620-\uA629\uA66F-\uA673\uA67C-\uA67E\uA700-\uA716\uA720\uA721\uA789\uA78A\uA802\uA806\uA80B\uA823-\uA82B\uA874-\uA877\uA880\uA881\uA8B4-\uA8C4\uA8CE-\uA8D9\uA900-\uA909\uA926-\uA92F\uA947-\uA953\uA95F\uAA29-\uAA36\uAA43\uAA4C\uAA4D\uAA50-\uAA59\uAA5C-\uAA5F\uD800\uDB7F\uDB80\uDBFF\uDC00\uDFFF\uE000\uF8FF\uFB1E\uFB29\uFD3E\uFD3F\uFDFC\uFDFD\uFE00-\uFE19\uFE20-\uFE26\uFE30-\uFE52\uFE54-\uFE66\uFE68-\uFE6B\uFEFF\uFF01-\uFF20\uFF3B-\uFF40\uFF5B-\uFF65\uFFE0-\uFFE6\uFFE8-\uFFEE\uFFF9-\uFFFD])/, - - tokenize: function (args) { - // Split on non-word characters. - for (var type in args) { - args[type] = args[type].split(WordDiff.nonWord).filter(function (s) { - return s.length; - }); - } - - // Calculate the indexes and offsets for common suffixes and prefixes. - var i = -1, j = args.del.length, k = args.ins.length; - while (args.del[++i] === args.ins[i] && i <= j); - while (j >= i && k >= i && args.del[--j] === args.ins[--k]); - - args.prefix = args.del.slice(0, i).join(''); - args.suffix = args.del.slice(j + 1).join(''); - args.del = args.del.slice(i, ++j); - args.ins = args.ins.slice(i, ++k); - }, - - lcs: function (args) { - var matrix = []; - - for (var i = 0; i < args.del.length; i++) { - matrix[i] = []; - for (var j = 0; j < args.ins.length; j++) { - if (args.del[i] === args.ins[j]) { - matrix[i][j] = (matrix[i - 1] && matrix[i - 1][j - 1] || 0) + args.del[i].length; - } - else { - matrix[i][j] = Math.max(matrix[i][j - 1] || 0, matrix[i - 1] && matrix[i - 1][j] || 0); - } - } - } - - return matrix; - }, - - changeset: function (args, matrix) { - var result = {}; - - ['del', 'ins'].forEach(function (type) { - result[type] = args[type].map(function (a) { return new Fragment(a); }); - }); - - // Backtrack through the matrix. - for (var i = result.del.length - 1, j = result.ins.length - 1; i >= 0; i--, j--) { - if (j < 0 || result.del[i].content !== result.ins[j].content) { - if (j < 0 || (j > 0 && matrix[i - 1] && (matrix[i][j - 1] < matrix[i - 1][j]))) { - j++; - } - else { - i++; - } - } - else { - result.del[i] = result.ins[j]; - result.del[i].equiv = true; - } - } - - // Fill up gaps. - for (var i = 0; i < result.del.length; i++) { - if (result.del[i].equiv && result.del[i].content.length < 3) { - var j = result.ins.indexOf(result.del[i]); - if (result.del[i-1] && result.del[i+1] && result.ins[j-1] && result.ins[j+1] && !result.del[i-1].equiv && !result.del[i+1].equiv && !result.ins[j-1].equiv && !result.ins[j+1].equiv){ - result.del[i].equiv = false; - result.ins[j] = clone(result.del[i]); - } - } - } - - ['del', 'ins'].forEach(function (type) { - // Try to move changes to the end. - for (var i = 0; i < result[type].length; i++) - moveToEnd(result[type][i], i, result[type]); - - // Aggregate subsequent changes to minimize ins/del tags. - for (var i = 0; i < result[type].length; i++) - aggregate(result[type][i], i, result[type]); - }); - - return result; - }, - - htmlRender: function (args, result) { - var diff = { - del: args.prefix + join(result.del, 'del') + args.suffix, - ins: args.prefix + join(result.ins, 'ins') + args.suffix - }; - - return diff; - }, - - htmlDiff: function (del, ins) { - var args = { 'del': del, 'ins': ins }; - - WordDiff.tokenize(args); - var matrix = WordDiff.lcs(args); - var result = WordDiff.changeset(args, matrix); - return WordDiff.htmlRender(args, result); - }, - - render: function (args, result) { - var join = function (what, type) { - return what.map(function (a) { - if (!a) return; - if (a.equiv) return a.content; - if (type == 'del') return '\033[31;4m' + a.content + '\033[0m'; - if (type == 'ins') return '\033[32;4m' + a.content + '\033[0m'; - }).join(''); - }; - - return { - del: args.prefix + join(result.del, 'del') + args.suffix, - ins: args.prefix + join(result.ins, 'ins') + args.suffix - }; - }, - - diff: function(del, ins) { - var args = { 'del': del, 'ins': ins }; - - WordDiff.tokenize(args); - var matrix = WordDiff.lcs(args); - var result = WordDiff.changeset(args, matrix); - return WordDiff.render(args, result); - } -}; - - - - -var fs = require('fs'), _ = require('lodash'); - -function getKeys(lang, keys, prefix) { - keys = keys || []; - prefix = prefix || ''; - for (var i in lang) { - keys.push(prefix + i); - if (typeof lang[i] === 'object') { - getKeys(lang[i], keys, i + '.'); - } - } - return keys; -} - -var languages = ['de', 'en', 'es', 'fr', 'lv', 'tr']; -var langkeys = {}; - -eval(fs.readFileSync('./locale.js', 'utf8')); -for (var i = 0; i < languages.length; i++) { - eval(fs.readFileSync('./' + languages[i] + '.js', 'utf8')); - langkeys[languages[i]] = getKeys(locale[languages[i]]).sort(); -} - -// for (var i = 1; i < languages.length - 1; i++) { -// -// var changes = WordDiff.diff( -// langkeys[languages[i]].join(','), -// langkeys[languages[i + 1]].join(',')); -// -// -// console.warn('actual:' + '\n' + changes.del); -// console.warn('expected:' + '\n' + changes.ins); -// } - -var allkeys = []; -_.forEach(langkeys, function(l) { - allkeys = _.union(allkeys, l); -}); - -// console.warn('\n\n------------------------------------------'); -// console.warn('all keys ---------------------------------\n\n'); - -// console.log(allkeys.join(',')); - -_.forEach(langkeys, function(l, k) { - var missing = _.difference(allkeys, l); - if (missing.length) { - console.log('\n', k, 'is missing\n\n', _.difference(allkeys, l).join(',')); - } -}); diff --git a/test/index.html b/test/index.html index 3ba4e3bd8..d9640c687 100644 --- a/test/index.html +++ b/test/index.html @@ -138,7 +138,12 @@ - + + + + + + @@ -197,6 +202,7 @@ + diff --git a/test/spec/translation.js b/test/spec/translation.js new file mode 100644 index 000000000..963f59fe0 --- /dev/null +++ b/test/spec/translation.js @@ -0,0 +1,35 @@ +describe('translations', function() { + var languages = [], languageKeys = {}; + + function getKeys(lang, keys, prefix) { + keys = keys || []; + prefix = prefix || ''; + for (var i in lang) { + keys.push(prefix + i); + if (typeof lang[i] === 'object') { + getKeys(lang[i], keys, i + '.'); + } + } + return keys; + } + + describe('#translation-differences', function() { + + it('does not differ between languages', function() { + languages = _(locale).keys() + .without('current', '_current').value(); + + languageKeys = _.reduce(languages, function(mem, lang) { + mem[lang] = getKeys(locale[lang]); + return mem; + }, {}); + + var allkeys = _.flatten(_.values(languageKeys)); + + _.forEach(languageKeys, function(l, k) { + expect(_.difference(allkeys, l)).to.eql([]); + }); + }); + + }); +}); From 072f85e2b17cf9454ffcd9251e1a215d3fc3a43d Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 13:24:09 -0800 Subject: [PATCH 271/332] Redraw nodes that move off the screen (fixes #735) --- js/id/core/difference.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/id/core/difference.js b/js/id/core/difference.js index e2159271a..15ae88df3 100644 --- a/js/id/core/difference.js +++ b/js/id/core/difference.js @@ -89,7 +89,9 @@ iD.Difference = function (base, head) { b = change.base, entity = h || b; - if (extent && !entity.intersects(extent, h ? head : base)) + if (extent && + (!h || !h.intersects(extent, head)) && + (!b || !b.intersects(extent, base))) continue; result[id] = h; From 61d7ec45b5b64238f3a5e6a1334a3b1a7ff02000 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Tue, 12 Feb 2013 16:35:47 -0500 Subject: [PATCH 272/332] Banish function ( style --- js/id/actions/connect.js | 4 ++-- js/id/actions/delete_multiple.js | 2 +- js/id/actions/delete_way.js | 2 +- js/id/actions/join.js | 2 +- js/id/actions/merge.js | 4 ++-- js/id/actions/move_way.js | 4 ++-- js/id/actions/reverse.js | 4 ++-- js/id/actions/split.js | 2 +- js/id/behavior/drag_node.js | 2 +- js/id/behavior/draw_way.js | 2 +- js/id/behavior/hover.js | 4 ++-- js/id/connection.js | 6 +++--- js/id/core/difference.js | 6 +++--- js/id/core/graph.js | 4 ++-- js/id/core/relation.js | 14 +++++++------- js/id/core/way.js | 2 +- js/id/geo.js | 4 ++-- js/id/geo/extent.js | 6 +++--- js/id/renderer/map.js | 4 ++-- js/id/svg.js | 6 +++--- js/id/svg/areas.js | 6 +++--- js/id/svg/lines.js | 2 +- js/id/svg/member_classes.js | 2 +- js/id/svg/midpoints.js | 2 +- js/id/ui/geocoder.js | 2 +- js/id/ui/radial_menu.js | 8 ++++---- js/id/ui/save.js | 6 +++--- test/index_packaged.html | 1 + test/spec/actions/delete_way.js | 16 ++++++++-------- test/spec/actions/move_way.js | 6 +++--- 30 files changed, 68 insertions(+), 67 deletions(-) diff --git a/js/id/actions/connect.js b/js/id/actions/connect.js index 73c1af09a..3c69b8a0f 100644 --- a/js/id/actions/connect.js +++ b/js/id/actions/connect.js @@ -19,11 +19,11 @@ iD.actions.Connect = function(nodeIds) { for (var i = 0; i < nodeIds.length - 1; i++) { var node = graph.entity(nodeIds[i]), index; - graph.parentWays(node).forEach(function (parent) { + graph.parentWays(node).forEach(function(parent) { graph = graph.replace(parent.replaceNode(node.id, survivor.id)); }); - graph.parentRelations(node).forEach(function (parent) { + graph.parentRelations(node).forEach(function(parent) { graph = graph.replace(parent.replaceMember(node, survivor)); }); diff --git a/js/id/actions/delete_multiple.js b/js/id/actions/delete_multiple.js index a33dba4de..cd7c022a1 100644 --- a/js/id/actions/delete_multiple.js +++ b/js/id/actions/delete_multiple.js @@ -6,7 +6,7 @@ iD.actions.DeleteMultiple = function(ids) { relation: iD.actions.DeleteRelation }; - ids.forEach(function (id) { + ids.forEach(function(id) { var entity = graph.entity(id); if (entity) { // It may have been deleted aready. graph = actions[entity.type](id)(graph); diff --git a/js/id/actions/delete_way.js b/js/id/actions/delete_way.js index 211447228..95888470e 100644 --- a/js/id/actions/delete_way.js +++ b/js/id/actions/delete_way.js @@ -8,7 +8,7 @@ iD.actions.DeleteWay = function(wayId) { graph = graph.replace(parent.removeMember(wayId)); }); - way.nodes.forEach(function (nodeId) { + way.nodes.forEach(function(nodeId) { var node = graph.entity(nodeId); // Circular ways include nodes more than once, so they diff --git a/js/id/actions/join.js b/js/id/actions/join.js index feccfebaf..d3b353dc5 100644 --- a/js/id/actions/join.js +++ b/js/id/actions/join.js @@ -47,7 +47,7 @@ iD.actions.Join = function(ids) { nodes = a.nodes.concat(b.nodes.slice().slice(1)); } - graph.parentRelations(b).forEach(function (parent) { + graph.parentRelations(b).forEach(function(parent) { graph = graph.replace(parent.replaceMember(b, a)); }); diff --git a/js/id/actions/merge.js b/js/id/actions/merge.js index 5b515bcc5..4c4fb2abc 100644 --- a/js/id/actions/merge.js +++ b/js/id/actions/merge.js @@ -9,10 +9,10 @@ iD.actions.Merge = function(ids) { area = geometries.area[0], points = geometries.point; - points.forEach(function (point) { + points.forEach(function(point) { area = area.mergeTags(point.tags); - graph.parentRelations(point).forEach(function (parent) { + graph.parentRelations(point).forEach(function(parent) { graph = graph.replace(parent.replaceMember(point, area)); }); diff --git a/js/id/actions/move_way.js b/js/id/actions/move_way.js index 8301539c4..376e31d6e 100644 --- a/js/id/actions/move_way.js +++ b/js/id/actions/move_way.js @@ -1,9 +1,9 @@ iD.actions.MoveWay = function(wayId, delta, projection) { return function(graph) { - return graph.update(function (graph) { + return graph.update(function(graph) { var way = graph.entity(wayId); - _.uniq(way.nodes).forEach(function (id) { + _.uniq(way.nodes).forEach(function(id) { var node = graph.entity(id), start = projection(node.loc), end = projection.invert([start[0] + delta[0], start[1] + delta[1]]); diff --git a/js/id/actions/reverse.js b/js/id/actions/reverse.js index 5c8315467..f9696ceb1 100644 --- a/js/id/actions/reverse.js +++ b/js/id/actions/reverse.js @@ -62,8 +62,8 @@ iD.actions.Reverse = function(wayId) { tags[reverseKey(key)] = reverseValue(key, way.tags[key]); } - graph.parentRelations(way).forEach(function (relation) { - relation.members.forEach(function (member, index) { + graph.parentRelations(way).forEach(function(relation) { + relation.members.forEach(function(member, index) { if (member.id === way.id && (role = {forward: 'backward', backward: 'forward'}[member.role])) { relation = relation.updateMember({role: role}, index); graph = graph.replace(relation); diff --git a/js/id/actions/split.js b/js/id/actions/split.js index 92f9296f7..cf5d56f84 100644 --- a/js/id/actions/split.js +++ b/js/id/actions/split.js @@ -14,7 +14,7 @@ iD.actions.Split = function(nodeId, newWayId) { var node = graph.entity(nodeId), parents = graph.parentWays(node); - return parents.filter(function (parent) { + return parents.filter(function(parent) { return parent.isClosed() || (parent.first() !== nodeId && parent.last() !== nodeId); diff --git a/js/id/behavior/drag_node.js b/js/id/behavior/drag_node.js index 0cf732b4d..eff4c1174 100644 --- a/js/id/behavior/drag_node.js +++ b/js/id/behavior/drag_node.js @@ -61,7 +61,7 @@ iD.behavior.DragNode = function(context) { context.surface() .classed('behavior-drag-node', true) .selectAll('.node, .way') - .filter(function (d) { return activeIDs.indexOf(d.id) >= 0; }) + .filter(function(d) { return activeIDs.indexOf(d.id) >= 0; }) .classed('active', true); } diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 0a05c7e9d..a9a34e37c 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -30,7 +30,7 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { if (datum.id === end.id || datum.id === segment.id) { context.surface().selectAll('.way, .node') - .filter(function (d) { + .filter(function(d) { return d.id === end.id || d.id === segment.id; }) .classed('active', true); diff --git a/js/id/behavior/hover.js b/js/id/behavior/hover.js index b724f13b1..0f5786676 100644 --- a/js/id/behavior/hover.js +++ b/js/id/behavior/hover.js @@ -15,14 +15,14 @@ iD.behavior.Hover = function() { var datum = d3.event.target.__data__; if (datum) { selection.selectAll('*') - .filter(function (d) { return d === datum; }) + .filter(function(d) { return d === datum; }) .classed('hover', true); } } selection.on('mouseover.hover', mouseover); - selection.on('mouseout.hover', function () { + selection.on('mouseout.hover', function() { selection.selectAll('.hover') .classed('hover', false); }); diff --git a/js/id/connection.js b/js/id/connection.js index df9c318ca..ea90c5591 100644 --- a/js/id/connection.js +++ b/js/id/connection.js @@ -200,19 +200,19 @@ iD.Connection = function(context) { comment: comment, created_by: 'iD ' + iD.version })) - }, function (err, changeset_id) { + }, function(err, changeset_id) { if (err) return callback(err); oauth.xhr({ method: 'POST', path: '/api/0.6/changeset/' + changeset_id + '/upload', options: { header: { 'Content-Type': 'text/xml' } }, content: JXON.stringify(connection.osmChangeJXON(user.id, changeset_id, changes)) - }, function (err) { + }, function(err) { if (err) return callback(err); oauth.xhr({ method: 'PUT', path: '/api/0.6/changeset/' + changeset_id + '/close' - }, function (err) { + }, function(err) { callback(err, changeset_id); }); }); diff --git a/js/id/core/difference.js b/js/id/core/difference.js index e2159271a..0138844cb 100644 --- a/js/id/core/difference.js +++ b/js/id/core/difference.js @@ -6,7 +6,7 @@ of entities that will require a redraw, taking into account child and parent relationships. */ -iD.Difference = function (base, head) { +iD.Difference = function(base, head) { var changes = {}, length = 0; _.each(head.entities, function(h, id) { @@ -27,7 +27,7 @@ iD.Difference = function (base, head) { var difference = {}; - difference.length = function () { + difference.length = function() { return length; }; @@ -105,7 +105,7 @@ iD.Difference = function (base, head) { } diff = _.difference(nb, nh); - for (var i = 0; i < diff.length; i++) { + for (i = 0; i < diff.length; i++) { result[diff[i]] = head.entity(diff[i]); } } diff --git a/js/id/core/graph.js b/js/id/core/graph.js index eae11cc15..96dca44e7 100644 --- a/js/id/core/graph.js +++ b/js/id/core/graph.js @@ -192,14 +192,14 @@ iD.Graph.prototype = { if (this.entities[entity.id] === entity) return this; - return this.update(function () { + return this.update(function() { this._updateCalculated(this.entities[entity.id], entity); this.entities[entity.id] = entity; }); }, remove: function(entity) { - return this.update(function () { + return this.update(function() { this._updateCalculated(entity, undefined); this.entities[entity.id] = undefined; }); diff --git a/js/id/core/relation.js b/js/id/core/relation.js index 950f56230..778568917 100644 --- a/js/id/core/relation.js +++ b/js/id/core/relation.js @@ -14,7 +14,7 @@ _.extend(iD.Relation.prototype, { extent: function(resolver) { return resolver.transient(this, 'extent', function() { - return this.members.reduce(function (extent, member) { + return this.members.reduce(function(extent, member) { member = resolver.entity(member.id); if (member) { return extent.extend(member.extent(resolver)); @@ -156,8 +156,8 @@ _.extend(iD.Relation.prototype, { // multipolygon: function(resolver) { var members = this.members - .filter(function (m) { return m.type === 'way' && resolver.entity(m.id); }) - .map(function (m) { return { role: m.role || 'outer', id: m.id, nodes: resolver.childNodes(resolver.entity(m.id)) }; }); + .filter(function(m) { return m.type === 'way' && resolver.entity(m.id); }) + .map(function(m) { return { role: m.role || 'outer', id: m.id, nodes: resolver.childNodes(resolver.entity(m.id)) }; }); function join(ways) { var joined = [], current, first, last, i, how, what; @@ -202,7 +202,7 @@ _.extend(iD.Relation.prototype, { } } - return joined.map(function (nodes) { return _.pluck(nodes, 'loc'); }); + return joined.map(function(nodes) { return _.pluck(nodes, 'loc'); }); } function findOuter(inner) { @@ -221,9 +221,9 @@ _.extend(iD.Relation.prototype, { } } - var outers = join(members.filter(function (m) { return m.role === 'outer'; })), - inners = join(members.filter(function (m) { return m.role === 'inner'; })), - result = outers.map(function (o) { return [o]; }); + var outers = join(members.filter(function(m) { return m.role === 'outer'; })), + inners = join(members.filter(function(m) { return m.role === 'inner'; })), + result = outers.map(function(o) { return [o]; }); for (var i = 0; i < inners.length; i++) { var o = findOuter(inners[i]); diff --git a/js/id/core/way.js b/js/id/core/way.js index 5229040ab..e9fb7b71d 100644 --- a/js/id/core/way.js +++ b/js/id/core/way.js @@ -14,7 +14,7 @@ _.extend(iD.Way.prototype, { extent: function(resolver) { return resolver.transient(this, 'extent', function() { - return this.nodes.reduce(function (extent, id) { + return this.nodes.reduce(function(extent, id) { return extent.extend(resolver.entity(id).extent(resolver)); }, iD.geo.Extent()); }); diff --git a/js/id/geo.js b/js/id/geo.js index 61bd9273e..682980758 100644 --- a/js/id/geo.js +++ b/js/id/geo.js @@ -64,13 +64,13 @@ iD.geo.pointInPolygon = function(point, polygon) { }; iD.geo.polygonContainsPolygon = function(outer, inner) { - return _.every(inner, function (point) { + return _.every(inner, function(point) { return iD.geo.pointInPolygon(point, outer); }); }; iD.geo.polygonIntersectsPolygon = function(outer, inner) { - return _.some(inner, function (point) { + return _.some(inner, function(point) { return iD.geo.pointInPolygon(point, outer); }); }; diff --git a/js/id/geo/extent.js b/js/id/geo/extent.js index 4422b5e39..243ebad4f 100644 --- a/js/id/geo/extent.js +++ b/js/id/geo/extent.js @@ -14,7 +14,7 @@ iD.geo.Extent = function geoExtent(min, max) { iD.geo.Extent.prototype = [[], []]; _.extend(iD.geo.Extent.prototype, { - extend: function (obj) { + extend: function(obj) { if (!(obj instanceof iD.geo.Extent)) obj = new iD.geo.Extent(obj); return iD.geo.Extent([Math.min(obj[0][0], this[0][0]), Math.min(obj[0][1], this[0][1])], @@ -22,12 +22,12 @@ _.extend(iD.geo.Extent.prototype, { Math.max(obj[1][1], this[1][1])]); }, - center: function () { + center: function() { return [(this[0][0] + this[1][0]) / 2, (this[0][1] + this[1][1]) / 2]; }, - intersects: function (obj) { + intersects: function(obj) { if (!(obj instanceof iD.geo.Extent)) obj = new iD.geo.Extent(obj); return obj[0][0] <= this[1][0] && obj[0][1] <= this[1][1] && diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index af828b49d..1bea28a17 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -319,14 +319,14 @@ iD.Map = function(context) { } }; - map.flush = function () { + map.flush = function() { context.connection().flush(); context.history().reset(); return map; }; var usedTails = {}; - map.tail = function (_) { + map.tail = function(_) { if (!_ || usedTails[_] === undefined) { tail.text(_); usedTails[_] = true; diff --git a/js/id/svg.js b/js/id/svg.js index dda91ec99..36d1d060d 100644 --- a/js/id/svg.js +++ b/js/id/svg.js @@ -32,10 +32,10 @@ iD.svg = { }; }, - MultipolygonMemberTags: function (graph) { - return function (entity) { + MultipolygonMemberTags: function(graph) { + return function(entity) { var tags = entity.tags; - graph.parentRelations(entity).forEach(function (relation) { + graph.parentRelations(entity).forEach(function(relation) { if (relation.isMultipolygon()) { tags = _.extend({}, relation.tags, tags); } diff --git a/js/id/svg/areas.js b/js/id/svg/areas.js index cd9525d4d..46df5544c 100644 --- a/js/id/svg/areas.js +++ b/js/id/svg/areas.js @@ -28,11 +28,11 @@ iD.svg.Areas = function(projection) { paths.enter() .append('path') - .attr('class', function (d) { return d.type + ' area ' + klass; }); + .attr('class', function(d) { return d.type + ' area ' + klass; }); paths .order() - .attr('d', function (entity) { return path(entity.asGeoJSON(graph)); }) + .attr('d', function(entity) { return path(entity.asGeoJSON(graph)); }) .call(tagClasses) .call(iD.svg.MemberClasses(graph)); @@ -44,7 +44,7 @@ iD.svg.Areas = function(projection) { areas = _.pluck(areas, 'entity'); - var strokes = areas.filter(function (area) { + var strokes = areas.filter(function(area) { return area.type === 'way'; }); diff --git a/js/id/svg/lines.js b/js/id/svg/lines.js index 2fd7b9152..59fec46e1 100644 --- a/js/id/svg/lines.js +++ b/js/id/svg/lines.js @@ -95,7 +95,7 @@ iD.svg.Lines = function(projection) { // Determine the lengths of oneway paths var lengths = {}, - oneways = strokes.filter(function (d) { return d.isOneWay(); }).each(function(d) { + oneways = strokes.filter(function(d) { return d.isOneWay(); }).each(function(d) { lengths[d.id] = Math.floor(this.getTotalLength() / alength); }).data(); diff --git a/js/id/svg/member_classes.js b/js/id/svg/member_classes.js index d5745a466..713cc898e 100644 --- a/js/id/svg/member_classes.js +++ b/js/id/svg/member_classes.js @@ -17,7 +17,7 @@ iD.svg.MemberClasses = function(graph) { classes += ' member'; } - relations.forEach(function (relation) { + relations.forEach(function(relation) { classes += ' member-type-' + relation.tags.type; classes += ' member-role-' + relation.memberById(d.id).role; }); diff --git a/js/id/svg/midpoints.js b/js/id/svg/midpoints.js index 174012cf3..fc1a5bbc3 100644 --- a/js/id/svg/midpoints.js +++ b/js/id/svg/midpoints.js @@ -35,7 +35,7 @@ iD.svg.Midpoints = function(projection) { var groups = surface.select('.layer-hit').selectAll('g.midpoint') .filter(filter) - .data(_.values(midpoints), function (d) { return d.id; }); + .data(_.values(midpoints), function(d) { return d.id; }); var group = groups.enter() .insert('g', ':first-child') diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index 0643c41d1..7b79c2158 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -23,7 +23,7 @@ iD.ui.geocoder = function(context) { .text(t('geocoder.no_results', {name: searchVal})); } else if (resp.length > 1) { var spans = resultsList.selectAll('span') - .data(resp, function (d) { return d.place_id; }); + .data(resp, function(d) { return d.place_id; }); spans.enter() .append('span') diff --git a/js/id/ui/radial_menu.js b/js/id/ui/radial_menu.js index fe0d7ee86..106a48196 100644 --- a/js/id/ui/radial_menu.js +++ b/js/id/ui/radial_menu.js @@ -42,10 +42,10 @@ iD.ui.RadialMenu = function(operations) { }); button.append('circle') - .attr('class', function (d) { return 'radial-menu-item radial-menu-item-' + d.id; }) + .attr('class', function(d) { return 'radial-menu-item radial-menu-item-' + d.id; }) .attr('r', 15) - .attr('title', function (d) { return d.title; }) - .classed('disabled', function (d) { return !d.enabled(); }) + .attr('title', function(d) { return d.title; }) + .classed('disabled', function(d) { return !d.enabled(); }) .on('click', click) .on('mouseover', mouseover) .on('mouseout', mouseout); @@ -58,7 +58,7 @@ iD.ui.RadialMenu = function(operations) { .attr('y', -10); image.append('xhtml:span') - .attr('class', function (d) { return 'icon icon-operation icon-operation-' + d.id; }); + .attr('class', function(d) { return 'icon icon-operation icon-operation-' + d.id; }); var tooltip = menu.append('foreignObject') .style('display', 'none') diff --git a/js/id/ui/save.js b/js/id/ui/save.js index e19097ec9..b8f9d619a 100644 --- a/js/id/ui/save.js +++ b/js/id/ui/save.js @@ -10,7 +10,7 @@ iD.ui.Save = function(context) { if (!history.hasChanges()) return; - connection.authenticate(function (err) { + connection.authenticate(function(err) { var modal = iD.ui.modal(context.container()); var changes = history.changes(); changes.connection = connection; @@ -18,7 +18,7 @@ iD.ui.Save = function(context) { .classed('commit-modal', true) .datum(changes) .call(iD.ui.commit(context) - .on('cancel', function () { + .on('cancel', function() { modal.remove(); }) .on('fix', clickFix) @@ -73,7 +73,7 @@ iD.ui.Save = function(context) { modal.remove(); } - return function (selection) { + return function(selection) { var button = selection.append('button') .attr('class', 'save col12 disabled') .attr('tabindex', -1) diff --git a/test/index_packaged.html b/test/index_packaged.html index 874639094..c7d8ad7de 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -23,6 +23,7 @@ + diff --git a/test/spec/actions/delete_way.js b/test/spec/actions/delete_way.js index d550803fa..130f5f759 100644 --- a/test/spec/actions/delete_way.js +++ b/test/spec/actions/delete_way.js @@ -1,12 +1,12 @@ -describe("iD.actions.DeleteWay", function () { - it("removes the way from the graph", function () { +describe("iD.actions.DeleteWay", function() { + it("removes the way from the graph", function() { var way = iD.Way(), action = iD.actions.DeleteWay(way.id), graph = iD.Graph([way]).update(action); expect(graph.entity(way.id)).to.be.undefined; }); - it("removes a way from parent relations", function () { + it("removes a way from parent relations", function() { var way = iD.Way(), relation = iD.Relation({members: [{ id: way.id }]}), action = iD.actions.DeleteWay(way.id), @@ -14,7 +14,7 @@ describe("iD.actions.DeleteWay", function () { expect(_.pluck(graph.entity(relation.id).members, 'id')).not.to.contain(way.id); }); - it("deletes member nodes not referenced by another parent", function () { + it("deletes member nodes not referenced by another parent", function() { var node = iD.Node(), way = iD.Way({nodes: [node.id]}), action = iD.actions.DeleteWay(way.id), @@ -22,7 +22,7 @@ describe("iD.actions.DeleteWay", function () { expect(graph.entity(node.id)).to.be.undefined; }); - it("does not delete member nodes referenced by another parent", function () { + it("does not delete member nodes referenced by another parent", function() { var node = iD.Node(), way1 = iD.Way({nodes: [node.id]}), way2 = iD.Way({nodes: [node.id]}), @@ -31,7 +31,7 @@ describe("iD.actions.DeleteWay", function () { expect(graph.entity(node.id)).not.to.be.undefined; }); - it("deletes multiple member nodes", function () { + it("deletes multiple member nodes", function() { var a = iD.Node(), b = iD.Node(), way = iD.Way({nodes: [a.id, b.id]}), @@ -41,7 +41,7 @@ describe("iD.actions.DeleteWay", function () { expect(graph.entity(b.id)).to.be.undefined; }); - it("deletes a circular way's start/end node", function () { + it("deletes a circular way's start/end node", function() { var a = iD.Node(), b = iD.Node(), c = iD.Node(), @@ -53,7 +53,7 @@ describe("iD.actions.DeleteWay", function () { expect(graph.entity(c.id)).to.be.undefined; }); - it("does not delete member nodes with interesting tags", function () { + it("does not delete member nodes with interesting tags", function() { var node = iD.Node({tags: {highway: 'traffic_signals'}}), way = iD.Way({nodes: [node.id]}), action = iD.actions.DeleteWay(way.id), diff --git a/test/spec/actions/move_way.js b/test/spec/actions/move_way.js index 38f191a3c..24bc81593 100644 --- a/test/spec/actions/move_way.js +++ b/test/spec/actions/move_way.js @@ -1,5 +1,5 @@ -describe("iD.actions.MoveWay", function () { - it("moves all nodes in a way by the given amount", function () { +describe("iD.actions.MoveWay", function() { + it("moves all nodes in a way by the given amount", function() { var node1 = iD.Node({loc: [0, 0]}), node2 = iD.Node({loc: [5, 10]}), way = iD.Way({nodes: [node1.id, node2.id]}), @@ -14,7 +14,7 @@ describe("iD.actions.MoveWay", function () { expect(loc2[1]).to.be.closeTo( 7.866, 0.001); }); - it("moves repeated nodes only once", function () { + it("moves repeated nodes only once", function() { var node = iD.Node({loc: [0, 0]}), way = iD.Way({nodes: [node.id, node.id]}), delta = [2, 3], From b720a23897656dff6a9f5bf7450f2e800abab196 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Tue, 12 Feb 2013 16:37:13 -0500 Subject: [PATCH 273/332] Jshint fixes --- js/id/ui/modes.js | 2 +- js/id/ui/undo_redo.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/js/id/ui/modes.js b/js/id/ui/modes.js index a08b2b13b..b382ab295 100644 --- a/js/id/ui/modes.js +++ b/js/id/ui/modes.js @@ -64,5 +64,5 @@ iD.ui.Modes = function(context) { d3.select(document) .call(keybinding); - } + }; }; diff --git a/js/id/ui/undo_redo.js b/js/id/ui/undo_redo.js index f38066af4..9eef75364 100644 --- a/js/id/ui/undo_redo.js +++ b/js/id/ui/undo_redo.js @@ -43,5 +43,5 @@ iD.ui.UndoRedo = function(context) { .attr('data-original-title', iD.ui.tooltipHtml(redo || t('nothing_to_redo'), iD.ui.cmd('⌘⇧Z'))) .call(refreshTooltip); }); - } + }; }; From 4cbee01be18c50d1fb6742915e7de9677d2d521d Mon Sep 17 00:00:00 2001 From: Neogeografen Date: Tue, 12 Feb 2013 23:18:45 +0100 Subject: [PATCH 274/332] Update locale/da.js Working on more danish translation - not finish yet --- locale/da.js | 78 ++++++++++++++++++++++++++-------------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/locale/da.js b/locale/da.js index 217f597f6..347a58962 100644 --- a/locale/da.js +++ b/locale/da.js @@ -34,27 +34,27 @@ locale.da = { operations: { add: { annotation: { - point: "Added a point.", - vertex: "Added a node to a way." + point: "Tilføjede et punkt.", + vertex: "Tilføjede en node til en vej." } }, start: { annotation: { - line: "Started a line.", - area: "Started an area." + line: "Startede en linje.", + area: "Startede et område." } }, - 'continue': { + 'Forsæt': { annotation: { - line: "Continued a line.", - area: "Continued an area." + line: "Forsatte en linje.", + area: "Forsatte et område." } }, cancel_draw: { - annotation: "Cancelled drawing." + annotation: "Annulleret indtegning." }, change_tags: { - annotation: "Changed tags." + annotation: "Ændret tags." }, circularize: { title: "Circularize", @@ -74,17 +74,17 @@ locale.da = { area: "Squared the corners of an area." } }, - 'delete': { - title: "Delete", - description: "Remove this from the map.", + 'slet': { + title: "Slet", + description: "Fjern dette fra kortet.", key: "⌫", annotation: { - point: "Deleted a point.", - vertex: "Deleted a node from a way.", - line: "Deleted a line.", - area: "Deleted an area.", - relation: "Deleted a relation.", - multiple: "Deleted {n} objects." + point: "Slettede et punkt.", + vertex: "Slettede en node fra en vej.", + line: "Slettede en linje.", + area: "Slettede et område.", + relation: "Sletede en relation.", + multiple: "Slettede {n} objekter." } }, connect: { @@ -108,14 +108,14 @@ locale.da = { annotation: "Merged {n} lines." }, move: { - title: "Move", - description: "Move this to a different location.", + title: "Flyt", + description: "Flyt dette til anden lokation.", key: "M", annotation: { - point: "Moved a point.", - vertex: "Moved a node in a way.", - line: "Moved a line.", - area: "Moved an area." + point: "Flyttede et punktMoved.", + vertex: "Flyttede en node i en vej.", + line: "Flyttede en linje.", + area: "Flyttede et område." } }, reverse: { @@ -125,10 +125,10 @@ locale.da = { annotation: "Reversed a line." }, split: { - title: "Split", - description: "Split this into two ways at this point.", + title: "Del op", + description: "Del op i to vej ved dette punkt.", key: "X", - annotation: "Split a way." + annotation: "Del op en vej." } }, @@ -140,17 +140,17 @@ locale.da = { deprecated_tags: "Deprecated tags: {tags}" }, - save: "Save", - unsaved_changes: "You have unsaved changes", - save_help: "Save changes to OpenStreetMap, making them visible to other users", - no_changes: "You don't have any changes to save.", - save_error: "An error occurred while trying to save", - uploading_changes: "Uploading changes to OpenStreetMap.", - just_edited: "You Just Edited OpenStreetMap!", - okay: "Okay", + save: "Gem", + unsaved_changes: "Du har ændringer der ikke er gemt endnu", + save_help: "Gem ændringer til OpenStreetMap gør dem synlige for andre brugere", + no_changes: "Du har ingen ændringer til at gemme endnu.", + save_error: "Der skete en fejl da du prøvede at gemme", + uploading_changes: "Gemmer nu ændringer til OpenStreetMap.", + just_edited: "Du har lige rettede i OpenStreetMap!", + okay: "Ok", - "zoom-in": "Zoom ind", - "zoom-out": "Zoom ud", + "zoom-ind": "Zoom ind", + "zoom-ud": "Zoom ud", nothing_to_undo: "Nothing to undo.", nothing_to_redo: "Nothing to redo.", @@ -158,8 +158,8 @@ locale.da = { browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", inspector: { - no_documentation_combination: "This is no documentation available for this tag combination", - no_documentation_key: "This is no documentation available for this key", + no_documentation_combination: "Der er ingen dokumentation for denne tag kombination", + no_documentation_key: "Der er ingen dokumenation tilgængelig for denne nøgle", new_tag: "Nyt Tag" }, From 334f963f3cc57cb4f583072431960dc956f69ddd Mon Sep 17 00:00:00 2001 From: Saman Bemel-Benrud Date: Tue, 12 Feb 2013 17:31:16 -0500 Subject: [PATCH 275/332] fix change count, closes #638 --- css/app.css | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/css/app.css b/css/app.css index 2d0b91bc4..29f5b82b5 100644 --- a/css/app.css +++ b/css/app.css @@ -19,6 +19,7 @@ body { } .limiter { + position: relative; max-width: 1200px; } @@ -185,7 +186,8 @@ ul.toggle-list li a { border-top: 1px solid white; display:block; border-top: 1px solid rgba(0, 0, 0, .5); - text-wrap:no-wrap; + white-space:nowrap; + text-overflow:ellipsis; overflow:hidden; } ul.toggle-list li a:hover { background-color: #ececec;} @@ -352,8 +354,7 @@ button.save .count { button.save.has-count .count { display: block; position: absolute; - top: 0; - bottom: 0; + top: 5px; background: rgba(255, 255, 255, .5); color: #333; padding: 10px; From 3f19a293aae2929a15390d1cabbe6cff87bd01b5 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 14:04:39 -0800 Subject: [PATCH 276/332] Make selection less laggy --- js/id/behavior/select.js | 61 ++-------------------------------------- js/id/modes/select.js | 7 ++++- 2 files changed, 9 insertions(+), 59 deletions(-) diff --git a/js/id/behavior/select.js b/js/id/behavior/select.js index 7369af97c..c0b942520 100644 --- a/js/id/behavior/select.js +++ b/js/id/behavior/select.js @@ -1,13 +1,6 @@ iD.behavior.Select = function(context) { - var behavior = function(selection) { - - var timeout = null, - // the position of the first mousedown - pos = null; - - function click(event) { - d3.event = event; + function click() { var datum = d3.event.target.__data__; if (datum instanceof iD.Entity) { if (d3.event.shiftKey) { @@ -20,59 +13,11 @@ iD.behavior.Select = function(context) { } } - function mousedown() { - var datum = d3.event.target.__data__; - pos = [d3.event.clientX, d3.event.clientY]; - if (datum instanceof iD.Entity || (datum && datum.type === 'midpoint')) { - selection - .on('mousemove.select', mousemove) - .on('touchmove.select', mousemove); - - // we've seen a mousedown within 400ms of this one, so ignore - // both because they will be a double click - if (timeout !== null) { - window.clearTimeout(timeout); - selection.on('mousemove.select', null); - timeout = null; - } else { - // queue the click handler to fire in 400ms if no other clicks - // are detected - timeout = window.setTimeout((function(event) { - return function() { - click(event); - timeout = null; - selection.on('mousemove.select', null); - }; - // save the event for the click handler - })(d3.event), 200); - } - } - } - - // allow mousemoves to cancel the click - function mousemove() { - if (iD.geo.dist([d3.event.clientX, d3.event.clientY], pos) > 4) { - window.clearTimeout(timeout); - timeout = null; - } - } - - function mouseup() { - selection.on('mousemove.select', null); - if (pos && d3.event.clientX === pos[0] && d3.event.clientY === pos[1] && - !(d3.event.target.__data__ instanceof iD.Entity)) { - context.enter(iD.modes.Browse(context)); - } - } - - selection - .on('mousedown.select', mousedown) - .on('mouseup.select', mouseup) - .on('touchstart.select', mousedown); + selection.on('click.select', click); }; behavior.off = function(selection) { - selection.on('mousedown.select', null); + selection.on('click.select', null); }; return behavior; diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 9b12c8134..db2b5f238 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -6,6 +6,7 @@ iD.modes.Select = function(context, selection, initial) { var inspector = iD.ui.inspector().initial(!!initial), keybinding = d3.keybinding('select'), + radialTime = null, behaviors = [ iD.behavior.Hover(), iD.behavior.Select(context), @@ -174,7 +175,9 @@ iD.modes.Select = function(context, selection, initial) { loc = entity.loc; } - context.surface().call(radialMenu, context.projection(loc)); + radialTime = window.setTimeout(function() { + context.surface().call(radialMenu, context.projection(loc)); + }, 300); } }; @@ -183,6 +186,8 @@ iD.modes.Select = function(context, selection, initial) { changeTags(singular(), inspector.tags()); } + if (radialTime) window.clearTimeout(radialTime); + context.container() .select('.inspector-wrap') .style('display', 'none') From 419aa088e33a865b87fb07cdb943528121072554 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 14:10:17 -0800 Subject: [PATCH 277/332] RadialMenu#center --- js/id/modes/select.js | 8 ++++---- js/id/ui/radial_menu.js | 11 +++++++++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/js/id/modes/select.js b/js/id/modes/select.js index db2b5f238..f1a35d225 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -169,14 +169,14 @@ iD.modes.Select = function(context, selection, initial) { radialMenu = iD.ui.RadialMenu(operations); if (d3.event && !initial) { - var loc = context.map().mouseCoordinates(); - if (entity && entity.type === 'node') { - loc = entity.loc; + radialMenu.center(context.projection(entity.loc)); + } else { + radialMenu.center(d3.mouse(context.surface().node())); } radialTime = window.setTimeout(function() { - context.surface().call(radialMenu, context.projection(loc)); + context.surface().call(radialMenu); }, 300); } }; diff --git a/js/id/ui/radial_menu.js b/js/id/ui/radial_menu.js index 106a48196..e718cfe51 100644 --- a/js/id/ui/radial_menu.js +++ b/js/id/ui/radial_menu.js @@ -1,7 +1,8 @@ iD.ui.RadialMenu = function(operations) { - var menu; + var menu, + center = [0, 0]; - var radialMenu = function(selection, center) { + var radialMenu = function(selection) { if (!operations.length) return; @@ -94,5 +95,11 @@ iD.ui.RadialMenu = function(operations) { } }; + radialMenu.center = function(_) { + if (!arguments.length) return center; + center = _; + return radialMenu; + }; + return radialMenu; }; From 0dbdd7c79766658353a2374933bbfd3a423f057b Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 14:20:07 -0800 Subject: [PATCH 278/332] Shift-click deselects a selected entity --- js/id/behavior/select.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/js/id/behavior/select.js b/js/id/behavior/select.js index c0b942520..f3d52a71c 100644 --- a/js/id/behavior/select.js +++ b/js/id/behavior/select.js @@ -2,14 +2,20 @@ iD.behavior.Select = function(context) { var behavior = function(selection) { function click() { var datum = d3.event.target.__data__; - if (datum instanceof iD.Entity) { - if (d3.event.shiftKey) { - context.enter(iD.modes.Select(context, context.selection().concat([datum.id]))); - } else { - context.enter(iD.modes.Select(context, [datum.id])); - } - } else if (!d3.event.shiftKey) { + if (!(datum instanceof iD.Entity) && !d3.event.shiftKey) { context.enter(iD.modes.Browse(context)); + + } else if (!d3.event.shiftKey) { + context.enter(iD.modes.Select(context, [datum.id])); + + } else if (context.selection().indexOf(datum.id) >= 0) { + var selection = _.without(context.selection(), datum.id); + context.enter(selection.length ? + iD.modes.Select(context, selection) : + iD.modes.Browse(context)); + + } else { + context.enter(iD.modes.Select(context, context.selection().concat([datum.id]))); } } From 89fe4bff09695b0113e4380fb3b85b6f39e73c29 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 14:28:58 -0800 Subject: [PATCH 279/332] Only add vertex when double-clicking the selected entity Previously double-clicking would add a vertex to any way, as long as anything was selected. --- js/id/behavior/select.js | 4 +++- js/id/modes/select.js | 19 +++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/js/id/behavior/select.js b/js/id/behavior/select.js index f3d52a71c..6b60b2da0 100644 --- a/js/id/behavior/select.js +++ b/js/id/behavior/select.js @@ -6,7 +6,9 @@ iD.behavior.Select = function(context) { context.enter(iD.modes.Browse(context)); } else if (!d3.event.shiftKey) { - context.enter(iD.modes.Select(context, [datum.id])); + // Avoid re-entering Select mode with same entity. + if (context.selection().length !== 1 || context.selection()[0] !== datum.id) + context.enter(iD.modes.Select(context, [datum.id])); } else if (context.selection().indexOf(datum.id) >= 0) { var selection = _.without(context.selection(), datum.id); diff --git a/js/id/modes/select.js b/js/id/modes/select.js index f1a35d225..c17a02ca1 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -6,7 +6,7 @@ iD.modes.Select = function(context, selection, initial) { var inspector = iD.ui.inspector().initial(!!initial), keybinding = d3.keybinding('select'), - radialTime = null, + timeout = null, behaviors = [ iD.behavior.Hover(), iD.behavior.Select(context), @@ -161,24 +161,27 @@ iD.modes.Select = function(context, selection, initial) { .call(keybinding); context.surface() - .on('dblclick.select', dblclick) .selectAll("*") .filter(selected) .classed('selected', true); radialMenu = iD.ui.RadialMenu(operations); + var showMenu = d3.event && !initial; - if (d3.event && !initial) { + if (showMenu) { if (entity && entity.type === 'node') { radialMenu.center(context.projection(entity.loc)); } else { radialMenu.center(d3.mouse(context.surface().node())); } - - radialTime = window.setTimeout(function() { - context.surface().call(radialMenu); - }, 300); } + + timeout = window.setTimeout(function() { + if (showMenu) context.surface().call(radialMenu); + + context.surface() + .on('dblclick.select', dblclick) + }, 300); }; mode.exit = function() { @@ -186,7 +189,7 @@ iD.modes.Select = function(context, selection, initial) { changeTags(singular(), inspector.tags()); } - if (radialTime) window.clearTimeout(radialTime); + if (timeout) window.clearTimeout(timeout); context.container() .select('.inspector-wrap') From 6bebb9197c9280103ff880d71b67e8df81f28631 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 14:58:54 -0800 Subject: [PATCH 280/332] 200ms --- js/id/modes/select.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/modes/select.js b/js/id/modes/select.js index c17a02ca1..ad1571d20 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -181,7 +181,7 @@ iD.modes.Select = function(context, selection, initial) { context.surface() .on('dblclick.select', dblclick) - }, 300); + }, 200); }; mode.exit = function() { From 3cce5b28dd2771240de3e5da26ba02e8651f3377 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 15:22:38 -0800 Subject: [PATCH 281/332] Fix, expand iD.behavior.Select tests --- js/id/behavior/select.js | 5 +++-- test/spec/behavior/select.js | 41 +++++++++++++++++++----------------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/js/id/behavior/select.js b/js/id/behavior/select.js index 6b60b2da0..162535764 100644 --- a/js/id/behavior/select.js +++ b/js/id/behavior/select.js @@ -2,8 +2,9 @@ iD.behavior.Select = function(context) { var behavior = function(selection) { function click() { var datum = d3.event.target.__data__; - if (!(datum instanceof iD.Entity) && !d3.event.shiftKey) { - context.enter(iD.modes.Browse(context)); + if (!(datum instanceof iD.Entity)) { + if (!d3.event.shiftKey) + context.enter(iD.modes.Browse(context)); } else if (!d3.event.shiftKey) { // Avoid re-entering Select mode with same entity. diff --git a/test/spec/behavior/select.js b/test/spec/behavior/select.js index 5e9bf9ea3..ecbb7e7b4 100644 --- a/test/spec/behavior/select.js +++ b/test/spec/behavior/select.js @@ -20,6 +20,8 @@ describe("iD.behavior.Select", function() { .enter().append('circle') .attr('class', function(d) { return d.id; }); + context.enter(iD.modes.Browse(context)); + behavior = iD.behavior.Select(context); context.install(behavior); }); @@ -30,32 +32,33 @@ describe("iD.behavior.Select", function() { container.remove(); }); - specify("click on entity selects the entity", function(done) { - happen.mousedown(context.surface().select('.' + a.id).node()); - window.setTimeout(function() { - expect(context.selection()).to.eql([a.id]); - done(); - }, 600); + specify("click on entity selects the entity", function() { + happen.click(context.surface().select('.' + a.id).node()); + expect(context.selection()).to.eql([a.id]); }); - specify("click on empty space clears the selection", function(done) { + specify("click on empty space clears the selection", function() { context.enter(iD.modes.Select(context, [a.id])); happen.click(context.surface().node()); - happen.mousedown(context.surface().node()); - happen.mouseup(context.surface().node()); - window.setTimeout(function() { - expect(context.selection()).to.eql([]); - done(); - }, 600); + expect(context.mode().id).to.eql('browse'); }); - specify("shift-click on entity adds the entity to the selection", function(done) { + specify("shift-click on unselected entity adds it to the selection", function() { context.enter(iD.modes.Select(context, [a.id])); - happen.mousedown(context.surface().select('.' + b.id).node(), {shiftKey: true}); - window.setTimeout(function() { - expect(context.selection()).to.eql([a.id, b.id]); - done(); - }, 600); + happen.click(context.surface().select('.' + b.id).node(), {shiftKey: true}); + expect(context.selection()).to.eql([a.id, b.id]); + }); + + specify("shift-click on selected entity removes it from the selection", function() { + context.enter(iD.modes.Select(context, [a.id, b.id])); + happen.click(context.surface().select('.' + b.id).node(), {shiftKey: true}); + expect(context.selection()).to.eql([a.id]); + }); + + specify("shift-click on last selected entity clears the selection", function() { + context.enter(iD.modes.Select(context, [a.id])); + happen.click(context.surface().select('.' + a.id).node(), {shiftKey: true}); + expect(context.mode().id).to.eql('browse'); }); specify("shift-click on empty space leaves the selection unchanged", function() { From f071e9cf189e0e657fa7d302174d4f082d6bc6d1 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 15:28:14 -0800 Subject: [PATCH 282/332] Fix da translation --- locale/da.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/locale/da.js b/locale/da.js index 347a58962..342273223 100644 --- a/locale/da.js +++ b/locale/da.js @@ -44,7 +44,7 @@ locale.da = { area: "Startede et område." } }, - 'Forsæt': { + 'continue': { annotation: { line: "Forsatte en linje.", area: "Forsatte et område." @@ -74,7 +74,7 @@ locale.da = { area: "Squared the corners of an area." } }, - 'slet': { + 'delete': { title: "Slet", description: "Fjern dette fra kortet.", key: "⌫", @@ -149,8 +149,8 @@ locale.da = { just_edited: "Du har lige rettede i OpenStreetMap!", okay: "Ok", - "zoom-ind": "Zoom ind", - "zoom-ud": "Zoom ud", + "zoom-in": "Zoom ind", + "zoom-out": "Zoom ud", nothing_to_undo: "Nothing to undo.", nothing_to_redo: "Nothing to redo.", From dc2dbbe183dcb327a08488a9bd3854fe4e68e086 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 15:45:12 -0800 Subject: [PATCH 283/332] Extract iD.ui.Zoom, add tooltips with key hint --- index.html | 1 + js/id/id.js | 2 ++ js/id/ui.js | 21 +++------------------ js/id/ui/zoom.js | 40 ++++++++++++++++++++++++++++++++++++++++ locale/da.js | 8 +++++--- locale/de.js | 8 +++++--- locale/en.js | 8 +++++--- locale/es.js | 8 +++++--- locale/fr.js | 8 +++++--- locale/ja.js | 8 +++++--- locale/lv.js | 8 +++++--- locale/tr.js | 8 +++++--- 12 files changed, 86 insertions(+), 42 deletions(-) create mode 100644 js/id/ui/zoom.js diff --git a/index.html b/index.html index 4ea9d9721..009ee4133 100644 --- a/index.html +++ b/index.html @@ -82,6 +82,7 @@ + diff --git a/js/id/id.js b/js/id/id.js index f0747ad02..5db609593 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -85,6 +85,8 @@ window.iD = function () { context.projection = map.projection; context.tail = map.tail; context.redraw = map.redraw; + context.zoomIn = map.zoomIn; + context.zoomOut = map.zoomOut; context.container = function(_) { if (!arguments.length) return container; diff --git a/js/id/ui.js b/js/id/ui.js index 535c825eb..7ae2c9e28 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -37,20 +37,9 @@ iD.ui = function(context) { .attr('class', 'button-wrap col1') .call(iD.ui.Save(context)); - var zoom = container.append('div') + container.append('div') .attr('class', 'zoombuttons map-control') - .selectAll('button') - .data([['zoom-in', '+', map.zoomIn, t('zoom-in')], ['zoom-out', '-', map.zoomOut, t('zoom-out')]]) - .enter() - .append('button') - .attr('tabindex', -1) - .attr('class', function(d) { return d[0]; }) - .attr('title', function(d) { return d[3]; }) - .on('click.editor', function(d) { return d[2](); }) - .append('span') - .attr('class', function(d) { - return d[0] + ' icon'; - }); + .call(iD.ui.Zoom(context)); if (navigator.geolocation) { container.append('div') @@ -139,11 +128,7 @@ iD.ui = function(context) { .on('←', pan([pa, 0])) .on('↑', pan([0, pa])) .on('→', pan([-pa, 0])) - .on('↓', pan([0, -pa])) - .on('⇧=', function() { map.zoomIn(); }) - .on('+', function() { map.zoomIn(); }) - .on('-', function() { map.zoomOut(); }) - .on('dash', function() { map.zoomOut(); }); + .on('↓', pan([0, -pa])); d3.select(document) .call(keybinding); diff --git a/js/id/ui/zoom.js b/js/id/ui/zoom.js new file mode 100644 index 000000000..823bc120f --- /dev/null +++ b/js/id/ui/zoom.js @@ -0,0 +1,40 @@ +iD.ui.Zoom = function(context) { + var zooms = [{ + id: 'zoom-in', + title: t('zoom.in'), + action: context.zoomIn, + key: '+' + }, { + id: 'zoom-out', + title: t('zoom.out'), + action: context.zoomOut, + key: '-' + }]; + + return function(selection) { + var button = selection.selectAll('button') + .data(zooms) + .enter().append('button') + .attr('tabindex', -1) + .attr('class', function(d) { return d.id; }) + .on('click.editor', function(d) { d.action(); }) + .call(bootstrap.tooltip() + .placement('right') + .html(true) + .title(function(d) { + return iD.ui.tooltipHtml(d.title, d.key); + })); + + button.append('span') + .attr('class', function(d) { return d.id + ' icon'; }); + + var keybinding = d3.keybinding('zoom') + .on('+', function() { context.zoomIn(); }) + .on('-', function() { context.zoomOut(); }) + .on('⇧=', function() { context.zoomIn(); }) + .on('dash', function() { context.zoomOut(); }); + + d3.select(document) + .call(keybinding); + } +}; diff --git a/locale/da.js b/locale/da.js index 342273223..29e23c6f2 100644 --- a/locale/da.js +++ b/locale/da.js @@ -149,9 +149,6 @@ locale.da = { just_edited: "Du har lige rettede i OpenStreetMap!", okay: "Ok", - "zoom-in": "Zoom ind", - "zoom-out": "Zoom ud", - nothing_to_undo: "Nothing to undo.", nothing_to_redo: "Nothing to redo.", @@ -197,5 +194,10 @@ locale.da = { source_switch: { live: "live", dev: "dev" + }, + + zoom: { + in: "Zoom ind", + out: "Zoom ud" } }; diff --git a/locale/de.js b/locale/de.js index 543eaab2d..f1f3ef850 100644 --- a/locale/de.js +++ b/locale/de.js @@ -145,9 +145,6 @@ locale.de = { just_edited: "Sie haben gerade OpenStreetMap editiert!", okay: "OK", - "zoom-in": "Hineinzoomen", - "zoom-out": "Herauszoomen", - nothing_to_undo: "Nichts zum Rückgängigmachen.", nothing_to_redo: "Nichts zum Wiederherstellen.", @@ -193,5 +190,10 @@ locale.de = { source_switch: { live: "live", dev: "dev" + }, + + zoom: { + in: "Hineinzoomen", + out: "Herauszoomen" } }; diff --git a/locale/en.js b/locale/en.js index 007c8e5be..501f36a37 100644 --- a/locale/en.js +++ b/locale/en.js @@ -145,9 +145,6 @@ locale.en = { just_edited: "You Just Edited OpenStreetMap!", okay: "Okay", - "zoom-in": "Zoom In", - "zoom-out": "Zoom Out", - nothing_to_undo: "Nothing to undo.", nothing_to_redo: "Nothing to redo.", @@ -193,5 +190,10 @@ locale.en = { source_switch: { live: "live", dev: "dev" + }, + + zoom: { + in: "Zoom In", + out: "Zoom Out" } }; diff --git a/locale/es.js b/locale/es.js index 4bb5f3959..0468405b5 100644 --- a/locale/es.js +++ b/locale/es.js @@ -145,9 +145,6 @@ locale.es = { just_edited: "Acabas de editar OpenStreetMap!", //"You Just Edited OpenStreetMap!", okay: "OK", //"Okay", - "zoom-in": "Aumentar", // "Zoom In", - "zoom-out": "Alejar", //"Zoom Out", - nothing_to_undo: "Nada para deshacer", //"Nothing to undo.", nothing_to_redo: "Nada para rehacer", //"Nothing to redo.", @@ -193,5 +190,10 @@ locale.es = { source_switch: { live: "en vivo", //"live", dev: "dev" + }, + + zoom: { + in: "Aumentar", // "Zoom In", + out: "Alejar" //"Zoom Out", } }; diff --git a/locale/fr.js b/locale/fr.js index adf4a2d26..4530924a0 100644 --- a/locale/fr.js +++ b/locale/fr.js @@ -145,9 +145,6 @@ locale.fr = { just_edited: "Vous venez de participer à OpenStreetMap!", okay: "Okay", - "zoom-in": "Zoomer", - "zoom-out": "Dézoomer", - nothing_to_undo: "Rien à annuler.", nothing_to_redo: "Rien à refaire.", @@ -193,5 +190,10 @@ locale.fr = { source_switch: { live: "live", dev: "dev" + }, + + zoom: { + in: "Zoomer", + out: "Dézoomer" } }; diff --git a/locale/ja.js b/locale/ja.js index b002f54bf..ada737bd1 100644 --- a/locale/ja.js +++ b/locale/ja.js @@ -145,9 +145,6 @@ locale.ja = { just_edited: "OpenStreetMap編集完了!", okay: "OK", - "zoom-in": "ズームイン", - "zoom-out": "ズームアウト", - nothing_to_undo: "やり直す変更点がありません", nothing_to_redo: "やり直した変更点がありません", @@ -193,5 +190,10 @@ locale.ja = { source_switch: { live: "本番サーバ", dev: "開発サーバ" + }, + + zoom: { + in: "ズームイン", + out: "ズームアウト" } }; diff --git a/locale/lv.js b/locale/lv.js index 1d55450f8..fd8be7cfe 100644 --- a/locale/lv.js +++ b/locale/lv.js @@ -145,9 +145,6 @@ locale.lv = { just_edited: "Jūs nupat rediģējāt OpenStreetMap", okay: "Labi", - "zoom-in": "Pietuvināt", - "zoom-out": "Attālināt", - nothing_to_undo: "Nav nekā, ko atcelt", nothing_to_redo: "Nav nekā, ko atsaukt", @@ -193,5 +190,10 @@ locale.lv = { source_switch: { live: "live", dev: "dev" + }, + + zoom: { + in: "Pietuvināt", + out: "Attālināt" } }; diff --git a/locale/tr.js b/locale/tr.js index d14f08ecb..a9b3e4b47 100644 --- a/locale/tr.js +++ b/locale/tr.js @@ -145,9 +145,6 @@ locale.tr = { just_edited: "Şu an OpenStreetMap'de bir değişiklik yaptınız!", okay: "Tamam", - "zoom-in": "Yaklaş", - "zoom-out": "Uzaklaş", - nothing_to_undo: "Geri alınacak birşey yok.", nothing_to_redo: "Tekrar yapılacak birşey yok.", @@ -193,5 +190,10 @@ locale.tr = { source_switch: { live: "canlı", dev: "geliştirme" + }, + + zoom: { + in: "Yaklaş", + out: "Uzaklaş" } }; From b9860f222fdbfab04f77313db7e66673a5d209f1 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 15:52:17 -0800 Subject: [PATCH 284/332] Cleanup; consistent tooltips on remaining buttons --- css/app.css | 4 ++-- js/id/ui.js | 19 ++++++++++--------- js/id/ui/geocoder.js | 10 +++++++--- js/id/ui/geolocate.js | 25 ++++++++++++++----------- js/id/ui/layerswitcher.js | 9 ++++++--- 5 files changed, 39 insertions(+), 28 deletions(-) diff --git a/css/app.css b/css/app.css index 29f5b82b5..9f9e54c01 100644 --- a/css/app.css +++ b/css/app.css @@ -849,13 +849,13 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} margin: 4px; } -.geocode-control div { +.geocode-control div.content { top: 50px; width: 340px; margin: 4px; padding: 5px; } -.geocode-control div span { +.geocode-control div.content span { display: inline-block; border-bottom: 1px solid #333; } diff --git a/js/id/ui.js b/js/id/ui.js index 7ae2c9e28..e1daad3a1 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -38,20 +38,21 @@ iD.ui = function(context) { .call(iD.ui.Save(context)); container.append('div') - .attr('class', 'zoombuttons map-control') + .attr('class', 'map-control zoombuttons') .call(iD.ui.Zoom(context)); - if (navigator.geolocation) { - container.append('div') - .call(iD.ui.geolocate(map)); - } + container.append('div') + .attr('class', 'map-control geocode-control') + .call(iD.ui.Geocoder(context)); - container.append('div').attr('class', 'geocode-control map-control') - .call(iD.ui.geocoder(context)); - - container.append('div').attr('class', 'map-control layerswitcher-control') + container.append('div') + .attr('class', 'map-control layerswitcher-control') .call(iD.ui.layerswitcher(context)); + container.append('div') + .attr('class', 'map-control geolocate-control') + .call(iD.ui.Geolocate(map)); + container.append('div') .style('display', 'none') .attr('class', 'inspector-wrap fr col5'); diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index 7b79c2158..d0479e61d 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -1,4 +1,4 @@ -iD.ui.geocoder = function(context) { +iD.ui.Geocoder = function(context) { function resultExtent(bounds) { return new iD.geo.Extent( [parseFloat(bounds[3]), parseFloat(bounds[0])], @@ -75,8 +75,12 @@ iD.ui.geocoder = function(context) { var button = selection.append('button') .attr('tabindex', -1) .attr('title', t('geocoder.title')) - .html('') - .on('click', toggle); + .on('click', toggle) + .call(bootstrap.tooltip() + .placement('right')); + + button.append('span') + .attr('class', 'icon geocode'); var gcForm = selection.append('form'); diff --git a/js/id/ui/geolocate.js b/js/id/ui/geolocate.js index 14b060e8a..5356039dd 100644 --- a/js/id/ui/geolocate.js +++ b/js/id/ui/geolocate.js @@ -1,4 +1,8 @@ -iD.ui.geolocate = function(map) { +iD.ui.Geolocate = function(map) { + function click() { + navigator.geolocation.getCurrentPosition( + success, error); + } function success(position) { map.center([position.coords.longitude, position.coords.latitude]); @@ -7,17 +11,16 @@ iD.ui.geolocate = function(map) { function error() { } return function(selection) { - selection - .attr('class', 'geolocate-control map-control') - .append('button') + if (!navigator.geolocation) return; + + var button = selection.append('button') .attr('tabindex', -1) .attr('title', 'Show My Location') - .on('click', function() { - navigator.geolocation.getCurrentPosition( - success, error); - }) - .append('span') - .attr('class','icon geolocate'); - }; + .on('click', click) + .call(bootstrap.tooltip() + .placement('right')); + button.append('span') + .attr('class', 'icon geolocate'); + }; }; diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index 9ae4f3fbe..60fd674e0 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -23,10 +23,13 @@ iD.ui.layerswitcher = function(context) { .attr('tabindex', -1) .attr('class', 'fillD') .attr('title', t('layerswitcher.description')) - .html("") - .on('click.layerswitcher-toggle', toggle); + .on('click.layerswitcher-toggle', toggle) + .call(bootstrap.tooltip() + .placement('right')); + + button.append('span') + .attr('class', 'layers icon'); - function show() { setVisible(true); } function hide() { setVisible(false); } function toggle() { setVisible(content.classed('hide')); } From 8d0225e9389c9cf81e145676c7be5af9018ed541 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 16:11:22 -0800 Subject: [PATCH 285/332] i18n for geolocate --- js/id/ui/geolocate.js | 2 +- locale/en.js | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/js/id/ui/geolocate.js b/js/id/ui/geolocate.js index 5356039dd..d1d3f3772 100644 --- a/js/id/ui/geolocate.js +++ b/js/id/ui/geolocate.js @@ -15,7 +15,7 @@ iD.ui.Geolocate = function(map) { var button = selection.append('button') .attr('tabindex', -1) - .attr('title', 'Show My Location') + .attr('title', t('geolocate.title')) .on('click', click) .call(bootstrap.tooltip() .placement('right')); diff --git a/locale/en.js b/locale/en.js index 501f36a37..7581b5fcb 100644 --- a/locale/en.js +++ b/locale/en.js @@ -168,6 +168,10 @@ locale.en = { no_results: "Couldn't locate a place named '{name}'" }, + geolocate: { + title: "Show My Location" + }, + description: "Description", logout: "logout", From 1c5a894f1e37bb5ebbeb66eaf2938df678610d0b Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 16:16:52 -0800 Subject: [PATCH 286/332] Add to translation README --- locale/README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/locale/README.md b/locale/README.md index b1610b311..0feb1f631 100644 --- a/locale/README.md +++ b/locale/README.md @@ -22,6 +22,10 @@ Let's look at an example line from `en.js`: no_results: "Couldn't locate a place named '{name}'" ``` +`no_results` is the translation _key_, and should not be translated. +The text to the right of the colon, `"Couldn't locate a place named '{name}'"`, +is the string to be translated. + The word in brackets, `{name}`, should **not** be translated into a new language: it's replaced with a place name when iD presents the text. So a French translation would look like @@ -30,6 +34,15 @@ a French translation would look like no_results: "Impossible de localiser l'endroit nommé '{name}'" ``` +For technical reasons, a few translation keys are quoted. For example: + +``` +'delete': "Delete" +``` + +Only translate the value to the right of the colon, not the quoted key on +the left. + ## License Contributions to translations are under the same liberal From 7da3ef791f479721bb33f022194548aaa7c4f63b Mon Sep 17 00:00:00 2001 From: Saman Bemel-Benrud Date: Tue, 12 Feb 2013 19:17:00 -0500 Subject: [PATCH 287/332] style and layout refinements to layer switcher. --- css/app.css | 23 +++++++++-------------- js/id/ui/layerswitcher.js | 13 +++++++------ 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/css/app.css b/css/app.css index 29f5b82b5..3593628d7 100644 --- a/css/app.css +++ b/css/app.css @@ -225,7 +225,7 @@ ul.link-list li:last-child { .fillD { background:rgba(0,0,0,.8); - color: #a9a9a9; + color: #6C6C6C; } @@ -660,10 +660,11 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} } .map-overlay { - width: 150px; - position:absolute; - left:40px; - top:0; + right: 75%; + max-width: 260px; + min-width: 210px; + position: fixed; + left: 40px; display: block; border-radius: 4px; } @@ -689,10 +690,6 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} top:190px; } -.layerswitcher-control .map-overlay { - width:250px; -} - .nudge-container { margin-top: 10px; } @@ -702,10 +699,8 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} font-size:10px; padding:0 5px 3px 5px; background: white; - border:0; text-transform: uppercase; font-weight: bold; - } .layerswitcher-control .adjustments button:hover { @@ -742,9 +737,9 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} .layerswitcher-control .nudge { text-indent: -9999px; overflow: hidden; - width:20px; + width:16.6666%; border-radius: 0; - margin-right:1px; + border-right: 1px solid rgba(0, 0, 0, .5); position: relative; } @@ -787,7 +782,7 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} } .layerswitcher-control .reset { - width: 45px; + width: 33.3333%; border-radius: 0 4px 4px 0; } diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index 9ae4f3fbe..312605f54 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -133,7 +133,7 @@ iD.ui.layerswitcher = function(context) { return d.data.name; }); layerLinks.exit().remove(); - layerLinks.enter() + var LayerInner = layerLinks.enter() .append('li') .append('a') .attr('data-original-title', function(d) { @@ -141,18 +141,19 @@ iD.ui.layerswitcher = function(context) { }) .attr('href', '#') .attr('class', 'layer') - .text(function(d) { - return d.data.name; - }) .each(function(d) { // only set tooltips for layers with tooltips if (d.data.description) { d3.select(this).call(bootstrap.tooltip().placement('right')); } }) - .on('click.set-source', clickSetSource) - .insert('span') + .on('click.set-source', clickSetSource); + LayerInner.insert('span') .attr('class','icon toggle'); + LayerInner.insert('span').text(function(d) { + return d.data.name; + }); + selectLayer(context.background().source()); } From 5c9832e2ef5a28f89d9a364b5608fc9d9e16fc6e Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 16:18:55 -0800 Subject: [PATCH 288/332] Fix test --- test/spec/ui/geocoder.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/spec/ui/geocoder.js b/test/spec/ui/geocoder.js index 54a7e571c..91e107ee2 100644 --- a/test/spec/ui/geocoder.js +++ b/test/spec/ui/geocoder.js @@ -1,6 +1,6 @@ -describe("iD.ui.geocoder", function () { +describe("iD.ui.Geocoder", function () { it('can be instantiated', function () { - var geocoder = iD.ui.geocoder(); + var geocoder = iD.ui.Geocoder(); expect(geocoder).to.be.ok; }); }); From 50e01150a7379b91867747e3c3eb77c16f0137c8 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 16:25:39 -0800 Subject: [PATCH 289/332] Fix global leak --- js/id/ui/tag_reference.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/ui/tag_reference.js b/js/id/ui/tag_reference.js index cdb22b505..6b7b9fb9f 100644 --- a/js/id/ui/tag_reference.js +++ b/js/id/ui/tag_reference.js @@ -19,7 +19,7 @@ iD.ui.tagReference = function(selection) { header.append('span') .text(g('title')); - referenceBody = selection.append('div') + var referenceBody = selection.append('div') .attr('class','modal-section fillL2'); referenceBody From 1e60b0b7fa7c2057cc7f22478fe2e71f16aada3a Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 16:28:02 -0800 Subject: [PATCH 290/332] iD constructors are camel case --- js/id/behavior/lasso.js | 2 +- js/id/modes/select.js | 2 +- js/id/ui.js | 6 +++--- js/id/ui/commit.js | 2 +- js/id/ui/contributors.js | 2 +- js/id/ui/geocoder.js | 2 +- js/id/ui/inspector.js | 4 ++-- js/id/ui/lasso.js | 6 +++--- js/id/ui/layerswitcher.js | 4 ++-- js/id/ui/save.js | 4 ++-- js/id/ui/success.js | 2 +- js/id/ui/toggle.js | 2 +- js/id/ui/userpanel.js | 2 +- test/spec/ui/inspector.js | 4 ++-- 14 files changed, 22 insertions(+), 22 deletions(-) diff --git a/js/id/behavior/lasso.js b/js/id/behavior/lasso.js index 0a26f311d..6196cbf2e 100644 --- a/js/id/behavior/lasso.js +++ b/js/id/behavior/lasso.js @@ -12,7 +12,7 @@ iD.behavior.Lasso = function(context) { pos = [d3.event.clientX, d3.event.clientY]; - lasso = iD.ui.lasso().a(d3.mouse(context.surface().node())); + lasso = iD.ui.Lasso().a(d3.mouse(context.surface().node())); context.surface().call(lasso); diff --git a/js/id/modes/select.js b/js/id/modes/select.js index ad1571d20..ac6ad4943 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -4,7 +4,7 @@ iD.modes.Select = function(context, selection, initial) { button: 'browse' }; - var inspector = iD.ui.inspector().initial(!!initial), + var inspector = iD.ui.Inspector().initial(!!initial), keybinding = d3.keybinding('select'), timeout = null, behaviors = [ diff --git a/js/id/ui.js b/js/id/ui.js index e1daad3a1..48e5682bf 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -47,7 +47,7 @@ iD.ui = function(context) { container.append('div') .attr('class', 'map-control layerswitcher-control') - .call(iD.ui.layerswitcher(context)); + .call(iD.ui.LayerSwitcher(context)); container.append('div') .attr('class', 'map-control geolocate-control') @@ -103,7 +103,7 @@ iD.ui = function(context) { linkList.append('li') .attr('id', 'user-list') - .call(iD.ui.contributors(context)); + .call(iD.ui.Contributors(context)); window.onbeforeunload = function() { history.save(); @@ -142,7 +142,7 @@ iD.ui = function(context) { map.centerZoom([-77.02271, 38.90085], 20); } - userContainer.call(iD.ui.userpanel(connection) + userContainer.call(iD.ui.UserPanel(connection) .on('logout.editor', connection.logout) .on('login.editor', connection.authenticate)); diff --git a/js/id/ui/commit.js b/js/id/ui/commit.js index 8d724a732..6708ece49 100644 --- a/js/id/ui/commit.js +++ b/js/id/ui/commit.js @@ -1,4 +1,4 @@ -iD.ui.commit = function(context) { +iD.ui.Commit = function(context) { var event = d3.dispatch('cancel', 'save', 'fix'); function zipSame(d) { diff --git a/js/id/ui/contributors.js b/js/id/ui/contributors.js index 596c576ad..2389f266a 100644 --- a/js/id/ui/contributors.js +++ b/js/id/ui/contributors.js @@ -1,4 +1,4 @@ -iD.ui.contributors = function(context) { +iD.ui.Contributors = function(context) { function update(selection) { var users = {}, limit = 3, diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index d0479e61d..cc0ce205c 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -64,7 +64,7 @@ iD.ui.Geocoder = function(context) { function setVisible(show) { if (show !== shown) { button.classed('active', show); - gcForm.call(iD.ui.toggle(show)); + gcForm.call(iD.ui.Toggle(show)); if (!show) resultsList.classed('hide', !show); if (show) inputNode.node().focus(); else inputNode.node().blur(); diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js index 836f73cff..173594101 100644 --- a/js/id/ui/inspector.js +++ b/js/id/ui/inspector.js @@ -1,4 +1,4 @@ -iD.ui.inspector = function() { +iD.ui.Inspector = function() { var event = d3.dispatch('changeTags', 'close'), taginfo = iD.taginfo(), initial = false, @@ -43,7 +43,7 @@ iD.ui.inspector = function() { .attr('class', 'inspector-buttons pad1 fillD') .call(drawButtons); - inspector.call(iD.ui.toggle(true)); + inspector.call(iD.ui.Toggle(true)); } function drawHead(selection) { diff --git a/js/id/ui/lasso.js b/js/id/ui/lasso.js index 511df2a7e..4644364c3 100644 --- a/js/id/ui/lasso.js +++ b/js/id/ui/lasso.js @@ -1,4 +1,4 @@ -iD.ui.lasso = function() { +iD.ui.Lasso = function() { var center, box, group, @@ -13,7 +13,7 @@ iD.ui.lasso = function() { box = group.append('rect') .attr('class', 'lasso-box'); - group.call(iD.ui.toggle(true)); + group.call(iD.ui.Toggle(true)); } @@ -50,7 +50,7 @@ iD.ui.lasso = function() { lasso.close = function(selection) { if (group) { - group.call(iD.ui.toggle(false, function() { + group.call(iD.ui.Toggle(false, function() { d3.select(this).remove(); })); } diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index 60fd674e0..6724715e0 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -1,4 +1,4 @@ -iD.ui.layerswitcher = function(context) { +iD.ui.LayerSwitcher = function(context) { var event = d3.dispatch('cancel', 'save'), opacities = [1, 0.5, 0]; @@ -36,7 +36,7 @@ iD.ui.layerswitcher = function(context) { function setVisible(show) { if (show !== shown) { button.classed('active', show); - content.call(iD.ui.toggle(show)); + content.call(iD.ui.Toggle(show)); shown = show; } } diff --git a/js/id/ui/save.js b/js/id/ui/save.js index b8f9d619a..b4eed1dd0 100644 --- a/js/id/ui/save.js +++ b/js/id/ui/save.js @@ -17,7 +17,7 @@ iD.ui.Save = function(context) { modal.select('.content') .classed('commit-modal', true) .datum(changes) - .call(iD.ui.commit(context) + .call(iD.ui.Commit(context) .on('cancel', function() { modal.remove(); }) @@ -60,7 +60,7 @@ iD.ui.Save = function(context) { id: changeset_id, comment: e.comment }) - .call(iD.ui.success(connection) + .call(iD.ui.Success(connection) .on('cancel', function() { modal.remove(); })); diff --git a/js/id/ui/success.js b/js/id/ui/success.js index fffa7b6ab..17a577fb3 100644 --- a/js/id/ui/success.js +++ b/js/id/ui/success.js @@ -1,4 +1,4 @@ -iD.ui.success = function(connection) { +iD.ui.Success = function(connection) { var event = d3.dispatch('cancel', 'save'); function success(selection) { diff --git a/js/id/ui/toggle.js b/js/id/ui/toggle.js index 3a7500023..c3c299905 100644 --- a/js/id/ui/toggle.js +++ b/js/id/ui/toggle.js @@ -2,7 +2,7 @@ // hide class, which sets display=none, and a d3 transition for opacity. // this will cause blinking when called repeatedly, so check that the // value actually changes between calls. -iD.ui.toggle = function(show, callback) { +iD.ui.Toggle = function(show, callback) { return function(selection) { selection.style('opacity', show ? 0 : 1) .classed('hide', false) diff --git a/js/id/ui/userpanel.js b/js/id/ui/userpanel.js index fda2fc463..3dab2a5bd 100644 --- a/js/id/ui/userpanel.js +++ b/js/id/ui/userpanel.js @@ -1,4 +1,4 @@ -iD.ui.userpanel = function(connection) { +iD.ui.UserPanel = function(connection) { var event = d3.dispatch('logout', 'login'); function user(selection) { diff --git a/test/spec/ui/inspector.js b/test/spec/ui/inspector.js index e92604a7a..17160ddb4 100644 --- a/test/spec/ui/inspector.js +++ b/test/spec/ui/inspector.js @@ -1,10 +1,10 @@ -describe("iD.ui.inspector", function () { +describe("iD.ui.Inspector", function () { var inspector, element, tags = {highway: 'residential'}, entity, graph, context; function render() { - inspector = iD.ui.inspector().context(context); + inspector = iD.ui.Inspector().context(context); element = d3.select('body') .append('div') .attr('id', 'inspector-wrap') From 902ae8026709fdb9467a87a8764b521c44584781 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 16:51:31 -0800 Subject: [PATCH 291/332] Include all locale files --- Makefile | 4 ++-- index.html | 11 ++++++----- {locale => js/lib}/locale.js | 0 3 files changed, 8 insertions(+), 7 deletions(-) rename {locale => js/lib}/locale.js (100%) diff --git a/Makefile b/Makefile index 826337109..bb6844399 100644 --- a/Makefile +++ b/Makefile @@ -52,8 +52,8 @@ all: \ js/id/ui/*.js \ js/id/validate.js \ js/id/end.js \ - locale/locale.js \ - locale/en.js + js/lib/locale.js \ + locale/*.js iD.js: Makefile @rm -f $@ diff --git a/index.html b/index.html index 009ee4133..e79c178e4 100644 --- a/index.html +++ b/index.html @@ -147,14 +147,15 @@ - - - - + + + + + + - diff --git a/locale/locale.js b/js/lib/locale.js similarity index 100% rename from locale/locale.js rename to js/lib/locale.js From 211431bdb87287808328490d57ae04f72eced161 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 16:58:44 -0800 Subject: [PATCH 292/332] Fix "Browse" button in non-English locales --- css/app.css | 2 +- js/id/ui/modes.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/css/app.css b/css/app.css index 9f9e54c01..765c58f1f 100644 --- a/css/app.css +++ b/css/app.css @@ -327,7 +327,7 @@ button.centered { border-radius:0 4px 4px 0; } -button.Browse .label { display: none;} +button.browse .label { display: none;} button.action { background: #7092ff; diff --git a/js/id/ui/modes.js b/js/id/ui/modes.js index b382ab295..d3db0074a 100644 --- a/js/id/ui/modes.js +++ b/js/id/ui/modes.js @@ -11,7 +11,7 @@ iD.ui.Modes = function(context) { buttons.enter().append('button') .attr('tabindex', -1) - .attr('class', function(mode) { return mode.title + ' add-button col3'; }) + .attr('class', function(mode) { return mode.id + ' add-button col3'; }) .on('click.mode-buttons', function(mode) { context.enter(mode); }) .call(bootstrap.tooltip() .placement('bottom') From 5ee554be0d0585152eac58b9726fbb3df065b6ba Mon Sep 17 00:00:00 2001 From: Saman Bemel-Benrud Date: Tue, 12 Feb 2013 20:19:23 -0500 Subject: [PATCH 293/332] styling geocoder. --- css/app.css | 48 ++++++++++++++++++++------------------------- js/id/ui/restore.js | 17 ++++++++-------- 2 files changed, 30 insertions(+), 35 deletions(-) diff --git a/css/app.css b/css/app.css index 3593628d7..f42fbaf26 100644 --- a/css/app.css +++ b/css/app.css @@ -23,7 +23,7 @@ body { max-width: 1200px; } -div, textarea, input, span, ul, li, ol, a, button { +div, textarea, input, form, span, ul, li, ol, a, button { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; @@ -121,7 +121,7 @@ input[type=text]:focus { } input[type=text] { - padding:4px 10px; + padding:5px 10px; height:30px; resize: none; } @@ -182,10 +182,9 @@ ul li { list-style: none;} ul.toggle-list li a { font-weight: bold; color: #333; - padding: 10px; - border-top: 1px solid white; + padding: 5px 10px; display:block; - border-top: 1px solid rgba(0, 0, 0, .5); + border-top: 1px solid #ccc; white-space:nowrap; text-overflow:ellipsis; overflow:hidden; @@ -268,10 +267,6 @@ button:hover { background-color: #ececec; } -button.col3:hover { - background: #bde5aa; -} - button.active { cursor:url(../img/cursor-pointing.png) 6 1, auto; } @@ -282,7 +277,7 @@ button.disabled { } button.active:not([disabled]):not(.disabled) { - background: #6bc641; + background: #7092ff; } button.minor { @@ -656,7 +651,7 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} } .map-control > button.active:hover { - background: #6bc641; + background: #7092ff; } .map-overlay { @@ -816,8 +811,8 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} .layerswitcher-control li:hover .select-box, .layerswitcher-control li.selected .select-box { - border: 2px solid #6bc641; - background: rgba(107, 198, 65, .5); + border: 2px solid #7092ff; + background: rgba(89, 123, 231, .5); opacity: .5; } .layerswitcher-control li.selected:hover .select-box, @@ -834,25 +829,29 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} /* Geocoder */ -.geocode-control { +.geocode-control, .geocode-control form { top:150px; } +.geocode-control form { + padding: 4px; +} + .geocode-control input { - width: 140px; - border: 1px solid #ccc; - margin: 4px; + width: 100%; } .geocode-control div { - top: 50px; - width: 340px; - margin: 4px; - padding: 5px; + z-index: 100; + top: 190px; + max-height: 300px; + overflow-y: auto; } + .geocode-control div span { display: inline-block; border-bottom: 1px solid #333; + padding: 5px 10px; } /* Geolocator */ @@ -1146,11 +1145,6 @@ div.typeahead a:first-child { padding: 20px; } -.modal-section .buttons { - padding-top: 10px; - width: 100%; -} - .modal-section img.wiki-image { max-width: 100%; max-height: 300px; @@ -1214,7 +1208,7 @@ a.success-action { } .notice .zoom-to:hover { - background: #bde5aa; + background: #d8e1ff; } .notice .zoom-to .icon { diff --git a/js/id/ui/restore.js b/js/id/ui/restore.js index 3df275d89..aaadae950 100644 --- a/js/id/ui/restore.js +++ b/js/id/ui/restore.js @@ -4,16 +4,17 @@ iD.ui.restore = function(selection, history) { modal.select('.modal') .attr('class', 'modal-splash modal'); - var introModal = modal.select('.content') - .append('div') - .attr('class', 'modal-section fillL') - .text('You have unsaved changes from a previous editing session. Do you wish to restore these changes?'); + var introModal = modal.select('.content'); - buttons = introModal - .append('div') - .attr('class', 'buttons cf') + introModal.append('div') + .attr('class', 'modal-section fillL') + .append('h3').text('You have unsaved changes from a previous editing session. Do you wish to restore these changes?'); + var buttonWrap = introModal.append('div') + .attr('class', 'modal-section fillD cf col12'); + + buttons = buttonWrap .append('div') - .attr('class', 'button-wrap joined col4'); + .attr('class', 'button-wrap joined col6'); buttons.append('button') .attr('class', 'save action button col6') From c3a06c681a6c0534f024dceb45c275d871c72c65 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 17:21:52 -0800 Subject: [PATCH 294/332] Translate/cleanup iD.ui.Commit --- js/id/ui/commit.js | 96 ++++++++++++++++++++++++---------------------- locale/en.js | 12 ++++++ 2 files changed, 63 insertions(+), 45 deletions(-) diff --git a/js/id/ui/commit.js b/js/id/ui/commit.js index 6708ece49..da6fe5c02 100644 --- a/js/id/ui/commit.js +++ b/js/id/ui/commit.js @@ -31,47 +31,44 @@ iD.ui.Commit = function(context) { header = selection.append('div').attr('class', 'header modal-section fillL'), body = selection.append('div').attr('class', 'body'); - header.append('h2').text('Save Changes'); + header.append('h2') + .text(t('commit.title')); - // Comment Box - var comment_section = body.append('div').attr('class','modal-section fillD'); - var commentField = comment_section.append('textarea') + var commentSection = body.append('div') + .attr('class', 'modal-section fillD'); + + var commentField = commentSection.append('textarea') .attr('class', 'changeset-comment') - .attr('placeholder', 'Brief Description of your contributions') + .attr('placeholder', t('commit.description_placeholder')) .property('value', context.storage('comment') || ''); commentField.node().select(); - var commit_info = - comment_section - .append('p') - .attr('class','commit-info'); + var userLink = d3.select(document.createElement('div')); - commit_info.append('span').text('The changes you upload as '); - - var user_link = commit_info.append('a') - .attr('class','user-info') - .text(user.display_name) - .attr('href', connection.url() + '/user/' + user.display_name) - .attr('target', '_blank'); - - commit_info.append('span').text(' will be visible on all maps that use OpenStreetMap data:'); + userLink.append('a') + .attr('class','user-info') + .text(user.display_name) + .attr('href', connection.url() + '/user/' + user.display_name) + .attr('target', '_blank'); if (user.image_url) { - user_link - .append('img') - .attr('src', user.image_url) - .attr('class', 'icon icon-pre-text user-icon'); + userLink.append('img') + .attr('src', user.image_url) + .attr('class', 'icon icon-pre-text user-icon'); } - // Confirm / Cancel Buttons - var buttonwrap = comment_section.append('div') - .attr('class', 'buttons cf') - .append('div') - .attr('class', 'button-wrap joined col4'); + commentSection.append('p') + .attr('class', 'commit-info') + .html(t('commit.upload_explanation', {user: userLink.html()})); - var savebutton = buttonwrap - .append('button') + // Confirm / Cancel Buttons + var buttonWrap = commentSection.append('div') + .attr('class', 'buttons cf') + .append('div') + .attr('class', 'button-wrap joined col4'); + + var saveButton = buttonWrap.append('button') .attr('class', 'save action col6 button') .on('click.save', function() { var comment = commentField.node().value; @@ -80,62 +77,71 @@ iD.ui.Commit = function(context) { comment: comment }); }); - savebutton.append('span').attr('class','label').text('Save'); - var cancelbutton = buttonwrap.append('button') + saveButton.append('span') + .attr('class', 'label') + .text(t('commit.save')); + + var cancelButton = buttonWrap.append('button') .attr('class', 'cancel col6 button') .on('click.cancel', function() { event.cancel(); }); - cancelbutton.append('span').attr('class','label').text('Cancel'); + + cancelButton.append('span') + .attr('class', 'label') + .text(t('commit.cancel')); var warnings = body.selectAll('div.warning-section') .data(iD.validate(changes, context.graph())) .enter() - .append('div').attr('class', 'modal-section warning-section fillL'); + .append('div') + .attr('class', 'modal-section warning-section fillL'); warnings.append('h3') - .text('Warnings'); + .text(t('commit.warnings')); - var warning_li = warnings.append('ul') + var warningLi = warnings.append('ul') .attr('class', 'changeset-list') .selectAll('li') .data(function(d) { return d; }) .enter() .append('li'); - warning_li.append('button') + warningLi.append('button') .attr('class', 'minor') .on('click', event.fix) .append('span') .attr('class', 'icon warning'); - warning_li.append('strong').text(function(d) { + warningLi.append('strong').text(function(d) { return d.message; }); var section = body.selectAll('div.commit-section') .data(['modified', 'deleted', 'created'].filter(changesLength)) .enter() - .append('div').attr('class', 'commit-section modal-section fillL2'); + .append('div') + .attr('class', 'commit-section modal-section fillL2'); - section.append('h3').text(function(d) { - return d.charAt(0).toUpperCase() + d.slice(1); - }) + section.append('h3') + .text(function(d) { return t('commit.' + d); }) .append('small') .attr('class', 'count') .text(changesLength); var li = section.append('ul') - .attr('class','changeset-list') + .attr('class', 'changeset-list') .selectAll('li') .data(function(d) { return zipSame(changes[d]); }) .enter() .append('li'); - li.append('strong').text(function(d) { - return (d.count > 1) ? d.type + 's ' : d.type + ' '; - }); + li.append('strong') + .text(function(d) { + return (d.count > 1) ? d.type + 's ' : d.type + ' '; + }); + li.append('span') .text(function(d) { return d.name; }) .attr('title', function(d) { return d.tagText; }); diff --git a/locale/en.js b/locale/en.js index 7581b5fcb..3902fb20e 100644 --- a/locale/en.js +++ b/locale/en.js @@ -186,6 +186,18 @@ locale.en = { reset: "reset" }, + commit: { + title: "Save Changes", + description_placeholder: "Brief description of your contributions", + upload_explanation: "The changes you upload as {user} will be visible on all maps that use OpenStreetMap data.", + save: "Save", + cancel: "Cancel", + warnings: "Warnings", + modified: "Modified", + deleted: "Deleted", + created: "Created" + }, + contributors: { list: "Viewing contributions by {users}", truncated_list: "Viewing contributions by {users} and {count} others" From 6bbea0784abd6bd639092cf2be626a30438ab68c Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 17:31:59 -0800 Subject: [PATCH 295/332] Fix locale includes --- test/index.html | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/test/index.html b/test/index.html index d9640c687..dcea8e28c 100644 --- a/test/index.html +++ b/test/index.html @@ -136,14 +136,15 @@ - - - - + + + + + + - From 5f0bef89e1b4039e2f57809706289dd68874c72f Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 17:38:55 -0800 Subject: [PATCH 296/332] Sync translations --- locale/da.js | 28 ++++++++++++++++++++-------- locale/de.js | 16 ++++++++++++++++ locale/es.js | 16 ++++++++++++++++ locale/fr.js | 16 ++++++++++++++++ locale/ja.js | 16 ++++++++++++++++ locale/lv.js | 16 ++++++++++++++++ locale/tr.js | 16 ++++++++++++++++ test/spec/translation.js | 3 ++- 8 files changed, 118 insertions(+), 9 deletions(-) diff --git a/locale/da.js b/locale/da.js index 29e23c6f2..770b7681e 100644 --- a/locale/da.js +++ b/locale/da.js @@ -3,25 +3,21 @@ locale.da = { add_area: { title: "Område", description: "Tilføj parker, bygninger, søer, eller andre områder til kortet.", - tail: "Klik på kortet for at indtegne et område fx en park, sø eller bygning.", - key: "A" + tail: "Klik på kortet for at indtegne et område fx en park, sø eller bygning." }, add_line: { title: "Linje", description: "Linjer kan være veje, gader eller stier selv kanaler kan være linjer.", - tail: "Klik på koret for at indtegne en vej, sti eller rute.", - key: "L" + tail: "Klik på koret for at indtegne en vej, sti eller rute." }, add_point: { title: "Punkt", description: "Restauranter, mindesmærker og postkasser er punkter.", - tail: "Klik på kortet for at tilføje et punkt.", - key: "P" + tail: "Klik på kortet for at tilføje et punkt." }, browse: { title: "Browse", - description: "Træk rundt og zoom på kortet.", - key: "B" + description: "Træk rundt og zoom på kortet." }, draw_area: { tail: "Klik her for at tilføje punkter til dit område. Click the first point to finish the area." @@ -172,6 +168,10 @@ locale.da = { no_results: "Kunne ikke finde '{name}'" }, + geolocate: { + title: "Show My Location" + }, + description: "Description", logout: "log ud", @@ -186,6 +186,18 @@ locale.da = { reset: "nulstill" }, + commit: { + title: "Save Changes", + description_placeholder: "Brief description of your contributions", + upload_explanation: "The changes you upload as {user} will be visible on all maps that use OpenStreetMap data.", + save: "Save", + cancel: "Cancel", + warnings: "Warnings", + modified: "Modified", + deleted: "Deleted", + created: "Created" + }, + contributors: { list: "Vis bidrag fra {users}", truncated_list: "Vis bidrag fra {users} og {count} andre" diff --git a/locale/de.js b/locale/de.js index f1f3ef850..0a74f78f6 100644 --- a/locale/de.js +++ b/locale/de.js @@ -168,12 +168,28 @@ locale.de = { no_results: "Der Ort '{name}' konnte nicht gefunden werden" }, + geolocate: { + title: "Show My Location" + }, + description: "Beschreibung", report_a_bug: "Programmfehler melden", logout: "Abmelden", + commit: { + title: "Save Changes", + description_placeholder: "Brief description of your contributions", + upload_explanation: "The changes you upload as {user} will be visible on all maps that use OpenStreetMap data.", + save: "Save", + cancel: "Cancel", + warnings: "Warnings", + modified: "Modified", + deleted: "Deleted", + created: "Created" + }, + contributors: { list: "Diese Kartenansicht enthält Beiträge von:", truncated_list: "Diese Kartenansicht enthält Beiträge von: {users} und {count} Anderen" diff --git a/locale/es.js b/locale/es.js index 0468405b5..1de8aa06c 100644 --- a/locale/es.js +++ b/locale/es.js @@ -168,6 +168,10 @@ locale.es = { no_results: "No se pudo encontrar el lugar llamado '{name}'" //"Couldn't locate a place named '{name}'" }, + geolocate: { + title: "Show My Location" + }, + description: "Descripción", //"Description", logout: "cerrar sesión", //"logout", @@ -182,6 +186,18 @@ locale.es = { reset: "reiniciar" //"reset" }, + commit: { + title: "Save Changes", + description_placeholder: "Brief description of your contributions", + upload_explanation: "The changes you upload as {user} will be visible on all maps that use OpenStreetMap data.", + save: "Save", + cancel: "Cancel", + warnings: "Warnings", + modified: "Modified", + deleted: "Deleted", + created: "Created" + }, + contributors: { list: "Viendo las contribuciones de usuarios {users}", //"Viewing contributions by {users}", truncated_list: "Viendo las contribuciones de {users} y {count} más" //"Viewing contributions by {users} and {count} others" diff --git a/locale/fr.js b/locale/fr.js index 4530924a0..02a23b7db 100644 --- a/locale/fr.js +++ b/locale/fr.js @@ -168,6 +168,10 @@ locale.fr = { no_results: "Impossible de localiser l'endroit nommé '{name}'" }, + geolocate: { + title: "Show My Location" + }, + description: "Déscription", logout: "Déconnexion", @@ -179,6 +183,18 @@ locale.fr = { truncated_list: "Consulter les contributions de {users} et {count} les autres" }, + commit: { + title: "Save Changes", + description_placeholder: "Brief description of your contributions", + upload_explanation: "The changes you upload as {user} will be visible on all maps that use OpenStreetMap data.", + save: "Save", + cancel: "Cancel", + warnings: "Warnings", + modified: "Modified", + deleted: "Deleted", + created: "Created" + }, + layerswitcher: { title: "Fond de carte", description: "Paramètres du fond de carte", diff --git a/locale/ja.js b/locale/ja.js index ada737bd1..d08e0a976 100644 --- a/locale/ja.js +++ b/locale/ja.js @@ -168,6 +168,10 @@ locale.ja = { no_results: "'{name}' という名称の地点が見つかりません" }, + geolocate: { + title: "Show My Location" + }, + description: "説明", logout: "ログアウト", @@ -182,6 +186,18 @@ locale.ja = { reset: "設定リセット" }, + commit: { + title: "Save Changes", + description_placeholder: "Brief description of your contributions", + upload_explanation: "The changes you upload as {user} will be visible on all maps that use OpenStreetMap data.", + save: "Save", + cancel: "Cancel", + warnings: "Warnings", + modified: "Modified", + deleted: "Deleted", + created: "Created" + }, + contributors: { list: "{users} による編集履歴を確認", truncated_list: "{users} とその他 {count} 人による編集履歴を表示" diff --git a/locale/lv.js b/locale/lv.js index fd8be7cfe..9ca9fd440 100644 --- a/locale/lv.js +++ b/locale/lv.js @@ -168,6 +168,10 @@ locale.lv = { no_results: "Nevar atrast vietu '{name}'" }, + geolocate: { + title: "Show My Location" + }, + description: "Apraksts", logout: "atslēgties", @@ -182,6 +186,18 @@ locale.lv = { reset: "Pārstatīt" }, + commit: { + title: "Save Changes", + description_placeholder: "Brief description of your contributions", + upload_explanation: "The changes you upload as {user} will be visible on all maps that use OpenStreetMap data.", + save: "Save", + cancel: "Cancel", + warnings: "Warnings", + modified: "Modified", + deleted: "Deleted", + created: "Created" + }, + contributors: { list: "{users} papildinājumi redzami", truncated_list: "{users} un {count} citu papildinājumi redzami" diff --git a/locale/tr.js b/locale/tr.js index a9b3e4b47..3b0602658 100644 --- a/locale/tr.js +++ b/locale/tr.js @@ -168,6 +168,10 @@ locale.tr = { no_results: "'{name}' ismindeki yer bulunamadı" }, + geolocate: { + title: "Show My Location" + }, + description: "Açıklama", logout: "Çıkış", @@ -182,6 +186,18 @@ locale.tr = { reset: "Sıfırla" }, + commit: { + title: "Save Changes", + description_placeholder: "Brief description of your contributions", + upload_explanation: "The changes you upload as {user} will be visible on all maps that use OpenStreetMap data.", + save: "Save", + cancel: "Cancel", + warnings: "Warnings", + modified: "Modified", + deleted: "Deleted", + created: "Created" + }, + contributors: { list: "{users} tarafından yapılan katkılar görünmektedir", truncated_list: "{users} ve diğer {count} tarafından yapılan katkılar görünmektedir" diff --git a/test/spec/translation.js b/test/spec/translation.js index 963f59fe0..41f2e07df 100644 --- a/test/spec/translation.js +++ b/test/spec/translation.js @@ -27,7 +27,8 @@ describe('translations', function() { var allkeys = _.flatten(_.values(languageKeys)); _.forEach(languageKeys, function(l, k) { - expect(_.difference(allkeys, l)).to.eql([]); + var diff = _.difference(allkeys, l).join(", "); + expect(diff).to.equal(""); }); }); From d92d87ebf5b9516b969991f05f6bf4affd3615a3 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 19:55:09 -0800 Subject: [PATCH 297/332] Organize translations --- js/id/ui.js | 2 +- js/id/ui/inspector.js | 4 +- js/id/ui/save.js | 8 ++-- js/id/ui/tag_reference.js | 2 +- locale/da.js | 80 ++++++++++++++++---------------- locale/de.js | 82 ++++++++++++++++----------------- locale/en.js | 96 +++++++++++++++++++-------------------- locale/es.js | 96 +++++++++++++++++++-------------------- locale/fr.js | 90 ++++++++++++++++++------------------ locale/ja.js | 96 +++++++++++++++++++-------------------- locale/lv.js | 96 +++++++++++++++++++-------------------- locale/tr.js | 96 +++++++++++++++++++-------------------- 12 files changed, 366 insertions(+), 382 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index 48e5682bf..4e6ec4c24 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -107,7 +107,7 @@ iD.ui = function(context) { window.onbeforeunload = function() { history.save(); - if (history.hasChanges()) return t('unsaved_changes'); + if (history.hasChanges()) return t('save.unsaved_changes'); }; d3.select(window).on('resize.editor', function() { diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js index 173594101..1b1c78be6 100644 --- a/js/id/ui/inspector.js +++ b/js/id/ui/inspector.js @@ -22,7 +22,7 @@ iD.ui.Inspector = function() { .attr('class', 'inspector-inner tag-wrap fillL2'); inspectorwrap.append('h4') - .text(t('edit_tags')); + .text(t('inspector.edit_tags')); tagList = inspectorwrap.append('ul'); @@ -65,7 +65,7 @@ iD.ui.Inspector = function() { .attr('class', 'apply action') .on('click', apply); - inspectorButton.append('span').attr('class','label').text(t('okay')); + inspectorButton.append('span').attr('class','label').text(t('inspector.okay')); var minorButtons = selection.append('div').attr('class','minor-buttons fl'); diff --git a/js/id/ui/save.js b/js/id/ui/save.js index b4eed1dd0..8b0ef57ce 100644 --- a/js/id/ui/save.js +++ b/js/id/ui/save.js @@ -30,7 +30,7 @@ iD.ui.Save = function(context) { context.container().select('.shaded') .remove(); - var loading = iD.ui.loading(context.container(), t('uploading_changes'), true); + var loading = iD.ui.loading(context.container(), t('save.uploading'), true); connection.putChangeset( history.changes(), @@ -44,7 +44,7 @@ iD.ui.Save = function(context) { var desc = iD.ui.confirm() .select('.description'); desc.append('h2') - .text(t('save_error')); + .text(t('save.error')); desc.append('p').text(err.responseText); } else { success(e, changeset_id); @@ -81,11 +81,11 @@ iD.ui.Save = function(context) { .call(bootstrap.tooltip() .placement('bottom') .html(true) - .title(iD.ui.tooltipHtml(t('save_help'), key))); + .title(iD.ui.tooltipHtml(t('save.help'), key))); button.append('span') .attr('class', 'label') - .text(t('save')); + .text(t('save.title')); button.append('span') .attr('class', 'count'); diff --git a/js/id/ui/tag_reference.js b/js/id/ui/tag_reference.js index 6b7b9fb9f..dc583b932 100644 --- a/js/id/ui/tag_reference.js +++ b/js/id/ui/tag_reference.js @@ -24,7 +24,7 @@ iD.ui.tagReference = function(selection) { referenceBody .append('h5') - .text(t('description')); + .text(t('tag_reference.description')); if (selection.datum().image) { referenceBody diff --git a/locale/da.js b/locale/da.js index 770b7681e..17b75c340 100644 --- a/locale/da.js +++ b/locale/da.js @@ -128,54 +128,14 @@ locale.da = { } }, - validations: { - untagged_point: "Untagged point which is not part of a line or area", - untagged_line: "Untagged line", - untagged_area: "Untagged area", - tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area", - deprecated_tags: "Deprecated tags: {tags}" - }, - - save: "Gem", - unsaved_changes: "Du har ændringer der ikke er gemt endnu", - save_help: "Gem ændringer til OpenStreetMap gør dem synlige for andre brugere", - no_changes: "Du har ingen ændringer til at gemme endnu.", - save_error: "Der skete en fejl da du prøvede at gemme", - uploading_changes: "Gemmer nu ændringer til OpenStreetMap.", - just_edited: "Du har lige rettede i OpenStreetMap!", - okay: "Ok", - nothing_to_undo: "Nothing to undo.", nothing_to_redo: "Nothing to redo.", + just_edited: "Du har lige rettede i OpenStreetMap!", browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", - - inspector: { - no_documentation_combination: "Der er ingen dokumentation for denne tag kombination", - no_documentation_key: "Der er ingen dokumenation tilgængelig for denne nøgle", - new_tag: "Nyt Tag" - }, - view_on_osm: "Vis på OSM", - zoom_in_edit: "zoom ind for at rette kortet", - - edit_tags: "Ret tags", - - geocoder: { - title: "Find et sted", - placeholder: "find et sted", - no_results: "Kunne ikke finde '{name}'" - }, - - geolocate: { - title: "Show My Location" - }, - - description: "Description", - logout: "log ud", - report_a_bug: "report a bug", layerswitcher: { @@ -203,13 +163,51 @@ locale.da = { truncated_list: "Vis bidrag fra {users} og {count} andre" }, + geocoder: { + title: "Find et sted", + placeholder: "find et sted", + no_results: "Kunne ikke finde '{name}'" + }, + + geolocate: { + title: "Show My Location" + }, + + inspector: { + no_documentation_combination: "Der er ingen dokumentation for denne tag kombination", + no_documentation_key: "Der er ingen dokumenation tilgængelig for denne nøgle", + new_tag: "Nyt Tag", + edit_tags: "Ret tags", + okay: "Ok" + }, + + save: { + title: "Gem", + help: "Gem ændringer til OpenStreetMap gør dem synlige for andre brugere", + error: "Der skete en fejl da du prøvede at gemme", + uploading: "Gemmer nu ændringer til OpenStreetMap.", + unsaved_changes: "Du har ændringer der ikke er gemt endnu", + }, + source_switch: { live: "live", dev: "dev" }, + tag_reference: { + description: "Description" + }, + zoom: { in: "Zoom ind", out: "Zoom ud" + }, + + validations: { + untagged_point: "Untagged point which is not part of a line or area", + untagged_line: "Untagged line", + untagged_area: "Untagged area", + tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area", + deprecated_tags: "Deprecated tags: {tags}" } }; diff --git a/locale/de.js b/locale/de.js index 0a74f78f6..60f4c6155 100644 --- a/locale/de.js +++ b/locale/de.js @@ -128,55 +128,15 @@ locale.de = { } }, - validations: { - untagged_point: "Punkt ohne Attribute, der kein Teil einer Linie oder Fläche ist", - untagged_line: "Linie ohne Attribute", - untagged_area: "Fläche ohne Attribute", - tag_suggests_area: "Das Attribut {tag} suggeriert eine Fläche, ist aber keine Fläche", - deprecated_tags: "Veralterte Attribute: {tags}" - }, - - save: "Speichern", - unsaved_changes: "Ungespeicherte Änderugen vorhanden", - save_help: "Speichere Änderungen auf OpenStreetMap, um diese für andere Nutzer sichtbar zu machen", - no_changes: "Keine Änderungen zum Speichern vorhanden.", - save_error: "Beim Speichern ist ein Fehler aufgetreten", - uploading_changes: "Änderungen werden zu OpenStreetMap hochgeladen.", - just_edited: "Sie haben gerade OpenStreetMap editiert!", - okay: "OK", - nothing_to_undo: "Nichts zum Rückgängigmachen.", nothing_to_redo: "Nichts zum Wiederherstellen.", + just_edited: "Sie haben gerade OpenStreetMap editiert!", browser_notice: "Dieser Editor wird von Firefox, Chrome, Safari, Opera, und Internet Explorer (Version 9 und höher) unterstzützt. Bitte aktualisieren Sie Ihren Browser oder nutzen Sie Potlatch 2, um die Karte zu modifizieren.", - - inspector: { - no_documentation_combination: "Für dieses Attribut ist keine Dokumentation verfügbar.", - no_documentation_key: "Für dises Schlüsselwort ist keine Dokumentation verfügbar", - new_tag: "Neues Attribut" - }, - view_on_osm: "Auf OSM anschauen", - zoom_in_edit: "Hineinzoomen, um die Karte zu bearbeiten", - - edit_tags: "Attribute bearbeiten", - - geocoder: { - title: "Suche einen Ort", - placeholder: "suche einen Ort", - no_results: "Der Ort '{name}' konnte nicht gefunden werden" - }, - - geolocate: { - title: "Show My Location" - }, - - description: "Beschreibung", - - report_a_bug: "Programmfehler melden", - logout: "Abmelden", + report_a_bug: "Programmfehler melden", commit: { title: "Save Changes", @@ -195,6 +155,24 @@ locale.de = { truncated_list: "Diese Kartenansicht enthält Beiträge von: {users} und {count} Anderen" }, + geocoder: { + title: "Suche einen Ort", + placeholder: "suche einen Ort", + no_results: "Der Ort '{name}' konnte nicht gefunden werden" + }, + + geolocate: { + title: "Show My Location" + }, + + inspector: { + no_documentation_combination: "Für dieses Attribut ist keine Dokumentation verfügbar.", + no_documentation_key: "Für dises Schlüsselwort ist keine Dokumentation verfügbar", + new_tag: "Neues Attribut", + edit_tags: "Attribute bearbeiten", + okay: "OK" + }, + layerswitcher: { title: "Hintergrund", description: "Hintergrundeinstellungen", @@ -203,11 +181,31 @@ locale.de = { reset: "Zurücksetzen" }, + save: { + title: "Speichern", + help: "Speichere Änderungen auf OpenStreetMap, um diese für andere Nutzer sichtbar zu machen", + error: "Beim Speichern ist ein Fehler aufgetreten", + uploading: "Änderungen werden zu OpenStreetMap hochgeladen.", + unsaved_changes: "Ungespeicherte Änderugen vorhanden", + }, + source_switch: { live: "live", dev: "dev" }, + tag_reference: { + description: "Beschreibung" + }, + + validations: { + untagged_point: "Punkt ohne Attribute, der kein Teil einer Linie oder Fläche ist", + untagged_line: "Linie ohne Attribute", + untagged_area: "Fläche ohne Attribute", + tag_suggests_area: "Das Attribut {tag} suggeriert eine Fläche, ist aber keine Fläche", + deprecated_tags: "Veralterte Attribute: {tags}" + }, + zoom: { in: "Hineinzoomen", out: "Herauszoomen" diff --git a/locale/en.js b/locale/en.js index 3902fb20e..9060f7b66 100644 --- a/locale/en.js +++ b/locale/en.js @@ -128,64 +128,16 @@ locale.en = { } }, - validations: { - untagged_point: "Untagged point which is not part of a line or area", - untagged_line: "Untagged line", - untagged_area: "Untagged area", - tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area", - deprecated_tags: "Deprecated tags: {tags}" - }, - - save: "Save", - unsaved_changes: "You have unsaved changes", - save_help: "Save changes to OpenStreetMap, making them visible to other users", - no_changes: "You don't have any changes to save.", - save_error: "An error occurred while trying to save", - uploading_changes: "Uploading changes to OpenStreetMap.", - just_edited: "You Just Edited OpenStreetMap!", - okay: "Okay", - nothing_to_undo: "Nothing to undo.", nothing_to_redo: "Nothing to redo.", + just_edited: "You Just Edited OpenStreetMap!", browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", - - inspector: { - no_documentation_combination: "There is no documentation available for this tag combination", - no_documentation_key: "There is no documentation available for this key", - new_tag: "New Tag" - }, - view_on_osm: "View on OSM", - zoom_in_edit: "zoom in to edit the map", - - edit_tags: "Edit tags", - - geocoder: { - title: "Find A Place", - placeholder: "find a place", - no_results: "Couldn't locate a place named '{name}'" - }, - - geolocate: { - title: "Show My Location" - }, - - description: "Description", - logout: "logout", - report_a_bug: "report a bug", - layerswitcher: { - title: "Background", - description: "Background Settings", - percent_brightness: "{opacity}% brightness", - fix_misalignment: "Fix misalignment", - reset: "reset" - }, - commit: { title: "Save Changes", description_placeholder: "Brief description of your contributions", @@ -203,11 +155,57 @@ locale.en = { truncated_list: "Viewing contributions by {users} and {count} others" }, + geocoder: { + title: "Find A Place", + placeholder: "find a place", + no_results: "Couldn't locate a place named '{name}'" + }, + + geolocate: { + title: "Show My Location" + }, + + inspector: { + no_documentation_combination: "There is no documentation available for this tag combination", + no_documentation_key: "There is no documentation available for this key", + new_tag: "New Tag", + edit_tags: "Edit tags", + okay: "Okay" + }, + + layerswitcher: { + title: "Background", + description: "Background Settings", + percent_brightness: "{opacity}% brightness", + fix_misalignment: "Fix misalignment", + reset: "reset" + }, + + save: { + title: "Save", + help: "Save changes to OpenStreetMap, making them visible to other users", + error: "An error occurred while trying to save", + uploading: "Uploading changes to OpenStreetMap.", + unsaved_changes: "You have unsaved changes" + }, + source_switch: { live: "live", dev: "dev" }, + tag_reference: { + description: 'Description' + }, + + validations: { + untagged_point: "Untagged point which is not part of a line or area", + untagged_line: "Untagged line", + untagged_area: "Untagged area", + tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area", + deprecated_tags: "Deprecated tags: {tags}" + }, + zoom: { in: "Zoom In", out: "Zoom Out" diff --git a/locale/es.js b/locale/es.js index 1de8aa06c..c440429db 100644 --- a/locale/es.js +++ b/locale/es.js @@ -128,64 +128,16 @@ locale.es = { } }, - validations: { - untagged_point: "Punto sin etiquetar que no es parte de una línea ni zona.", //"Untagged point which is not part of a line or area", - untagged_line: "Línea sin etiquetar", //"Untagged line", - untagged_area: "Zona sin etiquetar", //"Untagged area", - tag_suggests_area: "La etiqueta {tag} sugiere que esta línea debería ser una zona, pero no lo es.", //"The tag {tag} suggests line should be area, but it is not an area", - deprecated_tags: "Etiquetas obsoletas: {tags}" //"Deprecated tags: {tags}" - }, - - save: "Guardar", //"Save", - unsaved_changes: "Tienes cambios sin guardar", //"You have unsaved changes", - save_help: "Guardar los cambios en OpenStreetMap haciéndolos visibles a otros usuarios", //"Save changes to OpenStreetMap, making them visible to other users", - no_changes: "No tienes cambios sin guardar", //"You don't have any changes to save.", - save_error: "Ha ocurrido un error tratando de guardar", //"An error occurred while trying to save", - uploading_changes: "Subiendo cambios a OpenStreetMap", //"Uploading changes to OpenStreetMap.", - just_edited: "Acabas de editar OpenStreetMap!", //"You Just Edited OpenStreetMap!", - okay: "OK", //"Okay", - nothing_to_undo: "Nada para deshacer", //"Nothing to undo.", nothing_to_redo: "Nada para rehacer", //"Nothing to redo.", + just_edited: "Acabas de editar OpenStreetMap!", //"You Just Edited OpenStreetMap!", browser_notice: "Este editor soporta Firefox, Chrome, Safari, Opera e Internet Explorer 9 o superior. Por favor actualiza tu navegador o utiliza Potlatch 2 para editar el mapa.", //"This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", - - inspector: { - no_documentation_combination: "No hay documentación disponible para esta combinación de etiquetas", //"This is no documentation available for this tag combination", - no_documentation_key: "No hay documentación disponible para esta tecla", //"This is no documentation available for this key", - new_tag: "Nueve etiqueta" //"New Tag" - }, - view_on_osm: "Ver en OSM", //"View on OSM", - zoom_in_edit: "acercar para editar el mapa", //"zoom in to edit the map", - - edit_tags: "Editar etiquetas", //"Edit tags", - - geocoder: { - title: "Encontrar un lugar", //"Find A Place", - placeholder: "encontrar un lugar", //"find a place", - no_results: "No se pudo encontrar el lugar llamado '{name}'" //"Couldn't locate a place named '{name}'" - }, - - geolocate: { - title: "Show My Location" - }, - - description: "Descripción", //"Description", - logout: "cerrar sesión", //"logout", - report_a_bug: "reportar un error", //"report a bug", - layerswitcher: { - title: "Fondo", //"Background", - description: "Configuración de fondo", //"Background Settings", - percent_brightness: "{opacity}% brillo", //"{opacity}% brightness", - fix_misalignment: "Arreglar alineamiento", //"Fix misalignment", - reset: "reiniciar" //"reset" - }, - commit: { title: "Save Changes", description_placeholder: "Brief description of your contributions", @@ -203,11 +155,57 @@ locale.es = { truncated_list: "Viendo las contribuciones de {users} y {count} más" //"Viewing contributions by {users} and {count} others" }, + geocoder: { + title: "Encontrar un lugar", //"Find A Place", + placeholder: "encontrar un lugar", //"find a place", + no_results: "No se pudo encontrar el lugar llamado '{name}'" //"Couldn't locate a place named '{name}'" + }, + + geolocate: { + title: "Show My Location" + }, + + inspector: { + no_documentation_combination: "No hay documentación disponible para esta combinación de etiquetas", //"This is no documentation available for this tag combination", + no_documentation_key: "No hay documentación disponible para esta tecla", //"This is no documentation available for this key", + new_tag: "Nueve etiqueta", //"New Tag" + edit_tags: "Editar etiquetas", //"Edit tags", + okay: "OK" //"Okay", + }, + + layerswitcher: { + title: "Fondo", //"Background", + description: "Configuración de fondo", //"Background Settings", + percent_brightness: "{opacity}% brillo", //"{opacity}% brightness", + fix_misalignment: "Arreglar alineamiento", //"Fix misalignment", + reset: "reiniciar" //"reset" + }, + + save: { + title: "Guardar", //"Save", + help: "Guardar los cambios en OpenStreetMap haciéndolos visibles a otros usuarios", //"Save changes to OpenStreetMap, making them visible to other users", + error: "Ha ocurrido un error tratando de guardar", //"An error occurred while trying to save", + uploading: "Subiendo cambios a OpenStreetMap", //"Uploading changes to OpenStreetMap.", + unsaved_changes: "Tienes cambios sin guardar" //"You have unsaved changes", + }, + source_switch: { live: "en vivo", //"live", dev: "dev" }, + tag_reference: { + description: "Descripción" //"Description", + }, + + validations: { + untagged_point: "Punto sin etiquetar que no es parte de una línea ni zona.", //"Untagged point which is not part of a line or area", + untagged_line: "Línea sin etiquetar", //"Untagged line", + untagged_area: "Zona sin etiquetar", //"Untagged area", + tag_suggests_area: "La etiqueta {tag} sugiere que esta línea debería ser una zona, pero no lo es.", //"The tag {tag} suggests line should be area, but it is not an area", + deprecated_tags: "Etiquetas obsoletas: {tags}" //"Deprecated tags: {tags}" + }, + zoom: { in: "Aumentar", // "Zoom In", out: "Alejar" //"Zoom Out", diff --git a/locale/fr.js b/locale/fr.js index 02a23b7db..d6c36189a 100644 --- a/locale/fr.js +++ b/locale/fr.js @@ -128,61 +128,16 @@ locale.fr = { } }, - validations: { - untagged_point: "Point sans aucun tag ne faisant partie ni d'une ligne, ni d'un polygone", - untagged_line: "Ligne sans aucun tag", - untagged_area: "Polygone sans aucun tag", - tag_suggests_area: "Ce tag {tag} suppose que cette ligne devrait être un polygone, or ce n'est pas le cas", - deprecated_tags: "Tags obsolètes : {tags}" - }, - - save: "Sauvegarder", - unsaved_changes: "Vous avez des modifications non enregistrées", - save_help: "Envoie des modifications au serveyr OpenStreetMap afin qu'elles soient visibles par les autres contributeurs.", - no_changes: "Vous n'avez aucune modification à enregistrer.", - save_error: "Une erreur est survenue lors de l'enregistrement des données", - uploading_changes: "Envoie des modifications vers OpenStreetMap.", - just_edited: "Vous venez de participer à OpenStreetMap!", - okay: "Okay", - nothing_to_undo: "Rien à annuler.", nothing_to_redo: "Rien à refaire.", + just_edited: "Vous venez de participer à OpenStreetMap!", browser_notice: "Les navigateurs supportés par cet éditeur sont : Firefox, Chrome, Safari, Opera et Internet Explorer (version 9 et supérieures). Pour éditer la carte, veuillez mettre à jour votre navigateur ou utiliser Potlatch 2.", - - inspector: { - no_documentation_combination: "Aucune documentation n'est disponible pour cette combinaison de tag", - no_documentation_key: "Aucune documentation n'est disponible pour cette clé", - new_tag: "Nouveau tag" - }, - view_on_osm: "Consulter dans OSM", - zoom_in_edit: "Zoomer pour modifier la carte", - - edit_tags: "Editer les tags", - - geocoder: { - title: "Trouver un emplacement", - placeholder: "Trouver un endroit", - no_results: "Impossible de localiser l'endroit nommé '{name}'" - }, - - geolocate: { - title: "Show My Location" - }, - - description: "Déscription", - logout: "Déconnexion", - report_a_bug: "Signaler un bug", - contributors: { - list: "Consulter les contributions de {users}", - truncated_list: "Consulter les contributions de {users} et {count} les autres" - }, - commit: { title: "Save Changes", description_placeholder: "Brief description of your contributions", @@ -195,6 +150,29 @@ locale.fr = { created: "Created" }, + contributors: { + list: "Consulter les contributions de {users}", + truncated_list: "Consulter les contributions de {users} et {count} les autres" + }, + + geocoder: { + title: "Trouver un emplacement", + placeholder: "Trouver un endroit", + no_results: "Impossible de localiser l'endroit nommé '{name}'" + }, + + geolocate: { + title: "Show My Location" + }, + + inspector: { + no_documentation_combination: "Aucune documentation n'est disponible pour cette combinaison de tag", + no_documentation_key: "Aucune documentation n'est disponible pour cette clé", + new_tag: "Nouveau tag", + edit_tags: "Editer les tags", + okay: "Okay" + }, + layerswitcher: { title: "Fond de carte", description: "Paramètres du fond de carte", @@ -203,11 +181,31 @@ locale.fr = { reset: "reset" }, + save: { + title: "Sauvegarder", + help: "Envoie des modifications au serveyr OpenStreetMap afin qu'elles soient visibles par les autres contributeurs.", + error: "Une erreur est survenue lors de l'enregistrement des données", + uploading: "Envoie des modifications vers OpenStreetMap.", + unsaved_changes: "Vous avez des modifications non enregistrées" + }, + source_switch: { live: "live", dev: "dev" }, + tag_reference: { + description: "Déscription" + }, + + validations: { + untagged_point: "Point sans aucun tag ne faisant partie ni d'une ligne, ni d'un polygone", + untagged_line: "Ligne sans aucun tag", + untagged_area: "Polygone sans aucun tag", + tag_suggests_area: "Ce tag {tag} suppose que cette ligne devrait être un polygone, or ce n'est pas le cas", + deprecated_tags: "Tags obsolètes : {tags}" + }, + zoom: { in: "Zoomer", out: "Dézoomer" diff --git a/locale/ja.js b/locale/ja.js index d08e0a976..61b76ace5 100644 --- a/locale/ja.js +++ b/locale/ja.js @@ -128,64 +128,16 @@ locale.ja = { } }, - validations: { - untagged_point: "ポイントにタグが付与されておらず、ラインやエリアの一部でもありません", - untagged_line: "ラインにタグが付与されていません", - untagged_area: "エリアにタグが付与されていません", - tag_suggests_area: "ラインに {tag} タグが付与されています。エリアで描かれるべきです", - deprecated_tags: "タグの重複: {tags}" - }, - - save: "Save", - unsaved_changes: "変更が保存されていません", - save_help: "変更点をOpenStreetMapに保存し、他ユーザが確認できるようにします", - no_changes: "変更点がありません", - save_error: "データ保存中にエラーが発生しました", - uploading_changes: "変更点をOpenStreetMapへアップロードしています", - just_edited: "OpenStreetMap編集完了!", - okay: "OK", - nothing_to_undo: "やり直す変更点がありません", nothing_to_redo: "やり直した変更点がありません", + just_edited: "OpenStreetMap編集完了!", browser_notice: "このエディタは Firefox, Chrome, Safari, Opera, および Internet Explorer 9 以上をサポートしています。ブラウザのバージョンを更新するか、Potlatch 2を使用して編集してください", - - inspector: { - no_documentation_combination: "このタグの組み合わせに関する説明文はありません", - no_documentation_key: "このキーに対する説明文はありません", - new_tag: "新規タグ" - }, - view_on_osm: "OSMで確認", - zoom_in_edit: "編集するにはさらに地図を拡大してください", - - edit_tags: "タグを編集", - - geocoder: { - title: "特定地点を検索", - placeholder: "地点を検索", - no_results: "'{name}' という名称の地点が見つかりません" - }, - - geolocate: { - title: "Show My Location" - }, - - description: "説明", - logout: "ログアウト", - report_a_bug: "バグを報告", - layerswitcher: { - title: "背景画像", - description: "背景画像設定", - percent_brightness: "{opacity}% 輝度", - fix_misalignment: "背景画像を移動", - reset: "設定リセット" - }, - commit: { title: "Save Changes", description_placeholder: "Brief description of your contributions", @@ -203,11 +155,57 @@ locale.ja = { truncated_list: "{users} とその他 {count} 人による編集履歴を表示" }, + geocoder: { + title: "特定地点を検索", + placeholder: "地点を検索", + no_results: "'{name}' という名称の地点が見つかりません" + }, + + geolocate: { + title: "Show My Location" + }, + + inspector: { + no_documentation_combination: "このタグの組み合わせに関する説明文はありません", + no_documentation_key: "このキーに対する説明文はありません", + new_tag: "新規タグ", + edit_tags: "タグを編集", + okay: "OK" + }, + + layerswitcher: { + title: "背景画像", + description: "背景画像設定", + percent_brightness: "{opacity}% 輝度", + fix_misalignment: "背景画像を移動", + reset: "設定リセット" + }, + + save: { + title: "Save", + help: "変更点をOpenStreetMapに保存し、他ユーザが確認できるようにします", + error: "データ保存中にエラーが発生しました", + uploading: "変更点をOpenStreetMapへアップロードしています", + unsaved_changes: "変更が保存されていません" + }, + source_switch: { live: "本番サーバ", dev: "開発サーバ" }, + tag_reference: { + description: "説明" + }, + + validations: { + untagged_point: "ポイントにタグが付与されておらず、ラインやエリアの一部でもありません", + untagged_line: "ラインにタグが付与されていません", + untagged_area: "エリアにタグが付与されていません", + tag_suggests_area: "ラインに {tag} タグが付与されています。エリアで描かれるべきです", + deprecated_tags: "タグの重複: {tags}" + }, + zoom: { in: "ズームイン", out: "ズームアウト" diff --git a/locale/lv.js b/locale/lv.js index 9ca9fd440..3a799c2a9 100644 --- a/locale/lv.js +++ b/locale/lv.js @@ -128,64 +128,16 @@ locale.lv = { } }, - validations: { - untagged_point: "Neapzīmēts punkts", - untagged_line: "Neapzīmēta līnija", - untagged_area: "Neapzīmēts apgabals", - tag_suggests_area: "Apzīmējums {tag} parasti tiek lietots apgabaliem, bet objekts nav apgabals", - deprecated_tags: "Novecojuši apzīmējumi: {tags}" - }, - - save: "Saglabāt", - unsaved_changes: "Jums ir nesaglabātas izmaiņas", - save_help: "Saglabā izmaiņas, padarot tās redzamas citiem", - no_changes: "Jums nav izmaiņu, ko saglabāt", - save_error: "Kļūda. Nevarēja saglabāt maiņas", - uploading_changes: "Augšupielādē", - just_edited: "Jūs nupat rediģējāt OpenStreetMap", - okay: "Labi", - nothing_to_undo: "Nav nekā, ko atcelt", nothing_to_redo: "Nav nekā, ko atsaukt", + just_edited: "Jūs nupat rediģējāt OpenStreetMap", browser_notice: "Šis redaktors tiek atbalstīts ar Firefox, Chrome, Safari, Opera, un Internet Explorer 9 un jaunāku. Lūdzu, atjauniniet savu pārlūkprogrammu vai izmantojiet Potlatch 2 to kartes rediģēšanai", - - inspector: { - no_documentation_combination: "Šai apzīmējumu kombinācijai nav piejama dokumetācija", - no_documentation_key: "There is no documentation available for this key", - new_tag: "Jauns apzīmējums" - }, - view_on_osm: "Apskatīt OSM lapu", - zoom_in_edit: "pietuviniet, lai rediģētu karti", - - edit_tags: "Rediģēt apzīmējumus", - - geocoder: { - title: "Atrast vietu", - placeholder: "meklē vietu", - no_results: "Nevar atrast vietu '{name}'" - }, - - geolocate: { - title: "Show My Location" - }, - - description: "Apraksts", - logout: "atslēgties", - report_a_bug: "ziņot par kļūdu", - layerswitcher: { - title: "Fons", - description: "Fona iestatījumi", - percent_brightness: "{opacity}% gaišums", - fix_misalignment: "Labot fona nolīdzināšanu", - reset: "Pārstatīt" - }, - commit: { title: "Save Changes", description_placeholder: "Brief description of your contributions", @@ -203,11 +155,57 @@ locale.lv = { truncated_list: "{users} un {count} citu papildinājumi redzami" }, + geocoder: { + title: "Atrast vietu", + placeholder: "meklē vietu", + no_results: "Nevar atrast vietu '{name}'" + }, + + geolocate: { + title: "Show My Location" + }, + + inspector: { + no_documentation_combination: "Šai apzīmējumu kombinācijai nav piejama dokumetācija", + no_documentation_key: "There is no documentation available for this key", + new_tag: "Jauns apzīmējums", + edit_tags: "Rediģēt apzīmējumus", + okay: "Labi" + }, + + layerswitcher: { + title: "Fons", + description: "Fona iestatījumi", + percent_brightness: "{opacity}% gaišums", + fix_misalignment: "Labot fona nolīdzināšanu", + reset: "Pārstatīt" + }, + + save: { + title: "Saglabāt", + help: "Saglabā izmaiņas, padarot tās redzamas citiem", + error: "Kļūda. Nevarēja saglabāt maiņas", + uploading: "Augšupielādē", + unsaved_changes: "Jums ir nesaglabātas izmaiņas" + }, + source_switch: { live: "live", dev: "dev" }, + tag_reference: { + description: "Apraksts" + }, + + validations: { + untagged_point: "Neapzīmēts punkts", + untagged_line: "Neapzīmēta līnija", + untagged_area: "Neapzīmēts apgabals", + tag_suggests_area: "Apzīmējums {tag} parasti tiek lietots apgabaliem, bet objekts nav apgabals", + deprecated_tags: "Novecojuši apzīmējumi: {tags}" + }, + zoom: { in: "Pietuvināt", out: "Attālināt" diff --git a/locale/tr.js b/locale/tr.js index 3b0602658..f212ca373 100644 --- a/locale/tr.js +++ b/locale/tr.js @@ -128,64 +128,16 @@ locale.tr = { } }, - validations: { - untagged_point: "Herhangi bir çizgi ya da alana bağlantısı olmayan ve etiketlenmemiş bir nokta.", - untagged_line: "Etiketlenmemiş çizgi", - untagged_area: "Etiketlenmemiş alan", - tag_suggests_area: "{tag} etiketi buranın alan olmasını tavsiye ediyor ama alan değil.", - deprecated_tags: "Kullanımdan kaldırılmış etiket : {tags}" - }, - - save: "Kaydet", - unsaved_changes: "Kaydedilmemiş değişiklikleriniz var", - save_help: "Diğer kullanıcıların yaptığınız değişiklikleri görmesi için OpenStreetMap'e kaydediniz", - no_changes: "Kaydedecek hiçbir değişikliğiniz yok", - save_error: "Kaydederken bir hata oluştu", - uploading_changes: "Değişiklikleriniz OpenStreetMap'e gönderiliyor.", - just_edited: "Şu an OpenStreetMap'de bir değişiklik yaptınız!", - okay: "Tamam", - nothing_to_undo: "Geri alınacak birşey yok.", nothing_to_redo: "Tekrar yapılacak birşey yok.", + just_edited: "Şu an OpenStreetMap'de bir değişiklik yaptınız!", browser_notice: "Bu editör sadece Firefox, Chrome, Safari, Opera ile Internet Explorer 9 ve üstü tarayıcılarda çalışmaktadır. Lütfen tarayınıcı güncelleyin ya da Potlatch 2'yi kullanarak haritada güncelleme yapınız.", - - inspector: { - no_documentation_combination: "Bu etiket kombinasyonu için dökümantasyon bulunmamaktadır.", - no_documentation_key: "Bu anahtar için dökümantasyon bulunmamaktadır.", - new_tag: "Yeni Etiket" - }, - view_on_osm: "OSM üstünde Gör", - zoom_in_edit: "Güncelleme yapmak için haritada yakınlaşmalısınız", - - edit_tags: "Etiketleri güncelle", - - geocoder: { - title: "Bir Yer Bul", - placeholder: "bir yer bul", - no_results: "'{name}' ismindeki yer bulunamadı" - }, - - geolocate: { - title: "Show My Location" - }, - - description: "Açıklama", - logout: "Çıkış", - report_a_bug: "Hata rapor et", - layerswitcher: { - title: "Arkaplan", - description: "Arkaplan Ayarları", - percent_brightness: "{opacity}% parlaklık", - fix_misalignment: "Yanlış hizalamayı düzelt", - reset: "Sıfırla" - }, - commit: { title: "Save Changes", description_placeholder: "Brief description of your contributions", @@ -203,11 +155,57 @@ locale.tr = { truncated_list: "{users} ve diğer {count} tarafından yapılan katkılar görünmektedir" }, + geocoder: { + title: "Bir Yer Bul", + placeholder: "bir yer bul", + no_results: "'{name}' ismindeki yer bulunamadı" + }, + + geolocate: { + title: "Show My Location" + }, + + inspector: { + no_documentation_combination: "Bu etiket kombinasyonu için dökümantasyon bulunmamaktadır.", + no_documentation_key: "Bu anahtar için dökümantasyon bulunmamaktadır.", + new_tag: "Yeni Etiket", + edit_tags: "Etiketleri güncelle", + okay: "Tamam" + }, + + layerswitcher: { + title: "Arkaplan", + description: "Arkaplan Ayarları", + percent_brightness: "{opacity}% parlaklık", + fix_misalignment: "Yanlış hizalamayı düzelt", + reset: "Sıfırla" + }, + + save: { + title: "Kaydet", + help: "Diğer kullanıcıların yaptığınız değişiklikleri görmesi için OpenStreetMap'e kaydediniz", + error: "Kaydederken bir hata oluştu", + uploading: "Değişiklikleriniz OpenStreetMap'e gönderiliyor.", + unsaved_changes: "Kaydedilmemiş değişiklikleriniz var" + }, + source_switch: { live: "canlı", dev: "geliştirme" }, + tag_reference: { + description: "Açıklama" + }, + + validations: { + untagged_point: "Herhangi bir çizgi ya da alana bağlantısı olmayan ve etiketlenmemiş bir nokta.", + untagged_line: "Etiketlenmemiş çizgi", + untagged_area: "Etiketlenmemiş alan", + tag_suggests_area: "{tag} etiketi buranın alan olmasını tavsiye ediyor ama alan değil.", + deprecated_tags: "Kullanımdan kaldırılmış etiket : {tags}" + }, + zoom: { in: "Yaklaş", out: "Uzaklaş" From 8882023e5b6029e335a3c32e9a51a14b75d9e6d1 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 19:59:32 -0800 Subject: [PATCH 298/332] tag_reference translations --- js/id/ui/tag_reference.js | 5 +++-- locale/da.js | 4 +++- locale/de.js | 4 +++- locale/en.js | 4 +++- locale/es.js | 4 +++- locale/fr.js | 4 +++- locale/ja.js | 4 +++- locale/lv.js | 4 +++- locale/tr.js | 4 +++- 9 files changed, 27 insertions(+), 10 deletions(-) diff --git a/js/id/ui/tag_reference.js b/js/id/ui/tag_reference.js index dc583b932..8a8ea2041 100644 --- a/js/id/ui/tag_reference.js +++ b/js/id/ui/tag_reference.js @@ -11,7 +11,7 @@ iD.ui.tagReference = function(selection) { .enter() .append('span') .attr('title', function(d) { - return 'used with ' + d; + return t('tag_reference.used_with', {type: d}); }) .attr('class', function(d) { return 'icon big icon-pre-text big-' + d; @@ -36,6 +36,7 @@ iD.ui.tagReference = function(selection) { referenceBody .append('p') .text(g('description')); + referenceBody .append('a') .attr('target', '_blank') @@ -43,7 +44,7 @@ iD.ui.tagReference = function(selection) { return 'http://wiki.openstreetmap.org/wiki/' + d.title; }) .text(function(d) { - return d.title + ' on wiki.osm.org'; + return t('tag_reference.on_wiki', {tag: d.title}); }); }); }; diff --git a/locale/da.js b/locale/da.js index 17b75c340..199cc2f5d 100644 --- a/locale/da.js +++ b/locale/da.js @@ -195,7 +195,9 @@ locale.da = { }, tag_reference: { - description: "Description" + description: "Description", + on_wiki: "{tag} on wiki.osm.org", + used_with: "used with {type}" }, zoom: { diff --git a/locale/de.js b/locale/de.js index 60f4c6155..71d94c9f3 100644 --- a/locale/de.js +++ b/locale/de.js @@ -195,7 +195,9 @@ locale.de = { }, tag_reference: { - description: "Beschreibung" + description: "Beschreibung", + on_wiki: "{tag} on wiki.osm.org", + used_with: "used with {type}" }, validations: { diff --git a/locale/en.js b/locale/en.js index 9060f7b66..7e3534f71 100644 --- a/locale/en.js +++ b/locale/en.js @@ -195,7 +195,9 @@ locale.en = { }, tag_reference: { - description: 'Description' + description: "Description", + on_wiki: "{tag} on wiki.osm.org", + used_with: "used with {type}" }, validations: { diff --git a/locale/es.js b/locale/es.js index c440429db..e82e4c465 100644 --- a/locale/es.js +++ b/locale/es.js @@ -195,7 +195,9 @@ locale.es = { }, tag_reference: { - description: "Descripción" //"Description", + description: "Descripción", + on_wiki: "{tag} on wiki.osm.org", + used_with: "used with {type}" }, validations: { diff --git a/locale/fr.js b/locale/fr.js index d6c36189a..da6e54cf3 100644 --- a/locale/fr.js +++ b/locale/fr.js @@ -195,7 +195,9 @@ locale.fr = { }, tag_reference: { - description: "Déscription" + description: "Déscription", + on_wiki: "{tag} on wiki.osm.org", + used_with: "used with {type}" }, validations: { diff --git a/locale/ja.js b/locale/ja.js index 61b76ace5..fa4297828 100644 --- a/locale/ja.js +++ b/locale/ja.js @@ -195,7 +195,9 @@ locale.ja = { }, tag_reference: { - description: "説明" + description: "説明", + on_wiki: "{tag} on wiki.osm.org", + used_with: "used with {type}" }, validations: { diff --git a/locale/lv.js b/locale/lv.js index 3a799c2a9..60f9136ff 100644 --- a/locale/lv.js +++ b/locale/lv.js @@ -195,7 +195,9 @@ locale.lv = { }, tag_reference: { - description: "Apraksts" + description: "Apraksts", + on_wiki: "{tag} on wiki.osm.org", + used_with: "used with {type}" }, validations: { diff --git a/locale/tr.js b/locale/tr.js index f212ca373..1274cf221 100644 --- a/locale/tr.js +++ b/locale/tr.js @@ -195,7 +195,9 @@ locale.tr = { }, tag_reference: { - description: "Açıklama" + description: "Açıklama", + on_wiki: "{tag} on wiki.osm.org", + used_with: "used with {type}" }, validations: { From 6d3d00e78d70deedac194361431818b3cd7165af Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 20:02:35 -0800 Subject: [PATCH 299/332] inspector translation --- js/id/ui/inspector.js | 37 ++++++++++++++++++++++--------------- locale/da.js | 3 ++- locale/de.js | 3 ++- locale/en.js | 3 ++- locale/es.js | 3 ++- locale/fr.js | 3 ++- locale/ja.js | 3 ++- locale/lv.js | 3 ++- locale/tr.js | 3 ++- 9 files changed, 38 insertions(+), 23 deletions(-) diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js index 1b1c78be6..bc15cdbb4 100644 --- a/js/id/ui/inspector.js +++ b/js/id/ui/inspector.js @@ -27,15 +27,19 @@ iD.ui.Inspector = function() { tagList = inspectorwrap.append('ul'); var newTag = inspectorwrap.append('button') - .attr('class', 'add-tag'); + .attr('class', 'add-tag'); - newTag.on('click', function() { - addTag(); - focusNewKey(); - }); + newTag.on('click', function () { + addTag(); + focusNewKey(); + }); - newTag.append('span').attr('class', 'icon icon-pre-text plus'); - newTag.append('span').attr('class','label').text(t('inspector.new_tag')); + newTag.append('span') + .attr('class', 'icon icon-pre-text plus'); + + newTag.append('span') + .attr('class', 'label') + .text(t('inspector.new_tag')); drawTags(entity.tags); @@ -62,17 +66,20 @@ iD.ui.Inspector = function() { var entity = selection.datum(); var inspectorButton = selection.append('button') - .attr('class', 'apply action') - .on('click', apply); + .attr('class', 'apply action') + .on('click', apply); - inspectorButton.append('span').attr('class','label').text(t('inspector.okay')); + inspectorButton.append('span') + .attr('class','label') + .text(t('inspector.okay')); - var minorButtons = selection.append('div').attr('class','minor-buttons fl'); + var minorButtons = selection.append('div') + .attr('class','minor-buttons fl'); - minorButtons.append('a') - .attr('href', 'http://www.openstreetmap.org/browse/' + entity.type + '/' + entity.osmId()) - .attr('target', '_blank') - .text('View on OSM'); + minorButtons.append('a') + .attr('href', 'http://www.openstreetmap.org/browse/' + entity.type + '/' + entity.osmId()) + .attr('target', '_blank') + .text(t('inspector.view_on_osm')); } function drawTags(tags) { diff --git a/locale/da.js b/locale/da.js index 199cc2f5d..6f7e85d0b 100644 --- a/locale/da.js +++ b/locale/da.js @@ -178,7 +178,8 @@ locale.da = { no_documentation_key: "Der er ingen dokumenation tilgængelig for denne nøgle", new_tag: "Nyt Tag", edit_tags: "Ret tags", - okay: "Ok" + okay: "Ok", + view_on_osm: "View on OSM" }, save: { diff --git a/locale/de.js b/locale/de.js index 71d94c9f3..5291fb3aa 100644 --- a/locale/de.js +++ b/locale/de.js @@ -170,7 +170,8 @@ locale.de = { no_documentation_key: "Für dises Schlüsselwort ist keine Dokumentation verfügbar", new_tag: "Neues Attribut", edit_tags: "Attribute bearbeiten", - okay: "OK" + okay: "OK", + view_on_osm: "View on OSM" }, layerswitcher: { diff --git a/locale/en.js b/locale/en.js index 7e3534f71..015e4193c 100644 --- a/locale/en.js +++ b/locale/en.js @@ -170,7 +170,8 @@ locale.en = { no_documentation_key: "There is no documentation available for this key", new_tag: "New Tag", edit_tags: "Edit tags", - okay: "Okay" + okay: "Okay", + view_on_osm: "View on OSM" }, layerswitcher: { diff --git a/locale/es.js b/locale/es.js index e82e4c465..adc2150c9 100644 --- a/locale/es.js +++ b/locale/es.js @@ -170,7 +170,8 @@ locale.es = { no_documentation_key: "No hay documentación disponible para esta tecla", //"This is no documentation available for this key", new_tag: "Nueve etiqueta", //"New Tag" edit_tags: "Editar etiquetas", //"Edit tags", - okay: "OK" //"Okay", + okay: "OK", + view_on_osm: "View on OSM" }, layerswitcher: { diff --git a/locale/fr.js b/locale/fr.js index da6e54cf3..f0ddce52c 100644 --- a/locale/fr.js +++ b/locale/fr.js @@ -170,7 +170,8 @@ locale.fr = { no_documentation_key: "Aucune documentation n'est disponible pour cette clé", new_tag: "Nouveau tag", edit_tags: "Editer les tags", - okay: "Okay" + okay: "Okay", + view_on_osm: "View on OSM" }, layerswitcher: { diff --git a/locale/ja.js b/locale/ja.js index fa4297828..f2c1839a5 100644 --- a/locale/ja.js +++ b/locale/ja.js @@ -170,7 +170,8 @@ locale.ja = { no_documentation_key: "このキーに対する説明文はありません", new_tag: "新規タグ", edit_tags: "タグを編集", - okay: "OK" + okay: "OK", + view_on_osm: "View on OSM" }, layerswitcher: { diff --git a/locale/lv.js b/locale/lv.js index 60f9136ff..36d93c4aa 100644 --- a/locale/lv.js +++ b/locale/lv.js @@ -170,7 +170,8 @@ locale.lv = { no_documentation_key: "There is no documentation available for this key", new_tag: "Jauns apzīmējums", edit_tags: "Rediģēt apzīmējumus", - okay: "Labi" + okay: "Labi", + view_on_osm: "View on OSM" }, layerswitcher: { diff --git a/locale/tr.js b/locale/tr.js index 1274cf221..a26542333 100644 --- a/locale/tr.js +++ b/locale/tr.js @@ -170,7 +170,8 @@ locale.tr = { no_documentation_key: "Bu anahtar için dökümantasyon bulunmamaktadır.", new_tag: "Yeni Etiket", edit_tags: "Etiketleri güncelle", - okay: "Tamam" + okay: "Tamam", + view_on_osm: "View on OSM" }, layerswitcher: { From 0578d645bc1079a4a71dc8e47791bfc5d7b65ccc Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 20:05:51 -0800 Subject: [PATCH 300/332] restore translation --- js/id/ui/restore.js | 22 ++++++++++++---------- locale/da.js | 6 ++++++ locale/de.js | 6 ++++++ locale/en.js | 6 ++++++ locale/es.js | 6 ++++++ locale/fr.js | 6 ++++++ locale/ja.js | 6 ++++++ locale/lv.js | 6 ++++++ locale/tr.js | 6 ++++++ 9 files changed, 60 insertions(+), 10 deletions(-) diff --git a/js/id/ui/restore.js b/js/id/ui/restore.js index aaadae950..2fd8d9de9 100644 --- a/js/id/ui/restore.js +++ b/js/id/ui/restore.js @@ -6,19 +6,21 @@ iD.ui.restore = function(selection, history) { var introModal = modal.select('.content'); - introModal.append('div') - .attr('class', 'modal-section fillL') - .append('h3').text('You have unsaved changes from a previous editing session. Do you wish to restore these changes?'); - var buttonWrap = introModal.append('div') - .attr('class', 'modal-section fillD cf col12'); + introModal.append('div') + .attr('class', 'modal-section fillL') + .append('h3') + .text(t('restore.description')); - buttons = buttonWrap - .append('div') - .attr('class', 'button-wrap joined col6'); + var buttonWrap = introModal.append('div') + .attr('class', 'modal-section fillD cf col12'); + + var buttons = buttonWrap + .append('div') + .attr('class', 'button-wrap joined col6'); buttons.append('button') .attr('class', 'save action button col6') - .text('Restore') + .text(t('restore.restore')) .on('click', function() { history.load(); modal.remove(); @@ -26,7 +28,7 @@ iD.ui.restore = function(selection, history) { buttons.append('button') .attr('class', 'cancel button col6') - .text('Reset') + .text(t('restore.reset')) .on('click', function() { modal.remove(); }); diff --git a/locale/da.js b/locale/da.js index 6f7e85d0b..1e983c3de 100644 --- a/locale/da.js +++ b/locale/da.js @@ -182,6 +182,12 @@ locale.da = { view_on_osm: "View on OSM" }, + restore: { + description: "You have unsaved changes from a previous editing session. Do you wish to restore these changes?", + restore: "Restore", + reset: "Reset" + }, + save: { title: "Gem", help: "Gem ændringer til OpenStreetMap gør dem synlige for andre brugere", diff --git a/locale/de.js b/locale/de.js index 5291fb3aa..31f919a73 100644 --- a/locale/de.js +++ b/locale/de.js @@ -182,6 +182,12 @@ locale.de = { reset: "Zurücksetzen" }, + restore: { + description: "You have unsaved changes from a previous editing session. Do you wish to restore these changes?", + restore: "Restore", + reset: "Reset" + }, + save: { title: "Speichern", help: "Speichere Änderungen auf OpenStreetMap, um diese für andere Nutzer sichtbar zu machen", diff --git a/locale/en.js b/locale/en.js index 015e4193c..b5ea93202 100644 --- a/locale/en.js +++ b/locale/en.js @@ -182,6 +182,12 @@ locale.en = { reset: "reset" }, + restore: { + description: "You have unsaved changes from a previous editing session. Do you wish to restore these changes?", + restore: "Restore", + reset: "Reset" + }, + save: { title: "Save", help: "Save changes to OpenStreetMap, making them visible to other users", diff --git a/locale/es.js b/locale/es.js index adc2150c9..2e3eebcdd 100644 --- a/locale/es.js +++ b/locale/es.js @@ -182,6 +182,12 @@ locale.es = { reset: "reiniciar" //"reset" }, + restore: { + description: "You have unsaved changes from a previous editing session. Do you wish to restore these changes?", + restore: "Restore", + reset: "Reset" + }, + save: { title: "Guardar", //"Save", help: "Guardar los cambios en OpenStreetMap haciéndolos visibles a otros usuarios", //"Save changes to OpenStreetMap, making them visible to other users", diff --git a/locale/fr.js b/locale/fr.js index f0ddce52c..61ffcf675 100644 --- a/locale/fr.js +++ b/locale/fr.js @@ -182,6 +182,12 @@ locale.fr = { reset: "reset" }, + restore: { + description: "You have unsaved changes from a previous editing session. Do you wish to restore these changes?", + restore: "Restore", + reset: "Reset" + }, + save: { title: "Sauvegarder", help: "Envoie des modifications au serveyr OpenStreetMap afin qu'elles soient visibles par les autres contributeurs.", diff --git a/locale/ja.js b/locale/ja.js index f2c1839a5..0fd8c5229 100644 --- a/locale/ja.js +++ b/locale/ja.js @@ -182,6 +182,12 @@ locale.ja = { reset: "設定リセット" }, + restore: { + description: "You have unsaved changes from a previous editing session. Do you wish to restore these changes?", + restore: "Restore", + reset: "Reset" + }, + save: { title: "Save", help: "変更点をOpenStreetMapに保存し、他ユーザが確認できるようにします", diff --git a/locale/lv.js b/locale/lv.js index 36d93c4aa..58d05e4a4 100644 --- a/locale/lv.js +++ b/locale/lv.js @@ -182,6 +182,12 @@ locale.lv = { reset: "Pārstatīt" }, + restore: { + description: "You have unsaved changes from a previous editing session. Do you wish to restore these changes?", + restore: "Restore", + reset: "Reset" + }, + save: { title: "Saglabāt", help: "Saglabā izmaiņas, padarot tās redzamas citiem", diff --git a/locale/tr.js b/locale/tr.js index a26542333..566ee10d2 100644 --- a/locale/tr.js +++ b/locale/tr.js @@ -182,6 +182,12 @@ locale.tr = { reset: "Sıfırla" }, + restore: { + description: "You have unsaved changes from a previous editing session. Do you wish to restore these changes?", + restore: "Restore", + reset: "Reset" + }, + save: { title: "Kaydet", help: "Diğer kullanıcıların yaptığınız değişiklikleri görmesi için OpenStreetMap'e kaydediniz", From 02ed91096a1041f1c713057a154cab76367d1ef1 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 20:08:36 -0800 Subject: [PATCH 301/332] Follow ui pattern --- js/id/ui.js | 3 ++- js/id/ui/restore.js | 58 ++++++++++++++++++++++----------------------- 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index 4e6ec4c24..628a46d4a 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -154,7 +154,8 @@ iD.ui = function(context) { } if (history.lock() && history.restorableChanges()) { - iD.ui.restore(context.container(), history); + context.container() + .call(iD.ui.Restore(context)) } }; diff --git a/js/id/ui/restore.js b/js/id/ui/restore.js index 2fd8d9de9..518bf1ddd 100644 --- a/js/id/ui/restore.js +++ b/js/id/ui/restore.js @@ -1,37 +1,37 @@ -iD.ui.restore = function(selection, history) { - var modal = iD.ui.modal(selection); +iD.ui.Restore = function(context) { + return function(selection) { + var modal = iD.ui.modal(selection); - modal.select('.modal') - .attr('class', 'modal-splash modal'); + modal.select('.modal') + .attr('class', 'modal-splash modal'); - var introModal = modal.select('.content'); + var introModal = modal.select('.content'); - introModal.append('div') - .attr('class', 'modal-section fillL') - .append('h3') - .text(t('restore.description')); + introModal.append('div') + .attr('class', 'modal-section fillL') + .append('h3') + .text(t('restore.description')); - var buttonWrap = introModal.append('div') - .attr('class', 'modal-section fillD cf col12'); + var buttonWrap = introModal.append('div') + .attr('class', 'modal-section fillD cf col12'); - var buttons = buttonWrap - .append('div') - .attr('class', 'button-wrap joined col6'); + var buttons = buttonWrap + .append('div') + .attr('class', 'button-wrap joined col6'); - buttons.append('button') - .attr('class', 'save action button col6') - .text(t('restore.restore')) - .on('click', function() { - history.load(); - modal.remove(); - }); + buttons.append('button') + .attr('class', 'save action button col6') + .text(t('restore.restore')) + .on('click', function() { + context.history().load(); + modal.remove(); + }); - buttons.append('button') - .attr('class', 'cancel button col6') - .text(t('restore.reset')) - .on('click', function() { - modal.remove(); - }); - - return modal; + buttons.append('button') + .attr('class', 'cancel button col6') + .text(t('restore.reset')) + .on('click', function() { + modal.remove(); + }); + } }; From 806de963dbbac723d5c20cf5368eb2b289064650 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 20:16:53 -0800 Subject: [PATCH 302/332] Cleanup, translate Splash --- js/id/ui.js | 6 ++---- js/id/ui/splash.js | 41 ++++++++++++++++++++++++++--------------- locale/da.js | 5 +++++ locale/de.js | 5 +++++ locale/en.js | 5 +++++ locale/es.js | 5 +++++ locale/fr.js | 5 +++++ locale/ja.js | 5 +++++ locale/lv.js | 5 +++++ locale/tr.js | 5 +++++ 10 files changed, 68 insertions(+), 19 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index 628a46d4a..15fb55c09 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -148,10 +148,8 @@ iD.ui = function(context) { context.enter(iD.modes.Browse(context)); - if (!context.storage('sawSplash')) { - iD.ui.splash(context.container()); - context.storage('sawSplash', true); - } + context.container() + .call(iD.ui.Splash(context)); if (history.lock() && history.restorableChanges()) { context.container() diff --git a/js/id/ui/splash.js b/js/id/ui/splash.js index bfcbf3e05..c0e79261c 100644 --- a/js/id/ui/splash.js +++ b/js/id/ui/splash.js @@ -1,21 +1,32 @@ -iD.ui.splash = function(selection) { - var modal = iD.ui.modal(selection); +iD.ui.Splash = function(context) { + return function(selection) { + if (context.storage('sawSplash')) + return; - modal.select('.modal') - .attr('class', 'modal-splash modal'); + context.storage('sawSplash', true); - var introModal = modal.select('.content') - .append('div') - .attr('class', 'modal-section fillL'); + var modal = iD.ui.modal(selection); - introModal.append('div') - .attr('class','logo'); + modal.select('.modal') + .attr('class', 'modal-splash modal'); - introModal.append('div') - .html("

Welcome to the iD OpenStreetMap editor

" + - "This is development version 0.0.0-alpha1. " + - "For more information see ideditor.com" + - " and report bugs at github.com.systemed/iD.

"); + var introModal = modal.select('.content') + .append('div') + .attr('class', 'modal-section fillL'); - return modal; + introModal.append('div') + .attr('class', 'logo'); + + var div = introModal.append('div'); + + div.append("h2") + .text(t('splash.welcome')); + + div.append("p") + .html(t('splash.text', { + version: iD.version, + website: 'ideditor.com', + github: 'github.com' + })); + } }; diff --git a/locale/da.js b/locale/da.js index 1e983c3de..d42f95c61 100644 --- a/locale/da.js +++ b/locale/da.js @@ -196,6 +196,11 @@ locale.da = { unsaved_changes: "Du har ændringer der ikke er gemt endnu", }, + splash: { + welcome: "Welcome to the iD OpenStreetMap editor", + text: "This is development version {version}. For more information see {website} and report bugs at {github}." + }, + source_switch: { live: "live", dev: "dev" diff --git a/locale/de.js b/locale/de.js index 31f919a73..8629471be 100644 --- a/locale/de.js +++ b/locale/de.js @@ -196,6 +196,11 @@ locale.de = { unsaved_changes: "Ungespeicherte Änderugen vorhanden", }, + splash: { + welcome: "Welcome to the iD OpenStreetMap editor", + text: "This is development version {version}. For more information see {website} and report bugs at {github}." + }, + source_switch: { live: "live", dev: "dev" diff --git a/locale/en.js b/locale/en.js index b5ea93202..bd26f932b 100644 --- a/locale/en.js +++ b/locale/en.js @@ -196,6 +196,11 @@ locale.en = { unsaved_changes: "You have unsaved changes" }, + splash: { + welcome: "Welcome to the iD OpenStreetMap editor", + text: "This is development version {version}. For more information see {website} and report bugs at {github}." + }, + source_switch: { live: "live", dev: "dev" diff --git a/locale/es.js b/locale/es.js index 2e3eebcdd..2d47ecfe6 100644 --- a/locale/es.js +++ b/locale/es.js @@ -196,6 +196,11 @@ locale.es = { unsaved_changes: "Tienes cambios sin guardar" //"You have unsaved changes", }, + splash: { + welcome: "Welcome to the iD OpenStreetMap editor", + text: "This is development version {version}. For more information see {website} and report bugs at {github}." + }, + source_switch: { live: "en vivo", //"live", dev: "dev" diff --git a/locale/fr.js b/locale/fr.js index 61ffcf675..4716080cc 100644 --- a/locale/fr.js +++ b/locale/fr.js @@ -196,6 +196,11 @@ locale.fr = { unsaved_changes: "Vous avez des modifications non enregistrées" }, + splash: { + welcome: "Welcome to the iD OpenStreetMap editor", + text: "This is development version {version}. For more information see {website} and report bugs at {github}." + }, + source_switch: { live: "live", dev: "dev" diff --git a/locale/ja.js b/locale/ja.js index 0fd8c5229..3d4abec79 100644 --- a/locale/ja.js +++ b/locale/ja.js @@ -196,6 +196,11 @@ locale.ja = { unsaved_changes: "変更が保存されていません" }, + splash: { + welcome: "Welcome to the iD OpenStreetMap editor", + text: "This is development version {version}. For more information see {website} and report bugs at {github}." + }, + source_switch: { live: "本番サーバ", dev: "開発サーバ" diff --git a/locale/lv.js b/locale/lv.js index 58d05e4a4..9eb93f387 100644 --- a/locale/lv.js +++ b/locale/lv.js @@ -196,6 +196,11 @@ locale.lv = { unsaved_changes: "Jums ir nesaglabātas izmaiņas" }, + splash: { + welcome: "Welcome to the iD OpenStreetMap editor", + text: "This is development version {version}. For more information see {website} and report bugs at {github}." + }, + source_switch: { live: "live", dev: "dev" diff --git a/locale/tr.js b/locale/tr.js index 566ee10d2..402d822f1 100644 --- a/locale/tr.js +++ b/locale/tr.js @@ -196,6 +196,11 @@ locale.tr = { unsaved_changes: "Kaydedilmemiş değişiklikleriniz var" }, + splash: { + welcome: "Welcome to the iD OpenStreetMap editor", + text: "This is development version {version}. For more information see {website} and report bugs at {github}." + }, + source_switch: { live: "canlı", dev: "geliştirme" From 8928e757a413681e9dce321bb083c4cf53656227 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 20:19:36 -0800 Subject: [PATCH 303/332] Simplify --- js/id/ui.js | 9 ++------- js/id/ui/restore.js | 3 +++ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index 15fb55c09..fe35ccc16 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -149,13 +149,8 @@ iD.ui = function(context) { context.enter(iD.modes.Browse(context)); context.container() - .call(iD.ui.Splash(context)); - - if (history.lock() && history.restorableChanges()) { - context.container() - .call(iD.ui.Restore(context)) - } - + .call(iD.ui.Splash(context)) + .call(iD.ui.Restore(context)); }; }; diff --git a/js/id/ui/restore.js b/js/id/ui/restore.js index 518bf1ddd..d15f80d96 100644 --- a/js/id/ui/restore.js +++ b/js/id/ui/restore.js @@ -1,5 +1,8 @@ iD.ui.Restore = function(context) { return function(selection) { + if (!context.history().lock() || !context.history().restorableChanges()) + return; + var modal = iD.ui.modal(selection); modal.select('.modal') From cfb6f7b1d4e6616b7a5013f126c6e2e47078a751 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 20:29:16 -0800 Subject: [PATCH 304/332] Cleanup --- js/id/ui.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index fe35ccc16..3379ef4f1 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -63,17 +63,20 @@ iD.ui = function(context) { var userContainer = about.append('div') .attr('class', 'user-container'); - userContainer - .append('div') - .attr('class', 'hello'); + userContainer.append('div') + .attr('class', 'hello'); + + userContainer.call(iD.ui.UserPanel(connection) + .on('logout.editor', connection.logout) + .on('login.editor', connection.authenticate)); var aboutList = about.append('ul') - .attr('id','about') - .attr('class','link-list'); + .attr('id','about') + .attr('class', 'link-list'); var linkList = aboutList.append('ul') .attr('id','about') - .attr('class','pad1 fillD about-block link-list'); + .attr('class', 'pad1 fillD about-block link-list'); linkList.append('li') .append('a') @@ -142,10 +145,6 @@ iD.ui = function(context) { map.centerZoom([-77.02271, 38.90085], 20); } - userContainer.call(iD.ui.UserPanel(connection) - .on('logout.editor', connection.logout) - .on('login.editor', connection.authenticate)); - context.enter(iD.modes.Browse(context)); context.container() From f98cee40a7a0f34ef1b2a43e5c083fb2ca62d030 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 20:46:54 -0800 Subject: [PATCH 305/332] Extract iD.ui.Attribution --- index.html | 1 + js/id/ui.js | 12 +++--------- js/id/ui/attribution.js | 10 ++++++++++ 3 files changed, 14 insertions(+), 9 deletions(-) create mode 100644 js/id/ui/attribution.js diff --git a/index.html b/index.html index e79c178e4..6e8661145 100644 --- a/index.html +++ b/index.html @@ -57,6 +57,7 @@ + diff --git a/js/id/ui.js b/js/id/ui.js index 3379ef4f1..328d46b45 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -90,15 +90,9 @@ iD.ui = function(context) { .attr('href', 'http://github.com/systemed/iD/issues') .text(t('report_a_bug')); - var imagery = linkList.append('li') - .attr('id', 'attribution'); - - imagery.append('span') - .text('imagery'); - - imagery - .append('span') - .attr('class', 'provided-by'); + linkList.append('li') + .attr('id', 'attribution') + .call(iD.ui.Attribution(context)); linkList.append('li') .attr('class', 'source-switch') diff --git a/js/id/ui/attribution.js b/js/id/ui/attribution.js new file mode 100644 index 000000000..282833e7e --- /dev/null +++ b/js/id/ui/attribution.js @@ -0,0 +1,10 @@ +iD.ui.Attribution = function(context) { + return function(selection) { + selection.append('span') + .text('imagery'); + + selection + .append('span') + .attr('class', 'provided-by'); + } +}; From 6f6958ad98e618cfddd6b9cf6837c62b01d0fd27 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 20:50:17 -0800 Subject: [PATCH 306/332] Remove redundant ul, id -> class --- css/app.css | 2 +- js/id/ui.js | 12 ++++-------- js/id/ui/layerswitcher.js | 2 +- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/css/app.css b/css/app.css index 0e5651c9a..4cb56f910 100644 --- a/css/app.css +++ b/css/app.css @@ -930,7 +930,7 @@ img.tile { color:#fff; } -#user-list a:not(:last-child):after { +.user-list a:not(:last-child):after { content: ', '; } diff --git a/js/id/ui.js b/js/id/ui.js index 328d46b45..40e700801 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -70,12 +70,8 @@ iD.ui = function(context) { .on('logout.editor', connection.logout) .on('login.editor', connection.authenticate)); - var aboutList = about.append('ul') - .attr('id','about') - .attr('class', 'link-list'); - - var linkList = aboutList.append('ul') - .attr('id','about') + var linkList = about.append('ul') + .attr('id', 'about') .attr('class', 'pad1 fillD about-block link-list'); linkList.append('li') @@ -91,7 +87,7 @@ iD.ui = function(context) { .text(t('report_a_bug')); linkList.append('li') - .attr('id', 'attribution') + .attr('class', 'attribution') .call(iD.ui.Attribution(context)); linkList.append('li') @@ -99,7 +95,7 @@ iD.ui = function(context) { .call(iD.ui.SourceSwitch(context)); linkList.append('li') - .attr('id', 'user-list') + .attr('class', 'user-list') .call(iD.ui.Contributors(context)); window.onbeforeunload = function() { diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index f4d57a422..da800596f 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -92,7 +92,7 @@ iD.ui.LayerSwitcher = function(context) { return d === context.background().source(); }); - var provided_by = context.container().select('#attribution .provided-by') + var provided_by = context.container().select('.attribution .provided-by') .html(''); if (d.data.terms_url) { From 4bbed7e9437e52889bad30ea7d801a2cd5f55f6d Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 21:03:34 -0800 Subject: [PATCH 307/332] Cleanup, fix UserPanel --- js/id/connection.js | 2 +- js/id/ui.js | 12 ++---- js/id/ui/userpanel.js | 97 +++++++++++++++++++++---------------------- 3 files changed, 52 insertions(+), 59 deletions(-) diff --git a/js/id/connection.js b/js/id/connection.js index ea90c5591..f15aa80f8 100644 --- a/js/id/connection.js +++ b/js/id/connection.js @@ -228,7 +228,7 @@ iD.Connection = function(context) { if (img && img[0].getAttribute('href')) { image_url = img[0].getAttribute('href'); } - callback(connection.user({ + callback(undefined, connection.user({ display_name: u.attributes.display_name.nodeValue, image_url: image_url, id: u.attributes.id.nodeValue diff --git a/js/id/ui.js b/js/id/ui.js index 40e700801..115199edf 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -60,15 +60,9 @@ iD.ui = function(context) { var about = container.append('div') .attr('class','col12 about-block fillD pad1'); - var userContainer = about.append('div') - .attr('class', 'user-container'); - - userContainer.append('div') - .attr('class', 'hello'); - - userContainer.call(iD.ui.UserPanel(connection) - .on('logout.editor', connection.logout) - .on('login.editor', connection.authenticate)); + about.append('div') + .attr('class', 'user-container') + .call(iD.ui.UserPanel(context)); var linkList = about.append('ul') .attr('id', 'about') diff --git a/js/id/ui/userpanel.js b/js/id/ui/userpanel.js index 3dab2a5bd..f323706ae 100644 --- a/js/id/ui/userpanel.js +++ b/js/id/ui/userpanel.js @@ -1,54 +1,53 @@ -iD.ui.UserPanel = function(connection) { - var event = d3.dispatch('logout', 'login'); +iD.ui.UserPanel = function(context) { + var connection = context.connection(); - function user(selection) { - function update() { - if (connection.authenticated()) { - selection.style('display', 'block'); - connection.userDetails(function(err, user_details) { - - selection.html(''); - - if (err) return; - - // Link - var userLink = selection.append('a') - .attr('href', connection.url() + '/user/' + - user_details.display_name) - .attr('target', '_blank'); - - // Add thumbnail or dont - if (user_details.image_url) { - userLink.append('img') - .attr('class', 'icon icon-pre-text user-icon') - .attr('src', user_details.image_url); - } else { - userLink.append('span') - .attr('class','icon avatar icon-pre-text'); - } - - // Add user name - userLink.append('span') - .attr('class','label') - .text(user_details.display_name); - - selection - .append('a') - .attr('class', 'logout') - .attr('href', '#') - .text(t('logout')) - .on('click.logout', function() { - d3.event.preventDefault(); - event.logout(); - }); - }); - } else { - selection.html('').style('display', 'none'); - } + function update(selection) { + if (!connection.authenticated()) { + selection.html('') + .style('display', 'none'); + return; } - connection.on('auth', update); - update(); + + selection.style('display', 'block'); + + connection.userDetails(function(err, details) { + selection.html(''); + + if (err) return; + + // Link + var userLink = selection.append('a') + .attr('href', connection.url() + '/user/' + details.display_name) + .attr('target', '_blank'); + + // Add thumbnail or dont + if (details.image_url) { + userLink.append('img') + .attr('class', 'icon icon-pre-text user-icon') + .attr('src', details.image_url); + } else { + userLink.append('span') + .attr('class', 'icon avatar icon-pre-text'); + } + + // Add user name + userLink.append('span') + .attr('class', 'label') + .text(details.display_name); + + selection.append('a') + .attr('class', 'logout') + .attr('href', '#') + .text(t('logout')) + .on('click.logout', function() { + d3.event.preventDefault(); + connection.logout(); + }); + }); } - return d3.rebind(user, event, 'on'); + return function(selection) { + connection.on('auth', function() { update(selection); }); + update(selection); + }; }; From a25f2895d078b11f868c73f65ca79c9420bfcb84 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 21:04:59 -0800 Subject: [PATCH 308/332] Rename UserPanel -> Account --- combobox.html | 2 +- css/app.css | 4 ++-- index.html | 2 +- js/id/ui.js | 2 +- js/id/ui/{userpanel.js => account.js} | 2 +- test/index.html | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) rename js/id/ui/{userpanel.js => account.js} (97%) diff --git a/combobox.html b/combobox.html index 63a2c9757..383dbe325 100644 --- a/combobox.html +++ b/combobox.html @@ -61,7 +61,7 @@ - + diff --git a/css/app.css b/css/app.css index 4cb56f910..11e710970 100644 --- a/css/app.css +++ b/css/app.css @@ -936,11 +936,11 @@ img.tile { /* Account Information */ -.user-container { +.account { float: left; } -.user-container .logout { +.account .logout { margin-left:10px; border-left: 1px solid white; padding-left: 10px; diff --git a/index.html b/index.html index 6e8661145..61790eae0 100644 --- a/index.html +++ b/index.html @@ -66,7 +66,7 @@ - + diff --git a/js/id/ui.js b/js/id/ui.js index 115199edf..086cd59a4 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -61,7 +61,7 @@ iD.ui = function(context) { .attr('class','col12 about-block fillD pad1'); about.append('div') - .attr('class', 'user-container') + .attr('class', 'account') .call(iD.ui.UserPanel(context)); var linkList = about.append('ul') diff --git a/js/id/ui/userpanel.js b/js/id/ui/account.js similarity index 97% rename from js/id/ui/userpanel.js rename to js/id/ui/account.js index f323706ae..5e9fe7705 100644 --- a/js/id/ui/userpanel.js +++ b/js/id/ui/account.js @@ -1,4 +1,4 @@ -iD.ui.UserPanel = function(context) { +iD.ui.Account = function(context) { var connection = context.connection(); function update(selection) { diff --git a/test/index.html b/test/index.html index dcea8e28c..05482ab1f 100644 --- a/test/index.html +++ b/test/index.html @@ -63,7 +63,7 @@ - + From 9695a9cafaa275715a11cff443b38c7380e32bc1 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 21:11:16 -0800 Subject: [PATCH 309/332] Fix geocode tooltip --- css/app.css | 2 +- js/id/ui.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/css/app.css b/css/app.css index 11e710970..efcdd8b87 100644 --- a/css/app.css +++ b/css/app.css @@ -841,7 +841,7 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} width: 100%; } -.geocode-control div { +.geocode-control div.content { z-index: 100; top: 190px; max-height: 300px; diff --git a/js/id/ui.js b/js/id/ui.js index 086cd59a4..72712f861 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -62,7 +62,7 @@ iD.ui = function(context) { about.append('div') .attr('class', 'account') - .call(iD.ui.UserPanel(context)); + .call(iD.ui.Account(context)); var linkList = about.append('ul') .attr('id', 'about') From 5d11770e7e95f3d3c317fcbac6b668cf41a6ccf5 Mon Sep 17 00:00:00 2001 From: m0rix Date: Wed, 13 Feb 2013 11:33:34 +0100 Subject: [PATCH 310/332] Update locale/de.js --- locale/de.js | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/locale/de.js b/locale/de.js index 8629471be..7145dfe2b 100644 --- a/locale/de.js +++ b/locale/de.js @@ -139,15 +139,15 @@ locale.de = { report_a_bug: "Programmfehler melden", commit: { - title: "Save Changes", - description_placeholder: "Brief description of your contributions", - upload_explanation: "The changes you upload as {user} will be visible on all maps that use OpenStreetMap data.", - save: "Save", - cancel: "Cancel", - warnings: "Warnings", - modified: "Modified", - deleted: "Deleted", - created: "Created" + title: "Änderungen speichern", + description_placeholder: "Eine kurze Beschreibung deiner Beiträge", + upload_explanation: "Änderungen, die du als {user} hochlädst werden sichtbar auf allen Karte, die OpenStreetMap nutzen.", + save: "Speichern", + cancel: "Abbrechen", + warnings: "Warnungen", + modified: "Verändert", + deleted: "Gelöscht", + created: "Erstellt" }, contributors: { @@ -162,7 +162,7 @@ locale.de = { }, geolocate: { - title: "Show My Location" + title: "Zeige meine Position" }, inspector: { @@ -171,7 +171,7 @@ locale.de = { new_tag: "Neues Attribut", edit_tags: "Attribute bearbeiten", okay: "OK", - view_on_osm: "View on OSM" + view_on_osm: "auf OSM ansehen" }, layerswitcher: { @@ -183,9 +183,9 @@ locale.de = { }, restore: { - description: "You have unsaved changes from a previous editing session. Do you wish to restore these changes?", - restore: "Restore", - reset: "Reset" + description: "Es gibt ungespeicherte Änderungen aus einer vorherigen Sitzung. Möchtest du diese Änderungen wiederherstellen?", + restore: "Wiederherstellen", + reset: "Zurücksetzen" }, save: { @@ -197,8 +197,8 @@ locale.de = { }, splash: { - welcome: "Welcome to the iD OpenStreetMap editor", - text: "This is development version {version}. For more information see {website} and report bugs at {github}." + welcome: "Willkommen beim iD OpenStreetMap editor", + text: "Dies ist eine Entwicklungsversion {version}. Für weitere Informationen besuche {website} und melde Fehler unter {github}." }, source_switch: { @@ -208,8 +208,8 @@ locale.de = { tag_reference: { description: "Beschreibung", - on_wiki: "{tag} on wiki.osm.org", - used_with: "used with {type}" + on_wiki: "{tag} auf wiki.osm.org", + used_with: "benutzt mit {type}" }, validations: { From 7c10d37c1ebc9b3736a6cf1e7933f096274e53bc Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Wed, 13 Feb 2013 11:13:37 -0500 Subject: [PATCH 311/332] GA-based js error logging. Fixes #660 for the time being. --- index.html | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/index.html b/index.html index 4ea9d9721..2f3b5fb91 100644 --- a/index.html +++ b/index.html @@ -182,7 +182,18 @@ _gaq.push(['_trackPageview']); var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); + + // javascript errors + var lastev = ''; + window.onerror = function(message, file, lineNumber) { + var ev = ['_trackEvent', 'error', file + ':' + lineNumber, message + '']; + if (ev.join(',') !== lastev) { + _gaq.push(ev); + lastev = ev.join(','); + } + }; })(); + From f2d66285478385ea67e0449e8b2109b67b202aa9 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 13 Feb 2013 11:17:29 -0500 Subject: [PATCH 312/332] Don't break rebasing with loading --- js/id/core/history.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/id/core/history.js b/js/id/core/history.js index 1bedaeb60..4c6bbed6c 100644 --- a/js/id/core/history.js +++ b/js/id/core/history.js @@ -225,6 +225,7 @@ iD.History = function(context) { d.graph = iD.Graph(stack[0].graph).load(d.entities); return d; }); + stack[0].graph.inherited = false; dispatch.change(); } From a21b973a41fadc54a78ee51789105c359c137c37 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 13 Feb 2013 12:42:51 -0500 Subject: [PATCH 313/332] Fix saving deletions to storage, add tests --- js/id/core/graph.js | 4 ++- js/id/core/history.js | 9 +++++-- test/spec/core/history.js | 54 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 62 insertions(+), 5 deletions(-) diff --git a/js/id/core/graph.js b/js/id/core/graph.js index 96dca44e7..a3fb8e823 100644 --- a/js/id/core/graph.js +++ b/js/id/core/graph.js @@ -265,7 +265,9 @@ iD.Graph.prototype = { entity = entities[i]; prefix = i[0]; - if (prefix == 'n') { + if (entity === 'undefined') { + this.entities[i] = undefined; + } else if (prefix == 'n') { this.entities[i] = new iD.Node(entity); } else if (prefix == 'w') { diff --git a/js/id/core/history.js b/js/id/core/history.js index 4c6bbed6c..47c93c145 100644 --- a/js/id/core/history.js +++ b/js/id/core/history.js @@ -188,7 +188,10 @@ iD.History = function(context) { imagery_used: i.imagery_used, entities: i.graph.entities }; - })); + }), function includeUndefined(key, value) { + if (typeof value === 'undefined') return 'undefined'; + return value; + }); context.storage(getKey('history'), json); context.storage(getKey('nextIDs'), JSON.stringify(iD.Entity.id.next)); @@ -228,7 +231,9 @@ iD.History = function(context) { stack[0].graph.inherited = false; dispatch.change(); - } + }, + + _getKey: getKey }; diff --git a/test/spec/core/history.js b/test/spec/core/history.js index 0ecbed5d3..0618f3ec3 100644 --- a/test/spec/core/history.js +++ b/test/spec/core/history.js @@ -1,10 +1,13 @@ describe("iD.History", function () { - var history, spy, + var context, history, spy, action = function() { return iD.Graph(); }; beforeEach(function () { - history = iD.History(); + context = iD(); + history = context.history(); spy = sinon.spy(); + // clear lock + context.storage(history._getKey('lock'), null); }); describe("#graph", function () { @@ -246,4 +249,51 @@ describe("iD.History", function () { expect(spy).to.have.been.called; }); }); + + describe("#lock", function() { + it("acquires lock if possible", function() { + expect(history.lock()).to.be.true; + expect(history.lock()).to.be.false; + }); + }); + + describe("#save", function() { + + it("doesn't do anything if it doesn't have the lock", function() { + var key = history._getKey('history'); + context.storage(key, null); + history.save(); + expect(context.storage(key)).to.be.undefined; + context.storage(key, 'something'); + expect(context.storage(key)).to.equal('something'); + history.save(); + context.storage(key, null); + }); + + it("saves to localStorage", function() { + var node = iD.Node({ id: 'n' }); + history.lock(); + history.perform(iD.actions.AddEntity(node)); + history.save(); + var saved = JSON.parse(context.storage(history._getKey('history'))); + expect(saved[1].entities.n.id).to.eql('n'); + }); + }); + + describe("#load", function() { + it("saves and loads a created and deleted entities", function() { + var node = iD.Node({ id: 'n' }), + node2 = iD.Node({ id: 'n2' }); + history.lock(); + history.perform(iD.actions.AddEntity(node)); + history.perform(iD.actions.AddEntity(node2)); + history.perform(iD.actions.DeleteNode('n2')); + history.save(); + history.reset(); + expect(history.graph().entity('n')).to.be.undefined + history.load(); + expect(history.graph().entity('n').id).to.equal('n'); + expect(history.graph().entity('n2')).to.be.undefined; + }); + }); }); From 95e6b840ee0970d4dc5e1914e8a807b96999ee0c Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 13 Feb 2013 13:14:57 -0500 Subject: [PATCH 314/332] Fix key reference --- js/id/services/taginfo.js | 2 +- js/id/ui/inspector.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/js/id/services/taginfo.js b/js/id/services/taginfo.js index fc4e72542..850de9efd 100644 --- a/js/id/services/taginfo.js +++ b/js/id/services/taginfo.js @@ -76,7 +76,7 @@ iD.taginfo = function() { page: 1 }, parameters)), function(err, d) { if (err) return callback(err); - callback(null, d.data.filter(popularValues()).map(valKeyDescription)); + callback(null, d.data.filter(popularValues()).map(valKeyDescription), parameters); }); }; diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js index bc15cdbb4..7357646d1 100644 --- a/js/id/ui/inspector.js +++ b/js/id/ui/inspector.js @@ -176,16 +176,16 @@ iD.ui.Inspector = function() { } } - function keyReference(err, values) { - if (!err && values.data.length) { + function keyReference(err, values, params) { + if (!err && values.length) { iD.ui.modal(context.container()) .select('.content') .datum({ - data: values.data, + data: values, title: 'Key:' + params.key, geometry: params.geometry }) - .call(iD.keyReference(context)); + .call(iD.ui.keyReference); } else { iD.ui.flash(context.container()) .select('.content') From 65fbc808a1916e7c93b0ce06907ba333ce6619af Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 21:19:02 -0800 Subject: [PATCH 315/332] Map#pan redraws --- js/id/behavior/drag_node.js | 2 +- js/id/id.js | 1 + js/id/modes/move_way.js | 2 +- js/id/renderer/map.js | 2 +- js/id/ui.js | 6 ++---- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/js/id/behavior/drag_node.js b/js/id/behavior/drag_node.js index eff4c1174..e06bec43e 100644 --- a/js/id/behavior/drag_node.js +++ b/js/id/behavior/drag_node.js @@ -14,7 +14,7 @@ iD.behavior.DragNode = function(context) { function startNudge(nudge) { if (nudgeInterval) window.clearInterval(nudgeInterval); nudgeInterval = window.setInterval(function() { - context.map().pan(nudge).redraw(); + context.pan(nudge); }, 50); } diff --git a/js/id/id.js b/js/id/id.js index 5db609593..5b4b1fb73 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -85,6 +85,7 @@ window.iD = function () { context.projection = map.projection; context.tail = map.tail; context.redraw = map.redraw; + context.pan = map.pan; context.zoomIn = map.zoomIn; context.zoomOut = map.zoomOut; diff --git a/js/id/modes/move_way.js b/js/id/modes/move_way.js index 99a2ebb48..cdd9fa792 100644 --- a/js/id/modes/move_way.js +++ b/js/id/modes/move_way.js @@ -30,7 +30,7 @@ iD.modes.MoveWay = function(context, wayId) { function startNudge(nudge) { if (nudgeInterval) window.clearInterval(nudgeInterval); nudgeInterval = window.setInterval(function() { - context.map().pan(nudge).redraw(); + context.pan(nudge); }, 50); } diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 1bea28a17..29de5489b 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -241,7 +241,7 @@ iD.Map = function(context) { t[1] += d[1]; projection.translate(t); zoom.translate(projection.translate()); - return map; + return redraw(); }; map.size = function(_) { diff --git a/js/id/ui.js b/js/id/ui.js index 72712f861..6e7322848 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -2,8 +2,7 @@ iD.ui = function(context) { return function(container) { context.container(container); - var connection = context.connection(), - history = context.history(), + var history = context.history(), map = context.map(); if (!iD.detect().support) { @@ -103,8 +102,7 @@ iD.ui = function(context) { function pan(d) { return function() { - map.pan(d); - map.redraw(); + context.pan(d); }; } From 7fcf329ee876797f479980423ea917ae5bad0b99 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 12 Feb 2013 21:21:02 -0800 Subject: [PATCH 316/332] Fix browser_notice style --- js/id/ui.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index 6e7322848..3db4f6ece 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -6,8 +6,12 @@ iD.ui = function(context) { map = context.map(); if (!iD.detect().support) { - container.text(t('browser_notice')) - .style('text-align:center;font-style:italic;'); + container + .text(t('browser_notice')) + .style({ + 'text-align': 'center', + 'font-style': 'italic' + }); return; } From a53e604b5b3f8aa88916019ce13c4b24dab81fff Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 13 Feb 2013 11:57:32 -0800 Subject: [PATCH 317/332] Avoid unnecessary style recalculation --- js/id/renderer/map.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 29de5489b..231a2e082 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -152,8 +152,12 @@ iD.Map = function(context) { if (resetTransform()) difference = undefined; - surface.attr('data-zoom', ~~map.zoom()); - tilegroup.call(background); + var zoom = String(~~map.zoom()); + if (surface.attr('data-zoom') !== zoom) + surface.attr('data-zoom', zoom); + + if (!difference) + tilegroup.call(background); if (map.editable()) { context.connection().loadTiles(projection, dimensions); From d50f7f13abe1bc286af54e27112687dd4a1cadb9 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 13 Feb 2013 12:03:41 -0800 Subject: [PATCH 318/332] Cache properties in fastMouse --- js/id/util.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/js/id/util.js b/js/id/util.js index bec8bc075..cfcf453a9 100644 --- a/js/id/util.js +++ b/js/id/util.js @@ -80,12 +80,14 @@ iD.util.getStyle = function(selector) { // 2. Does not cause style recalculation iD.util.fastMouse = function(container) { var rect = _.clone(container.getBoundingClientRect()), + rectLeft = rect.left, + rectTop = rect.top, clientLeft = +container.clientLeft, clientTop = +container.clientTop; return function(e) { return [ - e.clientX - rect.left - container.clientLeft, - e.clientY - rect.top - container.clientTop]; + e.clientX - rectLeft - clientLeft, + e.clientY - rectTop - clientTop]; }; }; From fd423f4db7a5558ee30d75c657c2c2ae0e08e1d7 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 13 Feb 2013 15:39:22 -0500 Subject: [PATCH 319/332] Don't drag when shiftKey --- js/id/behavior/drag_node.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/js/id/behavior/drag_node.js b/js/id/behavior/drag_node.js index e06bec43e..c16ede85f 100644 --- a/js/id/behavior/drag_node.js +++ b/js/id/behavior/drag_node.js @@ -1,6 +1,7 @@ iD.behavior.DragNode = function(context) { var nudgeInterval, - wasMidpoint; + wasMidpoint, + cancelled; function edge(point, size) { var pad = [30, 100, 30, 100]; @@ -36,6 +37,9 @@ iD.behavior.DragNode = function(context) { } function start(entity) { + cancelled = d3.event.sourceEvent.shiftKey; + if (cancelled) return behavior.cancel(); + context.history() .on('undone.drag-node', cancel); @@ -74,6 +78,7 @@ iD.behavior.DragNode = function(context) { } function move(entity) { + if (cancelled) return; d3.event.sourceEvent.stopPropagation(); var nudge = edge(d3.event.point, context.map().size()); @@ -97,6 +102,7 @@ iD.behavior.DragNode = function(context) { } function end(entity) { + if (cancelled) return; off(); var d = datum(); From 8538339b44fef934fae29d9cc2880f0c4f842cd3 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Wed, 13 Feb 2013 17:21:21 -0500 Subject: [PATCH 320/332] Fix zooming to validated features. Fixes #748 --- js/id/modes/select.js | 2 +- js/id/renderer/map.js | 28 ++++++++++++++++------------ js/id/ui/save.js | 6 +++--- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/js/id/modes/select.js b/js/id/modes/select.js index ac6ad4943..d071d1484 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -180,7 +180,7 @@ iD.modes.Select = function(context, selection, initial) { if (showMenu) context.surface().call(radialMenu); context.surface() - .on('dblclick.select', dblclick) + .on('dblclick.select', dblclick); }, 200); }; diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 231a2e082..26ec19ebe 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -308,21 +308,25 @@ iD.Map = function(context) { return new iD.geo.Extent(projection.invert([0, dimensions[1]]), projection.invert([dimensions[0], 0])); } else { - var extent = iD.geo.Extent(_), - tl = projection([extent[0][0], extent[1][1]]), - br = projection([extent[1][0], extent[0][1]]); - - // Calculate maximum zoom that fits extent - var hFactor = (br[0] - tl[0]) / dimensions[0], - vFactor = (br[1] - tl[1]) / dimensions[1], - hZoomDiff = Math.log(Math.abs(hFactor)) / Math.LN2, - vZoomDiff = Math.log(Math.abs(vFactor)) / Math.LN2, - newZoom = map.zoom() - Math.max(hZoomDiff, vZoomDiff); - - map.centerZoom(extent.center(), newZoom); + map.centerZoom(extent.center(), map.extentZoom(extent)); } }; + map.extentZoom = function(_) { + var extent = iD.geo.Extent(_), + tl = projection([extent[0][0], extent[1][1]]), + br = projection([extent[1][0], extent[0][1]]); + + // Calculate maximum zoom that fits extent + var hFactor = (br[0] - tl[0]) / dimensions[0], + vFactor = (br[1] - tl[1]) / dimensions[1], + hZoomDiff = Math.log(Math.abs(hFactor)) / Math.LN2, + vZoomDiff = Math.log(Math.abs(vFactor)) / Math.LN2, + newZoom = map.zoom() - Math.max(hZoomDiff, vZoomDiff); + + return newZoom; + }; + map.flush = function() { context.connection().flush(); context.history().reset(); diff --git a/js/id/ui/save.js b/js/id/ui/save.js index 8b0ef57ce..5e05183b2 100644 --- a/js/id/ui/save.js +++ b/js/id/ui/save.js @@ -11,7 +11,7 @@ iD.ui.Save = function(context) { if (!history.hasChanges()) return; connection.authenticate(function(err) { - var modal = iD.ui.modal(context.container()); + modal = iD.ui.modal(context.container()); var changes = history.changes(); changes.connection = connection; modal.select('.content') @@ -67,8 +67,8 @@ iD.ui.Save = function(context) { } function clickFix(d) { - map.extent(d.entity.extent(context.graph())); - if (map.zoom() > 19) map.zoom(19); + var extent = d.entity.extent(context.graph()); + map.centerZoom(extent.center(), Math.min(19, map.extentZoom(extent))); context.enter(iD.modes.Select(context, [d.entity.id])); modal.remove(); } From 3be61a65e67efe83f814a32096c2c8ec1b072fc6 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 13 Feb 2013 14:26:21 -0800 Subject: [PATCH 321/332] Manual redraw debouncing This avoids a duplicate full redraw at the end of a drag pan. The redraw triggered by mouseup.zoom now cancels the pending debounced redraw. Also, bump the debounce interval to 300ms, which helps avoid full redraws during scroll zoom, making it more responsive. --- js/id/renderer/map.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 26ec19ebe..03562d3d8 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -146,6 +146,8 @@ iD.Map = function(context) { } function redraw(difference) { + clearTimeout(timeoutId); + // If we are in the middle of a zoom/pan, we can't do differenced redraws. // It would result in artifacts where differenced entities are redrawn with // one transform and unchanged entities with another. @@ -173,7 +175,11 @@ iD.Map = function(context) { return map; } - var queueRedraw = _.debounce(redraw, 200); + var timeoutId; + function queueRedraw() { + clearTimeout(timeoutId); + timeoutId = setTimeout(function() { redraw(); }, 300); + } function pointLocation(p) { var translate = projection.translate(), From e60c9ab16b1d6c7b4e7268a6e2e6ce8f5b4ac4d2 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 13 Feb 2013 17:31:23 -0500 Subject: [PATCH 322/332] Fix zoom limitation while drawing --- js/id/renderer/map.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 03562d3d8..900609af4 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -112,7 +112,7 @@ iD.Map = function(context) { iD.ui.flash(context.container()) .select('.content') .text('Cannot zoom out further in current mode.'); - return map.zoom(16); + return setZoom(16, true); } projection @@ -214,8 +214,8 @@ iD.Map = function(context) { return map; }; - function setZoom(z) { - if (z === map.zoom()) + function setZoom(z, force) { + if (z === map.zoom() && !force) return false; var scale = 256 * Math.pow(2, z), center = pxCenter(), From 1050faab8f8b66e163551b02131eb040b679110d Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 13 Feb 2013 17:42:46 -0500 Subject: [PATCH 323/332] Only delete saved on hitting reset, fixes #744 --- js/id/core/history.js | 14 ++++++++------ js/id/ui/restore.js | 3 ++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/js/id/core/history.js b/js/id/core/history.js index 47c93c145..bfd0d5a8c 100644 --- a/js/id/core/history.js +++ b/js/id/core/history.js @@ -175,12 +175,7 @@ iD.History = function(context) { if (!lock) return; context.storage(getKey('lock'), null); - if (stack.length <= 1) { - context.storage(getKey('history'), null); - context.storage(getKey('nextIDs'), null); - context.storage(getKey('index'), null); - return; - } + if (stack.length <= 1) return; var json = JSON.stringify(stack.map(function(i) { return { @@ -198,6 +193,13 @@ iD.History = function(context) { context.storage(getKey('index'), index); }, + clearSaved: function() { + if (!lock) return; + context.storage(getKey('history'), null); + context.storage(getKey('nextIDs'), null); + context.storage(getKey('index'), null); + }, + lock: function() { if (context.storage(getKey('lock'))) return false; context.storage(getKey('lock'), true); diff --git a/js/id/ui/restore.js b/js/id/ui/restore.js index d15f80d96..c6996ff23 100644 --- a/js/id/ui/restore.js +++ b/js/id/ui/restore.js @@ -34,7 +34,8 @@ iD.ui.Restore = function(context) { .attr('class', 'cancel button col6') .text(t('restore.reset')) .on('click', function() { + context.history().clearSaved(); modal.remove(); }); - } + }; }; From eb2a133ac64fb23c3cbefcb9baf91519085e4a35 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Wed, 13 Feb 2013 17:57:58 -0500 Subject: [PATCH 324/332] Hate multiline bracketless ifs --- js/id/renderer/map.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 900609af4..21313ee39 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -151,15 +151,18 @@ iD.Map = function(context) { // If we are in the middle of a zoom/pan, we can't do differenced redraws. // It would result in artifacts where differenced entities are redrawn with // one transform and unchanged entities with another. - if (resetTransform()) + if (resetTransform()) { difference = undefined; + } var zoom = String(~~map.zoom()); - if (surface.attr('data-zoom') !== zoom) + if (surface.attr('data-zoom') !== zoom) { surface.attr('data-zoom', zoom); + } - if (!difference) + if (!difference) { tilegroup.call(background); + } if (map.editable()) { context.connection().loadTiles(projection, dimensions); From b2f1de654ce2eb2043f1a1cd9f047f309b30ea74 Mon Sep 17 00:00:00 2001 From: Luis GC Date: Thu, 14 Feb 2013 00:01:56 +0100 Subject: [PATCH 325/332] Completed Spanish translations --- locale/es.js | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/locale/es.js b/locale/es.js index 2d47ecfe6..739e3de22 100644 --- a/locale/es.js +++ b/locale/es.js @@ -54,7 +54,7 @@ locale.es = { }, circularize: { title: "Redondear", //"Circularize", - description: "Hacer esto redondo.", //"Make this round.", + description: "Redondear esto.", //"Make this round.", key: "O", annotation: { line: "Redondear una línea.", //"Made a line circular.", @@ -139,15 +139,15 @@ locale.es = { report_a_bug: "reportar un error", //"report a bug", commit: { - title: "Save Changes", - description_placeholder: "Brief description of your contributions", - upload_explanation: "The changes you upload as {user} will be visible on all maps that use OpenStreetMap data.", - save: "Save", - cancel: "Cancel", - warnings: "Warnings", - modified: "Modified", - deleted: "Deleted", - created: "Created" + title: "Guardar Cambios", // "Save Changes" + description_placeholder: "Breve descripción de tus contribuciones", //"Brief description of your contributions" + upload_explanation: "Los cambios que subes como {user} serán visibles en todos los mapas que usen datos de OpenStreetMap.", //"The changes you upload as {user} will be visible on all maps that use OpenStreetMap data." + save: "Guardar", //"Save" + cancel: "Cancelar", //"Cancel" + warnings: "Avisos", //"Warnings" + modified: "Modificado", //"Modified" + deleted: "Borrado", //"Deleted" + created: "Creado" //"Created" }, contributors: { @@ -162,7 +162,7 @@ locale.es = { }, geolocate: { - title: "Show My Location" + title: "Mostrar Mi Localización" //"Show My Location" }, inspector: { @@ -171,7 +171,7 @@ locale.es = { new_tag: "Nueve etiqueta", //"New Tag" edit_tags: "Editar etiquetas", //"Edit tags", okay: "OK", - view_on_osm: "View on OSM" + view_on_osm: "Ver en OSM" //"View on OSM" }, layerswitcher: { @@ -183,9 +183,9 @@ locale.es = { }, restore: { - description: "You have unsaved changes from a previous editing session. Do you wish to restore these changes?", - restore: "Restore", - reset: "Reset" + description: "Tienes cambios no guardados de una sesión de edición previa. ¿Quieres recuperar esos cambios?", //"You have unsaved changes from a previous editing session. Do you wish to restore these changes?" + restore: "Restaurar", //"Restore" + reset: "Restablecer" //"Reset" }, save: { @@ -197,8 +197,8 @@ locale.es = { }, splash: { - welcome: "Welcome to the iD OpenStreetMap editor", - text: "This is development version {version}. For more information see {website} and report bugs at {github}." + welcome: "Bienvenido al editor de OpenStreetMap iD", //"Welcome to the iD OpenStreetMap editor" + text: "Esta es la versión {version} de desarrollo. Para más información visita {website} y reporta cualquier error en {github}." //"This is development version {version}. For more information see {website} and report bugs at {github}." }, source_switch: { @@ -208,8 +208,8 @@ locale.es = { tag_reference: { description: "Descripción", - on_wiki: "{tag} on wiki.osm.org", - used_with: "used with {type}" + on_wiki: "{tag} en wiki.osm.org", //"{tag} on wiki.osm.org" + used_with: "usado con {type}" //"used with {type}" }, validations: { From b8c79b8b480461f9b2d345089a830ae278f73340 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Wed, 13 Feb 2013 18:12:38 -0500 Subject: [PATCH 326/332] Fix map.extent --- js/id/renderer/map.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 21313ee39..c3d5ec696 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -317,6 +317,7 @@ iD.Map = function(context) { return new iD.geo.Extent(projection.invert([0, dimensions[1]]), projection.invert([dimensions[0], 0])); } else { + var extent = iD.geo.Extent(_); map.centerZoom(extent.center(), map.extentZoom(extent)); } }; From 5e66307500d250c42c4ea0b98a1b32fc09afd339 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 13 Feb 2013 16:01:13 -0800 Subject: [PATCH 327/332] Fix incorrect parentWays after reloading a split way When recalculating parent ways/relations during rebase, a graph should not add modified or deleted entities as parents. Such entities will already be correctly marked as parents or not. Graph had the correct behavior for deleted entities, but not for modified entities. This had the effect that if you split a way that was partially off screen, and then panned so that the way was re-retrieved, Graph#rebase would mistakenly add back the original way as a parent of all the nodes that were split into the new section, making them appear as shared. Fixes #751. --- js/id/core/graph.js | 5 +++-- test/spec/core/graph.js | 42 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/js/id/core/graph.js b/js/id/core/graph.js index a3fb8e823..a595f0df6 100644 --- a/js/id/core/graph.js +++ b/js/id/core/graph.js @@ -93,6 +93,7 @@ iD.Graph.prototype = { rebase: function(entities) { var base = this.base(), i, k, child, id, keys; + // Merging of data only needed if graph is the base graph if (!this.inherited) { for (i in entities) { @@ -110,7 +111,7 @@ iD.Graph.prototype = { if (base.parentWays[child]) { for (k = 0; k < base.parentWays[child].length; k++) { id = base.parentWays[child][k]; - if (this.entity(id) && !_.contains(this._parentWays[child], id)) { + if (!this.entities.hasOwnProperty(id) && !_.contains(this._parentWays[child], id)) { this._parentWays[child].push(id); } } @@ -123,7 +124,7 @@ iD.Graph.prototype = { if (base.parentRels[child]) { for (k = 0; k < base.parentRels[child].length; k++) { id = base.parentRels[child][k]; - if (this.entity(id) && !_.contains(this._parentRels[child], id)) { + if (!this.entities.hasOwnProperty(id) && !_.contains(this._parentRels[child], id)) { this._parentRels[child].push(id); } } diff --git a/test/spec/core/graph.js b/test/spec/core/graph.js index a099d8b61..ef6741bb9 100644 --- a/test/spec/core/graph.js +++ b/test/spec/core/graph.js @@ -107,19 +107,36 @@ describe('iD.Graph', function() { w3 = iD.Way({id: 'w3', nodes: ['n']}), graph = iD.Graph([n, w1]), graph2 = graph.replace(w2); + graph.rebase({ 'w3': w3 }); graph2.rebase({ 'w3': w3 }); expect(graph2.parentWays(n)).to.eql([w1, w2, w3]); }); - it("avoids re-adding removed parentWays", function() { + it("avoids re-adding a modified way as a parent way", function() { + var n1 = iD.Node({id: 'n1'}), + n2 = iD.Node({id: 'n2'}), + w1 = iD.Way({id: 'w1', nodes: ['n1', 'n2']}), + w2 = w1.removeNode('n2'), + graph = iD.Graph([n1, n2, w1]), + graph2 = graph.replace(w2); + + graph.rebase({ 'w1': w1 }); + graph2.rebase({ 'w1': w1 }); + + expect(graph2.parentWays(n2)).to.eql([]); + }); + + it("avoids re-adding a deleted way as a parent way", function() { var n = iD.Node({id: 'n'}), w1 = iD.Way({id: 'w1', nodes: ['n']}), graph = iD.Graph([n, w1]), graph2 = graph.remove(w1); + graph.rebase({ 'w1': w1 }); graph2.rebase({ 'w1': w1 }); + expect(graph2.parentWays(n)).to.eql([]); }); @@ -135,14 +152,29 @@ describe('iD.Graph', function() { expect(graph._parentRels.hasOwnProperty('n')).to.be.false; }); - it("avoids re-adding removed parentRels", function() { + it("avoids re-adding a modified relation as a parent relation", function() { + var n = iD.Node({id: 'n'}), + r1 = iD.Relation({id: 'r1', members: [{id: 'n'}]}), + r2 = r1.removeMember('n'), + graph = iD.Graph([n, r1]), + graph2 = graph.replace(r2); + + graph.rebase({ 'r1': r1 }); + graph2.rebase({ 'r1': r1 }); + + expect(graph2.parentRelations(n)).to.eql([]); + }); + + it("avoids re-adding a deleted relation as a parent relation", function() { var n = iD.Node({id: 'n'}), r1 = iD.Relation({id: 'r1', members: [{id: 'n'}]}), graph = iD.Graph([n, r1]), graph2 = graph.remove(r1); - graph.rebase({ 'w1': r1 }); - graph2.rebase({ 'w1': r1 }); - expect(graph2.parentWays(n)).to.eql([]); + + graph.rebase({ 'r1': r1 }); + graph2.rebase({ 'r1': r1 }); + + expect(graph2.parentRelations(n)).to.eql([]); }); it("updates parentRels for nodes with modified parentWays", function () { From 372e009888654c1f8b622fae356763de970465df Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 13 Feb 2013 16:15:49 -0800 Subject: [PATCH 328/332] Assign id at top level So you can do id.graph(), etc. in the console for debugging. --- index.html | 4 ++-- index_packaged.html | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/index.html b/index.html index 341fc1ad1..6941ce008 100644 --- a/index.html +++ b/index.html @@ -165,9 +165,9 @@ .current('en') .current(iD.detect().locale); - d3.json('keys.json', function(err, keys) { - var id = iD(); + var id = iD(); + d3.json('keys.json', function(err, keys) { id.connection() .keys(keys); diff --git a/index_packaged.html b/index_packaged.html index a961a5202..7da953292 100644 --- a/index_packaged.html +++ b/index_packaged.html @@ -17,9 +17,10 @@