From 7e7a4bf59f8c8daafdb6aef96e10ac71aa9bfaa7 Mon Sep 17 00:00:00 2001 From: Matthew Petroff Date: Mon, 7 Jan 2013 11:25:37 -0500 Subject: [PATCH 1/7] Fixed pointer cursor pointing position. --- css/app.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/css/app.css b/css/app.css index 1c4e8daed..a86c3b7bd 100644 --- a/css/app.css +++ b/css/app.css @@ -192,7 +192,7 @@ button { font-size:14px; display: inline-block; height:40px; - cursor:url(../img/cursor-pointer.png) 9 9, auto; + cursor:url(../img/cursor-pointer.png) 6 1, auto; } button:hover { @@ -201,7 +201,7 @@ button:hover { button:active { box-shadow: inset 0 0 0px 1px #fff, inset 0 0 6px 1px rgba(0,0,0,.35); - cursor:url(../img/cursor-pointing.png) 9 9, auto; + cursor:url(../img/cursor-pointing.png) 6 1, auto; } button.active:not([disabled]) { From 2d3ce6c125f6c9f4c3e4e6cf812fbf964d69baac Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 7 Dec 2012 14:43:42 -0500 Subject: [PATCH 2/7] Extract hover behavior --- index.html | 1 + js/id/behavior/hover.js | 33 +++++++++++++++++++++++++++++ js/id/modes/add_area.js | 4 +--- js/id/modes/add_line.js | 3 +-- js/id/modes/add_point.js | 3 --- js/id/modes/browse.js | 1 + js/id/modes/draw_area.js | 4 +--- js/id/modes/draw_line.js | 4 +--- js/id/modes/select.js | 1 + js/id/renderer/map.js | 41 +++---------------------------------- test/index.html | 3 +++ test/index_packaged.html | 2 ++ test/spec/behavior/hover.js | 36 ++++++++++++++++++++++++++++++++ 13 files changed, 84 insertions(+), 52 deletions(-) create mode 100644 js/id/behavior/hover.js create mode 100644 test/spec/behavior/hover.js diff --git a/index.html b/index.html index b0153fd9a..585b32281 100644 --- a/index.html +++ b/index.html @@ -68,6 +68,7 @@ + diff --git a/js/id/behavior/hover.js b/js/id/behavior/hover.js new file mode 100644 index 000000000..9d50cbbd0 --- /dev/null +++ b/js/id/behavior/hover.js @@ -0,0 +1,33 @@ +/* + The hover behavior adds the `.hover` class on mouseover to all elements to which + the identical datum is bound, and removes it on mouseout. + + The :hover pseudo-class is insufficient for iD's purposes because a datum's visual + representation may consist of several elements scattered throughout the DOM hierarchy. + Only one of these elements can have the :hover pseudo-class, but all of them will + have the .hover class. + */ +iD.behavior.Hover = function () { + var hover = function(selection) { + selection.on('mouseover.hover', function () { + var datum = d3.event.target.__data__; + if (datum) { + selection.selectAll('*') + .filter(function (d) { return d === datum; }) + .classed('hover', true); + } + }); + + selection.on('mouseout.hover', function () { + selection.selectAll('.hover') + .classed('hover', false); + }); + }; + + hover.off = function(selection) { + selection.on('mouseover.hover', null) + .on('mouseout.hover', null); + }; + + return hover; +}; diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js index 8051da962..33facb041 100644 --- a/js/id/modes/add_area.js +++ b/js/id/modes/add_area.js @@ -12,7 +12,6 @@ iD.modes.AddArea = function() { controller = mode.controller; map.dblclickEnable(false) - .hoverEnable(false) .hint('Click on the map to start drawing an area, like a park, lake, or building.'); map.surface.on('click.addarea', function() { @@ -48,8 +47,7 @@ iD.modes.AddArea = function() { mode.exit = function() { window.setTimeout(function() { - mode.map.dblclickEnable(true) - .hoverEnable(true); + mode.map.dblclickEnable(true); }, 1000); mode.map.hint(false); mode.map.surface.on('click.addarea', null); diff --git a/js/id/modes/add_line.js b/js/id/modes/add_line.js index 24a53a115..baa4364bc 100644 --- a/js/id/modes/add_line.js +++ b/js/id/modes/add_line.js @@ -13,7 +13,6 @@ iD.modes.AddLine = function() { controller = mode.controller; map.dblclickEnable(false) - .hoverEnable(false) .hint('Click on the map to start drawing an road, path, or route.'); map.surface.on('click.addline', function() { @@ -69,7 +68,7 @@ iD.modes.AddLine = function() { }; mode.exit = function() { - mode.map.dblclickEnable(true).hoverEnable(true); + mode.map.dblclickEnable(true); mode.map.hint(false); mode.map.surface.on('click.addline', null); mode.map.keybinding().on('⎋.addline', null); diff --git a/js/id/modes/add_point.js b/js/id/modes/add_point.js index 06eefd00b..37767f3de 100644 --- a/js/id/modes/add_point.js +++ b/js/id/modes/add_point.js @@ -10,8 +10,6 @@ iD.modes.AddPoint = function() { history = mode.history, controller = mode.controller; - map.hoverEnable(false); - map.hint('Click on the map to add a point.'); map.surface.on('click.addpoint', function() { @@ -30,7 +28,6 @@ iD.modes.AddPoint = function() { }; mode.exit = function() { - mode.map.hoverEnable(true); mode.map.hint(false); mode.map.surface.on('click.addpoint', null); mode.map.keybinding().on('⎋.addpoint', null); diff --git a/js/id/modes/browse.js b/js/id/modes/browse.js index 3d5654a87..60b08889f 100644 --- a/js/id/modes/browse.js +++ b/js/id/modes/browse.js @@ -12,6 +12,7 @@ iD.modes.Browse = function() { var surface = mode.map.surface; behaviors = [ + iD.behavior.Hover(), iD.behavior.DragNode(mode), iD.behavior.DragAccuracyHandle(mode)]; diff --git a/js/id/modes/draw_area.js b/js/id/modes/draw_area.js index e54163dda..00ef6590f 100644 --- a/js/id/modes/draw_area.js +++ b/js/id/modes/draw_area.js @@ -16,7 +16,6 @@ iD.modes.DrawArea = function(wayId) { node = iD.Node({loc: map.mouseCoordinates()}); map.dblclickEnable(false) - .hoverEnable(false) .fastEnable(false); map.hint('Click on the map to add points to your area. Finish the ' + 'area by clicking on your first point'); @@ -126,8 +125,7 @@ iD.modes.DrawArea = function(wayId) { mode.exit = function() { mode.map.hint(false); - mode.map.fastEnable(true) - .hoverEnable(true); + mode.map.fastEnable(true); mode.map.surface .on('mousemove.drawarea', null) diff --git a/js/id/modes/draw_line.js b/js/id/modes/draw_line.js index 56c5cfe6b..b7e614012 100644 --- a/js/id/modes/draw_line.js +++ b/js/id/modes/draw_line.js @@ -16,7 +16,6 @@ iD.modes.DrawLine = function(wayId, direction) { map.dblclickEnable(false) .fastEnable(false) - .hoverEnable(false) .hint('Click to add more points to the line. ' + 'Click on other lines to connect to them, and double-click to ' + 'end the line.'); @@ -122,8 +121,7 @@ iD.modes.DrawLine = function(wayId, direction) { mode.exit = function() { mode.map.hint(false); - mode.map.fastEnable(true) - .hoverEnable(true); + mode.map.fastEnable(true); mode.map.surface .on('mousemove.drawline', null) diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 8defb17ba..107d326e6 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -30,6 +30,7 @@ iD.modes.Select = function (entity) { var surface = mode.map.surface; behaviors = [ + iD.behavior.Hover(), iD.behavior.DragNode(mode), iD.behavior.DragWay(mode), iD.behavior.DragAccuracyHandle(mode)]; diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 4cc7de184..043771ad1 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -2,7 +2,6 @@ iD.Map = function() { var connection, history, dimensions = [], dispatch = d3.dispatch('move'), - hover = null, translateStart, keybinding = d3.keybinding(), projection = d3.geo.mercator().scale(1024), @@ -12,7 +11,6 @@ iD.Map = function() { .scaleExtent([1024, 256 * Math.pow(2, 24)]) .on('zoom', zoomPan), dblclickEnabled = true, - hoverEnabled = true, fastEnabled = true, notice, background = iD.Background() @@ -43,8 +41,6 @@ iD.Map = function() { .attr({ x: 0, y: 0 }); r = surface.append('g') - .on('mouseover', hoverIn) - .on('mouseout', hoverOut) .attr('clip-path', 'url(#clip)'); g = ['fill', 'casing', 'stroke', 'text', 'hit', 'temp'].reduce(function(mem, i) { @@ -66,7 +62,6 @@ iD.Map = function() { } function pxCenter() { return [dimensions[0] / 2, dimensions[1] / 2]; } - function classHover(d) { return d.id === hover; } function getline(d) { return d._line; } function key(d) { return d.id; } function nodeline(d) { @@ -163,8 +158,7 @@ iD.Map = function() { .attr('r', 4); circles.attr('transform', pointTransform) - .classed('shared', shared) - .classed('hover', classHover); + .classed('shared', shared); } function drawAccuracyHandles(waynodes, filter) { @@ -191,13 +185,11 @@ iD.Map = function() { .filter(filter) .data(data, key); lines.exit().remove(); - lines.enter().append('path') - .classed('hover', classHover); + lines.enter().append('path'); lines .order() .attr('d', getline) - .attr('class', class_gen) - .classed('hover', classHover); + .attr('class', class_gen); return lines; } @@ -210,7 +202,6 @@ iD.Map = function() { } function drawPoints(points, filter) { - var groups = g.hit.selectAll('g.point') .filter(filter) .data(points, key); @@ -234,7 +225,6 @@ iD.Map = function() { groups.attr('transform', pointTransform); - groups.classed('hover', classHover); groups.select('image').attr('xlink:href', iD.Style.pointImage); } @@ -276,25 +266,6 @@ iD.Map = function() { redraw(Object.keys(result.entities)); } - function hoverIn() { - if (!hoverEnabled) return; - var datum = d3.select(d3.event.target).datum(); - if (datum instanceof iD.Entity) { - hover = datum.id; - redraw([hover]); - d3.select('.messages').text(datum.tags.name || '#' + datum.id); - } - } - - function hoverOut() { - if (hoverEnabled && hover) { - var oldHover = hover; - hover = null; - redraw([oldHover]); - d3.select('.messages').text(''); - } - } - function zoomPan() { if (d3.event && d3.event.sourceEvent.type === 'dblclick') { if (!dblclickEnabled) { @@ -363,12 +334,6 @@ iD.Map = function() { return map; }; - map.hoverEnable = function(_) { - if (!arguments.length) return hoverEnabled; - hoverEnabled = _; - return map; - }; - map.fastEnable = function(_) { if (!arguments.length) return fastEnabled; fastEnabled = _; diff --git a/test/index.html b/test/index.html index f45262f73..e58eb7f39 100644 --- a/test/index.html +++ b/test/index.html @@ -66,6 +66,7 @@ + @@ -111,6 +112,8 @@ + + diff --git a/test/index_packaged.html b/test/index_packaged.html index 4866e8e30..eb211a0b7 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -38,6 +38,8 @@ + + diff --git a/test/spec/behavior/hover.js b/test/spec/behavior/hover.js new file mode 100644 index 000000000..817654d59 --- /dev/null +++ b/test/spec/behavior/hover.js @@ -0,0 +1,36 @@ +describe("iD.behavior.Hover", function() { + var container; + + beforeEach(function() { + container = d3.select('body').append('div'); + }); + + afterEach(function() { + container.remove(); + }); + + describe("mouseover", function () { + it("adds the 'hover' class to all elements to which the same datum is bound", function () { + container.selectAll('span') + .data(['a', 'b', 'a', 'b']) + .enter().append('span').attr('class', Object); + + container.call(iD.behavior.Hover()); + container.selectAll('.a').trigger('mouseover'); + + expect(container.selectAll('.a.hover')[0]).to.have.length(2); + expect(container.selectAll('.b.hover')[0]).to.have.length(0); + }); + }); + + describe("mouseout", function () { + it("removes the 'hover' class from all elements", function () { + container.append('span').attr('class', 'hover'); + + container.call(iD.behavior.Hover()); + container.selectAll('.hover').trigger('mouseout'); + + expect(container.selectAll('.hover')[0]).to.have.length(0); + }); + }); +}); From 12beb14d07d02aea6d1c48255abf14f810fcf2b3 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 7 Jan 2013 11:51:04 -0500 Subject: [PATCH 3/7] Work on #250 Implement inspector without a collapsed mode and with semi-intelligent detection of whether features will conflict with the position of the inspector. Also adds a `centerEase` fn to the map object. --- js/id/modes/select.js | 10 ++++++++++ js/id/renderer/map.js | 8 ++++++++ js/id/ui/inspector.js | 27 ++++++--------------------- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 8defb17ba..47f2faf57 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -44,6 +44,16 @@ iD.modes.Select = function (entity) { .datum(entity) .call(inspector); + var inspector_size = d3.select('.inspector-wrap').size(), + map_size = mode.map.size(), + entity_extent = entity.extent(mode.history.graph()), + left_edge = map_size[0] - inspector_size[0]; + + var left = mode.map.projection(entity_extent[1])[0]; + var right = mode.map.projection(entity_extent[0])[0]; + + if (left > left_edge) mode.map.centerEase(entity_extent[0]); + inspector.on('changeTags', function(d, tags) { mode.history.perform( iD.actions.ChangeEntityTags(d.id, tags), diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 4cc7de184..1c5a26689 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -423,6 +423,14 @@ iD.Map = function() { } }; + map.centerEase = function(loc) { + var from = map.center().slice(), t = 0; + d3.timer(function() { + map.center(iD.util.geo.interp(from, loc, (t += 1) / 10)); + return t == 10; + }, 20); + }; + map.extent = function() { return [projection.invert([0, 0]), projection.invert(dimensions)]; }; diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js index db9377d94..f00319d89 100644 --- a/js/id/ui/inspector.js +++ b/js/id/ui/inspector.js @@ -43,26 +43,7 @@ iD.Inspector = function() { .attr('class', 'inspector-buttons') .call(drawButtons); - var inHeight = inspectorbody.node().offsetHeight; - - inspectorbody.style('display', 'none') - .style('margin-top', (-inHeight) + 'px'); - - var inspectortoggle = selection.append('button') - .attr('class', 'inspector-toggle action') - .on('click', function() { - inspectortoggle.style('display', 'none'); - inspectorbody - .style('display', 'block') - .transition() - .style('margin-top', '0px'); - }); - formsel.selectAll('input').node().focus(); - - inspectortoggle.append('span') - .text('Details') - .attr('class','label'); } function drawHead(selection) { @@ -184,7 +165,8 @@ iD.Inspector = function() { } function pushMore() { - if (d3.event.keyCode === 9 && tagList.selectAll('li:last-child input.value').node() === this) { + if (d3.event.keyCode === 9 && + tagList.selectAll('li:last-child input.value').node() === this) { addTag(); } } @@ -205,7 +187,10 @@ iD.Inspector = function() { value.call(d3.typeahead() .data(function(_, callback) { - taginfo.values({key: key.property('value'), query: value.property('value')}, function(err, data) { + taginfo.values({ + key: key.property('value'), + query: value.property('value') + }, function(err, data) { callback(data.data.map(function (d) { return {value: d.value, title: d.description}; })); From de70e1e9763aa5d3dee20fc3848c08bd66bfc77f Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 7 Jan 2013 12:14:37 -0500 Subject: [PATCH 4/7] Pan the map for features that intersect with both edges --- js/id/modes/select.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 47f2faf57..bfd008fd9 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -44,15 +44,20 @@ iD.modes.Select = function (entity) { .datum(entity) .call(inspector); + // 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(), entity_extent = entity.extent(mode.history.graph()), - left_edge = map_size[0] - inspector_size[0]; + left_edge = map_size[0] - inspector_size[0], + left = mode.map.projection(entity_extent[1])[0], + right = mode.map.projection(entity_extent[0])[0]; - var left = mode.map.projection(entity_extent[1])[0]; - var right = mode.map.projection(entity_extent[0])[0]; - - if (left > left_edge) mode.map.centerEase(entity_extent[0]); + if (left > left_edge && + right > left_edge) mode.map.centerEase( + iD.util.geo.interp( + entity_extent[0], + entity_extent[1], 0.5)); inspector.on('changeTags', function(d, tags) { mode.history.perform( From 4b63823130f6b4ad1cadf97b446d2a0f3531d1e1 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 7 Jan 2013 09:54:00 -0800 Subject: [PATCH 5/7] Use Entity#geometry() --- js/id/renderer/map.js | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index aa079baa8..9e3841f20 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -93,23 +93,34 @@ iD.Map = function() { else editOn(); for (var i = 0; i < all.length; i++) { - var a = all[i]; - if (a.type === 'way') { - a._line = nodeline(a); - ways.push(a); - if (a.isArea()) areas.push(a); - else lines.push(a); - } else if (a._poi) { - points.push(a); - } else if (!a._poi && a.type === 'node' && a.intersects(extent)) { - vertices.push(a); + var entity = all[i]; + switch (entity.geometry()) { + case 'line': + entity._line = nodeline(entity); + lines.push(entity); + break; + + case 'area': + entity._line = nodeline(entity); + areas.push(entity); + break; + + case 'point': + points.push(entity); + break; + + case 'vertex': + vertices.push(entity); + break; } } + var parentStructure = graph.parentStructure(ways); var wayAccuracyHandles = []; for (i = 0; i < ways.length; i++) { accuracyHandles(ways[i], wayAccuracyHandles); } + drawVertices(vertices, parentStructure, filter); drawAccuracyHandles(wayAccuracyHandles, filter); drawCasings(lines, filter); From 57122c89687d878125d5c3e598172d5336c5d943 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 7 Jan 2013 10:03:41 -0800 Subject: [PATCH 6/7] Don't focus an existing key, only a blank one --- js/id/ui/inspector.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js index f00319d89..40bea7889 100644 --- a/js/id/ui/inspector.js +++ b/js/id/ui/inspector.js @@ -34,16 +34,14 @@ iD.Inspector = function() { .text('+ Add New Tag') .on('click', function() { addTag(); - tagList.selectAll('li:last-child input.key').node().focus(); + focusNewKey(); }); - var formsel = drawTags(entity.tags); + drawTags(entity.tags); inspectorbody.append('div') .attr('class', 'inspector-buttons') .call(drawButtons); - - formsel.selectAll('input').node().focus(); } function drawHead(selection) { @@ -161,6 +159,10 @@ iD.Inspector = function() { helpBtn.append('span') .attr('class', 'icon inspect'); + if (tags.length === 1 && tags[0].key === '' && tags[0].value === '') { + focusNewKey(); + } + return li; } @@ -198,6 +200,10 @@ iD.Inspector = function() { })); } + function focusNewKey() { + tagList.selectAll('li:last-child input.key').node().focus(); + } + function addTag() { var tags = inspector.tags(); tags[''] = ''; From f8ea7d1e981265cade9037940d406e43d6169028 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 7 Jan 2013 10:08:07 -0800 Subject: [PATCH 7/7] Fix accuracy handles --- js/id/renderer/map.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 9e3841f20..431282fa9 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -97,11 +97,13 @@ iD.Map = function() { switch (entity.geometry()) { case 'line': entity._line = nodeline(entity); + ways.push(entity); lines.push(entity); break; case 'area': entity._line = nodeline(entity); + ways.push(entity); areas.push(entity); break;