From 0917bc718913892b56cf2dbd5ff691190f45bc04 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Fri, 13 Mar 2020 15:56:13 -0400 Subject: [PATCH 01/67] Add template type detection for custom sources (closes #4977) Before custom sources were assumed tms, now we detect wms and guess 'EPSG:3857' projection --- modules/renderer/background_source.js | 111 ++++++++++++++++---------- 1 file changed, 71 insertions(+), 40 deletions(-) diff --git a/modules/renderer/background_source.js b/modules/renderer/background_source.js index 72af62029..87dfb0ed9 100644 --- a/modules/renderer/background_source.js +++ b/modules/renderer/background_source.js @@ -29,44 +29,44 @@ function vintageRange(vintage) { export function rendererBackgroundSource(data) { var source = Object.assign({}, data); // shallow copy - var offset = [0, 0]; - var name = source.name; - var description = source.description; - var best = !!source.best; - var template = source.encrypted ? utilAesDecrypt(source.template) : source.template; + var _offset = [0, 0]; + var _name = source.name; + var _description = source.description; + var _best = !!source.best; + var _template = source.encrypted ? utilAesDecrypt(source.template) : source.template; source.tileSize = data.tileSize || 256; source.zoomExtent = data.zoomExtent || [0, 22]; source.overzoom = data.overzoom !== false; source.offset = function(val) { - if (!arguments.length) return offset; - offset = val; + if (!arguments.length) return _offset; + _offset = val; return source; }; source.nudge = function(val, zoomlevel) { - offset[0] += val[0] / Math.pow(2, zoomlevel); - offset[1] += val[1] / Math.pow(2, zoomlevel); + _offset[0] += val[0] / Math.pow(2, zoomlevel); + _offset[1] += val[1] / Math.pow(2, zoomlevel); return source; }; source.name = function() { var id_safe = source.id.replace(/\./g, ''); - return t('imagery.' + id_safe + '.name', { default: name }); + return t('imagery.' + id_safe + '.name', { default: _name }); }; source.description = function() { var id_safe = source.id.replace(/\./g, ''); - return t('imagery.' + id_safe + '.description', { default: description }); + return t('imagery.' + id_safe + '.description', { default: _description }); }; source.best = function() { - return best; + return _best; }; @@ -82,15 +82,35 @@ export function rendererBackgroundSource(data) { }; - source.template = function(_) { - if (!arguments.length) return template; - if (source.id === 'custom') template = _; + source.template = function(val) { + if (!arguments.length) return _template; + if (source.id === 'custom') { + _template = val; + } return source; }; source.url = function(coord) { - if (this.type === 'wms') { + var result = _template; + if (result === '') return result; // source 'none' + + + // Guess a type based on the tokens present in the template + // (This is for 'custom' source, where we don't know) + if (!source.type) { + if (/\{(proj|wkid|bbox)\}/.test(_template)) { + source.type = 'wms'; + source.projection = 'EPSG:3857'; // guess + } else if (/\{(x|y)\}/.test(_template)) { + source.type = 'tms'; + } else if (/\{u\}/.test(_template)) { + source.type = 'bing'; + } + } + + + if (source.type === 'wms') { var tileToProjectedCoords = (function(x, y, z) { //polyfill for IE11, PhantomJS var sinh = Math.sinh || function(x) { @@ -102,7 +122,7 @@ export function rendererBackgroundSource(data) { var lon = x / zoomSize * Math.PI * 2 - Math.PI; var lat = Math.atan(sinh(Math.PI * (1 - 2 * y / zoomSize))); - switch (this.projection) { + switch (source.projection) { case 'EPSG:4326': return { x: lon * 180 / Math.PI, @@ -115,13 +135,14 @@ export function rendererBackgroundSource(data) { y: 20037508.34 / Math.PI * mercCoords[1] }; } - }).bind(this); + }); var tileSize = this.tileSize; var projection = this.projection; var minXmaxY = tileToProjectedCoords(coord[0], coord[1], coord[2]); var maxXminY = tileToProjectedCoords(coord[0]+1, coord[1]+1, coord[2]); - return template.replace(/\{(\w+)\}/g, function (token, key) { + + result = result.replace(/\{(\w+)\}/g, function (token, key) { switch (key) { case 'width': case 'height': @@ -144,28 +165,38 @@ export function rendererBackgroundSource(data) { return token; } }); + + } else if (source.type === 'tms') { + result = result + .replace('{x}', coord[0]) + .replace('{y}', coord[1]) + // TMS-flipped y coordinate + .replace(/\{[t-]y\}/, Math.pow(2, coord[2]) - coord[1] - 1) + .replace(/\{z(oom)?\}/, coord[2]); + + } else if (source.type === 'bing') { + result = result + .replace('{u}', function() { + var u = ''; + for (var zoom = coord[2]; zoom > 0; zoom--) { + var b = 0; + var mask = 1 << (zoom - 1); + if ((coord[0] & mask) !== 0) b++; + if ((coord[1] & mask) !== 0) b += 2; + u += b.toString(); + } + return u; + }); } - return template - .replace('{x}', coord[0]) - .replace('{y}', coord[1]) - // TMS-flipped y coordinate - .replace(/\{[t-]y\}/, Math.pow(2, coord[2]) - coord[1] - 1) - .replace(/\{z(oom)?\}/, coord[2]) - .replace(/\{switch:([^}]+)\}/, function(s, r) { - var subdomains = r.split(','); - return subdomains[(coord[0] + coord[1]) % subdomains.length]; - }) - .replace('{u}', function() { - var u = ''; - for (var zoom = coord[2]; zoom > 0; zoom--) { - var b = 0; - var mask = 1 << (zoom - 1); - if ((coord[0] & mask) !== 0) b++; - if ((coord[1] & mask) !== 0) b += 2; - u += b.toString(); - } - return u; - }); + + // these apply to any type.. + result = result.replace(/\{switch:([^}]+)\}/, function(s, r) { + var subdomains = r.split(','); + return subdomains[(coord[0] + coord[1]) % subdomains.length]; + }); + + + return result; }; From e1c4823e15d55b0dcbd2427cd1473e7dc007ff88 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Fri, 13 Mar 2020 16:43:04 -0400 Subject: [PATCH 02/67] Update the custom background instructions pane to include wms --- data/core.yaml | 2 +- dist/locales/en.json | 2 +- modules/renderer/background_source.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/core.yaml b/data/core.yaml index 1790983e8..3599337fc 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -739,7 +739,7 @@ en: custom_background: tooltip: Edit custom background header: Custom Background Settings - instructions: "Enter a tile URL template. Valid tokens are:\n {zoom} or {z}, {x}, {y} for Z/X/Y tile scheme\n {-y} or {ty} for flipped TMS-style Y coordinates\n {u} for quadtile scheme\n {switch:a,b,c} for DNS server multiplexing\n\nExample:\n{example}" + instructions: "Enter a tile URL template below.\n\nSupported WMS tokens:\n `{proj}`: requested projection (`EPSG:3857` only)\n `{wkid}`: same as proj, but without the EPSG (`3857` only)\n `{width}`, `{height}`: requested image dimensions (`256` only)\n `{bbox}`: requested bounding box (e.g. `minX,minY,maxX,maxY`)\n\nSupported TMS tokens:\n `{zoom}` or `{z}, `{x}`, `{y}`: Z/X/Y tile coordinates\n `{-y}` or `{ty}`: flipped TMS-style Y coordinates\n `{switch:a,b,c}`: DNS server multiplexing\n `{u}`: quadtile (Bing) scheme\n\nExample:\n{example}" template: placeholder: Enter a url template custom_data: diff --git a/dist/locales/en.json b/dist/locales/en.json index f85cdc92a..0775636fe 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -932,7 +932,7 @@ "custom_background": { "tooltip": "Edit custom background", "header": "Custom Background Settings", - "instructions": "Enter a tile URL template. Valid tokens are:\n {zoom} or {z}, {x}, {y} for Z/X/Y tile scheme\n {-y} or {ty} for flipped TMS-style Y coordinates\n {u} for quadtile scheme\n {switch:a,b,c} for DNS server multiplexing\n\nExample:\n{example}", + "instructions": "Enter a tile URL template below.\n\nSupported WMS tokens:\n `{proj}`: requested projection (`EPSG:3857` only)\n `{wkid}`: same as proj, but without the EPSG (`3857` only)\n `{width}`, `{height}`: requested image dimensions (`256` only)\n `{bbox}`: requested bounding box (e.g. `minX,minY,maxX,maxY`)\n\nSupported TMS tokens:\n `{zoom}` or `{z}, `{x}`, `{y}`: Z/X/Y tile coordinates\n `{-y}` or `{ty}`: flipped TMS-style Y coordinates\n `{switch:a,b,c}`: DNS server multiplexing\n `{u}`: quadtile (Bing) scheme\n\nExample:\n{example}", "template": { "placeholder": "Enter a url template" } diff --git a/modules/renderer/background_source.js b/modules/renderer/background_source.js index 87dfb0ed9..3b9d4950f 100644 --- a/modules/renderer/background_source.js +++ b/modules/renderer/background_source.js @@ -137,8 +137,8 @@ export function rendererBackgroundSource(data) { } }); - var tileSize = this.tileSize; - var projection = this.projection; + var tileSize = source.tileSize; + var projection = source.projection; var minXmaxY = tileToProjectedCoords(coord[0], coord[1], coord[2]); var maxXminY = tileToProjectedCoords(coord[0]+1, coord[1]+1, coord[2]); From d8033cd39c7fd2de67865fddf49a256e90331a9e Mon Sep 17 00:00:00 2001 From: guylamar2006 Date: Mon, 13 Apr 2020 16:48:02 -0500 Subject: [PATCH 03/67] Added deprecated tags Added deprecated tags. type -> substance. --- data/deprecated.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/data/deprecated.json b/data/deprecated.json index 4dac07403..4dd792a2f 100644 --- a/data/deprecated.json +++ b/data/deprecated.json @@ -1303,6 +1303,15 @@ "old": {"type": "extinct"}, "replace": {"volcano:status": "extinct"} }, + { + "old": {"type": "gas",}, + "replace": {"substance": "gas"} + }, + { + "old": {"type": "oil",}, + "replace": {"substance": "oil"} + }, + { "old": {"type": "scoria"}, "replace": {"volcano:type": "scoria"} @@ -1319,6 +1328,10 @@ "old": {"type": "video", "amenity": "studio"}, "replace": {"studio": "video"} }, + { + "old": {"type": "water",}, + "replace": {"substance": "water"} + }, { "old": {"unnamed": "*"}, "replace": {"noname": "$1"} From 7a3a84b7b2d1c8291e5addf9bae43f3f3b41be98 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Thu, 7 May 2020 10:26:50 -0700 Subject: [PATCH 04/67] Remove broken "tail" functionality (close #7560) --- css/80_app.css | 47 ----------------- data/core.yaml | 8 --- dist/locales/en.json | 10 ---- modules/behavior/add_way.js | 6 --- modules/behavior/draw.js | 17 ------- modules/behavior/draw_way.js | 6 --- modules/behavior/index.js | 1 - modules/behavior/tail.js | 99 ------------------------------------ modules/modes/add_area.js | 1 - modules/modes/add_line.js | 1 - modules/modes/add_note.js | 1 - modules/modes/add_point.js | 1 - modules/modes/draw_area.js | 1 - modules/modes/draw_line.js | 1 - test/spec/spec_helpers.js | 1 - 15 files changed, 201 deletions(-) delete mode 100644 modules/behavior/tail.js diff --git a/css/80_app.css b/css/80_app.css index 637747d24..4bfc9aedf 100644 --- a/css/80_app.css +++ b/css/80_app.css @@ -5084,53 +5084,6 @@ svg.mouseclick use.right { background-color: #fff; } -.tail { - width: 200px; - height: 400px; - pointer-events: none; - opacity: .8; - margin-top: -200px; - position: absolute; - background: transparent; -} -.tail::after { - content: ""; - position: absolute; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; - top: 50%; - right: -5px; - margin-top: -5px; - border-left-color: #fff; - border-width: 5px 0 5px 5px; -} - -.tail div { - border-radius: 3px; - padding: 10px; - background: #fff; - position: absolute; - top: 180px; - left: 0; - right: 0; - margin: auto; -} - -.left.tail::after { - content: ""; - position: absolute; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; - top: 50%; - left: -5px; - margin-top: -5px; - border-right-color: #fff; - border-width: 5px 5px 5px 0; -} .popover-arrow { position: absolute; width: 0; diff --git a/data/core.yaml b/data/core.yaml index 1790983e8..20ac2c286 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -33,33 +33,25 @@ 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." filter_tooltip: areas add_line: title: Line description: "Add highways, streets, pedestrian paths, canals or other lines to the map." - tail: "Click on the map to start drawing a road, path, or route." filter_tooltip: lines add_point: title: Point description: "Add restaurants, monuments, postal boxes or other points to the map." - tail: Click on the map to add a point. filter_tooltip: points add_note: title: Note label: Add Note description: "Spotted an issue? Let other mappers know." - tail: Click on the map to add a note. key: N add_preset: title: "Add {feature}" browse: title: Browse description: Pan and zoom the map. - draw_area: - tail: Click to add nodes to your area. Click the first node to finish the area. - draw_line: - tail: "Click to add more nodes to the line. Click on other lines to connect to them, and double-click to end the line." drag_node: connected_to_hidden: This can't be edited because it is connected to a hidden feature. operations: diff --git a/dist/locales/en.json b/dist/locales/en.json index 7f3e70d25..59b49b4d9 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -39,26 +39,22 @@ "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.", "filter_tooltip": "areas" }, "add_line": { "title": "Line", "description": "Add highways, streets, pedestrian paths, canals or other lines to the map.", - "tail": "Click on the map to start drawing a road, path, or route.", "filter_tooltip": "lines" }, "add_point": { "title": "Point", "description": "Add restaurants, monuments, postal boxes or other points to the map.", - "tail": "Click on the map to add a point.", "filter_tooltip": "points" }, "add_note": { "title": "Note", "label": "Add Note", "description": "Spotted an issue? Let other mappers know.", - "tail": "Click on the map to add a note.", "key": "N" }, "add_preset": { @@ -68,12 +64,6 @@ "title": "Browse", "description": "Pan and zoom the map." }, - "draw_area": { - "tail": "Click to add nodes to your area. Click the first node to finish the area." - }, - "draw_line": { - "tail": "Click to add more nodes to the line. Click on other lines to connect to them, and double-click to end the line." - }, "drag_node": { "connected_to_hidden": "This can't be edited because it is connected to a hidden feature." } diff --git a/modules/behavior/add_way.js b/modules/behavior/add_way.js index a4b0461ae..9479ca8da 100644 --- a/modules/behavior/add_way.js +++ b/modules/behavior/add_way.js @@ -37,11 +37,5 @@ export function behaviorAddWay(context) { }; - behavior.tail = function(text) { - draw.tail(text); - return behavior; - }; - - return utilRebind(behavior, dispatch, 'on'); } diff --git a/modules/behavior/draw.js b/modules/behavior/draw.js index 736d09f7c..7810a76c6 100644 --- a/modules/behavior/draw.js +++ b/modules/behavior/draw.js @@ -8,11 +8,9 @@ import { import { presetManager } from '../presets'; import { behaviorEdit } from './edit'; import { behaviorHover } from './hover'; -import { behaviorTail } from './tail'; import { geoChooseEdge, geoVecLength } from '../geo'; import { utilFastMouse, utilKeybinding, utilRebind } from '../util'; -var _usedTails = {}; var _disableSpace = false; var _lastSpace = null; @@ -26,7 +24,6 @@ export function behaviorDraw(context) { var _hover = behaviorHover(context).altDisables(true).ignoreVertex(true) .on('hover', context.ui().sidebar.hover); - var tail = behaviorTail(); var edit = behaviorEdit(context); var closeTolerance = 4; @@ -201,10 +198,6 @@ export function behaviorDraw(context) { context.install(_hover); context.install(edit); - if (!context.inIntro() && !_usedTails[tail.text()]) { - context.install(tail); - } - keybinding .on('โŒซ', backspace) .on('โŒฆ', del) @@ -231,11 +224,6 @@ export function behaviorDraw(context) { context.uninstall(_hover); context.uninstall(edit); - if (!context.inIntro() && !_usedTails[tail.text()]) { - context.uninstall(tail); - _usedTails[tail.text()] = true; - } - selection .on('mouseenter.draw', null) .on('mouseleave.draw', null) @@ -251,11 +239,6 @@ export function behaviorDraw(context) { }; - behavior.tail = function(_) { - tail.text(_); - return behavior; - }; - behavior.hover = function() { return _hover; }; diff --git a/modules/behavior/draw_way.js b/modules/behavior/draw_way.js index b5ea78ed9..ad0687106 100644 --- a/modules/behavior/draw_way.js +++ b/modules/behavior/draw_way.js @@ -428,11 +428,5 @@ export function behaviorDrawWay(context, wayID, index, mode, startGraph) { }; - drawWay.tail = function(text) { - behavior.tail(text); - return drawWay; - }; - - return utilRebind(drawWay, dispatch, 'on'); } diff --git a/modules/behavior/index.js b/modules/behavior/index.js index 2dfb951bf..054dbb6cf 100644 --- a/modules/behavior/index.js +++ b/modules/behavior/index.js @@ -11,4 +11,3 @@ export { behaviorLasso } from './lasso'; export { behaviorOperation } from './operation'; export { behaviorPaste } from './paste'; export { behaviorSelect } from './select'; -export { behaviorTail } from './tail'; diff --git a/modules/behavior/tail.js b/modules/behavior/tail.js deleted file mode 100644 index 3d30c1ba9..000000000 --- a/modules/behavior/tail.js +++ /dev/null @@ -1,99 +0,0 @@ -import { - event as d3_event, - select as d3_select -} from 'd3-selection'; - -import { utilSetTransform } from '../util'; -import { utilGetDimensions } from '../util/dimensions'; - - -export function behaviorTail() { - var container; - var xmargin = 25; - var tooltipSize = [0, 0]; - var selectionSize = [0, 0]; - var _text; - - - function behavior(selection) { - if (!_text) return; - - d3_select(window) - .on('resize.tail', function() { selectionSize = utilGetDimensions(selection); }); - - container = d3_select(document.body) - .append('div') - .style('display', 'none') - .attr('class', 'tail popover-inner'); - - container.append('div') - .text(_text); - - selection - .on('mousemove.tail', mousemove) - .on('mouseenter.tail', mouseenter) - .on('mouseleave.tail', mouseleave); - - container - .on('mousemove.tail', mousemove); - - tooltipSize = utilGetDimensions(container); - selectionSize = utilGetDimensions(selection); - - - function show() { - container.style('display', 'block'); - tooltipSize = utilGetDimensions(container); - } - - - function mousemove() { - if (container.style('display') === 'none') show(); - var xoffset = ((d3_event.clientX + tooltipSize[0] + xmargin) > selectionSize[0]) ? - -tooltipSize[0] - xmargin : xmargin; - container.classed('left', xoffset > 0); - utilSetTransform(container, d3_event.clientX + xoffset, d3_event.clientY); - } - - - function mouseleave() { - if (d3_event.relatedTarget !== container.node()) { - container.style('display', 'none'); - } - } - - - function mouseenter() { - if (d3_event.relatedTarget !== container.node()) { - show(); - } - } - } - - - behavior.off = function(selection) { - if (!_text) return; - - container - .on('mousemove.tail', null) - .remove(); - - selection - .on('mousemove.tail', null) - .on('mouseenter.tail', null) - .on('mouseleave.tail', null); - - d3_select(window) - .on('resize.tail', null); - }; - - - behavior.text = function(val) { - if (!arguments.length) return _text; - _text = val; - return behavior; - }; - - - return behavior; -} diff --git a/modules/modes/add_area.js b/modules/modes/add_area.js index 446639808..499c4b3b1 100644 --- a/modules/modes/add_area.js +++ b/modules/modes/add_area.js @@ -12,7 +12,6 @@ export function modeAddArea(context, mode) { mode.id = 'add-area'; var behavior = behaviorAddWay(context) - .tail(t('modes.add_area.tail')) .on('start', start) .on('startFromWay', startFromWay) .on('startFromNode', startFromNode); diff --git a/modules/modes/add_line.js b/modules/modes/add_line.js index 00b5e77fb..5db5913d5 100644 --- a/modules/modes/add_line.js +++ b/modules/modes/add_line.js @@ -12,7 +12,6 @@ export function modeAddLine(context, mode) { mode.id = 'add-line'; var behavior = behaviorAddWay(context) - .tail(t('modes.add_line.tail')) .on('start', start) .on('startFromWay', startFromWay) .on('startFromNode', startFromNode); diff --git a/modules/modes/add_note.js b/modules/modes/add_note.js index 6bb89f87e..b257663cd 100644 --- a/modules/modes/add_note.js +++ b/modules/modes/add_note.js @@ -16,7 +16,6 @@ export function modeAddNote(context) { }; var behavior = behaviorDraw(context) - .tail(t('modes.add_note.tail')) .on('click', add) .on('cancel', cancel) .on('finish', cancel); diff --git a/modules/modes/add_point.js b/modules/modes/add_point.js index c17064aaa..3ad4a301b 100644 --- a/modules/modes/add_point.js +++ b/modules/modes/add_point.js @@ -13,7 +13,6 @@ export function modeAddPoint(context, mode) { mode.id = 'add-point'; var behavior = behaviorDraw(context) - .tail(t('modes.add_point.tail')) .on('click', add) .on('clickWay', addWay) .on('clickNode', addNode) diff --git a/modules/modes/draw_area.js b/modules/modes/draw_area.js index cc8bdf118..7c050aba5 100644 --- a/modules/modes/draw_area.js +++ b/modules/modes/draw_area.js @@ -16,7 +16,6 @@ export function modeDrawArea(context, wayID, startGraph, button) { var way = context.entity(wayID); behavior = behaviorDrawWay(context, wayID, undefined, mode, startGraph) - .tail(t('modes.draw_area.tail')) .on('rejectedSelfIntersection.modeDrawArea', function() { context.ui().flash .text(t('self_intersection.error.areas'))(); diff --git a/modules/modes/draw_line.js b/modules/modes/draw_line.js index cf5d41be2..63dbc504d 100644 --- a/modules/modes/draw_line.js +++ b/modules/modes/draw_line.js @@ -20,7 +20,6 @@ export function modeDrawLine(context, wayID, startGraph, button, affix, continui var headID = (affix === 'prefix') ? way.first() : way.last(); behavior = behaviorDrawWay(context, wayID, index, mode, startGraph) - .tail(t('modes.draw_line.tail')) .on('rejectedSelfIntersection.modeDrawLine', function() { context.ui().flash .text(t('self_intersection.error.lines'))(); diff --git a/test/spec/spec_helpers.js b/test/spec/spec_helpers.js index 4090692f5..2f6898f0a 100644 --- a/test/spec/spec_helpers.js +++ b/test/spec/spec_helpers.js @@ -29,7 +29,6 @@ mocha.setup({ timeout: 5000, // 5 sec ui: 'bdd', globals: [ - '__onresize.tail-size', '__onmousemove.zoom', '__onmouseup.zoom', '__onkeydown.select', From b58127b98a522856a24f2223d9a5aac5c622be1e Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Thu, 7 May 2020 10:43:32 -0700 Subject: [PATCH 05/67] Improve names of several craft presets --- data/presets.yaml | 8 ++++---- data/presets/presets.json | 4 ++-- data/presets/presets/craft/handicraft.json | 2 +- data/presets/presets/craft/hvac.json | 2 +- data/taginfo.json | 4 ++-- dist/locales/en.json | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/data/presets.yaml b/data/presets.yaml index 10971ac4c..6038559e0 100644 --- a/data/presets.yaml +++ b/data/presets.yaml @@ -4656,13 +4656,13 @@ en: terms: '' craft/handicraft: # craft=handicraft - name: Handicraft - terms: '' + name: Handicraft Workspace + terms: '' craft/hvac: # craft=hvac - name: HVAC + name: HVAC Workplace # 'terms: heat*,vent*,air conditioning' - terms: '' + terms: '' craft/insulator: # craft=insulation name: Insulator diff --git a/data/presets/presets.json b/data/presets/presets.json index 0123390f5..c172b2504 100644 --- a/data/presets/presets.json +++ b/data/presets/presets.json @@ -411,8 +411,8 @@ "craft/floorer": {"icon": "temaki-brick_trowel", "geometry": ["point", "area"], "tags": {"craft": "floorer"}, "name": "Floorer"}, "craft/gardener": {"icon": "maki-garden-centre", "geometry": ["point", "area"], "terms": ["landscaper", "grounds keeper"], "tags": {"craft": "gardener"}, "name": "Gardener"}, "craft/glaziery": {"icon": "temaki-window", "geometry": ["point", "area"], "terms": ["glass", "stained-glass", "window"], "tags": {"craft": "glaziery"}, "name": "Glaziery"}, - "craft/handicraft": {"icon": "temaki-vase", "geometry": ["point", "area"], "tags": {"craft": "handicraft"}, "name": "Handicraft"}, - "craft/hvac": {"icon": "temaki-tools", "geometry": ["point", "area"], "terms": ["heat*", "vent*", "air conditioning"], "tags": {"craft": "hvac"}, "name": "HVAC"}, + "craft/handicraft": {"icon": "temaki-vase", "geometry": ["point", "area"], "tags": {"craft": "handicraft"}, "name": "Handicraft Workspace"}, + "craft/hvac": {"icon": "temaki-tools", "geometry": ["point", "area"], "terms": ["heat*", "vent*", "air conditioning"], "tags": {"craft": "hvac"}, "name": "HVAC Workplace"}, "craft/insulator": {"icon": "temaki-tools", "geometry": ["point", "area"], "tags": {"craft": "insulation"}, "name": "Insulator"}, "craft/joiner": {"icon": "temaki-tools", "geometry": ["point", "area"], "tags": {"craft": "joiner"}, "terms": ["furniture"], "name": "Joiner"}, "craft/key_cutter": {"icon": "fas-key", "geometry": ["point", "area"], "tags": {"craft": "key_cutter"}, "name": "Key Cutter"}, diff --git a/data/presets/presets/craft/handicraft.json b/data/presets/presets/craft/handicraft.json index f5958ba53..d56a1eae3 100644 --- a/data/presets/presets/craft/handicraft.json +++ b/data/presets/presets/craft/handicraft.json @@ -7,5 +7,5 @@ "tags": { "craft": "handicraft" }, - "name": "Handicraft" + "name": "Handicraft Workspace" } diff --git a/data/presets/presets/craft/hvac.json b/data/presets/presets/craft/hvac.json index 32a0fb6fb..fb9a0dc98 100644 --- a/data/presets/presets/craft/hvac.json +++ b/data/presets/presets/craft/hvac.json @@ -12,5 +12,5 @@ "tags": { "craft": "hvac" }, - "name": "HVAC" + "name": "HVAC Workplace" } diff --git a/data/taginfo.json b/data/taginfo.json index dd0537836..11a3e8675 100644 --- a/data/taginfo.json +++ b/data/taginfo.json @@ -407,8 +407,8 @@ {"key": "craft", "value": "floorer", "description": "๐Ÿ„ฟ Floorer", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/brick_trowel.svg"}, {"key": "craft", "value": "gardener", "description": "๐Ÿ„ฟ Gardener", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/mapbox/maki/icons/garden-centre-15.svg"}, {"key": "craft", "value": "glaziery", "description": "๐Ÿ„ฟ Glaziery", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/window.svg"}, - {"key": "craft", "value": "handicraft", "description": "๐Ÿ„ฟ Handicraft", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/vase.svg"}, - {"key": "craft", "value": "hvac", "description": "๐Ÿ„ฟ HVAC", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/tools.svg"}, + {"key": "craft", "value": "handicraft", "description": "๐Ÿ„ฟ Handicraft Workspace", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/vase.svg"}, + {"key": "craft", "value": "hvac", "description": "๐Ÿ„ฟ HVAC Workplace", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/tools.svg"}, {"key": "craft", "value": "insulation", "description": "๐Ÿ„ฟ Insulator", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/tools.svg"}, {"key": "craft", "value": "joiner", "description": "๐Ÿ„ฟ Joiner", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/tools.svg"}, {"key": "craft", "value": "key_cutter", "description": "๐Ÿ„ฟ Key Cutter", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/openstreetmap/iD@develop/svg/fontawesome/fas-key.svg"}, diff --git a/dist/locales/en.json b/dist/locales/en.json index 59b49b4d9..f8c796b03 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -6589,11 +6589,11 @@ "terms": "glass,stained-glass,window" }, "craft/handicraft": { - "name": "Handicraft", + "name": "Handicraft Workspace", "terms": "" }, "craft/hvac": { - "name": "HVAC", + "name": "HVAC Workplace", "terms": "heat*,vent*,air conditioning" }, "craft/insulator": { From 4478dba08dac81a5985624a6111e6ad930ce32b0 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Thu, 7 May 2020 10:50:27 -0700 Subject: [PATCH 06/67] Rename "Ferry Station / Terminal" presets to "Ferry Terminal" --- data/presets.yaml | 6 +++--- data/presets/presets.json | 4 ++-- data/presets/presets/amenity/_ferry_terminal.json | 2 +- data/presets/presets/public_transport/station_ferry.json | 2 +- data/taginfo.json | 4 ++-- dist/locales/en.json | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/data/presets.yaml b/data/presets.yaml index 6038559e0..aeba8ebe4 100644 --- a/data/presets.yaml +++ b/data/presets.yaml @@ -3286,7 +3286,7 @@ en: terms: '' amenity/ferry_terminal: # amenity=ferry_terminal - name: Ferry Station / Terminal + name: Ferry Terminal amenity/fire_station: # amenity=fire_station name: Fire Station @@ -7334,9 +7334,9 @@ en: terms: '' public_transport/station_ferry: # 'public_transport=station, ferry=yes' - name: Ferry Station / Terminal + name: Ferry Terminal # 'terms: boat,dock,ferry,pier,public transit,public transportation,station,terminal,transit,transportation' - terms: '' + terms: '' public_transport/station_light_rail: # 'public_transport=station, light_rail=yes' name: Light Rail Station diff --git a/data/presets/presets.json b/data/presets/presets.json index c172b2504..6c392ef2d 100644 --- a/data/presets/presets.json +++ b/data/presets/presets.json @@ -58,7 +58,7 @@ "amenity/bus_station": {"icon": "temaki-bus", "fields": ["name", "building_area", "operator", "internet_access", "internet_access/fee", "internet_access/ssid"], "geometry": ["point", "area"], "tags": {"amenity": "bus_station"}, "name": "Bus Station / Terminal", "searchable": false, "replacement": "public_transport/station_bus"}, "amenity/coworking_space": {"icon": "maki-commercial", "fields": ["name", "address", "building_area", "opening_hours", "opening_hours/covid19", "internet_access", "internet_access/fee", "internet_access/ssid"], "geometry": ["point", "area"], "tags": {"amenity": "coworking_space"}, "name": "Coworking Space", "searchable": false}, "amenity/embassy": {"icon": "temaki-embassy", "fields": ["name", "country", "address", "building_area"], "moreFields": ["email", "fax", "phone", "website", "wheelchair"], "geometry": ["point", "area"], "tags": {"amenity": "embassy"}, "searchable": false, "name": "Embassy"}, - "amenity/ferry_terminal": {"icon": "temaki-ferry", "fields": ["name", "network", "operator", "address", "building_area"], "geometry": ["point", "vertex", "area"], "tags": {"amenity": "ferry_terminal"}, "matchScore": 0.95, "name": "Ferry Station / Terminal", "searchable": false, "replacement": "public_transport/station_ferry"}, + "amenity/ferry_terminal": {"icon": "temaki-ferry", "fields": ["name", "network", "operator", "address", "building_area"], "geometry": ["point", "vertex", "area"], "tags": {"amenity": "ferry_terminal"}, "matchScore": 0.95, "name": "Ferry Terminal", "searchable": false, "replacement": "public_transport/station_ferry"}, "amenity/nursing_home": {"icon": "maki-wheelchair", "fields": ["{amenity/social_facility}", "wheelchair"], "geometry": ["point", "area"], "tags": {"amenity": "nursing_home"}, "reference": {"key": "social_facility", "value": "nursing_home"}, "name": "Nursing Home", "searchable": false}, "amenity/recycling": {"icon": "maki-recycling", "fields": ["recycling_type", "recycling_accepts", "collection_times"], "geometry": ["point", "area"], "tags": {"amenity": "recycling"}, "name": "Recycling", "searchable": false}, "amenity/animal_boarding": {"icon": "maki-veterinary", "fields": ["name", "operator", "address", "building_area", "opening_hours", "opening_hours/covid19", "animal_boarding"], "moreFields": ["email", "fax", "gnis/feature_id", "level", "payment_multi", "phone", "website", "wheelchair"], "geometry": ["point", "area"], "terms": ["boarding", "cat", "cattery", "dog", "horse", "kennel", "kitten", "pet", "pet boarding", "pet care", "pet hotel", "puppy", "reptile"], "tags": {"amenity": "animal_boarding"}, "name": "Animal Boarding Facility"}, @@ -981,7 +981,7 @@ "public_transport/platform/trolleybus": {"icon": "temaki-board_trolleybus", "fields": ["{public_transport/platform}"], "moreFields": ["{public_transport/platform}"], "geometry": ["line", "area"], "tags": {"public_transport": "platform", "trolleybus": "yes"}, "reference": {"key": "public_transport", "value": "platform"}, "terms": ["bus", "electric", "platform", "public transit", "public transportation", "streetcar", "trackless", "tram", "trolley", "transit", "transportation"], "name": "Trolleybus Platform"}, "public_transport/station_aerialway": {"icon": "temaki-gondola_lift", "fields": ["{public_transport/station}", "aerialway/access", "aerialway/summer/access"], "moreFields": ["{public_transport/station}"], "geometry": ["point", "vertex", "area"], "tags": {"aerialway": "station"}, "addTags": {"public_transport": "station", "aerialway": "station"}, "reference": {"key": "aerialway", "value": "station"}, "terms": ["aerialway", "cable car", "public transit", "public transportation", "station", "terminal", "transit", "transportation"], "name": "Aerialway Station"}, "public_transport/station_bus": {"icon": "temaki-bus", "fields": ["{public_transport/station}"], "moreFields": ["{public_transport/station}"], "geometry": ["point", "area"], "tags": {"public_transport": "station", "bus": "yes"}, "addTags": {"public_transport": "station", "bus": "yes", "amenity": "bus_station"}, "reference": {"key": "amenity", "value": "bus_station"}, "terms": ["bus", "public transit", "public transportation", "station", "terminal", "transit", "transportation"], "name": "Bus Station / Terminal"}, - "public_transport/station_ferry": {"icon": "temaki-ferry", "fields": ["{public_transport/station}"], "moreFields": ["{public_transport/station}"], "geometry": ["vertex", "point", "area"], "tags": {"public_transport": "station", "ferry": "yes"}, "addTags": {"public_transport": "station", "ferry": "yes", "amenity": "ferry_terminal"}, "reference": {"key": "amenity", "value": "ferry_terminal"}, "terms": ["boat", "dock", "ferry", "pier", "public transit", "public transportation", "station", "terminal", "transit", "transportation"], "name": "Ferry Station / Terminal"}, + "public_transport/station_ferry": {"icon": "temaki-ferry", "fields": ["{public_transport/station}"], "moreFields": ["{public_transport/station}"], "geometry": ["vertex", "point", "area"], "tags": {"public_transport": "station", "ferry": "yes"}, "addTags": {"public_transport": "station", "ferry": "yes", "amenity": "ferry_terminal"}, "reference": {"key": "amenity", "value": "ferry_terminal"}, "terms": ["boat", "dock", "ferry", "pier", "public transit", "public transportation", "station", "terminal", "transit", "transportation"], "name": "Ferry Terminal"}, "public_transport/station_light_rail": {"icon": "temaki-light_rail", "fields": ["{public_transport/station}"], "moreFields": ["{public_transport/station}"], "geometry": ["point", "area"], "tags": {"public_transport": "station", "light_rail": "yes"}, "addTags": {"public_transport": "station", "light_rail": "yes", "railway": "station", "station": "light_rail"}, "reference": {"key": "station", "value": "light_rail"}, "terms": ["electric", "light rail", "public transit", "public transportation", "rail", "station", "terminal", "track", "tram", "trolley", "transit", "transportation"], "name": "Light Rail Station"}, "public_transport/station_monorail": {"icon": "temaki-monorail", "fields": ["{public_transport/station}"], "moreFields": ["{public_transport/station}"], "geometry": ["point", "area"], "tags": {"public_transport": "station", "monorail": "yes"}, "addTags": {"public_transport": "station", "monorail": "yes", "railway": "station"}, "reference": {"key": "railway", "value": "station"}, "terms": ["monorail", "public transit", "public transportation", "rail", "station", "terminal", "transit", "transportation"], "name": "Monorail Station"}, "public_transport/station_subway": {"icon": "temaki-subway", "fields": ["{public_transport/station}"], "moreFields": ["{public_transport/station}"], "geometry": ["point", "area"], "tags": {"public_transport": "station", "subway": "yes"}, "addTags": {"public_transport": "station", "subway": "yes", "railway": "station", "station": "subway"}, "reference": {"key": "station", "value": "subway"}, "terms": ["metro", "public transit", "public transportation", "rail", "station", "subway", "terminal", "track", "transit", "transportation", "underground"], "name": "Subway Station"}, diff --git a/data/presets/presets/amenity/_ferry_terminal.json b/data/presets/presets/amenity/_ferry_terminal.json index 3fd6021e7..133bee0e8 100644 --- a/data/presets/presets/amenity/_ferry_terminal.json +++ b/data/presets/presets/amenity/_ferry_terminal.json @@ -16,7 +16,7 @@ "amenity": "ferry_terminal" }, "matchScore": 0.95, - "name": "Ferry Station / Terminal", + "name": "Ferry Terminal", "searchable": false, "replacement": "public_transport/station_ferry" } diff --git a/data/presets/presets/public_transport/station_ferry.json b/data/presets/presets/public_transport/station_ferry.json index 4905ccf5a..52edd223b 100644 --- a/data/presets/presets/public_transport/station_ferry.json +++ b/data/presets/presets/public_transport/station_ferry.json @@ -36,5 +36,5 @@ "transit", "transportation" ], - "name": "Ferry Station / Terminal" + "name": "Ferry Terminal" } diff --git a/data/taginfo.json b/data/taginfo.json index 11a3e8675..889bfc23c 100644 --- a/data/taginfo.json +++ b/data/taginfo.json @@ -62,7 +62,7 @@ {"key": "amenity", "value": "bus_station", "description": "๐Ÿ„ฟ Bus Station / Terminal (unsearchable)", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/bus.svg"}, {"key": "amenity", "value": "coworking_space", "description": "๐Ÿ„ฟ Coworking Space (unsearchable)", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/mapbox/maki/icons/commercial-15.svg"}, {"key": "amenity", "value": "embassy", "description": "๐Ÿ„ฟ Embassy (unsearchable), ๐Ÿ„ณ โžœ office=diplomatic", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/embassy.svg"}, - {"key": "amenity", "value": "ferry_terminal", "description": "๐Ÿ„ฟ Ferry Station / Terminal (unsearchable)", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/ferry.svg"}, + {"key": "amenity", "value": "ferry_terminal", "description": "๐Ÿ„ฟ Ferry Terminal (unsearchable)", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/ferry.svg"}, {"key": "amenity", "value": "nursing_home", "description": "๐Ÿ„ฟ Nursing Home (unsearchable)", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/mapbox/maki/icons/wheelchair-15.svg"}, {"key": "amenity", "value": "recycling", "description": "๐Ÿ„ฟ Recycling (unsearchable)", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/mapbox/maki/icons/recycling-15.svg"}, {"key": "amenity", "value": "animal_boarding", "description": "๐Ÿ„ฟ Animal Boarding Facility", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/mapbox/maki/icons/veterinary-15.svg"}, @@ -937,7 +937,7 @@ {"key": "power", "value": "transformer", "description": "๐Ÿ„ฟ Transformer", "object_types": ["node"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/power_transformer.svg"}, {"key": "public_transport", "value": "platform", "description": "๐Ÿ„ฟ Transit Stop / Platform, ๐Ÿ„ฟ Transit Platform", "object_types": ["node"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/sign_and_bench.svg"}, {"key": "aerialway", "value": "yes", "description": "๐Ÿ„ฟ Aerialway Stop / Platform (unsearchable), ๐Ÿ„ฟ Aerialway Platform, ๐Ÿ„ฟ Aerialway Stopping Location", "object_types": ["node"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/gondola_lift.svg"}, - {"key": "ferry", "value": "yes", "description": "๐Ÿ„ฟ Ferry Stop / Platform (unsearchable), ๐Ÿ„ฟ Ferry Platform, ๐Ÿ„ฟ Ferry Station / Terminal, ๐Ÿ„ฟ Ferry Stopping Location", "object_types": ["node"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/ferry.svg"}, + {"key": "ferry", "value": "yes", "description": "๐Ÿ„ฟ Ferry Stop / Platform (unsearchable), ๐Ÿ„ฟ Ferry Platform, ๐Ÿ„ฟ Ferry Terminal, ๐Ÿ„ฟ Ferry Stopping Location", "object_types": ["node"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/ferry.svg"}, {"key": "light_rail", "value": "yes", "description": "๐Ÿ„ฟ Light Rail Stop / Platform (unsearchable), ๐Ÿ„ฟ Light Rail Platform, ๐Ÿ„ฟ Light Rail Station, ๐Ÿ„ฟ Light Rail Stopping Location", "object_types": ["node"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/light_rail.svg"}, {"key": "monorail", "value": "yes", "description": "๐Ÿ„ฟ Monorail Stop / Platform (unsearchable), ๐Ÿ„ฟ Monorail Platform, ๐Ÿ„ฟ Monorail Station, ๐Ÿ„ฟ Monorail Stopping Location", "object_types": ["node"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/monorail.svg"}, {"key": "subway", "value": "yes", "description": "๐Ÿ„ฟ Subway Stop / Platform (unsearchable), ๐Ÿ„ฟ Subway Platform, ๐Ÿ„ฟ Subway Station, ๐Ÿ„ฟ Subway Stopping Location", "object_types": ["node"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/subway.svg"}, diff --git a/dist/locales/en.json b/dist/locales/en.json index f8c796b03..5c56d8b37 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -5187,7 +5187,7 @@ "name": "Embassy" }, "amenity/ferry_terminal": { - "name": "Ferry Station / Terminal" + "name": "Ferry Terminal" }, "amenity/nursing_home": { "name": "Nursing Home" @@ -8834,7 +8834,7 @@ "terms": "bus,public transit,public transportation,station,terminal,transit,transportation" }, "public_transport/station_ferry": { - "name": "Ferry Station / Terminal", + "name": "Ferry Terminal", "terms": "boat,dock,ferry,pier,public transit,public transportation,station,terminal,transit,transportation" }, "public_transport/station_light_rail": { From ff4eb971accd2d04f26a4d2c04072015f23f7ee3 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Thu, 7 May 2020 13:27:20 -0700 Subject: [PATCH 07/67] Fix various issues that made the walkthrough non-completable --- modules/behavior/select.js | 27 ++++++++++++--------------- modules/ui/intro/building.js | 14 +++++++------- modules/ui/intro/intro.js | 1 - modules/ui/intro/line.js | 2 +- modules/ui/intro/navigation.js | 12 ++++++------ modules/ui/intro/point.js | 10 +++++----- 6 files changed, 31 insertions(+), 35 deletions(-) diff --git a/modules/behavior/select.js b/modules/behavior/select.js index 9ee382ddb..667eb7619 100644 --- a/modules/behavior/select.js +++ b/modules/behavior/select.js @@ -14,8 +14,8 @@ import { utilFastMouse } from '../util/util'; export function behaviorSelect(context) { // legacy option to show menu on every click - var isShowAlways = +prefs('edit-menu-show-always') === 1; - var tolerance = 4; + var _alwaysShowMenu = +prefs('edit-menu-show-always') === 1; + var _tolerancePx = 4; var _lastMouse = null; var _showMenu = false; var _p1 = null; @@ -65,7 +65,7 @@ export function behaviorSelect(context) { d3_select(window) .on(_pointerPrefix + 'up.select', pointerup, true); - _showMenu = isShowAlways; + _showMenu = _alwaysShowMenu; } @@ -110,7 +110,7 @@ export function behaviorSelect(context) { var p2 = point(); var dist = geoVecLength(_p1, p2); _p1 = null; - if (dist > tolerance) return; + if (dist > _tolerancePx) return; var datum = d3_event.target.__data__ || (_lastMouse && _lastMouse.target.__data__); var isMultiselect = d3_event.shiftKey || context.surface().select('.lasso').node(); @@ -137,25 +137,22 @@ export function behaviorSelect(context) { context.selectedErrorID(null); if (!isMultiselect) { - if (selectedIDs.length > 1 && (_showMenu && !isShowAlways)) { + if (selectedIDs.length > 1 && (_showMenu && !_alwaysShowMenu)) { // multiple things already selected, just show the menu... mode.reselect().showMenu(); } else { - if (mode.id !== 'select' || !utilArrayIdentical(mode.selectedIDs(), [datum.id])) { - newMode = modeSelect(context, [datum.id]); - // select a single thing if it's not already selected - context.enter(newMode); - if (_showMenu) newMode.showMenu(); - } else { - mode.reselect(); - if (_showMenu) mode.showMenu(); - } + // always enter modeSelect even if the entity is already + // selected since listeners may expect `context.enter` events, + // e.g. in the walkthrough + newMode = modeSelect(context, [datum.id]); + context.enter(newMode); + if (_showMenu) newMode.showMenu(); } } else { if (selectedIDs.indexOf(datum.id) !== -1) { // clicked entity is already in the selectedIDs list.. - if (_showMenu && !isShowAlways) { + if (_showMenu && !_alwaysShowMenu) { // don't deselect clicked entity, just show the menu. mode.reselect().showMenu(); } else { diff --git a/modules/ui/intro/building.js b/modules/ui/intro/building.js index 7ed73d0f1..00f6a3ea8 100644 --- a/modules/ui/intro/building.js +++ b/modules/ui/intro/building.js @@ -343,7 +343,7 @@ export function uiIntroBuilding(context, reveal) { var node = selectMenuItem(context, 'orthogonalize').node(); if (!node) return; continueTo(clickSquare); - }, 300); // after menu visible + }, 50); // after menu visible }); context.map().on('move.intro drawn.intro', function() { @@ -386,7 +386,7 @@ export function uiIntroBuilding(context, reveal) { } }); - context.map().on('move.intro drawn.intro', function() { + context.map().on('move.intro', function() { var node = selectMenuItem(context, 'orthogonalize').node(); if (!wasChanged && !node) { return continueTo(rightClickHouse); } @@ -412,7 +412,7 @@ export function uiIntroBuilding(context, reveal) { function continueTo(nextStep) { context.on('enter.intro', null); - context.map().on('move.intro drawn.intro', null); + context.map().on('move.intro', null); context.history().on('change.intro', null); nextStep(); } @@ -661,7 +661,7 @@ export function uiIntroBuilding(context, reveal) { var node = selectMenuItem(context, 'circularize').node(); if (!node) return; continueTo(clickCircle); - }, 300); // after menu visible + }, 50); // after menu visible }); revealTank(tank, t('intro.buildings.rightclick_tank')); @@ -708,7 +708,7 @@ export function uiIntroBuilding(context, reveal) { } }); - context.map().on('move.intro drawn.intro', function() { + context.map().on('move.intro', function() { var node = selectMenuItem(context, 'circularize').node(); if (!wasChanged && !node) { return continueTo(rightClickTank); } @@ -724,7 +724,7 @@ export function uiIntroBuilding(context, reveal) { // Something changed. Wait for transition to complete and check undo annotation. timeout(function() { - if (context.history().undoAnnotation() === t('operations.circularize.annotation.area')) { + if (context.history().undoAnnotation() === t('operations.circularize.annotation.single')) { continueTo(play); } else { continueTo(retryClickCircle); @@ -734,7 +734,7 @@ export function uiIntroBuilding(context, reveal) { function continueTo(nextStep) { context.on('enter.intro', null); - context.map().on('move.intro drawn.intro', null); + context.map().on('move.intro', null); context.history().on('change.intro', null); nextStep(); } diff --git a/modules/ui/intro/intro.js b/modules/ui/intro/intro.js index 5994ad0e0..8e53a8fde 100644 --- a/modules/ui/intro/intro.js +++ b/modules/ui/intro/intro.js @@ -125,7 +125,6 @@ export function uiIntro(context) { let chapters = chapterFlow.map((chapter, i) => { let s = chapterUi[chapter](context, curtain.reveal) .on('done', () => { - presetManager.init(); // clear away "recent" presets buttons .filter(d => d.title === s.title) diff --git a/modules/ui/intro/line.js b/modules/ui/intro/line.js index 9696c2bcf..a296642f5 100644 --- a/modules/ui/intro/line.js +++ b/modules/ui/intro/line.js @@ -676,7 +676,7 @@ export function uiIntroLine(context, reveal) { var node = selectMenuItem(context, 'split').node(); if (!node) return; continueTo(splitIntersection); - }, 300); // after menu visible + }, 50); // after menu visible }); context.history().on('change.intro', function() { diff --git a/modules/ui/intro/navigation.js b/modules/ui/intro/navigation.js index 235ce9833..d34a3b69d 100644 --- a/modules/ui/intro/navigation.js +++ b/modules/ui/intro/navigation.js @@ -295,6 +295,11 @@ export function uiIntroNavigation(context, reveal) { var onClick = function() { continueTo(fieldsTownHall); }; + reveal('.entity-editor-pane .section-feature-type', + t('intro.navigation.preset_townhall', { preset: preset.name() }), + { buttonText: t('intro.ok'), buttonCallback: onClick } + ); + context.on('exit.intro', function() { continueTo(clickTownHall); }); @@ -305,11 +310,6 @@ export function uiIntroNavigation(context, reveal) { } }); - reveal('.entity-editor .preset-list-item', - t('intro.navigation.preset_townhall', { preset: preset.name() }), - { buttonText: t('intro.ok'), buttonCallback: onClick } - ); - function continueTo(nextStep) { context.on('exit.intro', null); context.history().on('change.intro', null); @@ -329,7 +329,7 @@ export function uiIntroNavigation(context, reveal) { var onClick = function() { continueTo(closeTownHall); }; - reveal('.inspector-body .preset-editor', + reveal('.entity-editor-pane .section-preset-fields', t('intro.navigation.fields_townhall'), { buttonText: t('intro.ok'), buttonCallback: onClick } ); diff --git a/modules/ui/intro/point.js b/modules/ui/intro/point.js index 5cf276787..a5ddcae03 100644 --- a/modules/ui/intro/point.js +++ b/modules/ui/intro/point.js @@ -388,7 +388,7 @@ export function uiIntroPoint(context, reveal) { reveal(box, t('intro.points.rightclick'), { duration: 600 }); timeout(function() { - context.map().on('move.intro drawn.intro', function() { + context.map().on('move.intro', function() { var entity = context.hasEntity(_pointID); if (!entity) return chapter.restart(); var box = pointBox(entity.loc, context); @@ -405,12 +405,12 @@ export function uiIntroPoint(context, reveal) { var node = selectMenuItem(context, 'delete').node(); if (!node) return; continueTo(enterDelete); - }, 300); // after menu visible + }, 50); // after menu visible }); function continueTo(nextStep) { context.on('enter.intro', null); - context.map().on('move.intro drawn.intro', null); + context.map().on('move.intro', null); nextStep(); } } @@ -429,7 +429,7 @@ export function uiIntroPoint(context, reveal) { ); timeout(function() { - context.map().on('move.intro drawn.intro', function() { + context.map().on('move.intro', function() { revealEditMenu(entity.loc, t('intro.points.delete', { button: icon('#iD-operation-delete', 'pre-text') }), { duration: 0} @@ -450,7 +450,7 @@ export function uiIntroPoint(context, reveal) { }); function continueTo(nextStep) { - context.map().on('move.intro drawn.intro', null); + context.map().on('move.intro', null); context.history().on('change.intro', null); context.on('exit.intro', null); nextStep(); From eb21bbfcde4e7cf820770dc5ac0eff9c5a1d555a Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Thu, 7 May 2020 13:49:58 -0700 Subject: [PATCH 08/67] Add Orthodontist preset (close #7575) --- data/presets.yaml | 5 +++ data/presets/presets.json | 1 + .../healthcare/dentist/orthodontics.json | 36 +++++++++++++++++++ data/taginfo.json | 1 + dist/locales/en.json | 4 +++ svg/fontawesome/fas-teeth.svg | 1 + 6 files changed, 48 insertions(+) create mode 100644 data/presets/presets/healthcare/dentist/orthodontics.json create mode 100644 svg/fontawesome/fas-teeth.svg diff --git a/data/presets.yaml b/data/presets.yaml index aeba8ebe4..23619af8e 100644 --- a/data/presets.yaml +++ b/data/presets.yaml @@ -4986,6 +4986,11 @@ en: # healthcare=counselling name: Counselling Center terms: '' + healthcare/dentist/orthodontics: + # 'healthcare=dentist, healthcare:speciality=orthodontics' + name: Orthodontist + # 'terms: braces,dentistry,dentofacial orthopedics,headgear,jaw alignment,teeth,tooth' + terms: '' healthcare/hospice: # healthcare=hospice name: Hospice diff --git a/data/presets/presets.json b/data/presets/presets.json index 6c392ef2d..58b187301 100644 --- a/data/presets/presets.json +++ b/data/presets/presets.json @@ -485,6 +485,7 @@ "healthcare/birthing_center": {"icon": "fas-baby", "geometry": ["point", "area"], "terms": ["baby", "childbirth", "delivery", "labour", "labor", "pregnancy"], "tags": {"healthcare": "birthing_center"}, "name": "Birthing Center"}, "healthcare/blood_donation": {"icon": "maki-blood-bank", "fields": ["{healthcare}", "blood_components"], "geometry": ["point", "area"], "terms": ["blood bank", "blood donation", "blood transfusion", "apheresis", "plasmapheresis", "plateletpheresis", "stem cell donation"], "tags": {"healthcare": "blood_donation"}, "name": "Blood Donor Center"}, "healthcare/counselling": {"icon": "fas-comments", "geometry": ["point", "area"], "tags": {"healthcare": "counselling"}, "name": "Counselling Center"}, + "healthcare/dentist/orthodontics": {"icon": "fas-teeth", "fields": ["{amenity/dentist}"], "moreFields": ["{amenity/dentist}"], "geometry": ["point", "area"], "terms": ["braces", "dentistry", "dentofacial orthopedics", "headgear", "jaw alignment", "teeth", "tooth"], "tags": {"healthcare": "dentist", "healthcare:speciality": "orthodontics"}, "addTags": {"healthcare": "dentist", "amenity": "dentist", "healthcare:speciality": "orthodontics"}, "reference": {"key": "healthcare:speciality", "value": "orthodontics"}, "name": "Orthodontist"}, "healthcare/hospice": {"icon": "maki-hospital", "geometry": ["point", "area"], "terms": ["terminal", "illness"], "tags": {"healthcare": "hospice"}, "name": "Hospice"}, "healthcare/laboratory": {"icon": "fas-vial", "fields": ["name", "operator", "website", "ref", "address", "opening_hours", "opening_hours/covid19"], "geometry": ["point", "area"], "terms": ["medical_laboratory", "medical_lab", "blood_check"], "tags": {"healthcare": "laboratory"}, "name": "Medical Laboratory"}, "healthcare/midwife": {"icon": "fas-baby", "geometry": ["point", "area"], "terms": ["baby", "childbirth", "delivery", "labour", "labor", "pregnancy"], "tags": {"healthcare": "midwife"}, "name": "Midwife"}, diff --git a/data/presets/presets/healthcare/dentist/orthodontics.json b/data/presets/presets/healthcare/dentist/orthodontics.json new file mode 100644 index 000000000..2c8310be1 --- /dev/null +++ b/data/presets/presets/healthcare/dentist/orthodontics.json @@ -0,0 +1,36 @@ +{ + "icon": "fas-teeth", + "fields": [ + "{amenity/dentist}" + ], + "moreFields": [ + "{amenity/dentist}" + ], + "geometry": [ + "point", + "area" + ], + "terms": [ + "braces", + "dentistry", + "dentofacial orthopedics", + "headgear", + "jaw alignment", + "teeth", + "tooth" + ], + "tags": { + "healthcare": "dentist", + "healthcare:speciality": "orthodontics" + }, + "addTags": { + "healthcare": "dentist", + "amenity": "dentist", + "healthcare:speciality": "orthodontics" + }, + "reference": { + "key": "healthcare:speciality", + "value": "orthodontics" + }, + "name": "Orthodontist" +} diff --git a/data/taginfo.json b/data/taginfo.json index 889bfc23c..249aaf111 100644 --- a/data/taginfo.json +++ b/data/taginfo.json @@ -481,6 +481,7 @@ {"key": "healthcare", "value": "birthing_center", "description": "๐Ÿ„ฟ Birthing Center", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/openstreetmap/iD@develop/svg/fontawesome/fas-baby.svg"}, {"key": "healthcare", "value": "blood_donation", "description": "๐Ÿ„ฟ Blood Donor Center", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/mapbox/maki/icons/blood-bank-15.svg"}, {"key": "healthcare", "value": "counselling", "description": "๐Ÿ„ฟ Counselling Center", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/openstreetmap/iD@develop/svg/fontawesome/fas-comments.svg"}, + {"key": "healthcare:speciality", "value": "orthodontics", "description": "๐Ÿ„ฟ Orthodontist", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/openstreetmap/iD@develop/svg/fontawesome/fas-teeth.svg"}, {"key": "healthcare", "value": "hospice", "description": "๐Ÿ„ฟ Hospice", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/mapbox/maki/icons/hospital-15.svg"}, {"key": "healthcare", "value": "laboratory", "description": "๐Ÿ„ฟ Medical Laboratory", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/openstreetmap/iD@develop/svg/fontawesome/fas-vial.svg"}, {"key": "healthcare", "value": "midwife", "description": "๐Ÿ„ฟ Midwife", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/openstreetmap/iD@develop/svg/fontawesome/fas-baby.svg"}, diff --git a/dist/locales/en.json b/dist/locales/en.json index 5c56d8b37..bfeeea8b8 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -6875,6 +6875,10 @@ "name": "Counselling Center", "terms": "" }, + "healthcare/dentist/orthodontics": { + "name": "Orthodontist", + "terms": "braces,dentistry,dentofacial orthopedics,headgear,jaw alignment,teeth,tooth" + }, "healthcare/hospice": { "name": "Hospice", "terms": "terminal,illness" diff --git a/svg/fontawesome/fas-teeth.svg b/svg/fontawesome/fas-teeth.svg new file mode 100644 index 000000000..2f444d5c6 --- /dev/null +++ b/svg/fontawesome/fas-teeth.svg @@ -0,0 +1 @@ + \ No newline at end of file From 38bc5131fad007e5f3a4d9f406778b2b738126f6 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Fri, 8 May 2020 09:40:27 -0700 Subject: [PATCH 09/67] Fix incorrect curtain positioning when iD's container doesn't match the window dimensions (close #7551) --- modules/ui/curtain.js | 23 ++++++++++++++--------- modules/ui/intro/intro.js | 2 +- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/modules/ui/curtain.js b/modules/ui/curtain.js index 0405ac2df..380aa4702 100644 --- a/modules/ui/curtain.js +++ b/modules/ui/curtain.js @@ -10,7 +10,7 @@ import { uiToggle } from './toggle'; // Tooltips and svg mask used to highlight certain features -export function uiCurtain() { +export function uiCurtain(containerNode) { var surface = d3_select(null), tooltip = d3_select(null), @@ -46,8 +46,8 @@ export function uiCurtain() { function resize() { surface - .attr('width', window.innerWidth) - .attr('height', window.innerHeight); + .attr('width', containerNode.clientWidth) + .attr('height', containerNode.clientHeight); curtain.cut(darkness.datum()); } } @@ -72,6 +72,9 @@ export function uiCurtain() { } if (box && box.getBoundingClientRect) { box = copyBox(box.getBoundingClientRect()); + var containerRect = containerNode.getBoundingClientRect(); + box.top -= containerRect.top; + box.left -= containerRect.left; } options = options || {}; @@ -121,8 +124,8 @@ export function uiCurtain() { } var tip = copyBox(tooltip.node().getBoundingClientRect()), - w = window.innerWidth, - h = window.innerHeight, + w = containerNode.clientWidth, + h = containerNode.clientHeight, tooltipWidth = 200, tooltipArrow = 5, side, pos; @@ -134,7 +137,7 @@ export function uiCurtain() { tip.height += 80; } - // trim box dimensions to just the portion that fits in the window.. + // trim box dimensions to just the portion that fits in the container.. if (tooltipBox.top + tooltipBox.height > h) { tooltipBox.height -= (tooltipBox.top + tooltipBox.height - h); } @@ -238,9 +241,11 @@ export function uiCurtain() { selection .attr('d', function(d) { - var string = 'M 0,0 L 0,' + window.innerHeight + ' L ' + - window.innerWidth + ',' + window.innerHeight + 'L' + - window.innerWidth + ',0 Z'; + var containerWidth = containerNode.clientWidth; + var containerHeight = containerNode.clientHeight; + var string = 'M 0,0 L 0,' + containerHeight + ' L ' + + containerWidth + ',' + containerHeight + 'L' + + containerWidth + ',0 Z'; if (!d) return string; return string + 'M' + diff --git a/modules/ui/intro/intro.js b/modules/ui/intro/intro.js index 8e53a8fde..fcebd9d5f 100644 --- a/modules/ui/intro/intro.js +++ b/modules/ui/intro/intro.js @@ -112,7 +112,7 @@ export function uiIntro(context) { context.container().selectAll('.main-map .layer-background').style('opacity', 1); - let curtain = uiCurtain(); + let curtain = uiCurtain(context.container().node()); selection.call(curtain); // Store that the user started the walkthrough.. From 77061e9c6cc659e7405c23db180c3eac784d9396 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Fri, 8 May 2020 09:42:02 -0700 Subject: [PATCH 10/67] Fix lint warnings --- modules/behavior/select.js | 1 - modules/modes/add_area.js | 1 - modules/modes/add_line.js | 1 - modules/ui/intro/intro.js | 1 - 4 files changed, 4 deletions(-) diff --git a/modules/behavior/select.js b/modules/behavior/select.js index 667eb7619..485ad91f7 100644 --- a/modules/behavior/select.js +++ b/modules/behavior/select.js @@ -8,7 +8,6 @@ import { modeSelectData } from '../modes/select_data'; import { modeSelectNote } from '../modes/select_note'; import { modeSelectError } from '../modes/select_error'; import { osmEntity, osmNote, QAItem } from '../osm'; -import { utilArrayIdentical } from '../util/array'; import { utilFastMouse } from '../util/util'; diff --git a/modules/modes/add_area.js b/modules/modes/add_area.js index 499c4b3b1..b253d425f 100644 --- a/modules/modes/add_area.js +++ b/modules/modes/add_area.js @@ -1,4 +1,3 @@ -import { t } from '../core/localizer'; import { actionAddEntity } from '../actions/add_entity'; import { actionAddMidpoint } from '../actions/add_midpoint'; import { actionAddVertex } from '../actions/add_vertex'; diff --git a/modules/modes/add_line.js b/modules/modes/add_line.js index 5db5913d5..42e9d5db9 100644 --- a/modules/modes/add_line.js +++ b/modules/modes/add_line.js @@ -1,4 +1,3 @@ -import { t } from '../core/localizer'; import { actionAddEntity } from '../actions/add_entity'; import { actionAddMidpoint } from '../actions/add_midpoint'; import { actionAddVertex } from '../actions/add_vertex'; diff --git a/modules/ui/intro/intro.js b/modules/ui/intro/intro.js index fcebd9d5f..d8c47b361 100644 --- a/modules/ui/intro/intro.js +++ b/modules/ui/intro/intro.js @@ -1,7 +1,6 @@ import { t, localizer } from '../../core/localizer'; import { localize } from './helper'; -import { presetManager } from '../../presets'; import { prefs } from '../../core/preferences'; import { fileFetcher } from '../../core/file_fetcher'; import { coreGraph } from '../../core/graph'; From a781847d6a472d84fc5a2eddb5ef9ba1b790e278 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Fri, 8 May 2020 12:01:38 -0700 Subject: [PATCH 11/67] Use pointer events for resizing the photoviewer on supported devices (re: #5505) --- modules/ui/photoviewer.js | 47 +++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/modules/ui/photoviewer.js b/modules/ui/photoviewer.js index 2c864a3ac..950d5c4fb 100644 --- a/modules/ui/photoviewer.js +++ b/modules/ui/photoviewer.js @@ -13,6 +13,8 @@ export function uiPhotoviewer(context) { var dispatch = d3_dispatch('resize'); + var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse'; + function photoviewer(selection) { selection .append('button') @@ -25,27 +27,34 @@ export function uiPhotoviewer(context) { .append('div') .call(svgIcon('#iD-icon-close')); + function preventDefault() { + d3_event.preventDefault(); + } + selection .append('button') .attr('class', 'resize-handle-xy') + .on('touchstart touchdown touchend', preventDefault) .on( - 'mousedown', + _pointerPrefix + 'down', buildResizeListener(selection, 'resize', dispatch, { resizeOnX: true, resizeOnY: true }) ); selection .append('button') .attr('class', 'resize-handle-x') + .on('touchstart touchdown touchend', preventDefault) .on( - 'mousedown', + _pointerPrefix + 'down', buildResizeListener(selection, 'resize', dispatch, { resizeOnX: true }) ); selection .append('button') .attr('class', 'resize-handle-y') + .on('touchstart touchdown touchend', preventDefault) .on( - 'mousedown', + _pointerPrefix + 'down', buildResizeListener(selection, 'resize', dispatch, { resizeOnY: true }) ); @@ -54,16 +63,23 @@ export function uiPhotoviewer(context) { services.openstreetcam.loadViewer(context); function buildResizeListener(target, eventName, dispatch, options) { + var resizeOnX = !!options.resizeOnX; var resizeOnY = !!options.resizeOnY; var minHeight = options.minHeight || 240; var minWidth = options.minWidth || 320; + var pointerId; var startX; var startY; var startWidth; var startHeight; function startResize() { + if (pointerId !== (d3_event.pointerId || 'mouse')) return; + + d3_event.preventDefault(); + d3_event.stopPropagation(); + var mapSize = context.map().dimensions(); if (resizeOnX) { @@ -86,19 +102,36 @@ export function uiPhotoviewer(context) { } function stopResize() { + if (pointerId !== (d3_event.pointerId || 'mouse')) return; + + d3_event.preventDefault(); + d3_event.stopPropagation(); + + // remove all the listeners we added d3_select(window) .on('.' + eventName, null); } return function initResize() { + d3_event.preventDefault(); + d3_event.stopPropagation(); + + pointerId = d3_event.pointerId || 'mouse'; + startX = d3_event.clientX; startY = d3_event.clientY; - startWidth = target.node().getBoundingClientRect().width; - startHeight = target.node().getBoundingClientRect().height; + var targetRect = target.node().getBoundingClientRect(); + startWidth = targetRect.width; + startHeight = targetRect.height; d3_select(window) - .on('mousemove.' + eventName, startResize, false) - .on('mouseup.' + eventName, stopResize, false); + .on(_pointerPrefix + 'move.' + eventName, startResize, false) + .on(_pointerPrefix + 'up.' + eventName, stopResize, false); + + if (_pointerPrefix === 'pointer') { + d3_select(window) + .on('pointercancel.' + eventName, stopResize, false); + } }; } } From a16a95e32e7cbeb95c9ef2727a536174bd13f85f Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Fri, 8 May 2020 12:09:35 -0700 Subject: [PATCH 12/67] Use pointer events in welcome walkthrough chapter on supported devices (re: #5505) --- modules/ui/intro/welcome.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/modules/ui/intro/welcome.js b/modules/ui/intro/welcome.js index 1454fa83b..892d517bb 100644 --- a/modules/ui/intro/welcome.js +++ b/modules/ui/intro/welcome.js @@ -168,6 +168,8 @@ function clickListener() { var tooltip = d3_select(null); var down = {}; + var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse'; + // `down` keeps track of which buttons/keys are down. // Setting a property in `down` happens immediately. // Unsetting a property in `down` is delayed because @@ -201,7 +203,7 @@ function clickListener() { } - function mousedown() { + function pointerdown() { var button = d3_event.button; if (button === 0 && !d3_event.ctrlKey) { tooltip.classed('leftclick', true); @@ -212,7 +214,7 @@ function clickListener() { } - function mouseup() { + function pointerup() { var button = d3_event.button; var endTime = d3_event.timeStamp; var startTime = down[button] || endTime; @@ -262,8 +264,8 @@ function clickListener() { d3_select(window) .on('keydown.intro', keydown) .on('keyup.intro', keyup) - .on('mousedown.intro', mousedown) - .on('mouseup.intro', mouseup) + .on(_pointerPrefix + 'down.intro', pointerdown) + .on(_pointerPrefix + 'up.intro', pointerup) .on('contextmenu.intro', contextmenu); }; @@ -272,8 +274,8 @@ function clickListener() { d3_select(window) .on('keydown.intro', null) .on('keyup.intro', null) - .on('mousedown.intro', null) - .on('mouseup.intro', null) + .on(_pointerPrefix + 'down.intro', null) + .on(_pointerPrefix + 'up.intro', null) .on('contextmenu.intro', null); tooltip From e838d0632843b470d7280cd83d519a3e0f90f395 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Fri, 8 May 2020 12:52:46 -0700 Subject: [PATCH 13/67] Use pointer events for the background offset on supported devices (re: #5505) --- modules/ui/sections/background_offset.js | 77 +++++++++++++++--------- 1 file changed, 49 insertions(+), 28 deletions(-) diff --git a/modules/ui/sections/background_offset.js b/modules/ui/sections/background_offset.js index 753f86a25..f1fb9b8be 100644 --- a/modules/ui/sections/background_offset.js +++ b/modules/ui/sections/background_offset.js @@ -16,6 +16,8 @@ export function uiSectionBackgroundOffset(context) { .disclosureContent(renderDisclosureContent) .expandedByDefault(false); + var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse'; + var _directions = [ ['right', [0.5, 0]], ['top', [0, -0.5]], @@ -24,7 +26,7 @@ export function uiSectionBackgroundOffset(context) { ]; - function d3_eventCancel() { + function cancelEvent() { d3_event.stopPropagation(); d3_event.preventDefault(); } @@ -59,7 +61,7 @@ export function uiSectionBackgroundOffset(context) { } - function clickNudgeButton(d) { + function pointerdownNudgeButton(d) { var interval; var timeout = window.setTimeout(function() { interval = window.setInterval(nudge.bind(null, d), 100); @@ -69,13 +71,13 @@ export function uiSectionBackgroundOffset(context) { window.clearTimeout(timeout); window.clearInterval(interval); d3_select(window) - .on('mouseup.buttonoffset', null, true) - .on('mousedown.buttonoffset', null, true); + .on(_pointerPrefix + 'up.buttonoffset', null, true) + .on(_pointerPrefix + 'down.buttonoffset', null, true); } d3_select(window) - .on('mouseup.buttonoffset', doneNudge, true) - .on('mousedown.buttonoffset', doneNudge, true); + .on(_pointerPrefix + 'up.buttonoffset', doneNudge, true) + .on(_pointerPrefix + 'down.buttonoffset', doneNudge, true); nudge(d); } @@ -107,30 +109,44 @@ export function uiSectionBackgroundOffset(context) { var origin = [d3_event.clientX, d3_event.clientY]; + var pointerId = d3_event.pointerId || 'mouse'; + context.container() .append('div') .attr('class', 'nudge-surface'); d3_select(window) - .on('mousemove.offset', function() { - var latest = [d3_event.clientX, d3_event.clientY]; - var d = [ - -(origin[0] - latest[0]) / 4, - -(origin[1] - latest[1]) / 4 - ]; + .on(_pointerPrefix + 'move.drag-bg-offset', pointermove) + .on(_pointerPrefix + 'up.drag-bg-offset', pointerup); - origin = latest; - nudge(d); - }) - .on('mouseup.offset', function() { - if (d3_event.button !== 0) return; - context.container().selectAll('.nudge-surface') - .remove(); + if (_pointerPrefix === 'pointer') { + d3_select(window) + .on('pointercancel.drag-bg-offset', pointerup); + } - d3_select(window) - .on('mousemove.offset', null) - .on('mouseup.offset', null); - }); + function pointermove() { + if (pointerId !== (d3_event.pointerId || 'mouse')) return; + + var latest = [d3_event.clientX, d3_event.clientY]; + var d = [ + -(origin[0] - latest[0]) / 4, + -(origin[1] - latest[1]) / 4 + ]; + + origin = latest; + nudge(d); + } + + function pointerup() { + if (pointerId !== (d3_event.pointerId || 'mouse')) return; + if (d3_event.button !== 0) return; + + context.container().selectAll('.nudge-surface') + .remove(); + + d3_select(window) + .on('.drag-bg-offset', null); + } } @@ -150,7 +166,11 @@ export function uiSectionBackgroundOffset(context) { var nudgeEnter = containerEnter .append('div') .attr('class', 'nudge-outer-rect') - .on('mousedown', dragOffset); + .on('touchstart touchmove touchend', function() { + // prevent scrolling while dragging + d3_event.preventDefault(); + }) + .on(_pointerPrefix + 'down', dragOffset); nudgeEnter .append('div') @@ -164,18 +184,19 @@ export function uiSectionBackgroundOffset(context) { .data(_directions).enter() .append('button') .attr('class', function(d) { return d[0] + ' nudge'; }) - .on('contextmenu', d3_eventCancel) - .on('mousedown', function(d) { + .on('contextmenu', cancelEvent) + .on(_pointerPrefix + 'down', function(d) { if (d3_event.button !== 0) return; - clickNudgeButton(d[1]); + pointerdownNudgeButton(d[1]); }); containerEnter .append('button') .attr('title', t('background.reset')) .attr('class', 'nudge-reset disabled') - .on('contextmenu', d3_eventCancel) + .on('contextmenu', cancelEvent) .on('click', function() { + d3_event.preventDefault(); if (d3_event.button !== 0) return; resetOffset(); }) From edc32849c049f72827a8ea0a850c14ce58d358cc Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Fri, 8 May 2020 15:01:15 -0700 Subject: [PATCH 14/67] Use pointer events in raw tag editor when supported (re: #5505) --- modules/ui/sections/raw_tag_editor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ui/sections/raw_tag_editor.js b/modules/ui/sections/raw_tag_editor.js index ea3a3a6b8..3ef28deb8 100644 --- a/modules/ui/sections/raw_tag_editor.js +++ b/modules/ui/sections/raw_tag_editor.js @@ -279,7 +279,7 @@ export function uiSectionRawTagEditor(id, context) { }); items.selectAll('button.remove') - .on('mousedown', removeTag); // 'click' fires too late - #5878 + .on(('PointerEvent' in window ? 'pointer' : 'mouse') + 'down', removeTag); // 'click' fires too late - #5878 } From 509ee8fe653ea7ea5790e6439fdbac9cf0be1656 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Fri, 8 May 2020 15:03:03 -0700 Subject: [PATCH 15/67] Import function from module instead of file --- modules/util/zoom_pan.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/util/zoom_pan.js b/modules/util/zoom_pan.js index ec731c10e..a5777303d 100644 --- a/modules/util/zoom_pan.js +++ b/modules/util/zoom_pan.js @@ -5,8 +5,9 @@ import { dispatch as d3_dispatch } from 'd3-dispatch'; import { interpolateZoom } from 'd3-interpolate'; import { event as d3_event, customEvent as d3_customEvent } from 'd3-selection'; import { interrupt as d3_interrupt } from 'd3-transition'; +import { zoomIdentity as d3_zoomIdentity } from 'd3-zoom'; import ZoomEvent from '../../node_modules/d3-zoom/src/event.js'; -import { Transform, identity } from '../../node_modules/d3-zoom/src/transform.js'; +import { Transform } from '../../node_modules/d3-zoom/src/transform.js'; import { utilFastMouse, utilFunctor } from './util'; @@ -29,7 +30,7 @@ function defaultExtent() { } function defaultTransform() { - return this.__zoom || identity; + return this.__zoom || d3_zoomIdentity; } function defaultWheelDelta() { @@ -117,7 +118,7 @@ export function utilZoomPan() { var e = extent.apply(this, arguments), t = this.__zoom, p0 = p == null ? centroid(e) : typeof p === 'function' ? p.apply(this, arguments) : p; - return constrain(identity.translate(p0[0], p0[1]).scale(t.k).translate( + return constrain(d3_zoomIdentity.translate(p0[0], p0[1]).scale(t.k).translate( typeof x === 'function' ? -x.apply(this, arguments) : -x, typeof y === 'function' ? -y.apply(this, arguments) : -y ), e, translateExtent); From 5a8f573889fdd1f0fde1d5234a8f86ead36354b7 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Fri, 8 May 2020 15:30:38 -0700 Subject: [PATCH 16/67] Continue zoom/pan of map when a down pointer moves off the map (re: #5505) Fix possibly #6745 --- modules/renderer/map.js | 2 +- modules/util/zoom_pan.js | 58 ++++++++++++++++++++++------------------ 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/modules/renderer/map.js b/modules/renderer/map.js index f7d545a89..7ec469c14 100644 --- a/modules/renderer/map.js +++ b/modules/renderer/map.js @@ -555,7 +555,7 @@ export function rendererMap(context) { y = y2; k = k2; eventTransform = d3_zoomIdentity.translate(x2, y2).scale(k2); - _selection.node().__zoom = eventTransform; + _zoomerPanner._transform(eventTransform); } } diff --git a/modules/util/zoom_pan.js b/modules/util/zoom_pan.js index a5777303d..ef21b9494 100644 --- a/modules/util/zoom_pan.js +++ b/modules/util/zoom_pan.js @@ -3,7 +3,7 @@ import { dispatch as d3_dispatch } from 'd3-dispatch'; import { interpolateZoom } from 'd3-interpolate'; -import { event as d3_event, customEvent as d3_customEvent } from 'd3-selection'; +import { event as d3_event, customEvent as d3_customEvent, select as d3_select } from 'd3-selection'; import { interrupt as d3_interrupt } from 'd3-transition'; import { zoomIdentity as d3_zoomIdentity } from 'd3-zoom'; import ZoomEvent from '../../node_modules/d3-zoom/src/event.js'; @@ -29,10 +29,6 @@ function defaultExtent() { return [[0, 0], [e.clientWidth, e.clientHeight]]; } -function defaultTransform() { - return this.__zoom || d3_zoomIdentity; -} - function defaultWheelDelta() { return -d3_event.deltaY * (d3_event.deltaMode === 1 ? 0.05 : d3_event.deltaMode ? 1 : 0.002); } @@ -57,22 +53,24 @@ export function utilZoomPan() { translateExtent = [[-Infinity, -Infinity], [Infinity, Infinity]], interpolate = interpolateZoom, listeners = d3_dispatch('start', 'zoom', 'end'), - _wheelDelay = 150; + _wheelDelay = 150, + _transform = d3_zoomIdentity, + _activeGesture; function zoom(selection) { selection - .property('__zoom', defaultTransform) .on('pointerdown.zoom', pointerdown) - .on('pointermove.zoom', pointermove) - .on('pointerup.zoom pointercancel.zoom', pointerup) .on('wheel.zoom', wheeled) .style('touch-action', 'none') .style('-webkit-tap-highlight-color', 'rgba(0,0,0,0)'); + + d3_select(window) + .on('pointermove.zoompan', pointermove) + .on('pointerup.zoompan pointercancel.zoompan', pointerup); } zoom.transform = function(collection, transform, point) { var selection = collection.selection ? collection.selection() : collection; - selection.property('__zoom', defaultTransform); if (collection !== selection) { schedule(collection, transform, point); } else { @@ -87,7 +85,7 @@ export function utilZoomPan() { zoom.scaleBy = function(selection, k, p) { zoom.scaleTo(selection, function() { - var k0 = this.__zoom.k, + var k0 = _transform.k, k1 = typeof k === 'function' ? k.apply(this, arguments) : k; return k0 * k1; }, p); @@ -96,7 +94,7 @@ export function utilZoomPan() { zoom.scaleTo = function(selection, k, p) { zoom.transform(selection, function() { var e = extent.apply(this, arguments), - t0 = this.__zoom, + t0 = _transform, p0 = p == null ? centroid(e) : typeof p === 'function' ? p.apply(this, arguments) : p, p1 = t0.invert(p0), k1 = typeof k === 'function' ? k.apply(this, arguments) : k; @@ -106,7 +104,7 @@ export function utilZoomPan() { zoom.translateBy = function(selection, x, y) { zoom.transform(selection, function() { - return constrain(this.__zoom.translate( + return constrain(_transform.translate( typeof x === 'function' ? x.apply(this, arguments) : x, typeof y === 'function' ? y.apply(this, arguments) : y ), extent.apply(this, arguments), translateExtent); @@ -116,7 +114,7 @@ export function utilZoomPan() { zoom.translateTo = function(selection, x, y, p) { zoom.transform(selection, function() { var e = extent.apply(this, arguments), - t = this.__zoom, + t = _transform, p0 = p == null ? centroid(e) : typeof p === 'function' ? p.apply(this, arguments) : p; return constrain(d3_zoomIdentity.translate(p0[0], p0[1]).scale(t.k).translate( typeof x === 'function' ? -x.apply(this, arguments) : -x, @@ -150,7 +148,7 @@ export function utilZoomPan() { e = extent.apply(that, args), p = point == null ? centroid(e) : typeof point === 'function' ? point.apply(that, args) : point, w = Math.max(e[1][0] - e[0][0], e[1][1] - e[0][1]), - a = that.__zoom, + a = _transform, b = typeof transform === 'function' ? transform.apply(that, args) : transform, i = interpolate(a.invert(p).concat(w / a.k), b.invert(p).concat(w / b.k)); return function(t) { @@ -162,7 +160,7 @@ export function utilZoomPan() { } function gesture(that, args, clean) { - return (!clean && that.__zooming) || new Gesture(that, args); + return (!clean && _activeGesture) || new Gesture(that, args); } function Gesture(that, args) { @@ -175,7 +173,7 @@ export function utilZoomPan() { Gesture.prototype = { start: function() { if (++this.active === 1) { - this.that.__zooming = this; + _activeGesture = this; this.emit('start'); } return this; @@ -184,26 +182,26 @@ export function utilZoomPan() { if (this.mouse && key !== 'mouse') this.mouse[1] = transform.invert(this.mouse[0]); if (this.pointer0 && key !== 'touch') this.pointer0[1] = transform.invert(this.pointer0[0]); if (this.pointer1 && key !== 'touch') this.pointer1[1] = transform.invert(this.pointer1[0]); - this.that.__zoom = transform; + _transform = transform; this.emit('zoom'); return this; }, end: function() { if (--this.active === 0) { - delete this.that.__zooming; + _activeGesture = null; this.emit('end'); } return this; }, emit: function(type) { - d3_customEvent(new ZoomEvent(zoom, type, this.that.__zoom), listeners.apply, listeners, [type, this.that, this.args]); + d3_customEvent(new ZoomEvent(zoom, type, _transform), listeners.apply, listeners, [type, this.that, this.args]); } }; function wheeled() { if (!filter.apply(this, arguments)) return; var g = gesture(this, arguments), - t = this.__zoom, + t = _transform, k = Math.max(scaleExtent[0], Math.min(scaleExtent[1], t.k * Math.pow(2, wheelDelta.apply(this, arguments)))), p = utilFastMouse(this)(d3_event); @@ -251,7 +249,7 @@ export function utilZoomPan() { d3_event.stopImmediatePropagation(); _pointerLocGetter = utilFastMouse(this); var loc = _pointerLocGetter(d3_event); - var p = [loc, this.__zoom.invert(loc), d3_event.pointerId]; + var p = [loc, _transform.invert(loc), d3_event.pointerId]; if (!g.pointer0) { g.pointer0 = p; started = true; @@ -267,7 +265,9 @@ export function utilZoomPan() { } function pointermove() { - if (!this.__zooming || !_pointerLocGetter) return; + if (!_downPointerIDs.has(d3_event.pointerId)) return; + + if (!_activeGesture || !_pointerLocGetter) return; var g = gesture(this, arguments); @@ -292,7 +292,7 @@ export function utilZoomPan() { if (isPointer0) g.pointer0[0] = loc; else if (isPointer1) g.pointer1[0] = loc; - t = g.that.__zoom; + t = _transform; if (g.pointer1) { var p0 = g.pointer0[0], l0 = g.pointer0[1], p1 = g.pointer1[0], l1 = g.pointer1[1], @@ -310,9 +310,11 @@ export function utilZoomPan() { } function pointerup() { + if (!_downPointerIDs.has(d3_event.pointerId)) return; + _downPointerIDs.delete(d3_event.pointerId); - if (!this.__zooming) return; + if (!_activeGesture) return; var g = gesture(this, arguments); @@ -325,7 +327,7 @@ export function utilZoomPan() { g.pointer0 = g.pointer1; delete g.pointer1; } - if (g.pointer0) g.pointer0[1] = this.__zoom.invert(g.pointer0[0]); + if (g.pointer0) g.pointer0[1] = _transform.invert(g.pointer0[0]); else { g.end(); } @@ -359,6 +361,10 @@ export function utilZoomPan() { return arguments.length ? (interpolate = _, zoom) : interpolate; }; + zoom._transform = function(_) { + return arguments.length ? (_transform = _, zoom) : _transform; + }; + zoom.on = function() { var value = listeners.on.apply(listeners, arguments); return value === listeners ? zoom : value; From 77552c44308f47798f091576abbfdaa633517aa8 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Fri, 8 May 2020 15:44:02 -0700 Subject: [PATCH 17/67] Fix map interaction on devices not supporting pointer events --- modules/renderer/map.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/renderer/map.js b/modules/renderer/map.js index 7ec469c14..68ace0a1a 100644 --- a/modules/renderer/map.js +++ b/modules/renderer/map.js @@ -555,7 +555,13 @@ export function rendererMap(context) { y = y2; k = k2; eventTransform = d3_zoomIdentity.translate(x2, y2).scale(k2); - _zoomerPanner._transform(eventTransform); + if (_zoomerPanner._transform) { + // utilZoomPan interface + _zoomerPanner._transform(eventTransform); + } else { + // d3_zoom interface + _selection.node().__zoom = eventTransform; + } } } From 95dc16b7a6c1b9f37c0bdd41ca1d6a2088c0d0f4 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Fri, 8 May 2020 16:10:15 -0700 Subject: [PATCH 18/67] Update more mouse events to use pointer events if available (re: #5505) --- modules/modes/select_data.js | 2 +- modules/modes/select_error.js | 2 +- modules/modes/select_note.js | 2 +- modules/services/streetside.js | 10 ++++++---- modules/ui/intro/line.js | 4 ++-- modules/ui/panels/location.js | 4 ++-- modules/ui/sections/feature_type.js | 6 +----- 7 files changed, 14 insertions(+), 16 deletions(-) diff --git a/modules/modes/select_data.js b/modules/modes/select_data.js index 1313ad3bc..a631798c9 100644 --- a/modules/modes/select_data.js +++ b/modules/modes/select_data.js @@ -47,7 +47,7 @@ export function modeSelectData(context, selectedDatum) { // Return to browse mode if selected DOM elements have // disappeared because the user moved them out of view.. var source = d3_event && d3_event.type === 'zoom' && d3_event.sourceEvent; - if (drawn && source && (source.type === 'mousemove' || source.type === 'touchmove')) { + if (drawn && source && (source.type === 'pointermove' || source.type === 'mousemove' || source.type === 'touchmove')) { context.enter(modeBrowse(context)); } } else { diff --git a/modules/modes/select_error.js b/modules/modes/select_error.js index 2270f2c2c..4a7cb8e80 100644 --- a/modules/modes/select_error.js +++ b/modules/modes/select_error.js @@ -124,7 +124,7 @@ export function modeSelectError(context, selectedErrorID, selectedErrorService) // Return to browse mode if selected DOM elements have // disappeared because the user moved them out of view.. var source = d3_event && d3_event.type === 'zoom' && d3_event.sourceEvent; - if (drawn && source && (source.type === 'mousemove' || source.type === 'touchmove')) { + if (drawn && source && (source.type === 'pointermove' || source.type === 'mousemove' || source.type === 'touchmove')) { context.enter(modeBrowse(context)); } diff --git a/modules/modes/select_note.js b/modules/modes/select_note.js index 1aacb1205..c7e652ee4 100644 --- a/modules/modes/select_note.js +++ b/modules/modes/select_note.js @@ -67,7 +67,7 @@ export function modeSelectNote(context, selectedNoteID) { // Return to browse mode if selected DOM elements have // disappeared because the user moved them out of view.. var source = d3_event && d3_event.type === 'zoom' && d3_event.sourceEvent; - if (drawn && source && (source.type === 'mousemove' || source.type === 'touchmove')) { + if (drawn && source && (source.type === 'pointermove' || source.type === 'mousemove' || source.type === 'touchmove')) { context.enter(modeBrowse(context)); } diff --git a/modules/services/streetside.js b/modules/services/streetside.js index 8c1b54719..3c81d218b 100644 --- a/modules/services/streetside.js +++ b/modules/services/streetside.js @@ -470,14 +470,16 @@ export default { _pannellumViewer = window.pannellum.viewer('ideditor-viewer-streetside', options); + var pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse'; + _pannellumViewer - .on('mousedown', () => { + .on(pointerPrefix + 'down', () => { d3_select(window) - .on('mousemove.pannellum', () => { dispatch.call('viewerChanged'); }); + .on(pointerPrefix + 'move.pannellum', () => { dispatch.call('viewerChanged'); }); }) - .on('mouseup', () => { + .on(pointerPrefix + 'up', () => { d3_select(window) - .on('mousemove.pannellum', null); + .on(pointerPrefix + 'move.pannellum', null); // continue dispatching events for a few seconds, in case viewer has inertia. let t = d3_timer(elapsed => { diff --git a/modules/ui/intro/line.js b/modules/ui/intro/line.js index a296642f5..6e4440dcc 100644 --- a/modules/ui/intro/line.js +++ b/modules/ui/intro/line.js @@ -200,7 +200,7 @@ export function uiIntroLine(context, reveal) { function retryIntersect() { - d3_select(window).on('mousedown.intro', eventCancel, true); + d3_select(window).on('pointerdown.intro mousedown.intro', eventCancel, true); var box = pad(tulipRoadIntersection, 80, context); reveal(box, @@ -1054,7 +1054,7 @@ export function uiIntroLine(context, reveal) { chapter.exit = function() { timeouts.forEach(window.clearTimeout); - d3_select(window).on('mousedown.intro', null, true); + d3_select(window).on('pointerdown.intro mousedown.intro', null, true); context.on('enter.intro exit.intro', null); context.map().on('move.intro drawn.intro', null); context.history().on('change.intro', null); diff --git a/modules/ui/panels/location.js b/modules/ui/panels/location.js index 59d04f344..21a7ae14f 100644 --- a/modules/ui/panels/location.js +++ b/modules/ui/panels/location.js @@ -57,14 +57,14 @@ export function uiPanelLocation(context) { selection.call(redraw); context.surface() - .on('mousemove.info-location', function() { + .on(('PointerEvent' in window ? 'pointer' : 'mouse') + 'move.info-location', function() { selection.call(redraw); }); }; panel.off = function() { context.surface() - .on('mousemove.info-location', null); + .on('.info-location', null); }; panel.id = 'location'; diff --git a/modules/ui/sections/feature_type.js b/modules/ui/sections/feature_type.js index 3bd55a427..a43ff335d 100644 --- a/modules/ui/sections/feature_type.js +++ b/modules/ui/sections/feature_type.js @@ -83,11 +83,7 @@ export function uiSectionFeatureType(context) { .on('click', function() { dispatch.call('choose', this, _presets); }) - .on('mousedown', function() { - d3_event.preventDefault(); - d3_event.stopPropagation(); - }) - .on('mouseup', function() { + .on('pointerdown pointerup mousedown mouseup', function() { d3_event.preventDefault(); d3_event.stopPropagation(); }); From 4a3fc577424d6e0f6dfbe7e2ae5b732084115e2d Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Fri, 8 May 2020 19:23:01 -0700 Subject: [PATCH 19/67] Show the edit menu when long-pressing or long-clicking (close #7577) --- modules/behavior/select.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/modules/behavior/select.js b/modules/behavior/select.js index 485ad91f7..0841eea31 100644 --- a/modules/behavior/select.js +++ b/modules/behavior/select.js @@ -18,6 +18,7 @@ export function behaviorSelect(context) { var _lastMouse = null; var _showMenu = false; var _p1 = null; + var _longPressTimeout; // use pointer events on supported platforms; fallback to mouse events var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse'; @@ -28,6 +29,8 @@ export function behaviorSelect(context) { function keydown() { + if (_longPressTimeout) window.clearTimeout(_longPressTimeout); + var e = d3_event; if (e && e.shiftKey) { context.surface() @@ -42,6 +45,8 @@ export function behaviorSelect(context) { function keyup() { + if (_longPressTimeout) window.clearTimeout(_longPressTimeout); + var e = d3_event; if (!e || !e.shiftKey) { context.surface() @@ -60,7 +65,29 @@ export function behaviorSelect(context) { function pointerdown() { if (!_p1) { _p1 = point(); + + if (_longPressTimeout) window.clearTimeout(_longPressTimeout); + + var node = this; + + _longPressTimeout = window.setTimeout(function didLongPress() { + // simulate context menu event + if (window.CustomEvent) { + node.dispatchEvent(new CustomEvent('contextmenu')); + } else if (document.createEvent) { + var e = document.createEvent('HTMLEvents'); + e.initEvent('contextmenu', true, false); + node.dispatchEvent(e); + } else { // IE + node.fireEvent('oncontextmenu'); + } + }, 500); } + + if (d3_event) { + _lastMouse = d3_event; + } + d3_select(window) .on(_pointerPrefix + 'up.select', pointerup, true); @@ -102,6 +129,8 @@ export function behaviorSelect(context) { function click() { + if (_longPressTimeout) window.clearTimeout(_longPressTimeout); + d3_select(window) .on(_pointerPrefix + 'up.select', null, true); From e6fc7e221ab1b553749a4851f051fe91a6bfeed3 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Sun, 10 May 2020 10:59:56 -0700 Subject: [PATCH 20/67] Fix menu position when long-pressing features on touchscreens (re: #7577) Improve comment --- modules/renderer/map.js | 10 ++++++---- modules/ui/edit_menu.js | 3 ++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/modules/renderer/map.js b/modules/renderer/map.js index 68ace0a1a..084179aa5 100644 --- a/modules/renderer/map.js +++ b/modules/renderer/map.js @@ -60,7 +60,7 @@ export function rendererMap(context) { var _isTransformed = false; var _minzoom = 0; var _getMouseCoords; - var _mouseEvent; + var _lastPointerEvent; var _lastWithinEditableZoom; // whether a pointerdown event started the zoom @@ -180,17 +180,19 @@ export function rendererMap(context) { }) .on('gesturechange.surface', gestureChange) .on(_pointerPrefix + 'down.zoom', function() { + _lastPointerEvent = d3_event; if (d3_event.button === 2) { d3_event.stopPropagation(); } }, true) .on(_pointerPrefix + 'up.zoom', function() { + _lastPointerEvent = d3_event; if (resetTransform()) { immediateRedraw(); } }) .on(_pointerPrefix + 'move.map', function() { - _mouseEvent = d3_event; + _lastPointerEvent = d3_event; }) .on(_pointerPrefix + 'over.vertices', function() { if (map.editableDataEnabled() && !_isTransformed) { @@ -599,7 +601,7 @@ export function rendererMap(context) { } if (source) { - _mouseEvent = event; + _lastPointerEvent = event; } _isTransformed = true; _transformLast = eventTransform; @@ -683,7 +685,7 @@ export function rendererMap(context) { map.mouse = function() { - var event = _mouseEvent || d3_event; + var event = _lastPointerEvent || d3_event; if (event) { var s; while ((s = event.sourceEvent)) { event = s; } diff --git a/modules/ui/edit_menu.js b/modules/ui/edit_menu.js index 3f6cd7a86..f4cfdb739 100644 --- a/modules/ui/edit_menu.js +++ b/modules/ui/edit_menu.js @@ -91,7 +91,8 @@ export function uiEditMenu(context) { } function pointerdown() { - d3_event.stopPropagation(); // https://github.com/openstreetmap/iD/issues/1869 + // don't let button presses also act as map input - #1869 + d3_event.stopPropagation(); } }; From 20fadf0b1abbec6086b96f5842c003aaa69d97d0 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Sun, 10 May 2020 12:02:36 -0700 Subject: [PATCH 21/67] Update the first two chapters of the walkthrough for non-mouse interaction --- data/core.yaml | 13 +-- dist/locales/en.json | 13 +-- modules/ui/intro/welcome.js | 224 +----------------------------------- 3 files changed, 11 insertions(+), 239 deletions(-) diff --git a/data/core.yaml b/data/core.yaml index 20ac2c286..4b64931b1 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -1836,18 +1836,15 @@ en: welcome: "Welcome! This walkthrough will teach you the basics of editing on OpenStreetMap." practice: "All of the data in this walkthrough is just for practicing, and any edits that you make in the walkthrough will not be saved." words: "This walkthrough will introduce some new words and concepts. When we introduce a new word, we'll use *italics*." - mouse: "You can use any input device to edit the map, but this walkthrough assumes you have a mouse with left and right buttons. **If you want to attach a mouse, do so now, then click OK.**" - leftclick: "When this tutorial asks you to click or double-click, we mean with the left button. On a trackpad it might be a single-click or single-finger tap. **Left-click {num} times.**" - rightclick: "Sometimes we'll also ask you to right-click. This might be the same as control-click, or two-finger tap on a trackpad. Your keyboard might even have a 'menu' key that works like right-click. **Right-click {num} times.**" - chapters: "So far, so good! You can use the buttons below to skip chapters at any time or to restart a chapter if you get stuck. Let's begin! **Click '{next}' to continue.**" + chapters: "You can use the buttons below to skip chapters at any time or to restart a chapter if you get stuck. Let's begin! **Press '{next}' to continue.**" navigation: title: "Navigation" - drag: "The main map area shows OpenStreetMap data on top of a background.{br}You can drag the map by pressing and holding the left mouse button while moving the mouse around. You can also use the arrow keys on your keyboard. **Drag the map!**" - zoom: "You can zoom in or out by scrolling with the mouse wheel or trackpad, or by clicking the {plus} / {minus} buttons. **Zoom the map!**" + drag: "The main map area shows OpenStreetMap data on top of a background.{br}You can click-and-drag or tap-and-drag the map to move it around. You can also use the arrow keys on a keyboard. **Drag the map!**" + zoom: "You can zoom in or out by scrolling with a mouse wheel, pinching on a touchscreen, or pressing the {plus} / {minus} buttons. **Zoom the map!**" features: "We use the word *features* to describe the things that appear on the map. Anything in the real world can be mapped as a feature on OpenStreetMap." points_lines_areas: "Map features are represented using *points, lines, or areas.*" nodes_ways: "In OpenStreetMap, points are sometimes called *nodes*, and lines and areas are sometimes called *ways*." - click_townhall: "All features on the map can be selected by clicking on them. **Click on the point to select it.**" + click_townhall: "All features on the map can be selected by clicking or tapping on them. **Select the point.**" selected_townhall: "Great! The point is now selected. Selected features are drawn with a pulsing glow." editor_townhall: "When a feature is selected, the *feature editor* is displayed alongside the map." preset_townhall: "The top part of the feature editor shows the feature's type. This point is a {preset}." @@ -1857,7 +1854,7 @@ en: choose_street: "**Choose {name} from the list to select it.**" selected_street: "Great! {name} is now selected." editor_street: "The fields shown for a street are different than the fields that were shown for the town hall.{br}For this selected street, the feature editor shows fields like '{field1}' and '{field2}'. **Close the feature editor by hitting escape or pressing the {button} button.**" - play: "Try moving the map and clicking on some other features to see what kinds of things can be added to OpenStreetMap. **When you are ready to continue to the next chapter, click '{next}'.**" + play: "Try exploring the map and selecting some other features to see what kinds of things can be added to OpenStreetMap. **When you are ready to continue to the next chapter, press '{next}'.**" points: title: "Points" add_point: "*Points* can be used to represent features such as shops, restaurants, and monuments.{br}They mark a specific location, and describe what's there. **Click the {button} Point button to add a new point.**" diff --git a/dist/locales/en.json b/dist/locales/en.json index 4425048e1..0c5eeecf1 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -2284,19 +2284,16 @@ "welcome": "Welcome! This walkthrough will teach you the basics of editing on OpenStreetMap.", "practice": "All of the data in this walkthrough is just for practicing, and any edits that you make in the walkthrough will not be saved.", "words": "This walkthrough will introduce some new words and concepts. When we introduce a new word, we'll use *italics*.", - "mouse": "You can use any input device to edit the map, but this walkthrough assumes you have a mouse with left and right buttons. **If you want to attach a mouse, do so now, then click OK.**", - "leftclick": "When this tutorial asks you to click or double-click, we mean with the left button. On a trackpad it might be a single-click or single-finger tap. **Left-click {num} times.**", - "rightclick": "Sometimes we'll also ask you to right-click. This might be the same as control-click, or two-finger tap on a trackpad. Your keyboard might even have a 'menu' key that works like right-click. **Right-click {num} times.**", - "chapters": "So far, so good! You can use the buttons below to skip chapters at any time or to restart a chapter if you get stuck. Let's begin! **Click '{next}' to continue.**" + "chapters": "You can use the buttons below to skip chapters at any time or to restart a chapter if you get stuck. Let's begin! **Press '{next}' to continue.**" }, "navigation": { "title": "Navigation", - "drag": "The main map area shows OpenStreetMap data on top of a background.{br}You can drag the map by pressing and holding the left mouse button while moving the mouse around. You can also use the arrow keys on your keyboard. **Drag the map!**", - "zoom": "You can zoom in or out by scrolling with the mouse wheel or trackpad, or by clicking the {plus} / {minus} buttons. **Zoom the map!**", + "drag": "The main map area shows OpenStreetMap data on top of a background.{br}You can click-and-drag or tap-and-drag the map to move it around. You can also use the arrow keys on a keyboard. **Drag the map!**", + "zoom": "You can zoom in or out by scrolling with a mouse wheel, pinching on a touchscreen, or pressing the {plus} / {minus} buttons. **Zoom the map!**", "features": "We use the word *features* to describe the things that appear on the map. Anything in the real world can be mapped as a feature on OpenStreetMap.", "points_lines_areas": "Map features are represented using *points, lines, or areas.*", "nodes_ways": "In OpenStreetMap, points are sometimes called *nodes*, and lines and areas are sometimes called *ways*.", - "click_townhall": "All features on the map can be selected by clicking on them. **Click on the point to select it.**", + "click_townhall": "All features on the map can be selected by clicking or tapping on them. **Select the point.**", "selected_townhall": "Great! The point is now selected. Selected features are drawn with a pulsing glow.", "editor_townhall": "When a feature is selected, the *feature editor* is displayed alongside the map.", "preset_townhall": "The top part of the feature editor shows the feature's type. This point is a {preset}.", @@ -2306,7 +2303,7 @@ "choose_street": "**Choose {name} from the list to select it.**", "selected_street": "Great! {name} is now selected.", "editor_street": "The fields shown for a street are different than the fields that were shown for the town hall.{br}For this selected street, the feature editor shows fields like '{field1}' and '{field2}'. **Close the feature editor by hitting escape or pressing the {button} button.**", - "play": "Try moving the map and clicking on some other features to see what kinds of things can be added to OpenStreetMap. **When you are ready to continue to the next chapter, click '{next}'.**" + "play": "Try exploring the map and selecting some other features to see what kinds of things can be added to OpenStreetMap. **When you are ready to continue to the next chapter, press '{next}'.**" }, "points": { "title": "Points", diff --git a/modules/ui/intro/welcome.js b/modules/ui/intro/welcome.js index 892d517bb..841606d57 100644 --- a/modules/ui/intro/welcome.js +++ b/modules/ui/intro/welcome.js @@ -1,8 +1,4 @@ import { dispatch as d3_dispatch } from 'd3-dispatch'; -import { - event as d3_event, - select as d3_select -} from 'd3-selection'; import { t } from '../../core/localizer'; import { utilRebind } from '../../util/rebind'; @@ -10,7 +6,6 @@ import { utilRebind } from '../../util/rebind'; export function uiIntroWelcome(context, reveal) { var dispatch = d3_dispatch('done'); - var listener = clickListener(); var chapter = { title: 'intro.welcome.title' @@ -35,101 +30,11 @@ export function uiIntroWelcome(context, reveal) { function words() { reveal('.intro-nav-wrap .chapter-welcome', t('intro.welcome.words'), - { buttonText: t('intro.ok'), buttonCallback: mouse } + { buttonText: t('intro.ok'), buttonCallback: chapters } ); } - function mouse() { - reveal('.intro-nav-wrap .chapter-welcome', - t('intro.welcome.mouse'), - { buttonText: t('intro.ok'), buttonCallback: leftClick } - ); - } - - - function leftClick() { - var counter = 0; - var times = 5; - - var tooltip = reveal('.intro-nav-wrap .chapter-welcome', - t('intro.welcome.leftclick', { num: times }), - { tooltipClass: 'intro-mouse' } - ); - - tooltip.selectAll('.popover-inner') - .insert('svg', 'span') - .attr('class', 'tooltip-illustration') - .append('use') - .attr('xlink:href', '#iD-walkthrough-mouse'); - - tooltip - .append('div') - .attr('class', 'counter'); - - tooltip.call(listener); - - listener.on('click', function(which) { - if (which === 'left') { - context.container().select('.curtain-tooltip.intro-mouse .counter') - .text(String(++counter)); - - if (counter === times) { - window.setTimeout(function() { continueTo(rightClick); }, 1000); - } - } - }); - - function continueTo(nextStep) { - listener.on('click', null); - tooltip.call(listener.off); - tooltip.select('.counter').remove(); - nextStep(); - } - } - - - function rightClick() { - var counter = 0; - var times = 5; - - var tooltip = reveal('.intro-nav-wrap .chapter-welcome', - t('intro.welcome.rightclick', { num: times }), - { tooltipClass: 'intro-mouse' } - ); - - tooltip.selectAll('.popover-inner') - .insert('svg', 'span') - .attr('class', 'tooltip-illustration') - .append('use') - .attr('xlink:href', '#iD-walkthrough-mouse'); - - tooltip - .append('div') - .attr('class', 'counter'); - - tooltip.call(listener); - - listener.on('click', function(which) { - if (which === 'right') { - context.container().select('.curtain-tooltip.intro-mouse .counter') - .text(String(++counter)); - - if (counter === times) { - window.setTimeout(function() { continueTo(chapters); }, 1000); - } - } - }); - - function continueTo(nextStep) { - listener.on('click', null); - tooltip.call(listener.off); - tooltip.select('.counter').remove(); - nextStep(); - } - } - - function chapters() { dispatch.call('done'); reveal('.intro-nav-wrap .chapter-navigation', @@ -144,7 +49,6 @@ export function uiIntroWelcome(context, reveal) { chapter.exit = function() { - listener.off(); context.container().select('.curtain-tooltip.intro-mouse') .selectAll('.counter') .remove(); @@ -159,129 +63,3 @@ export function uiIntroWelcome(context, reveal) { return utilRebind(chapter, dispatch, 'on'); } - - - -function clickListener() { - var dispatch = d3_dispatch('click'); - var minTime = 120; - var tooltip = d3_select(null); - var down = {}; - - var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse'; - - // `down` keeps track of which buttons/keys are down. - // Setting a property in `down` happens immediately. - // Unsetting a property in `down` is delayed because - // on Windows a contextmenu event happens after keyup/mouseup - - function keydown() { - if (d3_event.keyCode === 93) { // context menu - d3_event.preventDefault(); - d3_event.stopPropagation(); - down.menu = d3_event.timeStamp; - tooltip.classed('rightclick', true); - } - } - - - function keyup() { - if (d3_event.keyCode === 93) { // context menu - d3_event.preventDefault(); - d3_event.stopPropagation(); - var endTime = d3_event.timeStamp; - var startTime = down.menu || endTime; - var delay = (endTime - startTime < minTime) ? minTime : 0; - - window.setTimeout(function() { - tooltip.classed('rightclick', false); - down.menu = undefined; // delayed, for Windows - }, delay); - - dispatch.call('click', this, 'right'); - } - } - - - function pointerdown() { - var button = d3_event.button; - if (button === 0 && !d3_event.ctrlKey) { - tooltip.classed('leftclick', true); - } else if (button === 2) { - tooltip.classed('rightclick', true); - } - down[button] = d3_event.timeStamp; - } - - - function pointerup() { - var button = d3_event.button; - var endTime = d3_event.timeStamp; - var startTime = down[button] || endTime; - var delay = (endTime - startTime < minTime) ? minTime : 0; - - if (button === 0 && !d3_event.ctrlKey) { - window.setTimeout(function() { - tooltip.classed('leftclick', false); - down[button] = undefined; // delayed, for Windows - }, delay); - - dispatch.call('click', this, 'left'); - - } else if (button === 2) { - window.setTimeout(function() { - tooltip.classed('rightclick', false); - down[button] = undefined; // delayed, for Windows - }, delay); - - dispatch.call('click', this, 'right'); - - } else { - window.setTimeout(function() { - down[button] = undefined; // delayed, for Windows - }, delay); - } - } - - - function contextmenu() { - d3_event.preventDefault(); - d3_event.stopPropagation(); - if (!down[2] && !down.menu) { - tooltip.classed('rightclick', true); - window.setTimeout(function() { - tooltip.classed('rightclick', false); - }, minTime); - dispatch.call('click', this, 'right'); - } - } - - - var behavior = function(selection) { - tooltip = selection; - down = {}; - - d3_select(window) - .on('keydown.intro', keydown) - .on('keyup.intro', keyup) - .on(_pointerPrefix + 'down.intro', pointerdown) - .on(_pointerPrefix + 'up.intro', pointerup) - .on('contextmenu.intro', contextmenu); - }; - - - behavior.off = function() { - d3_select(window) - .on('keydown.intro', null) - .on('keyup.intro', null) - .on(_pointerPrefix + 'down.intro', null) - .on(_pointerPrefix + 'up.intro', null) - .on('contextmenu.intro', null); - - tooltip - .classed('leftclick', false) - .classed('rightclick', false); - }; - - return utilRebind(behavior, dispatch, 'on'); -} From 4fede5214aa2443d0afdf10bfe0937b019172f33 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Tue, 12 May 2020 16:10:07 -0400 Subject: [PATCH 22/67] Ensure that only one pointer is handled at a time in behaivorSelect --- modules/behavior/select.js | 56 ++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/modules/behavior/select.js b/modules/behavior/select.js index 0841eea31..694464a28 100644 --- a/modules/behavior/select.js +++ b/modules/behavior/select.js @@ -18,6 +18,7 @@ export function behaviorSelect(context) { var _lastMouse = null; var _showMenu = false; var _p1 = null; + var _downPointerId = null; var _longPressTimeout; // use pointer events on supported platforms; fallback to mouse events @@ -63,30 +64,29 @@ export function behaviorSelect(context) { function pointerdown() { - if (!_p1) { - _p1 = point(); + if (_p1) return; - if (_longPressTimeout) window.clearTimeout(_longPressTimeout); + _p1 = point(); + _downPointerId = d3_event.pointerId || 'mouse'; - var node = this; + if (_longPressTimeout) window.clearTimeout(_longPressTimeout); - _longPressTimeout = window.setTimeout(function didLongPress() { - // simulate context menu event - if (window.CustomEvent) { - node.dispatchEvent(new CustomEvent('contextmenu')); - } else if (document.createEvent) { - var e = document.createEvent('HTMLEvents'); - e.initEvent('contextmenu', true, false); - node.dispatchEvent(e); - } else { // IE - node.fireEvent('oncontextmenu'); - } - }, 500); - } + var node = this; - if (d3_event) { - _lastMouse = d3_event; - } + _longPressTimeout = window.setTimeout(function didLongPress() { + // simulate context menu event + if (window.CustomEvent) { + node.dispatchEvent(new CustomEvent('contextmenu')); + } else if (document.createEvent) { + var e = document.createEvent('HTMLEvents'); + e.initEvent('contextmenu', true, false); + node.dispatchEvent(e); + } else { // IE + node.fireEvent('oncontextmenu'); + } + }, 500); + + _lastMouse = d3_event; d3_select(window) .on(_pointerPrefix + 'up.select', pointerup, true); @@ -96,13 +96,19 @@ export function behaviorSelect(context) { function pointermove() { - if (d3_event) { - _lastMouse = d3_event; - } + if (_downPointerId && _downPointerId !== (d3_event.pointerId || 'mouse')) return; + + _lastMouse = d3_event; } function pointerup() { + if (_downPointerId !== (d3_event.pointerId || 'mouse')) return; + _downPointerId = null; + + d3_select(window) + .on(_pointerPrefix + 'up.select', null, true); + click(); } @@ -131,9 +137,6 @@ export function behaviorSelect(context) { function click() { if (_longPressTimeout) window.clearTimeout(_longPressTimeout); - d3_select(window) - .on(_pointerPrefix + 'up.select', null, true); - if (!_p1) return; var p2 = point(); var dist = geoVecLength(_p1, p2); @@ -230,6 +233,7 @@ export function behaviorSelect(context) { _lastMouse = null; _showMenu = false; _p1 = null; + _downPointerId = null; d3_select(window) .on('keydown.select', keydown) From 23714ba822d37ef71ec59a4cc0b1acd18c984064 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Tue, 12 May 2020 18:42:36 -0400 Subject: [PATCH 23/67] Position the edit menu above the anchor point if triggered via touch or stylus (re: #7577) Add additional code safety to behaivorSelect --- modules/behavior/select.js | 57 +++++++++++++++++++------------------- modules/modes/select.js | 9 +++--- modules/ui/edit_menu.js | 30 ++++++++++++++++++-- 3 files changed, 60 insertions(+), 36 deletions(-) diff --git a/modules/behavior/select.js b/modules/behavior/select.js index 694464a28..6c66802ae 100644 --- a/modules/behavior/select.js +++ b/modules/behavior/select.js @@ -19,13 +19,14 @@ export function behaviorSelect(context) { var _showMenu = false; var _p1 = null; var _downPointerId = null; - var _longPressTimeout; + var _longPressTimeout = null; + var _lastInteractionType = null; // use pointer events on supported platforms; fallback to mouse events var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse'; - function point() { - return utilFastMouse(context.container().node())(d3_event); + function point(event) { + return utilFastMouse(context.container().node())(event || d3_event); } @@ -40,7 +41,6 @@ export function behaviorSelect(context) { if (e && e.keyCode === 93) { // context menu e.preventDefault(); - e.stopPropagation(); } } @@ -57,7 +57,7 @@ export function behaviorSelect(context) { if (e && e.keyCode === 93) { // context menu e.preventDefault(); - e.stopPropagation(); + _lastInteractionType = 'menukey'; contextmenu(); } } @@ -70,21 +70,7 @@ export function behaviorSelect(context) { _downPointerId = d3_event.pointerId || 'mouse'; if (_longPressTimeout) window.clearTimeout(_longPressTimeout); - - var node = this; - - _longPressTimeout = window.setTimeout(function didLongPress() { - // simulate context menu event - if (window.CustomEvent) { - node.dispatchEvent(new CustomEvent('contextmenu')); - } else if (document.createEvent) { - var e = document.createEvent('HTMLEvents'); - e.initEvent('contextmenu', true, false); - node.dispatchEvent(e); - } else { // IE - node.fireEvent('oncontextmenu'); - } - }, 500); + _longPressTimeout = window.setTimeout(didLongPress, 500, d3_event.pointerType || 'mouse'); _lastMouse = d3_event; @@ -95,6 +81,14 @@ export function behaviorSelect(context) { } + function didLongPress(pointerType) { + _longPressTimeout = null; + _lastInteractionType = 'longdown-' + pointerType; + _showMenu = true; + click(); + } + + function pointermove() { if (_downPointerId && _downPointerId !== (d3_event.pointerId || 'mouse')) return; @@ -116,7 +110,6 @@ export function behaviorSelect(context) { function contextmenu() { var e = d3_event; e.preventDefault(); - e.stopPropagation(); if (!+e.clientX && !+e.clientY) { if (_lastMouse) { @@ -124,6 +117,9 @@ export function behaviorSelect(context) { } else { return; } + } else { + _lastMouse = d3_event; + _lastInteractionType = 'rightclick'; } if (!_p1) { @@ -138,13 +134,13 @@ export function behaviorSelect(context) { if (_longPressTimeout) window.clearTimeout(_longPressTimeout); if (!_p1) return; - var p2 = point(); + var p2 = point(_lastMouse); var dist = geoVecLength(_p1, p2); _p1 = null; if (dist > _tolerancePx) return; - var datum = d3_event.target.__data__ || (_lastMouse && _lastMouse.target.__data__); - var isMultiselect = d3_event.shiftKey || context.surface().select('.lasso').node(); + var datum = (d3_event && d3_event.target.__data__) || (_lastMouse && _lastMouse.target.__data__); + var isMultiselect = (d3_event && d3_event.shiftKey) || context.surface().select('.lasso').node(); processClick(datum, isMultiselect); } @@ -170,14 +166,14 @@ export function behaviorSelect(context) { if (!isMultiselect) { if (selectedIDs.length > 1 && (_showMenu && !_alwaysShowMenu)) { // multiple things already selected, just show the menu... - mode.reselect().showMenu(); + mode.reselect().showMenu(_lastInteractionType); } else { // always enter modeSelect even if the entity is already // selected since listeners may expect `context.enter` events, // e.g. in the walkthrough newMode = modeSelect(context, [datum.id]); context.enter(newMode); - if (_showMenu) newMode.showMenu(); + if (_showMenu) newMode.showMenu(_lastInteractionType); } } else { @@ -185,7 +181,7 @@ export function behaviorSelect(context) { // clicked entity is already in the selectedIDs list.. if (_showMenu && !_alwaysShowMenu) { // don't deselect clicked entity, just show the menu. - mode.reselect().showMenu(); + mode.reselect().showMenu(_lastInteractionType); } else { // deselect clicked entity, then reenter select mode or return to browse mode.. selectedIDs = selectedIDs.filter(function(id) { return id !== datum.id; }); @@ -197,7 +193,7 @@ export function behaviorSelect(context) { selectedIDs = selectedIDs.concat([datum.id]); newMode = modeSelect(context, selectedIDs); context.enter(newMode); - if (_showMenu) newMode.showMenu(); + if (_showMenu) newMode.showMenu(_lastInteractionType); } } @@ -234,6 +230,8 @@ export function behaviorSelect(context) { _showMenu = false; _p1 = null; _downPointerId = null; + _longPressTimeout = null; + _lastInteractionType = null; d3_select(window) .on('keydown.select', keydown) @@ -245,7 +243,6 @@ export function behaviorSelect(context) { var e = d3_event; if (+e.clientX === 0 && +e.clientY === 0) { d3_event.preventDefault(); - d3_event.stopPropagation(); } }); @@ -262,6 +259,8 @@ export function behaviorSelect(context) { behavior.off = function(selection) { + if (_longPressTimeout) window.clearTimeout(_longPressTimeout); + d3_select(window) .on('keydown.select', null) .on('keyup.select', null) diff --git a/modules/modes/select.js b/modules/modes/select.js index 6a8e03c60..358f7db08 100644 --- a/modules/modes/select.js +++ b/modules/modes/select.js @@ -149,7 +149,7 @@ export function modeSelect(context, selectedIDs) { .select('.edit-menu').remove(); } - mode.showMenu = function() { + mode.showMenu = function(triggerType) { // remove any displayed menu closeMenu(); @@ -178,6 +178,7 @@ export function modeSelect(context, selectedIDs) { _editMenu .anchorLoc(point) + .triggerType(triggerType) .operations(operations); // render the menu @@ -185,9 +186,9 @@ export function modeSelect(context, selectedIDs) { }; - function toggleMenu() { + function spacebar() { // toggle the menu if (context.map().supersurface.select('.edit-menu').empty()) { - mode.showMenu(); + mode.showMenu('spacebar'); } else { closeMenu(); } @@ -271,7 +272,7 @@ export function modeSelect(context, selectedIDs) { .on(['}', uiCmd('โŒ˜]'), 'end'], lastVertex) .on(['\\', 'pause'], nextParent) .on('โŽ‹', esc, true) - .on('space', toggleMenu); + .on('space', spacebar); d3_select(document) .call(keybinding); diff --git a/modules/ui/edit_menu.js b/modules/ui/edit_menu.js index f4cfdb739..c9861a353 100644 --- a/modules/ui/edit_menu.js +++ b/modules/ui/edit_menu.js @@ -11,7 +11,10 @@ export function uiEditMenu(context) { var _operations = []; // the position the menu should be displayed relative to var _anchorLoc = [0, 0]; + // a string indicating how the menu was opened + var _triggerType = ''; + var _vpTopMargin = 85; // viewport top margin var _vpBottomMargin = 45; // viewport bottom margin var _vpSideMargin = 35; // viewport side margin @@ -33,15 +36,30 @@ export function uiEditMenu(context) { var offset = [0, 0]; var viewport = context.surfaceRect(); + // position the menu above the anchor for stylus and finger input + // since the mapper's hand likely obscures the screen below the anchor + var menuTop = _triggerType.includes('touch') || _triggerType.includes('pen'); + var menuLeft = displayOnLeft(viewport); offset[0] = menuLeft ? -1 * (_menuSideMargin + _menuWidth) : _menuSideMargin; var menuHeight = _verticalPadding * 2 + _operations.length * _buttonHeight; - if (_anchorLoc[1] + menuHeight > (viewport.height - _vpBottomMargin)) { - // menu is near bottom viewport edge, shift upwards - offset[1] = -1 * (_anchorLoc[1] + menuHeight - viewport.height + _vpBottomMargin); + if (menuTop) { + if (_anchorLoc[1] - menuHeight < _vpTopMargin) { + // menu is near top viewport edge, shift downward + offset[1] = -_anchorLoc[1] + _vpTopMargin; + } else { + offset[1] = -menuHeight; + } + } else { + if (_anchorLoc[1] + menuHeight > (viewport.height - _vpBottomMargin)) { + // menu is near bottom viewport edge, shift upwards + offset[1] = -_anchorLoc[1] - menuHeight + viewport.height - _vpBottomMargin; + } else { + offset[1] = 0; + } } var origin = geoVecAdd(_anchorLoc, offset); @@ -153,6 +171,12 @@ export function uiEditMenu(context) { return editMenu; }; + editMenu.triggerType = function(val) { + if (!arguments.length) return _triggerType; + _triggerType = val; + return editMenu; + }; + editMenu.operations = function(val) { if (!arguments.length) return _operations; _operations = val; From 67bef3a692d1a8cbbaede9db3da2c131e88ec41d Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Wed, 13 May 2020 09:37:12 -0400 Subject: [PATCH 24/67] Add `sameSite=strict` attribute to lock cookies (close #7596) --- modules/util/session_mutex.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/util/session_mutex.js b/modules/util/session_mutex.js index 359e594f2..99fdc94d9 100644 --- a/modules/util/session_mutex.js +++ b/modules/util/session_mutex.js @@ -10,7 +10,7 @@ export function utilSessionMutex(name) { function renew() { var expires = new Date(); expires.setSeconds(expires.getSeconds() + 5); - document.cookie = name + '=1; expires=' + expires.toUTCString(); + document.cookie = name + '=1; expires=' + expires.toUTCString() + '; sameSite=strict'; } mutex.lock = function () { @@ -24,7 +24,7 @@ export function utilSessionMutex(name) { mutex.unlock = function () { if (!intervalID) return; - document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT'; + document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT; sameSite=strict'; clearInterval(intervalID); intervalID = null; }; From 0c2ac32d78a561f035a2295130e438aa8c4963f0 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Wed, 13 May 2020 09:47:19 -0400 Subject: [PATCH 25/67] Deprecate several alternatives to `landuse=flowerbed` --- data/deprecated.json | 24 ++++++++++++++++++++---- data/taginfo.json | 6 +++++- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/data/deprecated.json b/data/deprecated.json index f55ee6f40..43a6d8cef 100644 --- a/data/deprecated.json +++ b/data/deprecated.json @@ -622,6 +622,14 @@ "old": {"kerb": "flat"}, "replace": {"kerb": "flush"} }, + { + "old": {"landcover": "flower_bed"}, + "replace": {"landuse": "flowerbed"} + }, + { + "old": {"landcover": "flowerbed"}, + "replace": {"landuse": "flowerbed"} + }, { "old": {"landuse": "basin"}, "replace": {"natural": "water", "water": "basin"} @@ -718,6 +726,18 @@ "old": {"man_made": "cut_line"}, "replace": {"man_made": "cutline"} }, + { + "old": {"man_made": "flower_bed"}, + "replace": {"landuse": "flowerbed"} + }, + { + "old": {"man_made": "flowerbed"}, + "replace": {"landuse": "flowerbed"} + }, + { + "old": {"man_made": "fuel_storage_tank"}, + "replace": {"man_made": "storage_tank", "content": "fuel"} + }, { "old": {"man_made": "gas_well"}, "replace": {"man_made": "petroleum_well", "substance": "gas"} @@ -734,10 +754,6 @@ "old": {"man_made": "MDF"}, "replace": {"telecom": "exchange"} }, - { - "old": {"man_made": "fuel_storage_tank"}, - "replace": {"man_made": "storage_tank", "content": "fuel"} - }, { "old": {"man_made": "oil_tank"}, "replace": {"man_made": "storage_tank", "content": "oil"} diff --git a/data/taginfo.json b/data/taginfo.json index 535230a16..e6522c7cb 100644 --- a/data/taginfo.json +++ b/data/taginfo.json @@ -2118,6 +2118,8 @@ {"key": "internet_access:type", "description": "๐Ÿ„ณ โžœ internet_access=*"}, {"key": "kerb", "value": "dropped", "description": "๐Ÿ„ณ โžœ kerb=lowered"}, {"key": "kerb", "value": "flat", "description": "๐Ÿ„ณ โžœ kerb=flush"}, + {"key": "landcover", "value": "flower_bed", "description": "๐Ÿ„ณ โžœ landuse=flowerbed"}, + {"key": "landcover", "value": "flowerbed", "description": "๐Ÿ„ณ โžœ landuse=flowerbed"}, {"key": "landuse", "value": "conservation", "description": "๐Ÿ„ณ โžœ boundary=protected_area"}, {"key": "landuse", "value": "field", "description": "๐Ÿ„ณ โžœ landuse=farmland"}, {"key": "landuse", "value": "garden", "description": "๐Ÿ„ณ โžœ leisure=garden"}, @@ -2136,11 +2138,13 @@ {"key": "LEVELS", "description": "๐Ÿ„ณ โžœ building:levels=*"}, {"key": "levels_count", "description": "๐Ÿ„ณ โžœ building:levels=*"}, {"key": "man_made", "value": "cut_line", "description": "๐Ÿ„ณ โžœ man_made=cutline"}, + {"key": "man_made", "value": "flower_bed", "description": "๐Ÿ„ณ โžœ landuse=flowerbed"}, + {"key": "man_made", "value": "flowerbed", "description": "๐Ÿ„ณ โžœ landuse=flowerbed"}, + {"key": "man_made", "value": "fuel_storage_tank", "description": "๐Ÿ„ณ โžœ man_made=storage_tank + content=fuel"}, {"key": "man_made", "value": "gas_well", "description": "๐Ÿ„ณ โžœ man_made=petroleum_well + substance=gas"}, {"key": "man_made", "value": "jetty", "description": "๐Ÿ„ณ โžœ man_made=pier"}, {"key": "man_made", "value": "mdf", "description": "๐Ÿ„ณ โžœ telecom=exchange"}, {"key": "man_made", "value": "MDF", "description": "๐Ÿ„ณ โžœ telecom=exchange"}, - {"key": "man_made", "value": "fuel_storage_tank", "description": "๐Ÿ„ณ โžœ man_made=storage_tank + content=fuel"}, {"key": "man_made", "value": "oil_tank", "description": "๐Ÿ„ณ โžœ man_made=storage_tank + content=oil"}, {"key": "man_made", "value": "oil_well", "description": "๐Ÿ„ณ โžœ man_made=petroleum_well + substance=oil"}, {"key": "man_made", "value": "telephone_exchange", "description": "๐Ÿ„ณ โžœ telecom=exchange"}, From b0a9b8d7304ac2894c03fd192422630bc8467b5a Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Wed, 13 May 2020 10:05:02 -0400 Subject: [PATCH 26/67] Add Courtyard preset --- data/presets.yaml | 5 +++++ data/presets/presets.json | 1 + data/presets/presets/man_made/courtyard.json | 20 ++++++++++++++++++++ data/taginfo.json | 1 + dist/locales/en.json | 4 ++++ 5 files changed, 31 insertions(+) create mode 100644 data/presets/presets/man_made/courtyard.json diff --git a/data/presets.yaml b/data/presets.yaml index 01719f6b7..b32817389 100644 --- a/data/presets.yaml +++ b/data/presets.yaml @@ -6207,6 +6207,11 @@ en: name: Clearcut Forest # 'terms: cut,forest,lumber,tree,wood' terms: '' + man_made/courtyard: + # man_made=courtyard + name: Courtyard + # 'terms: court,enclosed open air,quadrangle,yard' + terms: '' man_made/crane: # man_made=crane name: Crane diff --git a/data/presets/presets.json b/data/presets/presets.json index 37f583601..26d3fb16f 100644 --- a/data/presets/presets.json +++ b/data/presets/presets.json @@ -742,6 +742,7 @@ "man_made/cairn": {"icon": "temaki-cairn", "geometry": ["point", "area"], "terms": ["rock pile", "stone stack", "stone pile", "cร rn"], "tags": {"man_made": "cairn"}, "name": "Cairn"}, "man_made/chimney": {"icon": "temaki-chimney", "fields": ["operator", "material", "height"], "geometry": ["point", "area"], "tags": {"man_made": "chimney"}, "name": "Chimney"}, "man_made/clearcut": {"icon": "maki-logging", "geometry": ["area"], "tags": {"man_made": "clearcut"}, "terms": ["cut", "forest", "lumber", "tree", "wood"], "name": "Clearcut Forest"}, + "man_made/courtyard": {"icon": "maki-square-stroked", "fields": ["name"], "moreFields": [], "geometry": ["area"], "tags": {"man_made": "courtyard"}, "terms": ["court", "enclosed open air", "quadrangle", "yard"], "name": "Courtyard"}, "man_made/crane": {"icon": "temaki-crane", "fields": ["operator", "manufacturer", "height", "crane/type"], "geometry": ["point", "line", "vertex", "area"], "tags": {"man_made": "crane"}, "name": "Crane"}, "man_made/cross": {"icon": "maki-religious-christian", "fields": ["name", "material", "height", "ele_node", "inscription", "direction"], "geometry": ["point", "vertex"], "tags": {"man_made": "cross"}, "name": "Summit Cross"}, "man_made/cutline": {"icon": "maki-logging", "geometry": ["line"], "tags": {"man_made": "cutline"}, "name": "Cut line"}, diff --git a/data/presets/presets/man_made/courtyard.json b/data/presets/presets/man_made/courtyard.json new file mode 100644 index 000000000..26ed23a61 --- /dev/null +++ b/data/presets/presets/man_made/courtyard.json @@ -0,0 +1,20 @@ +{ + "icon": "maki-square-stroked", + "fields": [ + "name" + ], + "moreFields": [], + "geometry": [ + "area" + ], + "tags": { + "man_made": "courtyard" + }, + "terms": [ + "court", + "enclosed open air", + "quadrangle", + "yard" + ], + "name": "Courtyard" +} diff --git a/data/taginfo.json b/data/taginfo.json index e6522c7cb..cb342853e 100644 --- a/data/taginfo.json +++ b/data/taginfo.json @@ -721,6 +721,7 @@ {"key": "man_made", "value": "cairn", "description": "๐Ÿ„ฟ Cairn", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/cairn.svg"}, {"key": "man_made", "value": "chimney", "description": "๐Ÿ„ฟ Chimney", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/chimney.svg"}, {"key": "man_made", "value": "clearcut", "description": "๐Ÿ„ฟ Clearcut Forest", "object_types": ["area"], "icon_url": "https://cdn.jsdelivr.net/gh/mapbox/maki/icons/logging-15.svg"}, + {"key": "man_made", "value": "courtyard", "description": "๐Ÿ„ฟ Courtyard", "object_types": ["area"], "icon_url": "https://cdn.jsdelivr.net/gh/mapbox/maki/icons/square-stroked-15.svg"}, {"key": "man_made", "value": "crane", "description": "๐Ÿ„ฟ Crane", "object_types": ["node", "way", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/crane.svg"}, {"key": "man_made", "value": "cross", "description": "๐Ÿ„ฟ Summit Cross", "object_types": ["node"], "icon_url": "https://cdn.jsdelivr.net/gh/mapbox/maki/icons/religious-christian-15.svg"}, {"key": "man_made", "value": "cutline", "description": "๐Ÿ„ฟ Cut line", "object_types": ["way"], "icon_url": "https://cdn.jsdelivr.net/gh/mapbox/maki/icons/logging-15.svg"}, diff --git a/dist/locales/en.json b/dist/locales/en.json index 0c5eeecf1..55a37ddb3 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -7886,6 +7886,10 @@ "name": "Clearcut Forest", "terms": "cut,forest,lumber,tree,wood" }, + "man_made/courtyard": { + "name": "Courtyard", + "terms": "court,enclosed open air,quadrangle,yard" + }, "man_made/crane": { "name": "Crane", "terms": "" From 04bdc4da1c632696465ce0b896dd740f898ff14b Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Wed, 13 May 2020 10:58:53 -0400 Subject: [PATCH 27/67] Make the extract operation a single step instead of immediately entering modeMove (close #6674) --- modules/actions/extract.js | 6 ++-- modules/operations/extract.js | 32 +++------------------- modules/validations/mismatched_geometry.js | 4 +-- 3 files changed, 9 insertions(+), 33 deletions(-) diff --git a/modules/actions/extract.js b/modules/actions/extract.js index 6c00a3882..5e5a391dc 100644 --- a/modules/actions/extract.js +++ b/modules/actions/extract.js @@ -1,8 +1,8 @@ -import { geoPath as d3_geoPath } from 'd3-geo'; +import { geoCentroid as d3_geoCentroid } from 'd3-geo'; import { osmNode } from '../osm/node'; -export function actionExtract(entityID, projection) { +export function actionExtract(entityID) { var extractedNodeID; @@ -43,7 +43,7 @@ export function actionExtract(entityID, projection) { var keysToRetain = ['area', 'type']; var buildingKeysToRetain = ['architect', 'building', 'height', 'layer']; - var centroid = d3_geoPath(projection).centroid(entity.asGeoJSON(graph, true)); + var centroid = d3_geoCentroid(entity.asGeoJSON(graph)); var isBuilding = entity.tags.building; diff --git a/modules/operations/extract.js b/modules/operations/extract.js index 003993dd7..6a660ac78 100644 --- a/modules/operations/extract.js +++ b/modules/operations/extract.js @@ -1,44 +1,20 @@ import { actionExtract } from '../actions/extract'; -import { actionMoveNode } from '../actions/move_node'; import { behaviorOperation } from '../behavior/operation'; -import { modeMove } from '../modes/move'; +import { modeSelect } from '../modes/select'; import { t } from '../core/localizer'; import { presetManager } from '../presets'; export function operationExtract(selectedIDs, context) { var entityID = selectedIDs.length && selectedIDs[0]; - var action = actionExtract(entityID, context.projection); + var action = actionExtract(entityID); var geometry = entityID && context.graph().hasEntity(entityID) && context.graph().geometry(entityID); var extent = geometry === 'area' && context.entity(entityID).extent(context.graph()); var operation = function () { - context.perform(action); // do the extract - context.validator().validate(); - - var extractedNodeID = action.getExtractedNodeID(); - - var mouse = context.map().mouseCoordinates(); - if (mouse.some(isNaN)) { - enterMoveMode(); - - } else { - // move detached node to the mouse location (transitioned) - context.perform(actionMoveNode(extractedNodeID, mouse)); - - // after transition completes, put at final mouse location and enter move mode. - window.setTimeout(function() { - mouse = context.map().mouseCoordinates(); - context.replace(actionMoveNode(extractedNodeID, mouse)); - enterMoveMode(); - }, 150); - } - - function enterMoveMode() { - var baseGraph = context.graph(); - context.enter(modeMove(context, [extractedNodeID], baseGraph)); - } + context.perform(action, operation.annotation()); // do the extract + context.enter(modeSelect(context, [action.getExtractedNodeID()])); }; diff --git a/modules/validations/mismatched_geometry.js b/modules/validations/mismatched_geometry.js index c079ff05d..d8577a2df 100644 --- a/modules/validations/mismatched_geometry.js +++ b/modules/validations/mismatched_geometry.js @@ -202,11 +202,11 @@ export function validationMismatchedGeometry() { var extractOnClick = null; if (!context.hasHiddenConnections(entityId) && - !actionExtract(entityId, context.projection).disabled(context.graph())) { + !actionExtract(entityId).disabled(context.graph())) { extractOnClick = function(context) { var entityId = this.issue.entityIds[0]; - var action = actionExtract(entityId, context.projection); + var action = actionExtract(entityId); context.perform( action, t('operations.extract.annotation.single') From e0a23723da21c939c5028240606b5e6607e90dec Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Wed, 13 May 2020 11:59:55 -0400 Subject: [PATCH 28/67] Enable extracting lines to POIs (close #7598) --- data/core.yaml | 2 ++ dist/locales/en.json | 3 +++ modules/actions/extract.js | 38 +++++++++++++++++++++-------------- modules/operations/extract.js | 5 +++-- 4 files changed, 31 insertions(+), 17 deletions(-) diff --git a/data/core.yaml b/data/core.yaml index 4b64931b1..ce1b23974 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -369,6 +369,8 @@ en: description: vertex: single: Extract this point from its parent lines/areas. + line: + single: Extract a point from this line. area: single: Extract a point from this area. annotation: diff --git a/dist/locales/en.json b/dist/locales/en.json index 55a37ddb3..bf0dd4c93 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -481,6 +481,9 @@ "vertex": { "single": "Extract this point from its parent lines/areas." }, + "line": { + "single": "Extract a point from this line." + }, "area": { "single": "Extract a point from this area." } diff --git a/modules/actions/extract.js b/modules/actions/extract.js index 5e5a391dc..08cbee56b 100644 --- a/modules/actions/extract.js +++ b/modules/actions/extract.js @@ -13,7 +13,7 @@ export function actionExtract(entityID) { return extractFromNode(entity, graph); } - return extractFromArea(entity, graph); + return extractFromWayOrRelation(entity, graph); }; function extractFromNode(node, graph) { @@ -37,19 +37,26 @@ export function actionExtract(entityID) { }, graph); } - function extractFromArea(entity, graph) { + function extractFromWayOrRelation(entity, graph) { + + var fromGeometry = entity.geometry(graph); var keysToCopyAndRetain = ['source', 'wheelchair']; - var keysToRetain = ['area', 'type']; + var keysToRetain = ['area']; var buildingKeysToRetain = ['architect', 'building', 'height', 'layer']; var centroid = d3_geoCentroid(entity.asGeoJSON(graph)); - var isBuilding = entity.tags.building; + var isBuilding = entity.tags.building && entity.tags.building !== 'no'; - var areaTags = Object.assign({}, entity.tags); // shallow copy + var entityTags = Object.assign({}, entity.tags); // shallow copy var pointTags = {}; - for (var key in areaTags) { + for (var key in entityTags) { + + if (entity.type === 'relation' && + key === 'type') { + continue; + } if (keysToRetain.indexOf(key) !== -1) { continue; @@ -62,21 +69,22 @@ export function actionExtract(entityID) { key.match(/^roof:.{1,}/)) continue; } - // copy the tag from the area to the point - pointTags[key] = areaTags[key]; + // copy the tag from the entity to the point + pointTags[key] = entityTags[key]; // leave addresses and some other tags so they're on both features - if (keysToCopyAndRetain.indexOf(key) !== -1 || key.match(/^addr:.{1,}/)) { + if (keysToCopyAndRetain.indexOf(key) !== -1 || + key.match(/^addr:.{1,}/)) { continue; } - // remove the tag from the area - delete areaTags[key]; + // remove the tag from the entity + delete entityTags[key]; } - if (!isBuilding) { - // ensure that the area keeps the area geometry - areaTags.area = 'yes'; + if (!isBuilding && fromGeometry === 'area') { + // ensure that areas keep area geometry + entityTags.area = 'yes'; } var replacement = osmNode({ loc: centroid, tags: pointTags }); @@ -84,7 +92,7 @@ export function actionExtract(entityID) { extractedNodeID = replacement.id; - return graph.replace(entity.update({tags: areaTags})); + return graph.replace(entity.update({tags: entityTags})); } action.getExtractedNodeID = function() { diff --git a/modules/operations/extract.js b/modules/operations/extract.js index 6a660ac78..51858111d 100644 --- a/modules/operations/extract.js +++ b/modules/operations/extract.js @@ -9,7 +9,7 @@ export function operationExtract(selectedIDs, context) { var action = actionExtract(entityID); var geometry = entityID && context.graph().hasEntity(entityID) && context.graph().geometry(entityID); - var extent = geometry === 'area' && context.entity(entityID).extent(context.graph()); + var extent = (geometry === 'area' || geometry === 'line') && context.entity(entityID).extent(context.graph()); var operation = function () { @@ -27,8 +27,9 @@ export function operationExtract(selectedIDs, context) { if (!entity.hasInterestingTags()) return false; - if (geometry === 'area') { + if (geometry === 'area' || geometry === 'line') { var preset = presetManager.match(entity, graph); + // only allow extraction from ways/multipolygons if the preset supports points return preset.geometry.indexOf('point') !== -1; } From 0278e700a0205d07eceab62c749786209fc5888d Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Wed, 13 May 2020 15:42:28 -0400 Subject: [PATCH 29/67] Convert feature copying functionality from a standalone behavior to an operation and add to edit menu (re: #2508) Show flash feedback message when copying features with keyboard shortcut Disallow copying of untagged vertices --- data/core.yaml | 8 ++ dist/locales/en.json | 11 ++ modules/behavior/index.js | 1 - modules/modes/select.js | 2 - modules/{behavior => operations}/copy.js | 124 ++++++++++++-------- modules/operations/index.js | 1 + svg/iD-sprite/operations/operation-copy.svg | 2 +- 7 files changed, 96 insertions(+), 53 deletions(-) rename modules/{behavior => operations}/copy.js (61%) diff --git a/data/core.yaml b/data/core.yaml index ce1b23974..1ed815305 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -80,6 +80,14 @@ en: annotation: Changed the role of a relation member. change_tags: annotation: Changed tags. + copy: + title: Copy + description: + single: Make this feature pasteable. + multiple: Make these features pasteable. + annotation: + single: Copied a feature. + multiple: "Copied {n} features." circularize: title: Circularize description: diff --git a/dist/locales/en.json b/dist/locales/en.json index bf0dd4c93..04722c333 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -103,6 +103,17 @@ "change_tags": { "annotation": "Changed tags." }, + "copy": { + "title": "Copy", + "description": { + "single": "Make this feature pasteable.", + "multiple": "Make these features pasteable." + }, + "annotation": { + "single": "Copied a feature.", + "multiple": "Copied {n} features." + } + }, "circularize": { "title": "Circularize", "description": { diff --git a/modules/behavior/index.js b/modules/behavior/index.js index 054dbb6cf..19b0a8da0 100644 --- a/modules/behavior/index.js +++ b/modules/behavior/index.js @@ -1,6 +1,5 @@ export { behaviorAddWay } from './add_way'; export { behaviorBreathe } from './breathe'; -export { behaviorCopy } from './copy'; export { behaviorDrag } from './drag'; export { behaviorDrawWay } from './draw_way'; export { behaviorDraw } from './draw'; diff --git a/modules/modes/select.js b/modules/modes/select.js index 358f7db08..f5191e080 100644 --- a/modules/modes/select.js +++ b/modules/modes/select.js @@ -6,7 +6,6 @@ import { actionAddMidpoint } from '../actions/add_midpoint'; import { actionDeleteRelation } from '../actions/delete_relation'; import { behaviorBreathe } from '../behavior/breathe'; -import { behaviorCopy } from '../behavior/copy'; import { behaviorHover } from '../behavior/hover'; import { behaviorLasso } from '../behavior/lasso'; import { behaviorPaste } from '../behavior/paste'; @@ -38,7 +37,6 @@ export function modeSelect(context, selectedIDs) { var keybinding = utilKeybinding('select'); var breatheBehavior = behaviorBreathe(context); var behaviors = [ - behaviorCopy(context), behaviorPaste(context), breatheBehavior, behaviorHover(context), diff --git a/modules/behavior/copy.js b/modules/operations/copy.js similarity index 61% rename from modules/behavior/copy.js rename to modules/operations/copy.js index 05da18630..95372463d 100644 --- a/modules/behavior/copy.js +++ b/modules/operations/copy.js @@ -1,10 +1,57 @@ import { event as d3_event } from 'd3-selection'; +import { t } from '../core/localizer'; +import { behaviorOperation } from '../behavior/operation'; import { uiCmd } from '../ui/cmd'; import { utilArrayGroupBy } from '../util'; +export function operationCopy(selectedIDs, context) { + + function getFilteredIdsToCopy() { + return selectedIDs.filter(function(selectedID) { + var entity = context.graph().hasEntity(selectedID); + // don't copy untagged vertices separately from ways + return entity.hasInterestingTags() || entity.geometry(context.graph()) !== 'vertex'; + }); + } + + var operation = function() { + + if (!getSelectionText()) { + d3_event.preventDefault(); + } + + var graph = context.graph(); + var selected = groupEntities(getFilteredIdsToCopy(), graph); + var canCopy = []; + var skip = {}; + var entity; + var i; + + for (i = 0; i < selected.relation.length; i++) { + entity = selected.relation[i]; + if (!skip[entity.id] && entity.isComplete(graph)) { + canCopy.push(entity.id); + skip = getDescendants(entity.id, graph, skip); + } + } + for (i = 0; i < selected.way.length; i++) { + entity = selected.way[i]; + if (!skip[entity.id]) { + canCopy.push(entity.id); + skip = getDescendants(entity.id, graph, skip); + } + } + for (i = 0; i < selected.node.length; i++) { + entity = selected.node[i]; + if (!skip[entity.id]) { + canCopy.push(entity.id); + } + } + + context.copyIDs(canCopy); + }; -export function behaviorCopy(context) { function groupEntities(ids, graph) { var entities = ids.map(function (id) { return graph.entity(id); }); @@ -45,55 +92,34 @@ export function behaviorCopy(context) { } - function doCopy() { - // prevent copy during low zoom selection - if (!context.map().withinEditableZoom()) return; - - if (!getSelectionText()) { - d3_event.preventDefault(); - } - - var graph = context.graph(); - var selected = groupEntities(context.selectedIDs(), graph); - var canCopy = []; - var skip = {}; - var entity; - var i; - - for (i = 0; i < selected.relation.length; i++) { - entity = selected.relation[i]; - if (!skip[entity.id] && entity.isComplete(graph)) { - canCopy.push(entity.id); - skip = getDescendants(entity.id, graph, skip); - } - } - for (i = 0; i < selected.way.length; i++) { - entity = selected.way[i]; - if (!skip[entity.id]) { - canCopy.push(entity.id); - skip = getDescendants(entity.id, graph, skip); - } - } - for (i = 0; i < selected.node.length; i++) { - entity = selected.node[i]; - if (!skip[entity.id]) { - canCopy.push(entity.id); - } - } - - context.copyIDs(canCopy); - } - - - function behavior() { - context.keybinding().on(uiCmd('โŒ˜C'), doCopy); - return behavior; - } - - behavior.off = function() { - context.keybinding().off(uiCmd('โŒ˜C')); + operation.available = function() { + return getFilteredIdsToCopy().length > 0; }; - return behavior; + operation.disabled = function() { + return false; + }; + + + operation.tooltip = function() { + return selectedIDs.length === 1 ? + t('operations.copy.description.single') : + t('operations.copy.description.multiple'); + }; + + + operation.annotation = function() { + return selectedIDs.length === 1 ? + t('operations.copy.annotation.single') : + t('operations.copy.annotation.multiple', { n: selectedIDs.length }); + }; + + + operation.id = 'copy'; + operation.keys = [uiCmd('โŒ˜C')]; + operation.title = t('operations.copy.title'); + operation.behavior = behaviorOperation(context).which(operation); + + return operation; } diff --git a/modules/operations/index.js b/modules/operations/index.js index 77be52df1..6584fa294 100644 --- a/modules/operations/index.js +++ b/modules/operations/index.js @@ -1,5 +1,6 @@ export { operationCircularize } from './circularize'; export { operationContinue } from './continue'; +export { operationCopy } from './copy'; export { operationDelete } from './delete'; export { operationDisconnect } from './disconnect'; export { operationDowngrade } from './downgrade'; diff --git a/svg/iD-sprite/operations/operation-copy.svg b/svg/iD-sprite/operations/operation-copy.svg index 30ebe5309..92e0d9d90 100644 --- a/svg/iD-sprite/operations/operation-copy.svg +++ b/svg/iD-sprite/operations/operation-copy.svg @@ -1,6 +1,6 @@ - + From 8799011abc740d75543ae3f119f7ef4b793f3c92 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Wed, 13 May 2020 16:04:40 -0400 Subject: [PATCH 30/67] Fix code tests --- modules/operations/extract.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/operations/extract.js b/modules/operations/extract.js index 51858111d..c71c087f4 100644 --- a/modules/operations/extract.js +++ b/modules/operations/extract.js @@ -9,7 +9,7 @@ export function operationExtract(selectedIDs, context) { var action = actionExtract(entityID); var geometry = entityID && context.graph().hasEntity(entityID) && context.graph().geometry(entityID); - var extent = (geometry === 'area' || geometry === 'line') && context.entity(entityID).extent(context.graph()); + var extent = (geometry === 'area' || geometry === 'line') && context.graph().entity(entityID).extent(context.graph()); var operation = function () { From dff426825626797a232da32fc838748a3e5500e8 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Thu, 14 May 2020 11:19:20 -0400 Subject: [PATCH 31/67] Allow selection, deselection, and multiselection with the spacebar (re: #3843) Don't open the edit menu on single spacebar press Open the edit menu for long spacebar press (re: #7577) --- data/core.yaml | 1 + data/shortcuts.json | 4 +- dist/locales/en.json | 1 + modules/behavior/select.js | 81 ++++++++++++++++++++++++-------------- modules/modes/select.js | 12 +----- modules/renderer/map.js | 5 +++ modules/ui/cmd.js | 1 + 7 files changed, 63 insertions(+), 42 deletions(-) diff --git a/data/core.yaml b/data/core.yaml index 1ed815305..327c8f78d 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -1967,6 +1967,7 @@ en: enter: Enter esc: Esc home: Home + menu: Menu option: Option pause: Pause pgdn: PgDn diff --git a/data/shortcuts.json b/data/shortcuts.json index d9b595d79..fec11cba0 100644 --- a/data/shortcuts.json +++ b/data/shortcuts.json @@ -107,7 +107,7 @@ "text": "shortcuts.browsing.selecting.title" }, { - "shortcuts": ["Left-click"], + "shortcuts": ["Left-click", "shortcuts.key.space"], "text": "shortcuts.browsing.selecting.select_one" }, { @@ -131,7 +131,7 @@ "text": "shortcuts.browsing.with_selected.title" }, { - "shortcuts": ["Right-click", "shortcuts.key.space"], + "shortcuts": ["Right-click", "โ˜ฐ"], "text": "shortcuts.browsing.with_selected.edit_menu" }, { diff --git a/dist/locales/en.json b/dist/locales/en.json index 04722c333..5d4862394 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -2428,6 +2428,7 @@ "enter": "Enter", "esc": "Esc", "home": "Home", + "menu": "Menu", "option": "Option", "pause": "Pause", "pgdn": "PgDn", diff --git a/modules/behavior/select.js b/modules/behavior/select.js index 6c66802ae..359f5d89e 100644 --- a/modules/behavior/select.js +++ b/modules/behavior/select.js @@ -15,7 +15,7 @@ export function behaviorSelect(context) { // legacy option to show menu on every click var _alwaysShowMenu = +prefs('edit-menu-show-always') === 1; var _tolerancePx = 4; - var _lastMouse = null; + var _lastPointerEvent = null; var _showMenu = false; var _p1 = null; var _downPointerId = null; @@ -26,21 +26,35 @@ export function behaviorSelect(context) { var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse'; function point(event) { - return utilFastMouse(context.container().node())(event || d3_event); + // don't use map().mouse() since additional pointers unrelated to selection can + // move between pointerdown and pointerup + return utilFastMouse(context.map().supersurface.node())(event || d3_event); } function keydown() { + + if (d3_event.keyCode === 93 || // context menu key + d3_event.keyCode === 32) { // spacebar + d3_event.preventDefault(); + } + + if (d3_event.repeat) return; // ignore repeated events for held keys + + // if any key is pressed the user is probably doing something other than long-pressing if (_longPressTimeout) window.clearTimeout(_longPressTimeout); - var e = d3_event; - if (e && e.shiftKey) { + if (d3_event.shiftKey) { context.surface() .classed('behavior-multiselect', true); } - if (e && e.keyCode === 93) { // context menu - e.preventDefault(); + if (d3_event.keyCode === 32) { // spacebar + if (!_p1) { + _p1 = point(_lastPointerEvent); + if (_longPressTimeout) window.clearTimeout(_longPressTimeout); + _longPressTimeout = window.setTimeout(didLongPress, 500, 'spacebar'); + } } } @@ -48,31 +62,33 @@ export function behaviorSelect(context) { function keyup() { if (_longPressTimeout) window.clearTimeout(_longPressTimeout); - var e = d3_event; - if (!e || !e.shiftKey) { + if (!d3_event.shiftKey) { context.surface() .classed('behavior-multiselect', false); } - - if (e && e.keyCode === 93) { // context menu - e.preventDefault(); + if (d3_event.keyCode === 93) { // context menu key + d3_event.preventDefault(); _lastInteractionType = 'menukey'; contextmenu(); + } else if (d3_event.keyCode === 32) { // spacebar + d3_event.preventDefault(); + _lastInteractionType = 'spacebar'; + _showMenu = _alwaysShowMenu; + click(); } } function pointerdown() { if (_p1) return; - _p1 = point(); _downPointerId = d3_event.pointerId || 'mouse'; if (_longPressTimeout) window.clearTimeout(_longPressTimeout); - _longPressTimeout = window.setTimeout(didLongPress, 500, d3_event.pointerType || 'mouse'); + _longPressTimeout = window.setTimeout(didLongPress, 500, 'longdown-' + (d3_event.pointerType || 'mouse')); - _lastMouse = d3_event; + _lastPointerEvent = d3_event; d3_select(window) .on(_pointerPrefix + 'up.select', pointerup, true); @@ -81,9 +97,10 @@ export function behaviorSelect(context) { } - function didLongPress(pointerType) { + function didLongPress(iType) { + // treat long presses like right-clicks _longPressTimeout = null; - _lastInteractionType = 'longdown-' + pointerType; + _lastInteractionType = iType; _showMenu = true; click(); } @@ -92,7 +109,7 @@ export function behaviorSelect(context) { function pointermove() { if (_downPointerId && _downPointerId !== (d3_event.pointerId || 'mouse')) return; - _lastMouse = d3_event; + _lastPointerEvent = d3_event; } @@ -112,13 +129,13 @@ export function behaviorSelect(context) { e.preventDefault(); if (!+e.clientX && !+e.clientY) { - if (_lastMouse) { - e.sourceEvent = _lastMouse; + if (_lastPointerEvent) { + e.sourceEvent = _lastPointerEvent; } else { return; } } else { - _lastMouse = d3_event; + _lastPointerEvent = d3_event; _lastInteractionType = 'rightclick'; } @@ -134,12 +151,12 @@ export function behaviorSelect(context) { if (_longPressTimeout) window.clearTimeout(_longPressTimeout); if (!_p1) return; - var p2 = point(_lastMouse); + var p2 = point(_lastPointerEvent); var dist = geoVecLength(_p1, p2); _p1 = null; if (dist > _tolerancePx) return; - var datum = (d3_event && d3_event.target.__data__) || (_lastMouse && _lastMouse.target.__data__); + var datum = (d3_event && d3_event.target.__data__) || (_lastPointerEvent && _lastPointerEvent.target.__data__); var isMultiselect = (d3_event && d3_event.shiftKey) || context.surface().select('.lasso').node(); processClick(datum, isMultiselect); @@ -220,18 +237,24 @@ export function behaviorSelect(context) { } } - // reset for next time.. + resetProperties(); + } + + + function resetProperties() { _showMenu = false; + _p1 = null; + _downPointerId = null; + if (_longPressTimeout) window.clearTimeout(_longPressTimeout); + _longPressTimeout = null; + _lastInteractionType = null; + // don't reset _lastPointerEvent since it might still be useful } function behavior(selection) { - _lastMouse = null; - _showMenu = false; - _p1 = null; - _downPointerId = null; - _longPressTimeout = null; - _lastInteractionType = null; + resetProperties(); + _lastPointerEvent = context.map().lastPointerEvent(); d3_select(window) .on('keydown.select', keydown) diff --git a/modules/modes/select.js b/modules/modes/select.js index f5191e080..8fa27d0ad 100644 --- a/modules/modes/select.js +++ b/modules/modes/select.js @@ -184,15 +184,6 @@ export function modeSelect(context, selectedIDs) { }; - function spacebar() { // toggle the menu - if (context.map().supersurface.select('.edit-menu').empty()) { - mode.showMenu('spacebar'); - } else { - closeMenu(); - } - } - - mode.selectedIDs = function() { return selectedIDs; }; @@ -269,8 +260,7 @@ export function modeSelect(context, selectedIDs) { .on(['{', uiCmd('โŒ˜['), 'home'], firstVertex) .on(['}', uiCmd('โŒ˜]'), 'end'], lastVertex) .on(['\\', 'pause'], nextParent) - .on('โŽ‹', esc, true) - .on('space', spacebar); + .on('โŽ‹', esc, true); d3_select(document) .call(keybinding); diff --git a/modules/renderer/map.js b/modules/renderer/map.js index 084179aa5..648bd9f2b 100644 --- a/modules/renderer/map.js +++ b/modules/renderer/map.js @@ -684,6 +684,11 @@ export function rendererMap(context) { }; + map.lastPointerEvent = function() { + return _lastPointerEvent; + }; + + map.mouse = function() { var event = _lastPointerEvent || d3_event; if (event) { diff --git a/modules/ui/cmd.js b/modules/ui/cmd.js index 648ce3050..c2203adb1 100644 --- a/modules/ui/cmd.js +++ b/modules/ui/cmd.js @@ -55,6 +55,7 @@ uiCmd.display = function(code) { 'โ‡Ÿ': mac ? 'โ‡Ÿ ' + t('shortcuts.key.end') : t('shortcuts.key.end'), 'โ†ต': mac ? 'โ†ต ' + t('shortcuts.key.return') : t('shortcuts.key.enter'), 'โŽ‹': mac ? 'โŽ‹ ' + t('shortcuts.key.esc') : t('shortcuts.key.esc'), + 'โ˜ฐ': mac ? 'โ˜ฐ ' + t('shortcuts.key.menu') : t('shortcuts.key.menu'), }; return replacements[code] || code; From a0ad92e6e054d760e545ff5712a01ebcf6212390 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Thu, 14 May 2020 12:32:43 -0400 Subject: [PATCH 32/67] Always show the edit menu at the location of the triggering event --- modules/behavior/select.js | 12 ++++++------ modules/modes/select.js | 11 +++-------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/modules/behavior/select.js b/modules/behavior/select.js index 359f5d89e..e77603f80 100644 --- a/modules/behavior/select.js +++ b/modules/behavior/select.js @@ -159,11 +159,11 @@ export function behaviorSelect(context) { var datum = (d3_event && d3_event.target.__data__) || (_lastPointerEvent && _lastPointerEvent.target.__data__); var isMultiselect = (d3_event && d3_event.shiftKey) || context.surface().select('.lasso').node(); - processClick(datum, isMultiselect); + processClick(datum, isMultiselect, p2); } - function processClick(datum, isMultiselect) { + function processClick(datum, isMultiselect, point) { var mode = context.mode(); var entity = datum && datum.properties && datum.properties.entity; @@ -183,14 +183,14 @@ export function behaviorSelect(context) { if (!isMultiselect) { if (selectedIDs.length > 1 && (_showMenu && !_alwaysShowMenu)) { // multiple things already selected, just show the menu... - mode.reselect().showMenu(_lastInteractionType); + mode.reselect().showMenu(point, _lastInteractionType); } else { // always enter modeSelect even if the entity is already // selected since listeners may expect `context.enter` events, // e.g. in the walkthrough newMode = modeSelect(context, [datum.id]); context.enter(newMode); - if (_showMenu) newMode.showMenu(_lastInteractionType); + if (_showMenu) newMode.showMenu(point, _lastInteractionType); } } else { @@ -198,7 +198,7 @@ export function behaviorSelect(context) { // clicked entity is already in the selectedIDs list.. if (_showMenu && !_alwaysShowMenu) { // don't deselect clicked entity, just show the menu. - mode.reselect().showMenu(_lastInteractionType); + mode.reselect().showMenu(point, _lastInteractionType); } else { // deselect clicked entity, then reenter select mode or return to browse mode.. selectedIDs = selectedIDs.filter(function(id) { return id !== datum.id; }); @@ -210,7 +210,7 @@ export function behaviorSelect(context) { selectedIDs = selectedIDs.concat([datum.id]); newMode = modeSelect(context, selectedIDs); context.enter(newMode); - if (_showMenu) newMode.showMenu(_lastInteractionType); + if (_showMenu) newMode.showMenu(point, _lastInteractionType); } } diff --git a/modules/modes/select.js b/modules/modes/select.js index 8fa27d0ad..94598f14b 100644 --- a/modules/modes/select.js +++ b/modules/modes/select.js @@ -11,7 +11,7 @@ import { behaviorLasso } from '../behavior/lasso'; import { behaviorPaste } from '../behavior/paste'; import { behaviorSelect } from '../behavior/select'; -import { geoExtent, geoChooseEdge, geoPointInPolygon } from '../geo'; +import { geoExtent, geoChooseEdge } from '../geo'; import { modeBrowse } from './browse'; import { modeDragNode } from './drag_node'; import { modeDragNote } from './drag_note'; @@ -147,7 +147,7 @@ export function modeSelect(context, selectedIDs) { .select('.edit-menu').remove(); } - mode.showMenu = function(triggerType) { + mode.showMenu = function(anchorPoint, triggerType) { // remove any displayed menu closeMenu(); @@ -160,11 +160,6 @@ export function modeSelect(context, selectedIDs) { return context.graph().geometry(id) === 'relation'; })) return; - var point = context.map().mouse(); - var viewport = geoExtent(context.projection.clipExtent()).polygon(); - // make sure a vaild position can be determined - if (!point || !geoPointInPolygon(point, viewport)) return; - var surfaceNode = context.surface().node(); if (surfaceNode.focus) { // FF doesn't support it // focus the surface or else clicking off the menu may not trigger modeBrowse @@ -175,7 +170,7 @@ export function modeSelect(context, selectedIDs) { if (!_editMenu) _editMenu = uiEditMenu(context); _editMenu - .anchorLoc(point) + .anchorLoc(anchorPoint) .triggerType(triggerType) .operations(operations); From c939924f834740b3e8ecf939f6c40de1bc4984f5 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Thu, 14 May 2020 13:09:25 -0400 Subject: [PATCH 33/67] Fix issue with pressing spacebar in select modes --- modules/behavior/select.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/behavior/select.js b/modules/behavior/select.js index e77603f80..2c3689741 100644 --- a/modules/behavior/select.js +++ b/modules/behavior/select.js @@ -34,6 +34,12 @@ export function behaviorSelect(context) { function keydown() { + if (d3_event.keyCode === 32) { + // don't react to spacebar events during text input + var activeNode = document.activeElement; + if (activeNode && new Set(['INPUT', 'TEXTAREA']).has(activeNode.nodeName)) return; + } + if (d3_event.keyCode === 93 || // context menu key d3_event.keyCode === 32) { // spacebar d3_event.preventDefault(); From db9eed2434414cc918b098a7f81cfbc746f2e89d Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Thu, 14 May 2020 15:49:35 -0400 Subject: [PATCH 34/67] Move the edit menu logic to uiInit Make context the first argument of operation objects Add Paste operation to edit menu when opening the context menu on a blank area of the map (close #2508) --- data/core.yaml | 9 ++ dist/locales/en.json | 12 +++ modules/behavior/paste.js | 8 +- modules/behavior/select.js | 27 ++---- modules/modes/browse.js | 6 ++ modules/modes/move.js | 12 +-- modules/modes/rotate.js | 12 +-- modules/modes/select.js | 71 +++------------ modules/operations/circularize.js | 2 +- modules/operations/continue.js | 2 +- modules/operations/copy.js | 4 +- modules/operations/delete.js | 2 +- modules/operations/disconnect.js | 2 +- modules/operations/downgrade.js | 2 +- modules/operations/extract.js | 2 +- modules/operations/index.js | 1 + modules/operations/merge.js | 2 +- modules/operations/move.js | 2 +- modules/operations/orthogonalize.js | 2 +- modules/operations/paste.js | 95 ++++++++++++++++++++ modules/operations/reflect.js | 10 +-- modules/operations/reverse.js | 2 +- modules/operations/rotate.js | 2 +- modules/operations/split.js | 2 +- modules/operations/straighten.js | 2 +- modules/ui/init.js | 43 +++++++++ modules/ui/preset_list.js | 2 +- modules/validations/disconnected_way.js | 2 +- modules/validations/missing_tag.js | 4 +- svg/iD-sprite/operations/operation-paste.svg | 2 +- test/spec/operations/extract.js | 28 +++--- test/spec/operations/straighten.js | 30 +++---- 32 files changed, 256 insertions(+), 148 deletions(-) create mode 100644 modules/operations/paste.js diff --git a/data/core.yaml b/data/core.yaml index 327c8f78d..bd98e79a4 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -88,6 +88,15 @@ en: annotation: single: Copied a feature. multiple: "Copied {n} features." + paste: + title: Paste + description: + single: "Add a duplicate {feature} here." + multiple: "Add {n} duplicate features here." + annotation: + single: Pasted a feature. + multiple: "Pasted {n} features." + nothing_copied: No features have been copied. circularize: title: Circularize description: diff --git a/dist/locales/en.json b/dist/locales/en.json index 5d4862394..e1aa33cb7 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -114,6 +114,18 @@ "multiple": "Copied {n} features." } }, + "paste": { + "title": "Paste", + "description": { + "single": "Add a duplicate {feature} here.", + "multiple": "Add {n} duplicate features here." + }, + "annotation": { + "single": "Pasted a feature.", + "multiple": "Pasted {n} features." + }, + "nothing_copied": "No features have been copied." + }, "circularize": { "title": "Circularize", "description": { diff --git a/modules/behavior/paste.js b/modules/behavior/paste.js index 5976bd4f0..f76cffee2 100644 --- a/modules/behavior/paste.js +++ b/modules/behavior/paste.js @@ -6,7 +6,7 @@ import { geoExtent, geoPointInPolygon, geoVecSubtract } from '../geo'; import { modeMove } from '../modes/move'; import { uiCmd } from '../ui/cmd'; - +// see also `operationPaste` export function behaviorPaste(context) { function doPaste() { @@ -22,13 +22,13 @@ export function behaviorPaste(context) { if (!geoPointInPolygon(mouse, viewport)) return; - var extent = geoExtent(); var oldIDs = context.copyIDs(); + if (!oldIDs.length) return; + + var extent = geoExtent(); var oldGraph = context.copyGraph(); var newIDs = []; - if (!oldIDs.length) return; - var action = actionCopyEntities(oldIDs, oldGraph); context.perform(action); diff --git a/modules/behavior/select.js b/modules/behavior/select.js index 2c3689741..f668f0c4a 100644 --- a/modules/behavior/select.js +++ b/modules/behavior/select.js @@ -1,7 +1,6 @@ import { event as d3_event, select as d3_select } from 'd3-selection'; import { geoVecLength } from '../geo'; -import { prefs } from '../core/preferences'; import { modeBrowse } from '../modes/browse'; import { modeSelect } from '../modes/select'; import { modeSelectData } from '../modes/select_data'; @@ -12,8 +11,6 @@ import { utilFastMouse } from '../util/util'; export function behaviorSelect(context) { - // legacy option to show menu on every click - var _alwaysShowMenu = +prefs('edit-menu-show-always') === 1; var _tolerancePx = 4; var _lastPointerEvent = null; var _showMenu = false; @@ -35,7 +32,7 @@ export function behaviorSelect(context) { function keydown() { if (d3_event.keyCode === 32) { - // don't react to spacebar events during text input + // don't react to spacebar events during text input var activeNode = document.activeElement; if (activeNode && new Set(['INPUT', 'TEXTAREA']).has(activeNode.nodeName)) return; } @@ -80,7 +77,6 @@ export function behaviorSelect(context) { } else if (d3_event.keyCode === 32) { // spacebar d3_event.preventDefault(); _lastInteractionType = 'spacebar'; - _showMenu = _alwaysShowMenu; click(); } } @@ -98,8 +94,6 @@ export function behaviorSelect(context) { d3_select(window) .on(_pointerPrefix + 'up.select', pointerup, true); - - _showMenu = _alwaysShowMenu; } @@ -176,6 +170,7 @@ export function behaviorSelect(context) { if (entity) datum = entity; if (datum && datum.type === 'midpoint') { + // treat targeting midpoints as if targeting the parent way datum = datum.parents[0]; } @@ -187,36 +182,27 @@ export function behaviorSelect(context) { context.selectedErrorID(null); if (!isMultiselect) { - if (selectedIDs.length > 1 && (_showMenu && !_alwaysShowMenu)) { - // multiple things already selected, just show the menu... - mode.reselect().showMenu(point, _lastInteractionType); - } else { + if (selectedIDs.length <= 1 || !_showMenu) { // always enter modeSelect even if the entity is already // selected since listeners may expect `context.enter` events, // e.g. in the walkthrough newMode = modeSelect(context, [datum.id]); context.enter(newMode); - if (_showMenu) newMode.showMenu(point, _lastInteractionType); } } else { if (selectedIDs.indexOf(datum.id) !== -1) { // clicked entity is already in the selectedIDs list.. - if (_showMenu && !_alwaysShowMenu) { - // don't deselect clicked entity, just show the menu. - mode.reselect().showMenu(point, _lastInteractionType); - } else { + if (!_showMenu) { // deselect clicked entity, then reenter select mode or return to browse mode.. selectedIDs = selectedIDs.filter(function(id) { return id !== datum.id; }); context.enter(selectedIDs.length ? modeSelect(context, selectedIDs) : modeBrowse(context)); } } else { - // clicked entity is not in the selected list, add it.. selectedIDs = selectedIDs.concat([datum.id]); newMode = modeSelect(context, selectedIDs); context.enter(newMode); - if (_showMenu) newMode.showMenu(point, _lastInteractionType); } } @@ -243,6 +229,11 @@ export function behaviorSelect(context) { } } + context.ui().closeEditMenu(); + + // always request to show the edit menu in case the mode needs it + if (_showMenu) context.ui().showEditMenu(point, _lastInteractionType); + resetProperties(); } diff --git a/modules/modes/browse.js b/modules/modes/browse.js index bae9dfec3..0f0af5100 100644 --- a/modules/modes/browse.js +++ b/modules/modes/browse.js @@ -8,6 +8,7 @@ import { behaviorSelect } from '../behavior/select'; import { modeDragNode } from './drag_node'; import { modeDragNote } from './drag_note'; +import { operationPaste } from '../operations/paste'; export function modeBrowse(context) { var mode = { @@ -60,5 +61,10 @@ export function modeBrowse(context) { }; + mode.operations = function() { + return [operationPaste(context)]; + }; + + return mode; } diff --git a/modules/modes/move.js b/modules/modes/move.js index 83a94146f..6a6a7e97a 100644 --- a/modules/modes/move.js +++ b/modules/modes/move.js @@ -30,12 +30,12 @@ export function modeMove(context, entityIDs, baseGraph) { var keybinding = utilKeybinding('move'); var behaviors = [ behaviorEdit(context), - operationCircularize(entityIDs, context).behavior, - operationDelete(entityIDs, context).behavior, - operationOrthogonalize(entityIDs, context).behavior, - operationReflectLong(entityIDs, context).behavior, - operationReflectShort(entityIDs, context).behavior, - operationRotate(entityIDs, context).behavior + operationCircularize(context, entityIDs).behavior, + operationDelete(context, entityIDs).behavior, + operationOrthogonalize(context, entityIDs).behavior, + operationReflectLong(context, entityIDs).behavior, + operationReflectShort(context, entityIDs).behavior, + operationRotate(context, entityIDs).behavior ]; var annotation = entityIDs.length === 1 ? t('operations.move.annotation.' + context.graph().geometry(entityIDs[0])) : diff --git a/modules/modes/rotate.js b/modules/modes/rotate.js index cec58a65e..d22bf8eee 100644 --- a/modules/modes/rotate.js +++ b/modules/modes/rotate.js @@ -34,12 +34,12 @@ export function modeRotate(context, entityIDs) { var keybinding = utilKeybinding('rotate'); var behaviors = [ behaviorEdit(context), - operationCircularize(entityIDs, context).behavior, - operationDelete(entityIDs, context).behavior, - operationMove(entityIDs, context).behavior, - operationOrthogonalize(entityIDs, context).behavior, - operationReflectLong(entityIDs, context).behavior, - operationReflectShort(entityIDs, context).behavior + operationCircularize(context, entityIDs).behavior, + operationDelete(context, entityIDs).behavior, + operationMove(context, entityIDs).behavior, + operationOrthogonalize(context, entityIDs).behavior, + operationReflectLong(context, entityIDs).behavior, + operationReflectShort(context, entityIDs).behavior ]; var annotation = entityIDs.length === 1 ? t('operations.rotate.annotation.' + context.graph().geometry(entityIDs[0])) : diff --git a/modules/modes/select.js b/modules/modes/select.js index 94598f14b..11572c71c 100644 --- a/modules/modes/select.js +++ b/modules/modes/select.js @@ -17,7 +17,6 @@ import { modeDragNode } from './drag_node'; import { modeDragNote } from './drag_note'; import { osmNode, osmWay } from '../osm'; import * as Operations from '../operations/index'; -import { uiEditMenu } from '../ui/edit_menu'; import { uiCmd } from '../ui/cmd'; import { utilArrayIntersection, utilDeepMemberSelector, utilEntityOrDeepMemberSelector, @@ -46,7 +45,6 @@ export function modeSelect(context, selectedIDs) { modeDragNote(context).behavior ]; var inspector; // unused? - var _editMenu; // uiEditMenu var _newFeature = false; var _follow = false; @@ -141,44 +139,6 @@ export function modeSelect(context, selectedIDs) { } - function closeMenu() { - // remove any existing menu no matter how it was added - context.map().supersurface - .select('.edit-menu').remove(); - } - - mode.showMenu = function(anchorPoint, triggerType) { - - // remove any displayed menu - closeMenu(); - - // disable menu if in wide selection, for example - if (!context.map().editableDataEnabled()) return; - - // don't show the menu for relations alone - if (selectedIDs.every(function(id) { - return context.graph().geometry(id) === 'relation'; - })) return; - - var surfaceNode = context.surface().node(); - if (surfaceNode.focus) { // FF doesn't support it - // focus the surface or else clicking off the menu may not trigger modeBrowse - surfaceNode.focus(); - } - - // don't load the menu until it's needed - if (!_editMenu) _editMenu = uiEditMenu(context); - - _editMenu - .anchorLoc(anchorPoint) - .triggerType(triggerType) - .operations(operations); - - // render the menu - context.map().supersurface.call(_editMenu); - }; - - mode.selectedIDs = function() { return selectedIDs; }; @@ -189,12 +149,6 @@ export function modeSelect(context, selectedIDs) { }; - mode.reselect = function() { - if (!checkSelectedIDs()) return; - return mode; - }; - - mode.newFeature = function(val) { if (!arguments.length) return _newFeature; _newFeature = val; @@ -219,12 +173,12 @@ export function modeSelect(context, selectedIDs) { }); operations = Object.values(Operations) - .map(function(o) { return o(selectedIDs, context); }) + .map(function(o) { return o(context, selectedIDs); }) .filter(function(o) { return o.available() && o.id !== 'delete' && o.id !== 'downgrade'; }); - var downgradeOperation = Operations.operationDowngrade(selectedIDs, context); + var downgradeOperation = Operations.operationDowngrade(context, selectedIDs); // don't allow delete if downgrade is available - var lastOperation = !context.inIntro() && downgradeOperation.available() ? downgradeOperation : Operations.operationDelete(selectedIDs, context); + var lastOperation = !context.inIntro() && downgradeOperation.available() ? downgradeOperation : Operations.operationDelete(context, selectedIDs); operations.push(lastOperation); @@ -235,9 +189,13 @@ export function modeSelect(context, selectedIDs) { }); // remove any displayed menu - closeMenu(); + context.ui().closeEditMenu(); } + mode.operations = function() { + return operations; + }; + mode.enter = function() { if (!checkSelectedIDs()) return; @@ -269,11 +227,10 @@ export function modeSelect(context, selectedIDs) { // reselect after change in case relation members were removed or added selectElements(); }) - .on('undone.select', update) - .on('redone.select', update); + .on('undone.select', checkSelectedIDs) + .on('redone.select', checkSelectedIDs); context.map() - .on('move.select', closeMenu) .on('drawn.select', selectElements) .on('crossEditableZoom.select', function() { selectElements(); @@ -301,12 +258,6 @@ export function modeSelect(context, selectedIDs) { } - function update() { - closeMenu(); - checkSelectedIDs(); - } - - function didDoubleUp(loc) { if (!context.map().withinEditableZoom()) return; @@ -500,7 +451,7 @@ export function modeSelect(context, selectedIDs) { d3_select(document) .call(keybinding.unbind); - closeMenu(); + context.ui().closeEditMenu(); context.history() .on('change.select', null) diff --git a/modules/operations/circularize.js b/modules/operations/circularize.js index 00aa18321..0ecbe3d1a 100644 --- a/modules/operations/circularize.js +++ b/modules/operations/circularize.js @@ -4,7 +4,7 @@ import { behaviorOperation } from '../behavior/operation'; import { utilGetAllNodes } from '../util'; -export function operationCircularize(selectedIDs, context) { +export function operationCircularize(context, selectedIDs) { var _extent; var _actions = selectedIDs.map(getAction).filter(Boolean); var _amount = _actions.length === 1 ? 'single' : 'multiple'; diff --git a/modules/operations/continue.js b/modules/operations/continue.js index d685f9e33..90bc32fb1 100644 --- a/modules/operations/continue.js +++ b/modules/operations/continue.js @@ -4,7 +4,7 @@ import { behaviorOperation } from '../behavior/operation'; import { utilArrayGroupBy } from '../util'; -export function operationContinue(selectedIDs, context) { +export function operationContinue(context, selectedIDs) { var graph = context.graph(); var entities = selectedIDs.map(function(id) { return graph.entity(id); }); var geometries = Object.assign( diff --git a/modules/operations/copy.js b/modules/operations/copy.js index 95372463d..270ddf732 100644 --- a/modules/operations/copy.js +++ b/modules/operations/copy.js @@ -5,7 +5,7 @@ import { behaviorOperation } from '../behavior/operation'; import { uiCmd } from '../ui/cmd'; import { utilArrayGroupBy } from '../util'; -export function operationCopy(selectedIDs, context) { +export function operationCopy(context, selectedIDs) { function getFilteredIdsToCopy() { return selectedIDs.filter(function(selectedID) { @@ -112,7 +112,7 @@ export function operationCopy(selectedIDs, context) { operation.annotation = function() { return selectedIDs.length === 1 ? t('operations.copy.annotation.single') : - t('operations.copy.annotation.multiple', { n: selectedIDs.length }); + t('operations.copy.annotation.multiple', { n: selectedIDs.length.toString() }); }; diff --git a/modules/operations/delete.js b/modules/operations/delete.js index f9b79f1fc..1ff66aa84 100644 --- a/modules/operations/delete.js +++ b/modules/operations/delete.js @@ -8,7 +8,7 @@ import { uiCmd } from '../ui/cmd'; import { utilGetAllNodes } from '../util'; -export function operationDelete(selectedIDs, context) { +export function operationDelete(context, selectedIDs) { var multi = (selectedIDs.length === 1 ? 'single' : 'multiple'); var action = actionDeleteMultiple(selectedIDs); var nodes = utilGetAllNodes(selectedIDs, context.graph()); diff --git a/modules/operations/disconnect.js b/modules/operations/disconnect.js index 52e34aa64..33288a29f 100644 --- a/modules/operations/disconnect.js +++ b/modules/operations/disconnect.js @@ -4,7 +4,7 @@ import { behaviorOperation } from '../behavior/operation'; import { utilGetAllNodes } from '../util/index'; -export function operationDisconnect(selectedIDs, context) { +export function operationDisconnect(context, selectedIDs) { var vertexIDs = []; var wayIDs = []; var otherIDs = []; diff --git a/modules/operations/downgrade.js b/modules/operations/downgrade.js index 2b76a0f2b..31c374594 100644 --- a/modules/operations/downgrade.js +++ b/modules/operations/downgrade.js @@ -5,7 +5,7 @@ import { t } from '../core/localizer'; import { uiCmd } from '../ui/cmd'; import { presetManager } from '../presets'; -export function operationDowngrade(selectedIDs, context) { +export function operationDowngrade(context, selectedIDs) { var affectedFeatureCount = 0; var downgradeType; diff --git a/modules/operations/extract.js b/modules/operations/extract.js index c71c087f4..be0409d84 100644 --- a/modules/operations/extract.js +++ b/modules/operations/extract.js @@ -4,7 +4,7 @@ import { modeSelect } from '../modes/select'; import { t } from '../core/localizer'; import { presetManager } from '../presets'; -export function operationExtract(selectedIDs, context) { +export function operationExtract(context, selectedIDs) { var entityID = selectedIDs.length && selectedIDs[0]; var action = actionExtract(entityID); diff --git a/modules/operations/index.js b/modules/operations/index.js index 6584fa294..c76dd5a1f 100644 --- a/modules/operations/index.js +++ b/modules/operations/index.js @@ -8,6 +8,7 @@ export { operationExtract } from './extract'; export { operationMerge } from './merge'; export { operationMove } from './move'; export { operationOrthogonalize } from './orthogonalize'; +export { operationPaste } from './paste'; export { operationReflectShort, operationReflectLong } from './reflect'; export { operationReverse } from './reverse'; export { operationRotate } from './rotate'; diff --git a/modules/operations/merge.js b/modules/operations/merge.js index da442715b..de4ba276f 100644 --- a/modules/operations/merge.js +++ b/modules/operations/merge.js @@ -9,7 +9,7 @@ import { behaviorOperation } from '../behavior/operation'; import { modeSelect } from '../modes/select'; import { presetManager } from '../presets'; -export function operationMerge(selectedIDs, context) { +export function operationMerge(context, selectedIDs) { var join = actionJoin(selectedIDs); var merge = actionMerge(selectedIDs); diff --git a/modules/operations/move.js b/modules/operations/move.js index 8f1e1f1ed..b2ae57016 100644 --- a/modules/operations/move.js +++ b/modules/operations/move.js @@ -5,7 +5,7 @@ import { modeMove } from '../modes/move'; import { utilGetAllNodes } from '../util'; -export function operationMove(selectedIDs, context) { +export function operationMove(context, selectedIDs) { var multi = (selectedIDs.length === 1 ? 'single' : 'multiple'); var nodes = utilGetAllNodes(selectedIDs, context.graph()); var coords = nodes.map(function(n) { return n.loc; }); diff --git a/modules/operations/orthogonalize.js b/modules/operations/orthogonalize.js index 68d362d43..d5ad69d17 100644 --- a/modules/operations/orthogonalize.js +++ b/modules/operations/orthogonalize.js @@ -4,7 +4,7 @@ import { behaviorOperation } from '../behavior/operation'; import { utilGetAllNodes } from '../util'; -export function operationOrthogonalize(selectedIDs, context) { +export function operationOrthogonalize(context, selectedIDs) { var _extent; var _type; var _actions = selectedIDs.map(chooseAction).filter(Boolean); diff --git a/modules/operations/paste.js b/modules/operations/paste.js new file mode 100644 index 000000000..8b36b3f61 --- /dev/null +++ b/modules/operations/paste.js @@ -0,0 +1,95 @@ + +import { actionCopyEntities } from '../actions/copy_entities'; +import { actionMove } from '../actions/move'; +import { modeSelect } from '../modes/select'; +import { geoExtent, geoVecSubtract } from '../geo'; +import { t } from '../core/localizer'; +import { uiCmd } from '../ui/cmd'; +import { utilDisplayLabel } from '../util/util'; + +// see also `behaviorPaste` +export function operationPaste(context) { + + var _point; + + var operation = function() { + + if (!_point) return; + + var oldIDs = context.copyIDs(); + if (!oldIDs.length) return; + + var projection = context.projection; + var extent = geoExtent(); + var oldGraph = context.copyGraph(); + var newIDs = []; + + var action = actionCopyEntities(oldIDs, oldGraph); + context.perform(action); + + var copies = action.copies(); + var originals = new Set(); + Object.values(copies).forEach(function(entity) { originals.add(entity.id); }); + + for (var id in copies) { + var oldEntity = oldGraph.entity(id); + var newEntity = copies[id]; + + extent._extend(oldEntity.extent(oldGraph)); + + // Exclude child nodes from newIDs if their parent way was also copied. + var parents = context.graph().parentWays(newEntity); + var parentCopied = parents.some(function(parent) { + return originals.has(parent.id); + }); + + if (!parentCopied) { + newIDs.push(newEntity.id); + } + } + + // Put pasted objects where mouse pointer is.. + var center = projection(extent.center()); + var delta = geoVecSubtract(_point, center); + + context.replace(actionMove(newIDs, delta, projection), operation.annotation()); + context.enter(modeSelect(context, newIDs)); + }; + + operation.point = function(val) { + _point = val; + return operation; + }; + + operation.available = function() { + return context.mode().id === 'browse'; + }; + + operation.disabled = function() { + return !context.copyIDs().length; + }; + + operation.tooltip = function() { + var oldGraph = context.copyGraph(); + var ids = context.copyIDs(); + if (!ids.length) { + return t('operations.paste.nothing_copied'); + } + return ids.length === 1 ? + t('operations.paste.description.single', { feature: utilDisplayLabel(oldGraph.entity(ids[0]), oldGraph) }) : + t('operations.paste.description.multiple', { n: ids.length.toString() }); + }; + + operation.annotation = function() { + var ids = context.copyIDs(); + return ids.length === 1 ? + t('operations.paste.annotation.single') : + t('operations.paste.annotation.multiple', { n: ids.length.toString() }); + }; + + operation.id = 'paste'; + operation.keys = [uiCmd('โŒ˜V')]; + operation.title = t('operations.paste.title'); + + return operation; +} diff --git a/modules/operations/reflect.js b/modules/operations/reflect.js index 2aebcb1b6..22b7534b6 100644 --- a/modules/operations/reflect.js +++ b/modules/operations/reflect.js @@ -5,17 +5,17 @@ import { geoExtent } from '../geo'; import { utilGetAllNodes } from '../util'; -export function operationReflectShort(selectedIDs, context) { - return operationReflect(selectedIDs, context, 'short'); +export function operationReflectShort(context, selectedIDs) { + return operationReflect(context, selectedIDs, 'short'); } -export function operationReflectLong(selectedIDs, context) { - return operationReflect(selectedIDs, context, 'long'); +export function operationReflectLong(context, selectedIDs) { + return operationReflect(context, selectedIDs, 'long'); } -export function operationReflect(selectedIDs, context, axis) { +export function operationReflect(context, selectedIDs, axis) { axis = axis || 'long'; var multi = (selectedIDs.length === 1 ? 'single' : 'multiple'); var nodes = utilGetAllNodes(selectedIDs, context.graph()); diff --git a/modules/operations/reverse.js b/modules/operations/reverse.js index bcc0a9320..16e854826 100644 --- a/modules/operations/reverse.js +++ b/modules/operations/reverse.js @@ -3,7 +3,7 @@ import { actionReverse } from '../actions/reverse'; import { behaviorOperation } from '../behavior/operation'; -export function operationReverse(selectedIDs, context) { +export function operationReverse(context, selectedIDs) { var operation = function() { context.perform(function combinedReverseAction(graph) { diff --git a/modules/operations/rotate.js b/modules/operations/rotate.js index 03aa25460..61ee33a1f 100644 --- a/modules/operations/rotate.js +++ b/modules/operations/rotate.js @@ -5,7 +5,7 @@ import { modeRotate } from '../modes/rotate'; import { utilGetAllNodes } from '../util'; -export function operationRotate(selectedIDs, context) { +export function operationRotate(context, selectedIDs) { var multi = (selectedIDs.length === 1 ? 'single' : 'multiple'); var nodes = utilGetAllNodes(selectedIDs, context.graph()); var coords = nodes.map(function(n) { return n.loc; }); diff --git a/modules/operations/split.js b/modules/operations/split.js index fb0444f0f..14f7cf496 100644 --- a/modules/operations/split.js +++ b/modules/operations/split.js @@ -4,7 +4,7 @@ import { behaviorOperation } from '../behavior/operation'; import { modeSelect } from '../modes/select'; -export function operationSplit(selectedIDs, context) { +export function operationSplit(context, selectedIDs) { var vertices = selectedIDs .filter(function(id) { return context.graph().geometry(id) === 'vertex'; }); var entityID = vertices[0]; diff --git a/modules/operations/straighten.js b/modules/operations/straighten.js index 09a5d63cf..51951d500 100644 --- a/modules/operations/straighten.js +++ b/modules/operations/straighten.js @@ -5,7 +5,7 @@ import { behaviorOperation } from '../behavior/operation'; import { utilArrayDifference, utilGetAllNodes } from '../util/index'; -export function operationStraighten(selectedIDs, context) { +export function operationStraighten(context, selectedIDs) { var wayIDs = selectedIDs.filter(function(id) { return id.charAt(0) === 'w'; }); var nodeIDs = selectedIDs.filter(function(id) { return id.charAt(0) === 'n'; }); diff --git a/modules/ui/init.js b/modules/ui/init.js index a5525eaea..0e9eaabb8 100644 --- a/modules/ui/init.js +++ b/modules/ui/init.js @@ -14,6 +14,7 @@ import { utilGetDimensions } from '../util/dimensions'; import { uiAccount } from './account'; import { uiAttribution } from './attribution'; import { uiContributors } from './contributors'; +import { uiEditMenu } from './edit_menu'; import { uiFeatureInfo } from './feature_info'; import { uiFlash } from './flash'; import { uiFullScreen } from './full_screen'; @@ -548,6 +549,48 @@ export function uiInit(context) { }; + var _editMenu; // uiEditMenu + + ui.showEditMenu = function(anchorPoint, triggerType, operations) { + + // remove any displayed menu + ui.closeEditMenu(); + + if (!operations && context.mode().operations) operations = context.mode().operations(); + if (!operations || !operations.length) return; + + // disable menu if in wide selection, for example + if (!context.map().editableDataEnabled()) return; + + var surfaceNode = context.surface().node(); + if (surfaceNode.focus) { // FF doesn't support it + // focus the surface or else clicking off the menu may not trigger modeBrowse + surfaceNode.focus(); + } + + // don't load the menu until it's needed + if (!_editMenu) _editMenu = uiEditMenu(context); + + operations.forEach(function(operation) { + if (operation.point) operation.point(anchorPoint); + }); + + _editMenu + .anchorLoc(anchorPoint) + .triggerType(triggerType) + .operations(operations); + + // render the menu + context.map().supersurface.call(_editMenu); + }; + + ui.closeEditMenu = function() { + // remove any existing menu no matter how it was added + context.map().supersurface + .select('.edit-menu').remove(); + }; + + var _saveLoading = d3_select(null); context.uploader() diff --git a/modules/ui/preset_list.js b/modules/ui/preset_list.js index d3cca64b8..8e6aa8629 100644 --- a/modules/ui/preset_list.js +++ b/modules/ui/preset_list.js @@ -53,7 +53,7 @@ export function uiPresetList(context) { d3_event.keyCode === utilKeybinding.keyCodes['โŒฆ'])) { d3_event.preventDefault(); d3_event.stopPropagation(); - operationDelete(_entityIDs, context)(); + operationDelete(context, _entityIDs)(); // hack to let undo work when search is autofocused } else if (search.property('value').length === 0 && diff --git a/modules/validations/disconnected_way.js b/modules/validations/disconnected_way.js index b895ca5ae..4f9afda8e 100644 --- a/modules/validations/disconnected_way.js +++ b/modules/validations/disconnected_way.js @@ -65,7 +65,7 @@ export function validationDisconnectedWay() { entityIds: [singleEntity.id], onClick: function(context) { var id = this.issue.entityIds[0]; - var operation = operationDelete([id], context); + var operation = operationDelete(context, [id]); if (!operation.disabled()) { operation(); } diff --git a/modules/validations/missing_tag.js b/modules/validations/missing_tag.js index efc7610b0..460cd352a 100644 --- a/modules/validations/missing_tag.js +++ b/modules/validations/missing_tag.js @@ -107,12 +107,12 @@ export function validationMissingTag(context) { var deleteOnClick; var id = this.entityIds[0]; - var operation = operationDelete([id], context); + var operation = operationDelete(context, [id]); var disabledReasonID = operation.disabled(); if (!disabledReasonID) { deleteOnClick = function(context) { var id = this.issue.entityIds[0]; - var operation = operationDelete([id], context); + var operation = operationDelete(context, [id]); if (!operation.disabled()) { operation(); } diff --git a/svg/iD-sprite/operations/operation-paste.svg b/svg/iD-sprite/operations/operation-paste.svg index 68f1e37c4..ee898b03f 100644 --- a/svg/iD-sprite/operations/operation-paste.svg +++ b/svg/iD-sprite/operations/operation-paste.svg @@ -2,6 +2,6 @@ - + diff --git a/test/spec/operations/extract.js b/test/spec/operations/extract.js index 3985e51d4..bc9727cdf 100644 --- a/test/spec/operations/extract.js +++ b/test/spec/operations/extract.js @@ -37,52 +37,52 @@ describe('iD.operationExtract', function () { }); it('is not available for no selected ids', function () { - var result = iD.operationExtract([], fakeContext).available(); + var result = iD.operationExtract(fakeContext, []).available(); expect(result).to.be.not.ok; }); it('is not available for two selected ids', function () { - var result = iD.operationExtract(['a', 'b'], fakeContext).available(); + var result = iD.operationExtract(fakeContext, ['a', 'b']).available(); expect(result).to.be.not.ok; }); it('is not available for unknown selected id', function () { - var result = iD.operationExtract(['z'], fakeContext).available(); + var result = iD.operationExtract(fakeContext, ['z']).available(); expect(result).to.be.not.ok; }); it('is not available for selected way', function () { - var result = iD.operationExtract(['x'], fakeContext).available(); + var result = iD.operationExtract(fakeContext, ['x']).available(); expect(result).to.be.not.ok; }); it('is not available for selected node with tags, no parent way', function () { - var result = iD.operationExtract(['e'], fakeContext).available(); + var result = iD.operationExtract(fakeContext, ['e']).available(); expect(result).to.be.not.ok; }); it('is not available for selected node with no tags, no parent way', function () { - var result = iD.operationExtract(['f'], fakeContext).available(); + var result = iD.operationExtract(fakeContext, ['f']).available(); expect(result).to.be.not.ok; }); it('is not available for selected node with no tags, parent way', function () { - var result = iD.operationExtract(['c'], fakeContext).available(); + var result = iD.operationExtract(fakeContext, ['c']).available(); expect(result).to.be.not.ok; }); it('is not available for selected node with no tags, two parent ways', function () { - var result = iD.operationExtract(['d'], fakeContext).available(); + var result = iD.operationExtract(fakeContext, ['d']).available(); expect(result).to.be.not.ok; }); it('is available for selected node with tags, parent way', function () { - var result = iD.operationExtract(['a'], fakeContext).available(); + var result = iD.operationExtract(fakeContext, ['a']).available(); expect(result).to.be.ok; }); it('is available for selected node with tags, two parent ways', function () { - var result = iD.operationExtract(['b'], fakeContext).available(); + var result = iD.operationExtract(fakeContext, ['b']).available(); expect(result).to.be.ok; }); }); @@ -96,7 +96,7 @@ describe('iD.operationExtract', function () { iD.osmNode(createFakeNode('c', false)), iD.osmWay({ id: 'x', nodes: ['a', 'b', 'c'] }) ]); - var result = iD.operationExtract(['b'], fakeContext).disabled(); + var result = iD.operationExtract(fakeContext, ['b']).disabled(); expect(result).to.be.not.ok; }); @@ -108,7 +108,7 @@ describe('iD.operationExtract', function () { iD.osmWay({ id: 'x', nodes: ['a', 'b', 'c'] }), iD.osmRelation({ id: 'r', members: [{ id: 'b', role: 'label' }] }) ]); - var result = iD.operationExtract(['b'], fakeContext).disabled(); + var result = iD.operationExtract(fakeContext, ['b']).disabled(); expect(result).to.be.not.ok; }); @@ -133,7 +133,7 @@ describe('iD.operationExtract', function () { ] }) ]); - var result = iD.operationExtract(['d'], fakeContext).disabled(); + var result = iD.operationExtract(fakeContext, ['d']).disabled(); expect(result).to.eql('restriction'); }); @@ -159,7 +159,7 @@ describe('iD.operationExtract', function () { ] }) ]); - var result = iD.operationExtract(['d'], fakeContext).disabled(); + var result = iD.operationExtract(fakeContext, ['d']).disabled(); expect(result).to.eql('restriction'); }); }); diff --git a/test/spec/operations/straighten.js b/test/spec/operations/straighten.js index 5d62cf229..f3ac89d85 100644 --- a/test/spec/operations/straighten.js +++ b/test/spec/operations/straighten.js @@ -42,77 +42,77 @@ describe('iD.operationStraighten', function () { }); it('is not available for no selected ids', function () { - var result = iD.operationStraighten([], fakeContext).available(); + var result = iD.operationStraighten(fakeContext, []).available(); expect(result).to.be.not.ok; }); it('is not available for way with only 2 nodes', function () { - var result = iD.operationStraighten(['w1'], fakeContext).available(); + var result = iD.operationStraighten(fakeContext, ['w1']).available(); expect(result).to.be.not.ok; }); it('is available for way with only 2 nodes connected to another 2-node way', function () { - var result = iD.operationStraighten(['w1', 'w1-2'], fakeContext).available(); + var result = iD.operationStraighten(fakeContext, ['w1', 'w1-2']).available(); expect(result).to.be.ok; }); it('is not available for non-continuous ways', function () { - var result = iD.operationStraighten(['w2', 'w4'], fakeContext).available(); + var result = iD.operationStraighten(fakeContext, ['w2', 'w4']).available(); expect(result).to.be.not.ok; }); it('is available for selected way with more than 2 nodes', function () { - var result = iD.operationStraighten(['w2'], fakeContext).available(); + var result = iD.operationStraighten(fakeContext, ['w2']).available(); expect(result).to.be.ok; }); it('is available for selected, ordered, continuous ways', function () { - var result = iD.operationStraighten(['w1', 'w2', 'w3'], fakeContext).available(); + var result = iD.operationStraighten(fakeContext, ['w1', 'w2', 'w3']).available(); expect(result).to.be.ok; }); it('is available for selected, un-ordered, continuous ways', function () { - var result = iD.operationStraighten(['w1', 'w3', 'w2'], fakeContext).available(); + var result = iD.operationStraighten(fakeContext, ['w1', 'w3', 'w2']).available(); expect(result).to.be.ok; }); it('is available for selected, continuous ways with different way-directions', function () { - var result = iD.operationStraighten(['w1', 'w3', 'w2-2'], fakeContext).available(); + var result = iD.operationStraighten(fakeContext, ['w1', 'w3', 'w2-2']).available(); expect(result).to.be.ok; }); it('is available for 2 selected nodes in the same way, more than one node apart', function () { - var result = iD.operationStraighten(['w5', 'n9', 'n11'], fakeContext).available(); + var result = iD.operationStraighten(fakeContext, ['w5', 'n9', 'n11']).available(); expect(result).to.be.ok; }); it('is available for 2 selected nodes in adjacent ways, more than one node apart', function () { - var result = iD.operationStraighten(['w2', 'w3', 'n5', 'n3'], fakeContext).available(); + var result = iD.operationStraighten(fakeContext, ['w2', 'w3', 'n5', 'n3']).available(); expect(result).to.be.ok; }); it('is available for 2 selected nodes in non-adjacent ways, providing inbetween ways are selected', function () { - var result = iD.operationStraighten(['n2', 'n7', 'w4', 'w1', 'w3', 'w2'], fakeContext).available(); + var result = iD.operationStraighten(fakeContext, ['n2', 'n7', 'w4', 'w1', 'w3', 'w2']).available(); expect(result).to.be.ok; }); it('is available for 2 selected nodes in non-adjacent, non-same-directional ways, providing inbetween ways are selected', function () { - var result = iD.operationStraighten(['n2', 'n7', 'w4', 'w1', 'w3', 'w2-2'], fakeContext).available(); + var result = iD.operationStraighten(fakeContext, ['n2', 'n7', 'w4', 'w1', 'w3', 'w2-2']).available(); expect(result).to.be.ok; }); it('is not available for nodes not on selected ways', function () { - var result = iD.operationStraighten(['w5', 'n4', 'n11'], fakeContext).available(); + var result = iD.operationStraighten(fakeContext, ['w5', 'n4', 'n11']).available(); expect(result).to.be.not.ok; }); it('is not available for one selected node', function () { - var result = iD.operationStraighten(['w5', 'n9'], fakeContext).available(); + var result = iD.operationStraighten(fakeContext, ['w5', 'n9']).available(); expect(result).to.be.not.ok; }); it('is not available for more than two selected nodes', function () { - var result = iD.operationStraighten(['w5', 'n9', 'n11', 'n12'], fakeContext).available(); + var result = iD.operationStraighten(fakeContext, ['w5', 'n9', 'n11', 'n12']).available(); expect(result).to.be.not.ok; }); }); From 834b8b4d6cc57ac7d266b8863fa2868fdc2aeec8 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Thu, 14 May 2020 16:56:29 -0400 Subject: [PATCH 35/67] Fix issue where dynamic tooltips titles might not get updated --- modules/ui/tooltip.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/ui/tooltip.js b/modules/ui/tooltip.js index 304995139..443e3ad9f 100644 --- a/modules/ui/tooltip.js +++ b/modules/ui/tooltip.js @@ -57,6 +57,7 @@ export function uiTooltip(klass) { headingSelect.enter() .append('div') .attr('class', 'tooltip-heading') + .merge(headingSelect) .html(heading); var textSelect = selection @@ -69,6 +70,7 @@ export function uiTooltip(klass) { textSelect.enter() .append('div') .attr('class', 'tooltip-text') + .merge(textSelect) .html(text); var keyhintWrap = selection From 21ed04ed65ddbaf36d233c6ecc6ad29194b09056 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Fri, 15 May 2020 09:51:50 -0400 Subject: [PATCH 36/67] Limit Copy operation by visible extent (close #7603) --- data/core.yaml | 3 +++ dist/locales/en.json | 4 ++++ modules/operations/copy.js | 19 +++++++++++++++---- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/data/core.yaml b/data/core.yaml index bd98e79a4..53704d40c 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -88,6 +88,9 @@ en: annotation: single: Copied a feature. multiple: "Copied {n} features." + too_large: + single: This can't be copied because not enough of it is currently visible. + multiple: These can't be copied because not enough of them are currently visible. paste: title: Paste description: diff --git a/dist/locales/en.json b/dist/locales/en.json index e1aa33cb7..f50319afe 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -112,6 +112,10 @@ "annotation": { "single": "Copied a feature.", "multiple": "Copied {n} features." + }, + "too_large": { + "single": "This can't be copied because not enough of it is currently visible.", + "multiple": "These can't be copied because not enough of them are currently visible." } }, "paste": { diff --git a/modules/operations/copy.js b/modules/operations/copy.js index 270ddf732..bd81c8943 100644 --- a/modules/operations/copy.js +++ b/modules/operations/copy.js @@ -3,10 +3,13 @@ import { event as d3_event } from 'd3-selection'; import { t } from '../core/localizer'; import { behaviorOperation } from '../behavior/operation'; import { uiCmd } from '../ui/cmd'; -import { utilArrayGroupBy } from '../util'; +import { geoExtent } from '../geo'; +import { utilArrayGroupBy, utilGetAllNodes } from '../util'; export function operationCopy(context, selectedIDs) { + var _multi = selectedIDs.length === 1 ? 'single' : 'multiple'; + function getFilteredIdsToCopy() { return selectedIDs.filter(function(selectedID) { var entity = context.graph().hasEntity(selectedID); @@ -98,14 +101,22 @@ export function operationCopy(context, selectedIDs) { operation.disabled = function() { + var nodes = utilGetAllNodes(getFilteredIdsToCopy(), context.graph()); + var extent = nodes.reduce(function(extent, node) { + return extent.extend(node.extent(context.graph())); + }, geoExtent()); + if (extent.area() && extent.percentContainedIn(context.map().extent()) < 0.8) { + return 'too_large'; + } return false; }; operation.tooltip = function() { - return selectedIDs.length === 1 ? - t('operations.copy.description.single') : - t('operations.copy.description.multiple'); + var disable = operation.disabled(); + return disable ? + t('operations.copy.' + disable + '.' + _multi) : + t('operations.copy.description' + '.' + _multi); }; From 91eaf15ba567c6e0dec322fe5fe8d60f67e29ac2 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Fri, 15 May 2020 09:59:27 -0400 Subject: [PATCH 37/67] Update copy operation description --- data/core.yaml | 4 ++-- dist/locales/en.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/core.yaml b/data/core.yaml index 53704d40c..e769580d7 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -83,8 +83,8 @@ en: copy: title: Copy description: - single: Make this feature pasteable. - multiple: Make these features pasteable. + single: Set this feature for pasting. + multiple: Set these features for pasting. annotation: single: Copied a feature. multiple: "Copied {n} features." diff --git a/dist/locales/en.json b/dist/locales/en.json index f50319afe..ab8a81be5 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -106,8 +106,8 @@ "copy": { "title": "Copy", "description": { - "single": "Make this feature pasteable.", - "multiple": "Make these features pasteable." + "single": "Set this feature for pasting.", + "multiple": "Set these features for pasting." }, "annotation": { "single": "Copied a feature.", From 084d9336c4705d3fbb26616b36315b70ed576a87 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Fri, 15 May 2020 10:55:23 -0400 Subject: [PATCH 38/67] Anchor pasted features to the position they were copied if done so with the edit menu (re: #2508) --- modules/behavior/operation.js | 1 + modules/behavior/paste.js | 4 ++-- modules/core/context.js | 7 +++++++ modules/operations/copy.js | 15 +++++++++++++++ modules/operations/paste.js | 15 +++++++++------ 5 files changed, 34 insertions(+), 8 deletions(-) diff --git a/modules/behavior/operation.js b/modules/behavior/operation.js index a3456c674..c4aa722a0 100644 --- a/modules/behavior/operation.js +++ b/modules/behavior/operation.js @@ -30,6 +30,7 @@ export function behaviorOperation(context) { .text(_operation.annotation() || _operation.title); flash(); + if (_operation.point) _operation.point(null); _operation(); } } diff --git a/modules/behavior/paste.js b/modules/behavior/paste.js index f76cffee2..a31a43d36 100644 --- a/modules/behavior/paste.js +++ b/modules/behavior/paste.js @@ -54,8 +54,8 @@ export function behaviorPaste(context) { } // Put pasted objects where mouse pointer is.. - var center = projection(extent.center()); - var delta = geoVecSubtract(mouse, center); + var copyPoint = (context.copyLonLat() && projection(context.copyLonLat())) || projection(extent.center()); + var delta = geoVecSubtract(mouse, copyPoint); context.perform(actionMove(newIDs, delta, projection)); context.enter(modeMove(context, newIDs, baseGraph)); diff --git a/modules/core/context.js b/modules/core/context.js index 0cba035db..13d9d6b52 100644 --- a/modules/core/context.js +++ b/modules/core/context.js @@ -282,6 +282,13 @@ export function coreContext() { return context; }; + let _copyLonLat; + context.copyLonLat = function(val) { + if (!arguments.length) return _copyLonLat; + _copyLonLat = val; + return context; + }; + /* Background */ let _background; diff --git a/modules/operations/copy.js b/modules/operations/copy.js index bd81c8943..a58724169 100644 --- a/modules/operations/copy.js +++ b/modules/operations/copy.js @@ -53,6 +53,14 @@ export function operationCopy(context, selectedIDs) { } context.copyIDs(canCopy); + if (_point && + (canCopy.length !== 1 || graph.entity(canCopy[0]).type !== 'node')) { + // store the anchor coordinates if copying more than a single node + context.copyLonLat(context.projection.invert(_point)); + } else { + context.copyLonLat(null); + } + }; @@ -127,6 +135,13 @@ export function operationCopy(context, selectedIDs) { }; + var _point; + operation.point = function(val) { + _point = val; + return operation; + }; + + operation.id = 'copy'; operation.keys = [uiCmd('โŒ˜C')]; operation.title = t('operations.copy.title'); diff --git a/modules/operations/paste.js b/modules/operations/paste.js index 8b36b3f61..297465346 100644 --- a/modules/operations/paste.js +++ b/modules/operations/paste.js @@ -10,11 +10,11 @@ import { utilDisplayLabel } from '../util/util'; // see also `behaviorPaste` export function operationPaste(context) { - var _point; + var _pastePoint; var operation = function() { - if (!_point) return; + if (!_pastePoint) return; var oldIDs = context.copyIDs(); if (!oldIDs.length) return; @@ -48,16 +48,19 @@ export function operationPaste(context) { } } - // Put pasted objects where mouse pointer is.. - var center = projection(extent.center()); - var delta = geoVecSubtract(_point, center); + // Use the location of the copy operation to offset the paste location, + // or else use the center of the pasted extent + var copyPoint = (context.copyLonLat() && projection(context.copyLonLat())) || + projection(extent.center()); + var delta = geoVecSubtract(_pastePoint, copyPoint); + // Move the pasted objects to be anchored at the paste location context.replace(actionMove(newIDs, delta, projection), operation.annotation()); context.enter(modeSelect(context, newIDs)); }; operation.point = function(val) { - _point = val; + _pastePoint = val; return operation; }; From e079e120bf1684242b2001e314c4b14c75407cdf Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Fri, 15 May 2020 11:31:36 -0400 Subject: [PATCH 39/67] Make behaviorDrag ignore non-active pointers (re: #6745) --- modules/behavior/drag.js | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/modules/behavior/drag.js b/modules/behavior/drag.js index 879ef55dc..3d222c3d4 100644 --- a/modules/behavior/drag.js +++ b/modules/behavior/drag.js @@ -34,6 +34,7 @@ export function behaviorDrag() { var _event; var _target; var _surface; + var _pointerId; // use pointer events on supported platforms; fallback to mouse events var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse'; @@ -49,12 +50,6 @@ export function behaviorDrag() { }; - function d3_eventCancel() { - d3_event.stopPropagation(); - d3_event.preventDefault(); - } - - function eventOf(thiz, argumentz) { return function(e1) { e1.target = behavior; @@ -64,13 +59,17 @@ export function behaviorDrag() { function pointerdown() { + + if (_pointerId) return; + + _pointerId = d3_event.pointerId || 'mouse'; + _target = this; _event = eventOf(_target, arguments); // only force reflow once per drag var pointerLocGetter = utilFastMouse(_surface || _target.parentNode); - var eventTarget = d3_event.target; var offset; var startOrigin = pointerLocGetter(d3_event); var started = false; @@ -91,6 +90,8 @@ export function behaviorDrag() { function pointermove() { + if (_pointerId !== (d3_event.pointerId || 'mouse')) return; + var p = pointerLocGetter(d3_event); var dx = p[0] - startOrigin[0]; var dy = p[1] - startOrigin[1]; @@ -99,7 +100,8 @@ export function behaviorDrag() { return; startOrigin = p; - d3_eventCancel(); + d3_event.stopPropagation(); + d3_event.preventDefault(); if (!started) { started = true; @@ -115,14 +117,15 @@ export function behaviorDrag() { function pointerup() { + if (_pointerId !== (d3_event.pointerId || 'mouse')) return; + + _pointerId = null; + if (started) { _event({ type: 'end' }); - d3_eventCancel(); - if (d3_event.target === eventTarget) { - d3_select(window) - .on('click.drag', click, true); - } + d3_event.stopPropagation(); + d3_event.preventDefault(); } d3_select(window) @@ -131,17 +134,11 @@ export function behaviorDrag() { selectEnable(); } - - - function click() { - d3_eventCancel(); - d3_select(window) - .on('click.drag', null); - } } function behavior(selection) { + _pointerId = null; var matchesSelector = utilPrefixDOMProperty('matchesSelector'); var delegate = pointerdown; From 1f45ad933a70f82722bc4ec05daad1e86d8215ef Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Fri, 15 May 2020 12:12:43 -0400 Subject: [PATCH 40/67] Don't show hover tooltips for non-mouse pointerenter events (re: #6035) --- modules/ui/popover.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/ui/popover.js b/modules/ui/popover.js index 4fe4205d4..bfbcb725a 100644 --- a/modules/ui/popover.js +++ b/modules/ui/popover.js @@ -152,8 +152,14 @@ export function uiPopover(klass) { var display = _displayType.apply(this, arguments); if (display === 'hover') { - anchor.on(_pointerPrefix + 'enter.popover', show); - anchor.on(_pointerPrefix + 'leave.popover', hide); + anchor.on(_pointerPrefix + 'enter.popover', function() { + if (d3_event.pointerType && d3_event.pointerType !== 'mouse') return; + show.apply(this, arguments); + }); + anchor.on(_pointerPrefix + 'leave.popover', function() { + if (d3_event.pointerType && d3_event.pointerType !== 'mouse') return; + hide.apply(this, arguments); + }); } else if (display === 'clickFocus') { anchor From 7aedb396a93f674c61ab6031b5bef83c0b194ea9 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Fri, 15 May 2020 13:32:53 -0400 Subject: [PATCH 41/67] Don't persist hover styles on touchscreens (close #7432) --- css/60_photos.css | 51 ++++++-- css/80_app.css | 325 ++++++++++++++++++++++++++++++++++------------ 2 files changed, 282 insertions(+), 94 deletions(-) diff --git a/css/60_photos.css b/css/60_photos.css index 8f1935c57..48f08a089 100644 --- a/css/60_photos.css +++ b/css/60_photos.css @@ -211,16 +211,26 @@ .layer-mapillary-detections .icon-detected rect { fill: none; } -.layer-mapillary-detections .icon-detected:hover rect { +.layer-mapillary-detections .icon-detected:active { + opacity: 1; +} +.layer-mapillary-detections .icon-detected:active rect { outline: 3px solid rgba(255, 238, 0, 0.6); } +@media (hover: hover) { + .layer-mapillary-detections .icon-detected:hover { + opacity: 1; + } + .layer-mapillary-detections .icon-detected:hover rect { + outline: 3px solid rgba(255, 238, 0, 0.6); + } +} +.layer-mapillary-detections .icon-detected.currentView { + opacity: 1; +} .layer-mapillary-detections .icon-detected.currentView rect { outline: 3px solid rgba(255, 238, 0, 1); } -.layer-mapillary-detections .icon-detected:hover, -.layer-mapillary-detections .icon-detected.currentView { - opacity: 1; -} /* OpenStreetCam Image Layer */ @@ -254,10 +264,14 @@ text-align: right; } -.ms-wrapper .photo-attribution a:active, -.ms-wrapper .photo-attribution a:hover { +.ms-wrapper .photo-attribution a:active { color: #0fffc4; } +@media (hover: hover) { + .ms-wrapper .photo-attribution a:hover { + color: #0fffc4; + } +} .ms-wrapper .pnlm-compass.pnlm-control { width: 26px; @@ -295,10 +309,14 @@ label.streetside-hires { display: none; } -.mly-wrapper .photo-attribution a:active, -.mly-wrapper .photo-attribution a:hover { +.mly-wrapper .photo-attribution a:active { color: #35af6d; } +@media (hover: hover) { + .mly-wrapper .photo-attribution a:hover { + color: #35af6d; + } +} .mly-wrapper .mapillary-js-dom { z-index: 9; @@ -314,10 +332,14 @@ label.streetside-hires { background-repeat: no-repeat; } -.osc-wrapper .photo-attribution a:active, -.osc-wrapper .photo-attribution a:hover { +.osc-wrapper .photo-attribution a:active { color: #20c4ff; } +@media (hover: hover) { + .osc-wrapper .photo-attribution a:hover { + color: #20c4ff; + } +} .osc-image-wrap { width: 100%; @@ -358,8 +380,13 @@ label.streetside-hires { .photo-controls button:last-of-type { border-radius: 0 3px 3px 0; } -.photo-controls button:hover, .photo-controls button:active { background: rgba(0,0,0,0.85); color: #fff; } +@media (hover: hover) { + .photo-controls button:hover { + background: rgba(0,0,0,0.85); + color: #fff; + } +} diff --git a/css/80_app.css b/css/80_app.css index 4bfc9aedf..2c4bbd1c4 100644 --- a/css/80_app.css +++ b/css/80_app.css @@ -126,11 +126,15 @@ em { strong { font-weight: bold; } -a:visited, a { +a, +a:visited, +a:active { color: #7092ff; } -a:hover { - color: #597be7; +@media (hover: hover) { + a:hover { + color: #597be7; + } } kbd { display: inline-block; @@ -183,6 +187,8 @@ input[type=email] { padding: 5px 10px 5px 20px; } +textarea:active, +input:active, textarea:focus, input:focus { background-color: #f1f1f1; @@ -308,9 +314,14 @@ button { } button:focus, -button:hover { +button:active { background-color: #ececec; } +@media (hover: hover) { + button:hover { + background-color: #ececec; + } +} button.active { background: #7092ff; } @@ -353,26 +364,39 @@ button.action { color: #fff; } button.action:focus, -button.action:hover { +button.action:active { background: #597be7; } button.secondary-action { background: #ececec; } button.secondary-action:focus, -button.secondary-action:hover { +button.secondary-action:active { background: #cccccc; } button.action.disabled, -button.action.disabled:hover, -button[disabled].action, -button[disabled].action:hover { +button[disabled].action { background: #cccccc; color: #888; cursor: not-allowed; } +@media (hover: hover) { + button.action:hover { + background: #597be7; + } + button.secondary-action:hover { + background: #cccccc; + } + button.action.disabled:hover, + button[disabled].action:hover { + background: #cccccc; + color: #888; + cursor: not-allowed; + } +} + /* Icons ------------------------------------------------------- */ @@ -897,10 +921,14 @@ a.hide-toggle { .ideditor[dir='rtl'] .geocode-item { left: -25%; } - -.geocode-item:hover { +.geocode-item:active { background-color: #aaa; } +@media (hover: hover) { + .geocode-item:hover { + background-color: #aaa; + } +} .feature-list-item { background-color: #fff; @@ -909,9 +937,14 @@ a.hide-toggle { line-height: 20px; display: flex; } -.feature-list-item:hover { +.feature-list-item:active { background-color: #ececec; } +@media (hover: hover) { + .feature-list-item:hover { + background-color: #ececec; + } +} .feature-list-item button { background: transparent; } @@ -940,9 +973,14 @@ a.hide-toggle { .feature-list-item .entity-type { color: #7092ff; } -.feature-list-item:hover .entity-type { +.feature-list-item:active .entity-type { color: #597be7; } +@media (hover: hover) { + .feature-list-item:hover .entity-type { + color: #597be7; + } +} .feature-list-item .entity-name { font-weight: normal; color: #666; @@ -1169,12 +1207,17 @@ a.hide-toggle { font-weight: normal; } -.preset-list-button:hover .label, .preset-list-button:focus .label, +.preset-list-button:active .label, .preset-list-button.disabled, .preset-list-button.disabled .label { background-color: #ececec; } +@media (hover: hover) { + .preset-list-button:hover .label { + background-color: #ececec; + } +} .preset-list-item button.tag-reference-button { height: 100%; @@ -1194,10 +1237,14 @@ a.hide-toggle { .ideditor[dir='rtl'] .preset-list-item button:last-child { border-radius: 4px 0 0 4px; } - -.preset-list-item button.tag-reference-button:hover { +.preset-list-item button.tag-reference-button:active { background: #f1f1f1; } +@media (hover: hover) { + .preset-list-item button.tag-reference-button:hover { + background: #f1f1f1; + } +} .preset-list-item button.tag-reference-button .icon { opacity: .5; } @@ -1352,9 +1399,14 @@ a.hide-toggle { border-left: none; border-right: 1px solid #ccc; } -.field-label button:hover { +.field-label button:active { background: #f1f1f1; } +@media (hover: hover) { + .field-label button:hover { + background: #f1f1f1; + } +} .field-label .icon { opacity: .5; } @@ -1420,9 +1472,14 @@ a.hide-toggle { border-left-width: 1px; border-right-width: 0; } -.form-field-button:hover { +.form-field-button:active { background-color: #f1f1f1; } +@media (hover: hover) { + .form-field-button:hover { + background-color: #f1f1f1; + } +} .form-field-button .icon { fill: #333; opacity: .5; @@ -1706,10 +1763,14 @@ a.hide-toggle { .form-field-input-check > .reverser.button.hide { display: none; } - -.form-field-input-check:hover { +.form-field-input-check:active { background: #f1f1f1; } +@media (hover: hover) { + .form-field-input-check:hover { + background: #f1f1f1; + } +} .form-field-input-check .set { color: inherit; } @@ -1742,9 +1803,14 @@ a.hide-toggle { .form-field-input-radio > label:last-child { border-radius: 0 0 4px 4px; } -.form-field-input-radio > label:hover { +.form-field-input-radio > label:active { background-color: #ececec; } +@media (hover: hover) { + .form-field-input-radio > label:hover { + background-color: #ececec; + } +} .form-field-input-radio > label.active { background-color: #e8ebff; } @@ -2082,9 +2148,14 @@ div.combobox { } .combobox a.selected, -.combobox a:hover { +.combobox a:active { background: #ececec; } +@media (hover: hover) { + .combobox a:hover { + background: #ececec; + } +} .combobox a:first-child { border-top: 0; @@ -2161,10 +2232,16 @@ div.combobox { color: #7092ff; border-bottom: 2px solid; } -.field-help-nav-item:hover { +.field-help-nav-item:active { color: #597be7; background-color: #efefef; } +@media (hover: hover) { + .field-help-nav-item:hover { + color: #597be7; + background-color: #efefef; + } +} .field-help-content { padding: 10px; @@ -2266,11 +2343,16 @@ button.raw-tag-option { margin: 0 3px; } button.raw-tag-option:focus, -button.raw-tag-option:hover, button.raw-tag-option.active { color: #fff; background: #597be7; } +@media (hover: hover) { + button.raw-tag-option:hover { + color: #fff; + background: #597be7; + } +} button.raw-tag-option.selected { color: #fff; background: #7092ff; @@ -2378,9 +2460,14 @@ button.raw-tag-option svg.icon { border-right-width: 0; } -.tag-row button:hover { +.tag-row button:active { background: #f1f1f1; } +@media (hover: hover) { + .tag-row button:hover { + background: #f1f1f1; + } +} .tag-row button .icon { opacity: .5; } @@ -2560,9 +2647,14 @@ input.key-trap { background: rgba(0,0,0,.5); } .add-row button:focus, -.add-row button:hover { +.add-row button:active { background: rgba(0,0,0,.8); } +@media (hover: hover) { + .add-row button:hover { + background: rgba(0,0,0,.8); + } +} .add-tag { border-radius: 0 0 4px 4px; @@ -2826,15 +2918,22 @@ input.key-trap { border-radius: 0; } -.map-control > button:not(.disabled):hover, -.map-control > button:not(.disabled):focus { +.map-control > button:not(.disabled):focus, +.map-control > button:not(.disabled):active { background: rgba(0, 0, 0, .8); } - .map-control > button.active, -.map-control > button.active:hover { +.map-control > button.active:active { background: #7092ff; } +@media (hover: hover) { + .map-control > button:not(.disabled):hover { + background: rgba(0, 0, 0, .8); + } + .map-control > button.active:hover { + background: #7092ff; + } +} .map-control > button.disabled .icon { color: rgba(255, 255, 255, 0.5); @@ -2859,10 +2958,14 @@ div.full-screen > button, div.full-screen > button.active { height: 40px; background: transparent; } - -div.full-screen > button:hover { +div.full-screen > button:active { background-color: rgba(0, 0, 0, .8); } +@media (hover: hover) { + div.full-screen > button:hover { + background-color: rgba(0, 0, 0, .8); + } +} /* Map Controls @@ -2953,9 +3056,14 @@ div.full-screen > button:hover { .layer-list li:not(:last-child) { border-bottom: 1px solid #ccc; } -.layer-list li:hover { +.layer-list li:active { background-color: #ececec; } +@media (hover: hover) { + .layer-list li:hover { + background-color: #ececec; + } +} .layer-list li.active button, .layer-list li.switch button, @@ -3092,10 +3200,15 @@ button.autofix.action { color: #fff; } button.autofix.action:focus, -button.autofix.action:hover, +button.autofix.action:active, button.autofix.action.active { background: #597be7; } +@media (hover: hover) { + button.autofix.action:hover { + background: #597be7; + } +} /* fix all */ .autofix-all { @@ -3135,11 +3248,6 @@ button.autofix.action.active { background: #ffa; } -.warnings-list .issue.severity-warning .issue-label:hover, -.issue.severity-warning .issue-fix-item.actionable:hover { - background: #ff8; -} - .issue.severity-warning .issue-icon { color: #f90; } @@ -3148,15 +3256,26 @@ button.autofix.action.active { .issue-container.active .issue.severity-warning .issue-info-button { color: #b15500; fill: #b15500; - /*color: #7092ff;*/ - /*fill: #7092ff;*/ } -.issue.severity-warning .issue-fix-item.actionable:hover, -.issue-container.active .issue.severity-warning .issue-info-button:hover { +.warnings-list .issue.severity-warning .issue-label:active, +.issue.severity-warning .issue-fix-item.actionable:active { + background: #ff8; +} +.issue.severity-warning .issue-fix-item.actionable:active, +.issue-container.active .issue.severity-warning .issue-info-button:active { color: #7f3d00; fill: #7f3d00; - /*color: #597be7;*/ - /*fill: #597be7;*/ +} +@media (hover: hover) { + .warnings-list .issue.severity-warning .issue-label:hover, + .issue.severity-warning .issue-fix-item.actionable:hover { + background: #ff8; + } + .issue.severity-warning .issue-fix-item.actionable:hover, + .issue-container.active .issue.severity-warning .issue-info-button:hover { + color: #7f3d00; + fill: #7f3d00; + } } @@ -3178,28 +3297,33 @@ button.autofix.action.active { background: #ffc6c6; } -.errors-list .issue.severity-error .issue-label:hover, -.issue.severity-error .issue-fix-item.actionable:hover { - background: #ffb6b6; -} - -.issue.severity-error .issue-icon { - color: #dd1400; -} - .issue.severity-error .issue-fix-item.actionable, .issue-container.active .issue.severity-error .issue-info-button { color: #b91201; fill: #b91201; - /*color: #7092ff;*/ - /*fill: #7092ff;*/ } -.issue.severity-error .issue-fix-item.actionable:hover, -.issue-container.active .issue.severity-error .issue-info-button:hover { +.issue.severity-error .issue-icon { + color: #dd1400; +} +.errors-list .issue.severity-error .issue-label:active, +.issue.severity-error .issue-fix-item.actionable:active { + background: #ffb6b6; +} +.issue.severity-error .issue-fix-item.actionable:active, +.issue-container.active .issue.severity-error .issue-info-button:active { color: #840c00; fill: #840c00; - /*color: #597be7;*/ - /*fill: #597be7;*/ +} +@media (hover: hover) { + .errors-list .issue.severity-error .issue-label:hover, + .issue.severity-error .issue-fix-item.actionable:hover { + background: #ffb6b6; + } + .issue.severity-error .issue-fix-item.actionable:hover, + .issue-container.active .issue.severity-error .issue-info-button:hover { + color: #840c00; + fill: #840c00; + } } @@ -3272,10 +3396,16 @@ input.square-degrees-input { border: 1px solid #ccc; background: #f6f6f6; } -.section-entity-issues .issue-container:not(.active) .issue-text:hover, -.section-entity-issues .issue-container:not(.active) .issue-info-button:hover { +.section-entity-issues .issue-container:not(.active) .issue-text:active, +.section-entity-issues .issue-container:not(.active) .issue-info-button:active { background: #f1f1f1; } +@media (hover: hover) { + .section-entity-issues .issue-container:not(.active) .issue-text:hover, + .section-entity-issues .issue-container:not(.active) .issue-info-button:hover { + background: #f1f1f1; + } +} .section-entity-issues .issue .issue-label .issue-text { padding-right: 10px; } @@ -3633,11 +3763,16 @@ li.issue-fix-item:not(.actionable) .fix-icon { .help-pane .toc li a { border-bottom: 0; } - -.help-pane .toc li a:hover, -.help-pane .nav a:hover { +.help-pane .toc li a:active, +.help-pane .nav a:active { background: #ececec; } +@media (hover: hover) { + .help-pane .toc li a:hover, + .help-pane .nav a:hover { + background: #ececec; + } +} .help-pane .toc li a.selected { background: #e8ebff; @@ -4098,10 +4233,14 @@ img.tile-debug { .ideditor[dir='rtl'] .panel-title button.close { float: left; } - -.panel-title button.close:hover { +.panel-title button.close:active { color: #fff; } +@media (hover: hover) { + .panel-title button.close:hover { + color: #fff; + } +} .panel-title button.close .icon { height: 20px; width: 16px; @@ -4227,10 +4366,14 @@ img.tile-debug { .attribution-wrap .attribution a:visited { color: #ccf; } - .attribution-wrap .attribution a:hover { color: #aaf; } +@media (hover: hover) { + .attribution-wrap .attribution a:hover { + color: #aaf; + } +} .attribution-wrap .attribution .source-image { height: 20px; @@ -4305,15 +4448,12 @@ img.tile-debug { .scale-block .scale { height: 30px; width: 100%; + cursor: pointer; } .ideditor[dir='rtl'] .scale-block .scale { transform: scaleX(-1); } -.scale-block .scale:hover { - cursor: pointer; -} - .scale-block .scale text { font: 12px sans-serif; stroke: none; @@ -4444,9 +4584,14 @@ img.tile-debug { color: #ccc; pointer-events: all; } -.api-status a:hover { +.api-status a:active { color: inherit; } +@media (hover: hover) { + .api-status a:hover { + color: inherit; + } +} /* Notification Badges ------------------------------------------------------- */ @@ -4591,9 +4736,6 @@ img.tile-debug { text-align: center; width: 100%; } -.modal-actions button:hover { - background-color: #ececec; -} .logo-small { height: 40px; @@ -4757,10 +4899,16 @@ img.tile-debug { color: #7092ff; border-bottom: 2px solid; } -.modal-shortcuts .tab:hover { +.modal-shortcuts .tab:active { color: #597be7; background-color: #efefef; } +@media (hover: hover) { + .modal-shortcuts .tab:hover { + color: #597be7; + background-color: #efefef; + } +} .modal-shortcuts .shortcut-tab { display: flex; @@ -4903,10 +5051,14 @@ svg.mouseclick use.right { padding: 5px 10px; cursor: pointer; } - -.ideditor.mode-save .changeset-list li:hover { +.ideditor.mode-save .changeset-list li:active { background-color: #ececec; } +@media (hover: hover) { + .ideditor.mode-save .changeset-list li:hover { + background-color: #ececec; + } +} .ideditor.mode-save .changeset-list .alert { opacity: 0.5; @@ -4992,10 +5144,15 @@ svg.mouseclick use.right { border-radius: 8px; } -.notice .zoom-to:hover, -.notice .zoom-to:focus { +.notice .zoom-to:focus, +.notice .zoom-to:active { background: rgba(0,0,0,0.6); } +@media (hover: hover) { + .notice .zoom-to:hover { + background: rgba(0,0,0,0.6); + } +} .notice .zoom-to .icon { width: 30px; @@ -5285,10 +5442,14 @@ li.hide + li.version .badge .tooltip .popover-arrow { border-width: 3px 3px 3px 4px; border-radius: 6px; } -::-webkit-scrollbar-track:hover, ::-webkit-scrollbar-track:active { background-color: rgba(0,0,0,.05); } +@media (hover: hover) { + ::-webkit-scrollbar-track:hover { + background-color: rgba(0,0,0,.05); + } +} /* Intro walkthrough From 3bebda08fd59e1d698373ba242885120e79162f1 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Fri, 15 May 2020 14:52:11 -0400 Subject: [PATCH 42/67] Don't display tooltips for non-mouse interaction on iOS 13.4 (re: #6035) --- modules/ui/popover.js | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/modules/ui/popover.js b/modules/ui/popover.js index bfbcb725a..8481e2709 100644 --- a/modules/ui/popover.js +++ b/modules/ui/popover.js @@ -152,12 +152,26 @@ export function uiPopover(klass) { var display = _displayType.apply(this, arguments); if (display === 'hover') { + var _lastNonMouseEnterTime; anchor.on(_pointerPrefix + 'enter.popover', function() { - if (d3_event.pointerType && d3_event.pointerType !== 'mouse') return; + + if (d3_event.pointerType) { + if (d3_event.pointerType !== 'mouse') { + _lastNonMouseEnterTime = d3_event.timeStamp; + // only allow hover behavior for mouse input + return; + } else if (_lastNonMouseEnterTime && + d3_event.timeStamp - _lastNonMouseEnterTime < 500) { + // HACK: iOS 13.4 sends an erroneous `mouse` type pointerenter + // event for non-mouse interactions right after sending + // the correct type pointerenter event. Workaround by discarding + // any mouse event that occurs immediately after a non-mouse event. + return; + } + } show.apply(this, arguments); }); anchor.on(_pointerPrefix + 'leave.popover', function() { - if (d3_event.pointerType && d3_event.pointerType !== 'mouse') return; hide.apply(this, arguments); }); @@ -185,15 +199,11 @@ export function uiPopover(klass) { function show() { - var displayType = _displayType.apply(this, arguments); - if (displayType === 'hover' && d3_event.pointerType === 'touch') { - // don't show hover popovers on touch devices - return; - } var anchor = d3_select(this); var popoverSelection = anchor.selectAll('.popover-' + _id); - if (popoverSelection.empty()) { // popover was removed somehow, put it back + if (popoverSelection.empty()) { + // popover was removed somehow, put it back anchor.call(popover.destroy); anchor.each(setup); popoverSelection = anchor.selectAll('.popover-' + _id); @@ -201,6 +211,7 @@ export function uiPopover(klass) { popoverSelection.classed('in', true); + var displayType = _displayType.apply(this, arguments); if (displayType === 'clickFocus') { anchor.classed('active', true); popoverSelection.node().focus(); From e319aebc418bcbb24877b3b3d1d5efdfe761898d Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Fri, 15 May 2020 15:22:36 -0400 Subject: [PATCH 43/67] Fix several circumstances where the edit menu could appear unexpectedly --- modules/behavior/select.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/modules/behavior/select.js b/modules/behavior/select.js index f668f0c4a..aa1e82a23 100644 --- a/modules/behavior/select.js +++ b/modules/behavior/select.js @@ -23,9 +23,10 @@ export function behaviorSelect(context) { var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse'; function point(event) { - // don't use map().mouse() since additional pointers unrelated to selection can - // move between pointerdown and pointerup - return utilFastMouse(context.map().supersurface.node())(event || d3_event); + // Don't use map().mouse() since additional pointers unrelated to selection can + // move between pointerdown and pointerup. Use the `main-map` coordinate system + // since the surface and supersurface are transformed when drag-panning. + return utilFastMouse(context.container().select('.main-map').node())(event || d3_event); } @@ -150,11 +151,17 @@ export function behaviorSelect(context) { function click() { if (_longPressTimeout) window.clearTimeout(_longPressTimeout); - if (!_p1) return; + if (!_p1) { + resetProperties(); + return; + } var p2 = point(_lastPointerEvent); var dist = geoVecLength(_p1, p2); _p1 = null; - if (dist > _tolerancePx) return; + if (dist > _tolerancePx) { + resetProperties(); + return; + } var datum = (d3_event && d3_event.target.__data__) || (_lastPointerEvent && _lastPointerEvent.target.__data__); var isMultiselect = (d3_event && d3_event.shiftKey) || context.surface().select('.lasso').node(); From 61ef843a18c795ffa9519778ec185037330f945a Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Fri, 15 May 2020 16:04:43 -0400 Subject: [PATCH 44/67] Update copy and paste icons --- svg/iD-sprite/operations/operation-copy.svg | 6 +++--- svg/iD-sprite/operations/operation-paste.svg | 7 +++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/svg/iD-sprite/operations/operation-copy.svg b/svg/iD-sprite/operations/operation-copy.svg index 92e0d9d90..33c92f6d1 100644 --- a/svg/iD-sprite/operations/operation-copy.svg +++ b/svg/iD-sprite/operations/operation-copy.svg @@ -1,6 +1,6 @@ - - - + + + diff --git a/svg/iD-sprite/operations/operation-paste.svg b/svg/iD-sprite/operations/operation-paste.svg index ee898b03f..98fdecee3 100644 --- a/svg/iD-sprite/operations/operation-paste.svg +++ b/svg/iD-sprite/operations/operation-paste.svg @@ -1,7 +1,6 @@ - - - - + + + From 24d83926e275c248ded910d64dff502ba37942dd Mon Sep 17 00:00:00 2001 From: SilentSpike Date: Sun, 17 May 2020 17:41:44 +0100 Subject: [PATCH 45/67] Fix white Osmose icons after changes upload Prevent those elements of the Osmose service cache from being purged on reset. Fixes #7609 --- modules/services/osmose.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/services/osmose.js b/modules/services/osmose.js index ad064043f..13e9c5a39 100644 --- a/modules/services/osmose.js +++ b/modules/services/osmose.js @@ -83,8 +83,13 @@ export default { }, reset() { + let _strings = {}; + let _colors = {}; if (_cache) { Object.values(_cache.inflightTile).forEach(abortRequest); + // Strings and colors are static and should not be re-populated + _strings = _cache.strings; + _colors = _cache.colors; } _cache = { data: {}, @@ -93,8 +98,8 @@ export default { inflightPost: {}, closed: {}, rtree: new RBush(), - strings: {}, - colors: {} + strings: _strings, + colors: _colors }; }, From b62fe3d87dcf3d0979aba1d78d1a2d78955ff312 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Sun, 17 May 2020 16:22:15 -0400 Subject: [PATCH 46/67] Clarify scope of deprecations from #7514 --- data/deprecated.json | 43 +++++++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/data/deprecated.json b/data/deprecated.json index 3757e5e17..fe483061f 100644 --- a/data/deprecated.json +++ b/data/deprecated.json @@ -1328,14 +1328,29 @@ "replace": {"volcano:status": "extinct"} }, { - "old": {"type": "gas",}, - "replace": {"substance": "gas"} - }, - { - "old": {"type": "oil",}, - "replace": {"substance": "oil"} + "old": {"type": "gas", "man_made": "pipeline"}, + "replace": {"substance": "gas", "man_made": "pipeline"} + }, + { + "old": {"type": "gas", "man_made": "pumping_rig"}, + "replace": {"substance": "gas", "man_made": "pumping_rig"} + }, + { + "old": {"type": "gas", "pipeline": "*"}, + "replace": {"substance": "gas", "pipeline": "$1"} + }, + { + "old": {"type": "oil", "man_made": "pipeline"}, + "replace": {"substance": "oil", "man_made": "pipeline"} + }, + { + "old": {"type": "oil", "man_made": "pumping_rig"}, + "replace": {"substance": "oil", "man_made": "pumping_rig"} + }, + { + "old": {"type": "oil", "pipeline": "*"}, + "replace": {"substance": "oil", "pipeline": "$1"} }, - { "old": {"type": "scoria"}, "replace": {"volcano:type": "scoria"} @@ -1353,9 +1368,17 @@ "replace": {"studio": "video"} }, { - "old": {"type": "water",}, - "replace": {"substance": "water"} - }, + "old": {"type": "water", "man_made": "pipeline"}, + "replace": {"substance": "water", "man_made": "pipeline"} + }, + { + "old": {"type": "water", "man_made": "reservoir_covered"}, + "replace": {"content": "water", "man_made": "reservoir_covered"} + }, + { + "old": {"type": "water", "pipeline": "*"}, + "replace": {"substance": "water", "pipeline": "$1"} + }, { "old": {"unnamed": "*"}, "replace": {"noname": "$1"} From a5091c3bd0a96abcaa2f9316585442cd7a87a6c2 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Mon, 18 May 2020 09:58:15 -0400 Subject: [PATCH 47/67] Account for pointerId and simplify position calculations in doubleUp (re: #7611) --- modules/renderer/map.js | 2 +- modules/util/double_up.js | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/modules/renderer/map.js b/modules/renderer/map.js index 648bd9f2b..44af8309c 100644 --- a/modules/renderer/map.js +++ b/modules/renderer/map.js @@ -919,7 +919,7 @@ export function rendererMap(context) { map.transformEase = function(t2, duration) { duration = duration || 250; - setTransform(t2, duration, false); + setTransform(t2, duration, false /* don't force */); return map; }; diff --git a/modules/util/double_up.js b/modules/util/double_up.js index ecd4be03f..569c9d261 100644 --- a/modules/util/double_up.js +++ b/modules/util/double_up.js @@ -27,19 +27,24 @@ export function utilDoubleUp() { // ignore right-click if (d3_event.ctrlKey || d3_event.button === 2) return; - var loc = utilFastMouse(this)(d3_event); + var loc = [d3_event.clientX, d3_event.clientY]; + // Don't rely on pointerId here since it can change between pointerdown + // events on touch devices if (_pointer && !pointerIsValidFor(loc)) { // if this pointer is no longer valid, clear it so another can be started _pointer = undefined; } + if (!_pointer) { - // don't rely on the pointerId since it can change between down events on touch devices _pointer = { startLoc: loc, startTime: new Date().getTime(), - upCount: 0 + upCount: 0, + pointerId: d3_event.pointerId }; + } else { // double down + _pointer.pointerId = d3_event.pointerId; } } @@ -48,14 +53,15 @@ export function utilDoubleUp() { // ignore right-click if (d3_event.ctrlKey || d3_event.button === 2) return; - if (!_pointer) return; + if (!_pointer || _pointer.pointerId !== d3_event.pointerId) return; _pointer.upCount += 1; if (_pointer.upCount === 2) { // double up! - var loc = utilFastMouse(this)(d3_event); + var loc = [d3_event.clientX, d3_event.clientY]; if (pointerIsValidFor(loc)) { - dispatch.call('doubleUp', this, loc); + var locInThis = utilFastMouse(this)(d3_event); + dispatch.call('doubleUp', this, locInThis); } // clear the pointer info in any case _pointer = undefined; From 8deec6daa9a5803e77164fb39cf898a306148309 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Mon, 18 May 2020 10:10:35 -0400 Subject: [PATCH 48/67] Fix code tests --- test/spec/behavior/select.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/spec/behavior/select.js b/test/spec/behavior/select.js index e3b3843b9..ea7365ab0 100644 --- a/test/spec/behavior/select.js +++ b/test/spec/behavior/select.js @@ -10,7 +10,10 @@ describe('iD.behaviorSelect', function() { context.perform(iD.actionAddEntity(a), iD.actionAddEntity(b)); - container.call(context.map()) + container + .append('div') + .attr('class', 'main-map') + .call(context.map()) .append('div') .attr('class', 'inspector-wrap'); From 3228d273ca4582eff8571478fb867c8c14b19416 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Mon, 18 May 2020 10:37:01 -0400 Subject: [PATCH 49/67] Add Collectibles Shop preset (close #7588) --- data/presets.yaml | 8 +++++++ data/presets/fields.json | 1 + data/presets/fields/collector.json | 5 +++++ data/presets/presets.json | 1 + data/presets/presets/shop/collector.json | 27 ++++++++++++++++++++++++ data/taginfo.json | 2 ++ dist/locales/en.json | 7 ++++++ svg/fontawesome/fas-th.svg | 1 + 8 files changed, 52 insertions(+) create mode 100644 data/presets/fields/collector.json create mode 100644 data/presets/presets/shop/collector.json create mode 100644 svg/fontawesome/fas-th.svg diff --git a/data/presets.yaml b/data/presets.yaml index b32817389..486a48154 100644 --- a/data/presets.yaml +++ b/data/presets.yaml @@ -469,6 +469,9 @@ en: collection_times: # collection_times=* label: Collection Times + collector: + # collector=* + label: Items colour: # colour=* label: Color @@ -7806,6 +7809,11 @@ en: # shop=coffee name: Coffee Store terms: '' + shop/collector: + # shop=collector + name: Collectibles Shop + # 'terms: antiques,coins,collection,collectors,comics,dolls,figurines,stamps,thrift' + terms: '' shop/computer: # shop=computer name: Computer Store diff --git a/data/presets/fields.json b/data/presets/fields.json index 5ef662591..9c27cd0ee 100644 --- a/data/presets/fields.json +++ b/data/presets/fields.json @@ -77,6 +77,7 @@ "clothes": {"key": "clothes", "type": "semiCombo", "label": "Clothes"}, "club": {"key": "club", "type": "typeCombo", "label": "Type"}, "collection_times": {"key": "collection_times", "type": "text", "label": "Collection Times"}, + "collector": {"key": "collector", "type": "semiCombo", "label": "Items"}, "colour": {"key": "colour", "type": "text", "label": "Color"}, "comment": {"key": "comment", "type": "textarea", "label": "Changeset Comment", "placeholder": "Brief description of your contributions (required)"}, "communication_multi": {"key": "communication:", "type": "multiCombo", "label": "Communication Types"}, diff --git a/data/presets/fields/collector.json b/data/presets/fields/collector.json new file mode 100644 index 000000000..d6d2ea4fe --- /dev/null +++ b/data/presets/fields/collector.json @@ -0,0 +1,5 @@ +{ + "key": "collector", + "type": "semiCombo", + "label": "Items" +} diff --git a/data/presets/presets.json b/data/presets/presets.json index 26d3fb16f..fed977550 100644 --- a/data/presets/presets.json +++ b/data/presets/presets.json @@ -1083,6 +1083,7 @@ "shop/clothes/underwear": {"icon": "maki-clothing-store", "geometry": ["point", "area"], "tags": {"shop": "clothes", "clothes": "underwear"}, "reference": {"key": "clothes", "value": "underwear"}, "terms": ["boutique", "bras", "brassieres", "briefs", "boxers", "fashion", "lingerie", "panties", "slips", "socks", "stockings", "underclothes", "undergarments", "underpants", "undies"], "name": "Underwear Store"}, "shop/clothes/wedding": {"icon": "temaki-gown", "geometry": ["point", "area"], "tags": {"shop": "clothes", "clothes": "wedding"}, "reference": {"key": "clothes", "value": "wedding"}, "terms": ["boutique", "bridal", "bride", "bridegroom", "bridesmaid", "groom", "groomsman", "tuxedo", "wedding dress", "wedding gown"], "name": "Wedding Clothes Store"}, "shop/coffee": {"icon": "temaki-coffee", "geometry": ["point", "area"], "tags": {"shop": "coffee"}, "name": "Coffee Store"}, + "shop/collector": {"icon": "fas-th", "fields": ["name", "collector", "{shop}"], "geometry": ["point", "area"], "terms": ["antiques", "coins", "collection", "collectors", "comics", "dolls", "figurines", "stamps", "thrift"], "tags": {"shop": "collector"}, "name": "Collectibles Shop"}, "shop/computer": {"icon": "fas-laptop", "geometry": ["point", "area"], "tags": {"shop": "computer"}, "terms": ["desktop", "laptop", "hardware", "operating system", "software"], "name": "Computer Store"}, "shop/confectionery": {"icon": "maki-confectionery", "geometry": ["point", "area"], "terms": ["sweet"], "tags": {"shop": "confectionery"}, "name": "Candy Store"}, "shop/convenience": {"icon": "fas-shopping-basket", "geometry": ["point", "area"], "tags": {"shop": "convenience"}, "name": "Convenience Store"}, diff --git a/data/presets/presets/shop/collector.json b/data/presets/presets/shop/collector.json new file mode 100644 index 000000000..91f457825 --- /dev/null +++ b/data/presets/presets/shop/collector.json @@ -0,0 +1,27 @@ +{ + "icon": "fas-th", + "fields": [ + "name", + "collector", + "{shop}" + ], + "geometry": [ + "point", + "area" + ], + "terms": [ + "antiques", + "coins", + "collection", + "collectors", + "comics", + "dolls", + "figurines", + "stamps", + "thrift" + ], + "tags": { + "shop": "collector" + }, + "name": "Collectibles Shop" +} diff --git a/data/taginfo.json b/data/taginfo.json index cb342853e..e1f40110a 100644 --- a/data/taginfo.json +++ b/data/taginfo.json @@ -1029,6 +1029,7 @@ {"key": "clothes", "value": "underwear", "description": "๐Ÿ„ฟ Underwear Store", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/mapbox/maki/icons/clothing-store-15.svg"}, {"key": "clothes", "value": "wedding", "description": "๐Ÿ„ฟ Wedding Clothes Store", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/gown.svg"}, {"key": "shop", "value": "coffee", "description": "๐Ÿ„ฟ Coffee Store", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/coffee.svg"}, + {"key": "shop", "value": "collector", "description": "๐Ÿ„ฟ Collectibles Shop", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/openstreetmap/iD@develop/svg/fontawesome/fas-th.svg"}, {"key": "shop", "value": "computer", "description": "๐Ÿ„ฟ Computer Store", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/openstreetmap/iD@develop/svg/fontawesome/fas-laptop.svg"}, {"key": "shop", "value": "confectionery", "description": "๐Ÿ„ฟ Candy Store", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/mapbox/maki/icons/confectionery-15.svg"}, {"key": "shop", "value": "convenience", "description": "๐Ÿ„ฟ Convenience Store", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/openstreetmap/iD@develop/svg/fontawesome/fas-shopping-basket.svg"}, @@ -1394,6 +1395,7 @@ {"key": "circumference", "description": "๐Ÿ„ต Circumference"}, {"key": "clothes", "description": "๐Ÿ„ต Clothes"}, {"key": "collection_times", "description": "๐Ÿ„ต Collection Times"}, + {"key": "collector", "description": "๐Ÿ„ต Items"}, {"key": "colour", "description": "๐Ÿ„ต Color"}, {"key": "comment", "description": "๐Ÿ„ต Changeset Comment"}, {"key": "communication:", "description": "๐Ÿ„ต Communication Types"}, diff --git a/dist/locales/en.json b/dist/locales/en.json index ab8a81be5..3be643e82 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -3021,6 +3021,9 @@ "collection_times": { "label": "Collection Times" }, + "collector": { + "label": "Items" + }, "colour": { "label": "Color", "terms": "" @@ -9262,6 +9265,10 @@ "name": "Coffee Store", "terms": "" }, + "shop/collector": { + "name": "Collectibles Shop", + "terms": "antiques,coins,collection,collectors,comics,dolls,figurines,stamps,thrift" + }, "shop/computer": { "name": "Computer Store", "terms": "desktop,laptop,hardware,operating system,software" diff --git a/svg/fontawesome/fas-th.svg b/svg/fontawesome/fas-th.svg new file mode 100644 index 000000000..f5f60a7ed --- /dev/null +++ b/svg/fontawesome/fas-th.svg @@ -0,0 +1 @@ + \ No newline at end of file From 420855b012807629f8c2675685f531e279202fc7 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Mon, 18 May 2020 10:47:59 -0400 Subject: [PATCH 50/67] Update to fontawesome 5.13.0 Update some icons --- data/presets/presets.json | 58 +++++++++---------- data/presets/presets/disused/_shop.json | 1 + .../presets/leisure/pitch/table_tennis.json | 2 +- data/presets/presets/shop/pet.json | 2 +- data/taginfo.json | 6 +- package.json | 8 +-- svg/fontawesome/fas-cat.svg | 1 + svg/fontawesome/fas-store-alt-slash.svg | 1 + svg/fontawesome/fas-table-tennis.svg | 1 + 9 files changed, 42 insertions(+), 38 deletions(-) create mode 100644 svg/fontawesome/fas-cat.svg create mode 100644 svg/fontawesome/fas-store-alt-slash.svg create mode 100644 svg/fontawesome/fas-table-tennis.svg diff --git a/data/presets/presets.json b/data/presets/presets.json index fed977550..5bbfe45de 100644 --- a/data/presets/presets.json +++ b/data/presets/presets.json @@ -443,7 +443,7 @@ "cycleway/asl": {"icon": "maki-bicycle", "fields": ["ref", "direction_vertex", "width"], "geometry": ["vertex"], "tags": {"cycleway": "asl"}, "terms": ["advanced stop box", "asl", "bicycle box", "bike box", "bikebox", "cycle box", "cycle stop marking"], "name": "Advanced Stop Line"}, "demolished/building": {"icon": "fas-house-damage", "fields": ["name", "address"], "geometry": ["area"], "tags": {"demolished:building": "*"}, "name": "Recently Demolished Building", "searchable": false}, "disused/railway": {"icon": "temaki-rail_profile", "fields": ["disused/railway"], "geometry": ["point", "vertex", "line", "area"], "tags": {"disused:railway": "*"}, "matchScore": 0.05, "searchable": false, "name": "Disused Railway Feature"}, - "disused/shop": {"fields": ["disused/shop"], "geometry": ["point", "area"], "tags": {"disused:shop": "*"}, "matchScore": 0.05, "searchable": false, "name": "Disused Shop"}, + "disused/shop": {"icon": "fas-store-alt-slash", "fields": ["disused/shop"], "geometry": ["point", "area"], "tags": {"disused:shop": "*"}, "matchScore": 0.05, "searchable": false, "name": "Disused Shop"}, "emergency/designated": {"fields": ["emergency_combo"], "geometry": ["line"], "tags": {"emergency": "designated"}, "name": "Emergency Access Designated", "searchable": false, "matchScore": 0.01}, "emergency/destination": {"fields": ["emergency_combo"], "geometry": ["line"], "tags": {"emergency": "destination"}, "name": "Emergency Access Destination", "searchable": false, "matchScore": 0.01}, "emergency/no": {"fields": ["emergency_combo"], "geometry": ["line"], "tags": {"emergency": "no"}, "name": "Emergency Access No", "searchable": false, "matchScore": 0.01}, @@ -711,7 +711,7 @@ "leisure/pitch/skateboard": {"icon": "maki-skateboard", "geometry": ["area", "point"], "tags": {"leisure": "pitch", "sport": "skateboard"}, "reference": {"key": "sport", "value": "skateboard"}, "terms": [], "name": "Skate Park"}, "leisure/pitch/soccer": {"icon": "maki-soccer", "geometry": ["area", "point"], "tags": {"leisure": "pitch", "sport": "soccer"}, "reference": {"key": "sport", "value": "soccer"}, "terms": ["football"], "name": "Soccer Field"}, "leisure/pitch/softball": {"icon": "maki-baseball", "geometry": ["area", "point"], "tags": {"leisure": "pitch", "sport": "softball"}, "reference": {"key": "sport", "value": "softball"}, "terms": ["softball", "diamond"], "name": "Softball Field"}, - "leisure/pitch/table_tennis": {"icon": "maki-tennis", "fields": ["name", "lit", "access_simple"], "geometry": ["area", "point"], "tags": {"leisure": "pitch", "sport": "table_tennis"}, "reference": {"key": "sport", "value": "table_tennis"}, "terms": ["table tennis", "ping pong"], "name": "Ping Pong Table"}, + "leisure/pitch/table_tennis": {"icon": "fas-table-tennis", "fields": ["name", "lit", "access_simple"], "geometry": ["area", "point"], "tags": {"leisure": "pitch", "sport": "table_tennis"}, "reference": {"key": "sport", "value": "table_tennis"}, "terms": ["table tennis", "ping pong"], "name": "Ping Pong Table"}, "leisure/pitch/tennis": {"icon": "maki-tennis", "fields": ["{leisure/pitch}", "access_simple"], "geometry": ["area", "point"], "tags": {"leisure": "pitch", "sport": "tennis"}, "reference": {"key": "sport", "value": "tennis"}, "terms": [], "name": "Tennis Court"}, "leisure/pitch/volleyball": {"icon": "maki-volleyball", "geometry": ["area", "point"], "tags": {"leisure": "pitch", "sport": "volleyball"}, "reference": {"key": "sport", "value": "volleyball"}, "terms": [], "name": "Volleyball Court"}, "leisure/playground": {"icon": "maki-playground", "fields": ["name", "operator", "playground/theme", "surface", "access_simple", "min_age", "max_age"], "moreFields": ["blind", "dog", "gnis/feature_id", "wheelchair"], "geometry": ["area", "point"], "terms": ["jungle gym", "play area"], "tags": {"leisure": "playground"}, "name": "Playground"}, @@ -1164,7 +1164,7 @@ "shop/pawnbroker": {"icon": "temaki-money_hand", "geometry": ["point", "area"], "tags": {"shop": "pawnbroker"}, "name": "Pawn Shop"}, "shop/perfumery": {"icon": "temaki-perfume", "geometry": ["point", "area"], "tags": {"shop": "perfumery"}, "terms": ["cologne", "fragrance", "purfume"], "name": "Perfume Store"}, "shop/pet_grooming": {"icon": "temaki-pet_grooming", "geometry": ["point", "area"], "terms": ["cat", "dog"], "tags": {"shop": "pet_grooming"}, "name": "Pet Grooming Store"}, - "shop/pet": {"icon": "maki-dog-park", "geometry": ["point", "area"], "terms": ["animal", "cat", "dog", "fish", "kitten", "puppy", "reptile"], "tags": {"shop": "pet"}, "name": "Pet Store"}, + "shop/pet": {"icon": "fas-cat", "geometry": ["point", "area"], "terms": ["animal", "cat", "dog", "fish", "kitten", "puppy", "reptile"], "tags": {"shop": "pet"}, "name": "Pet Store"}, "shop/photo": {"icon": "fas-camera-retro", "geometry": ["point", "area"], "terms": ["camera", "film", "lens", "photo"], "tags": {"shop": "photo"}, "name": "Photography Store"}, "shop/pottery": {"icon": "temaki-vase", "geometry": ["point", "area"], "terms": ["ceramic", "pot", "vase"], "tags": {"shop": "pottery"}, "name": "Pottery Store"}, "shop/printer_ink": {"icon": "fas-print", "geometry": ["point", "area"], "terms": ["copier ink", "fax ink", "ink cartridges", "toner"], "tags": {"shop": "printer_ink"}, "name": "Printer Ink Store"}, @@ -5548,32 +5548,32 @@ "shop/perfumery/O Boticรกrio": {"name": "O Boticรกrio", "icon": "temaki-perfume", "imageURL": "https://graph.facebook.com/oboticario/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q7073219", "shop": "perfumery"}, "addTags": {"brand": "O Boticรกrio", "brand:wikidata": "Q7073219", "brand:wikipedia": "en:O Boticรกrio", "name": "O Boticรกrio", "shop": "perfumery"}, "countryCodes": ["br"], "terms": [], "matchScore": 2, "suggestion": true}, "shop/perfumery/Perfumania": {"name": "Perfumania", "icon": "temaki-perfume", "imageURL": "https://graph.facebook.com/perfumania/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q72983916", "shop": "perfumery"}, "addTags": {"brand": "Perfumania", "brand:wikidata": "Q72983916", "name": "Perfumania", "shop": "perfumery"}, "countryCodes": ["us"], "terms": [], "matchScore": 2, "suggestion": true}, "shop/perfumery/The Perfume Shop": {"name": "The Perfume Shop", "icon": "temaki-perfume", "imageURL": "https://graph.facebook.com/theperfumeshoponline/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q7756719", "shop": "perfumery"}, "addTags": {"brand": "The Perfume Shop", "brand:wikidata": "Q7756719", "brand:wikipedia": "en:The Perfume Shop", "name": "The Perfume Shop", "shop": "perfumery"}, "countryCodes": ["gb"], "terms": [], "matchScore": 2, "suggestion": true}, - "shop/pet/Animalis": {"name": "Animalis", "icon": "maki-dog-park", "imageURL": "https://graph.facebook.com/Animalisfr/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q2850015", "shop": "pet"}, "addTags": {"brand": "Animalis", "brand:wikidata": "Q2850015", "name": "Animalis", "shop": "pet"}, "countryCodes": ["fr"], "terms": [], "matchScore": 2, "suggestion": true}, - "shop/pet/Das Futterhaus": {"name": "Das Futterhaus", "icon": "maki-dog-park", "imageURL": "https://graph.facebook.com/futterhaus.deutschland/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q1167914", "shop": "pet"}, "addTags": {"brand": "Das Futterhaus", "brand:wikidata": "Q1167914", "brand:wikipedia": "de:Das Futterhaus", "name": "Das Futterhaus", "shop": "pet"}, "countryCodes": ["at", "de"], "terms": [], "matchScore": 2, "suggestion": true}, - "shop/pet/Faunatar": {"name": "Faunatar", "icon": "maki-dog-park", "imageURL": "https://graph.facebook.com/Faunatar/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q11859415", "shop": "pet"}, "addTags": {"brand": "Faunatar", "brand:wikidata": "Q11859415", "brand:wikipedia": "fi:Faunatar", "name": "Faunatar", "shop": "pet"}, "countryCodes": ["fi"], "terms": [], "matchScore": 2, "suggestion": true}, - "shop/pet/Fressnapf": {"name": "Fressnapf", "icon": "maki-dog-park", "imageURL": "https://graph.facebook.com/Fressnapf/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q875796", "shop": "pet"}, "addTags": {"brand": "Fressnapf", "brand:wikidata": "Q875796", "brand:wikipedia": "en:Fressnapf", "name": "Fressnapf", "shop": "pet"}, "countryCodes": ["at", "ch", "de", "hu", "lu"], "terms": [], "matchScore": 2, "suggestion": true}, - "shop/pet/Global Pet Foods": {"name": "Global Pet Foods", "icon": "maki-dog-park", "imageURL": "https://graph.facebook.com/globalpetfoods/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q57985699", "shop": "pet"}, "addTags": {"brand": "Global Pet Foods", "brand:wikidata": "Q57985699", "name": "Global Pet Foods", "shop": "pet"}, "countryCodes": ["ca"], "terms": [], "matchScore": 2, "suggestion": true}, - "shop/pet/Jollyes": {"name": "Jollyes", "icon": "maki-dog-park", "imageURL": "https://graph.facebook.com/jollyesuk/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q45844955", "shop": "pet"}, "addTags": {"brand": "Jollyes", "brand:wikidata": "Q45844955", "name": "Jollyes", "shop": "pet"}, "countryCodes": ["gb"], "terms": [], "matchScore": 2, "suggestion": true}, - "shop/pet/Jumper": {"name": "Jumper", "icon": "maki-dog-park", "imageURL": "https://graph.facebook.com/JumperNL/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q87338743", "shop": "pet"}, "addTags": {"brand": "Jumper", "brand:wikidata": "Q87338743", "name": "Jumper", "shop": "pet"}, "countryCodes": ["nl"], "terms": [], "matchScore": 2, "suggestion": true}, - "shop/pet/Maxi Zoo": {"name": "Maxi Zoo", "icon": "maki-dog-park", "imageURL": "https://graph.facebook.com/Fressnapf/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q875796", "shop": "pet"}, "addTags": {"brand": "Maxi Zoo", "brand:wikidata": "Q875796", "brand:wikipedia": "en:Fressnapf", "name": "Maxi Zoo", "shop": "pet"}, "countryCodes": ["be", "ch", "dk", "fr", "ie", "it", "pl"], "terms": [], "matchScore": 2, "suggestion": true}, - "shop/pet/Mud Bay": {"name": "Mud Bay", "icon": "maki-dog-park", "imageURL": "https://graph.facebook.com/mudbay/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q30324179", "shop": "pet"}, "addTags": {"brand": "Mud Bay", "brand:wikidata": "Q30324179", "brand:wikipedia": "en:Mud Bay pet store", "name": "Mud Bay", "shop": "pet"}, "countryCodes": ["us"], "terms": [], "matchScore": 2, "suggestion": true}, - "shop/pet/Musti ja Mirri": {"name": "Musti ja Mirri", "icon": "maki-dog-park", "imageURL": "https://graph.facebook.com/mustijamirri/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q11883558", "shop": "pet"}, "addTags": {"brand": "Musti ja Mirri", "brand:wikidata": "Q11883558", "brand:wikipedia": "fi:Musti ja Mirri", "name": "Musti ja Mirri", "shop": "pet"}, "countryCodes": ["fi"], "terms": [], "matchScore": 2, "suggestion": true}, - "shop/pet/Mรฉdor et Compagnie": {"name": "Mรฉdor et Compagnie", "icon": "maki-dog-park", "imageURL": "https://graph.facebook.com/medoretcie/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q89344773", "shop": "pet"}, "addTags": {"brand": "Mรฉdor et Compagnie", "brand:wikidata": "Q89344773", "name": "Mรฉdor et Compagnie", "shop": "pet"}, "countryCodes": ["fr"], "terms": [], "matchScore": 2, "suggestion": true}, - "shop/pet/Pet Food Express": {"name": "Pet Food Express", "icon": "maki-dog-park", "imageURL": "https://graph.facebook.com/petfoodexpress/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q7171541", "shop": "pet"}, "addTags": {"brand": "Pet Food Express", "brand:wikidata": "Q7171541", "brand:wikipedia": "en:Pet Food Express", "name": "Pet Food Express", "shop": "pet"}, "countryCodes": ["us"], "terms": [], "matchScore": 2, "suggestion": true}, - "shop/pet/Pet Supermarket": {"name": "Pet Supermarket", "icon": "maki-dog-park", "imageURL": "https://graph.facebook.com/PetSupermarket/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q61968363", "shop": "pet"}, "addTags": {"brand": "Pet Supermarket", "brand:wikidata": "Q61968363", "name": "Pet Supermarket", "shop": "pet"}, "countryCodes": ["us"], "terms": [], "matchScore": 2, "suggestion": true}, - "shop/pet/Pet Supplies Plus": {"name": "Pet Supplies Plus", "icon": "maki-dog-park", "imageURL": "https://graph.facebook.com/petsuppliesplus/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q7171563", "shop": "pet"}, "addTags": {"brand": "Pet Supplies Plus", "brand:wikidata": "Q7171563", "brand:wikipedia": "en:Pet Supplies Plus", "name": "Pet Supplies Plus", "shop": "pet"}, "countryCodes": ["us"], "terms": [], "matchScore": 2, "suggestion": true}, - "shop/pet/Pet Valu": {"name": "Pet Valu", "icon": "maki-dog-park", "imageURL": "https://graph.facebook.com/PetValuUS/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q58009635", "shop": "pet"}, "addTags": {"brand": "Pet Valu", "brand:wikidata": "Q58009635", "name": "Pet Valu", "shop": "pet"}, "countryCodes": ["ca", "us"], "terms": [], "matchScore": 2, "suggestion": true}, - "shop/pet/PetSmart": {"name": "PetSmart", "icon": "maki-dog-park", "imageURL": "https://graph.facebook.com/PetSmart/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q3307147", "shop": "pet"}, "addTags": {"brand": "PetSmart", "brand:wikidata": "Q3307147", "brand:wikipedia": "en:PetSmart", "name": "PetSmart", "shop": "pet"}, "countryCodes": ["ca", "us"], "terms": [], "matchScore": 2, "suggestion": true}, - "shop/pet/Petco": {"name": "Petco", "icon": "maki-dog-park", "imageURL": "https://graph.facebook.com/Petco/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q7171798", "shop": "pet"}, "addTags": {"brand": "Petco", "brand:wikidata": "Q7171798", "brand:wikipedia": "en:Petco", "name": "Petco", "shop": "pet"}, "countryCodes": ["us"], "terms": [], "matchScore": 2, "suggestion": true}, - "shop/pet/Petland": {"name": "Petland", "icon": "maki-dog-park", "imageURL": "https://graph.facebook.com/PetlandUSA/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q17111474", "shop": "pet"}, "addTags": {"brand": "Petland", "brand:wikidata": "Q17111474", "brand:wikipedia": "en:Petland", "name": "Petland", "shop": "pet"}, "countryCodes": ["us"], "terms": [], "matchScore": 2, "suggestion": true}, - "shop/pet/Petland Discounts": {"name": "Petland Discounts", "icon": "maki-dog-park", "imageURL": "https://graph.facebook.com/petlanddiscount/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q7178463", "shop": "pet"}, "addTags": {"brand": "Petland Discounts", "brand:wikidata": "Q7178463", "brand:wikipedia": "en:Petland Discounts", "name": "Petland Discounts", "shop": "pet"}, "countryCodes": ["us"], "terms": [], "matchScore": 2, "suggestion": true}, - "shop/pet/Pets Corner": {"name": "Pets Corner", "icon": "maki-dog-park", "imageURL": "https://graph.facebook.com/petscorner/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q17018476", "shop": "pet"}, "addTags": {"brand": "Pets Corner", "brand:wikidata": "Q17018476", "brand:wikipedia": "en:Pets Corner", "name": "Pets Corner", "shop": "pet"}, "countryCodes": ["gb"], "terms": [], "matchScore": 2, "suggestion": true}, - "shop/pet/Pets at Home": {"name": "Pets at Home", "icon": "maki-dog-park", "imageURL": "https://graph.facebook.com/petsathomeUK/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q7179258", "shop": "pet"}, "addTags": {"brand": "Pets at Home", "brand:wikidata": "Q7179258", "brand:wikipedia": "en:Pets at Home", "name": "Pets at Home", "shop": "pet"}, "countryCodes": ["gb"], "terms": [], "matchScore": 2, "suggestion": true}, - "shop/pet/Unleashed": {"name": "Unleashed", "icon": "maki-dog-park", "imageURL": "https://graph.facebook.com/Petco/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q62122874", "shop": "pet"}, "addTags": {"alt_name": "Unleashed by Petco", "brand": "Unleashed", "brand:wikidata": "Q62122874", "name": "Unleashed", "shop": "pet"}, "countryCodes": ["us"], "terms": [], "matchScore": 2, "suggestion": true}, - "shop/pet/Wild Birds Unlimited": {"name": "Wild Birds Unlimited", "icon": "maki-dog-park", "imageURL": "https://pbs.twimg.com/profile_images/466209950788636672/DHjpTthh_bigger.jpeg", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q8000542", "shop": "pet"}, "addTags": {"brand": "Wild Birds Unlimited", "brand:wikidata": "Q8000542", "brand:wikipedia": "en:Wild Birds Unlimited", "name": "Wild Birds Unlimited", "shop": "pet"}, "countryCodes": ["us"], "terms": [], "matchScore": 2, "suggestion": true}, - "shop/pet/ะ‘ะตั‚ั…ะพะฒะตะฝ": {"name": "ะ‘ะตั‚ั…ะพะฒะตะฝ", "icon": "maki-dog-park", "imageURL": "https://graph.facebook.com/zoobethowenclub/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q62390798", "shop": "pet"}, "addTags": {"brand": "ะ‘ะตั‚ั…ะพะฒะตะฝ", "brand:wikidata": "Q62390798", "name": "ะ‘ะตั‚ั…ะพะฒะตะฝ", "shop": "pet"}, "countryCodes": ["ru"], "terms": [], "matchScore": 2, "suggestion": true}, - "shop/pet/ะงะตั‚ั‹ั€ะต ะปะฐะฟั‹": {"name": "ะงะตั‚ั‹ั€ะต ะปะฐะฟั‹", "icon": "maki-dog-park", "imageURL": "https://graph.facebook.com/4laps/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q62390783", "shop": "pet"}, "addTags": {"brand": "ะงะตั‚ั‹ั€ะต ะปะฐะฟั‹", "brand:wikidata": "Q62390783", "name": "ะงะตั‚ั‹ั€ะต ะปะฐะฟั‹", "shop": "pet"}, "countryCodes": ["kz", "ru"], "terms": [], "matchScore": 2, "suggestion": true}, - "shop/pet/ใ‚คใ‚ชใƒณใƒšใƒƒใƒˆ": {"name": "ใ‚คใ‚ชใƒณใƒšใƒƒใƒˆ", "icon": "maki-dog-park", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q11286064", "shop": "pet"}, "addTags": {"brand": "ใ‚คใ‚ชใƒณใƒšใƒƒใƒˆ", "brand:en": "Aeonpet", "brand:ja": "ใ‚คใ‚ชใƒณใƒšใƒƒใƒˆ", "brand:wikidata": "Q11286064", "brand:wikipedia": "ja:ใ‚คใ‚ชใƒณใƒšใƒƒใƒˆ", "name": "ใ‚คใ‚ชใƒณใƒšใƒƒใƒˆ", "name:en": "Aeonpet", "name:ja": "ใ‚คใ‚ชใƒณใƒšใƒƒใƒˆ", "shop": "pet"}, "countryCodes": ["jp"], "terms": [], "matchScore": 2, "suggestion": true}, + "shop/pet/Animalis": {"name": "Animalis", "icon": "fas-cat", "imageURL": "https://graph.facebook.com/Animalisfr/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q2850015", "shop": "pet"}, "addTags": {"brand": "Animalis", "brand:wikidata": "Q2850015", "name": "Animalis", "shop": "pet"}, "countryCodes": ["fr"], "terms": [], "matchScore": 2, "suggestion": true}, + "shop/pet/Das Futterhaus": {"name": "Das Futterhaus", "icon": "fas-cat", "imageURL": "https://graph.facebook.com/futterhaus.deutschland/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q1167914", "shop": "pet"}, "addTags": {"brand": "Das Futterhaus", "brand:wikidata": "Q1167914", "brand:wikipedia": "de:Das Futterhaus", "name": "Das Futterhaus", "shop": "pet"}, "countryCodes": ["at", "de"], "terms": [], "matchScore": 2, "suggestion": true}, + "shop/pet/Faunatar": {"name": "Faunatar", "icon": "fas-cat", "imageURL": "https://graph.facebook.com/Faunatar/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q11859415", "shop": "pet"}, "addTags": {"brand": "Faunatar", "brand:wikidata": "Q11859415", "brand:wikipedia": "fi:Faunatar", "name": "Faunatar", "shop": "pet"}, "countryCodes": ["fi"], "terms": [], "matchScore": 2, "suggestion": true}, + "shop/pet/Fressnapf": {"name": "Fressnapf", "icon": "fas-cat", "imageURL": "https://graph.facebook.com/Fressnapf/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q875796", "shop": "pet"}, "addTags": {"brand": "Fressnapf", "brand:wikidata": "Q875796", "brand:wikipedia": "en:Fressnapf", "name": "Fressnapf", "shop": "pet"}, "countryCodes": ["at", "ch", "de", "hu", "lu"], "terms": [], "matchScore": 2, "suggestion": true}, + "shop/pet/Global Pet Foods": {"name": "Global Pet Foods", "icon": "fas-cat", "imageURL": "https://graph.facebook.com/globalpetfoods/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q57985699", "shop": "pet"}, "addTags": {"brand": "Global Pet Foods", "brand:wikidata": "Q57985699", "name": "Global Pet Foods", "shop": "pet"}, "countryCodes": ["ca"], "terms": [], "matchScore": 2, "suggestion": true}, + "shop/pet/Jollyes": {"name": "Jollyes", "icon": "fas-cat", "imageURL": "https://graph.facebook.com/jollyesuk/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q45844955", "shop": "pet"}, "addTags": {"brand": "Jollyes", "brand:wikidata": "Q45844955", "name": "Jollyes", "shop": "pet"}, "countryCodes": ["gb"], "terms": [], "matchScore": 2, "suggestion": true}, + "shop/pet/Jumper": {"name": "Jumper", "icon": "fas-cat", "imageURL": "https://graph.facebook.com/JumperNL/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q87338743", "shop": "pet"}, "addTags": {"brand": "Jumper", "brand:wikidata": "Q87338743", "name": "Jumper", "shop": "pet"}, "countryCodes": ["nl"], "terms": [], "matchScore": 2, "suggestion": true}, + "shop/pet/Maxi Zoo": {"name": "Maxi Zoo", "icon": "fas-cat", "imageURL": "https://graph.facebook.com/Fressnapf/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q875796", "shop": "pet"}, "addTags": {"brand": "Maxi Zoo", "brand:wikidata": "Q875796", "brand:wikipedia": "en:Fressnapf", "name": "Maxi Zoo", "shop": "pet"}, "countryCodes": ["be", "ch", "dk", "fr", "ie", "it", "pl"], "terms": [], "matchScore": 2, "suggestion": true}, + "shop/pet/Mud Bay": {"name": "Mud Bay", "icon": "fas-cat", "imageURL": "https://graph.facebook.com/mudbay/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q30324179", "shop": "pet"}, "addTags": {"brand": "Mud Bay", "brand:wikidata": "Q30324179", "brand:wikipedia": "en:Mud Bay pet store", "name": "Mud Bay", "shop": "pet"}, "countryCodes": ["us"], "terms": [], "matchScore": 2, "suggestion": true}, + "shop/pet/Musti ja Mirri": {"name": "Musti ja Mirri", "icon": "fas-cat", "imageURL": "https://graph.facebook.com/mustijamirri/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q11883558", "shop": "pet"}, "addTags": {"brand": "Musti ja Mirri", "brand:wikidata": "Q11883558", "brand:wikipedia": "fi:Musti ja Mirri", "name": "Musti ja Mirri", "shop": "pet"}, "countryCodes": ["fi"], "terms": [], "matchScore": 2, "suggestion": true}, + "shop/pet/Mรฉdor et Compagnie": {"name": "Mรฉdor et Compagnie", "icon": "fas-cat", "imageURL": "https://graph.facebook.com/medoretcie/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q89344773", "shop": "pet"}, "addTags": {"brand": "Mรฉdor et Compagnie", "brand:wikidata": "Q89344773", "name": "Mรฉdor et Compagnie", "shop": "pet"}, "countryCodes": ["fr"], "terms": [], "matchScore": 2, "suggestion": true}, + "shop/pet/Pet Food Express": {"name": "Pet Food Express", "icon": "fas-cat", "imageURL": "https://graph.facebook.com/petfoodexpress/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q7171541", "shop": "pet"}, "addTags": {"brand": "Pet Food Express", "brand:wikidata": "Q7171541", "brand:wikipedia": "en:Pet Food Express", "name": "Pet Food Express", "shop": "pet"}, "countryCodes": ["us"], "terms": [], "matchScore": 2, "suggestion": true}, + "shop/pet/Pet Supermarket": {"name": "Pet Supermarket", "icon": "fas-cat", "imageURL": "https://graph.facebook.com/PetSupermarket/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q61968363", "shop": "pet"}, "addTags": {"brand": "Pet Supermarket", "brand:wikidata": "Q61968363", "name": "Pet Supermarket", "shop": "pet"}, "countryCodes": ["us"], "terms": [], "matchScore": 2, "suggestion": true}, + "shop/pet/Pet Supplies Plus": {"name": "Pet Supplies Plus", "icon": "fas-cat", "imageURL": "https://graph.facebook.com/petsuppliesplus/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q7171563", "shop": "pet"}, "addTags": {"brand": "Pet Supplies Plus", "brand:wikidata": "Q7171563", "brand:wikipedia": "en:Pet Supplies Plus", "name": "Pet Supplies Plus", "shop": "pet"}, "countryCodes": ["us"], "terms": [], "matchScore": 2, "suggestion": true}, + "shop/pet/Pet Valu": {"name": "Pet Valu", "icon": "fas-cat", "imageURL": "https://graph.facebook.com/PetValuUS/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q58009635", "shop": "pet"}, "addTags": {"brand": "Pet Valu", "brand:wikidata": "Q58009635", "name": "Pet Valu", "shop": "pet"}, "countryCodes": ["ca", "us"], "terms": [], "matchScore": 2, "suggestion": true}, + "shop/pet/PetSmart": {"name": "PetSmart", "icon": "fas-cat", "imageURL": "https://graph.facebook.com/PetSmart/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q3307147", "shop": "pet"}, "addTags": {"brand": "PetSmart", "brand:wikidata": "Q3307147", "brand:wikipedia": "en:PetSmart", "name": "PetSmart", "shop": "pet"}, "countryCodes": ["ca", "us"], "terms": [], "matchScore": 2, "suggestion": true}, + "shop/pet/Petco": {"name": "Petco", "icon": "fas-cat", "imageURL": "https://graph.facebook.com/Petco/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q7171798", "shop": "pet"}, "addTags": {"brand": "Petco", "brand:wikidata": "Q7171798", "brand:wikipedia": "en:Petco", "name": "Petco", "shop": "pet"}, "countryCodes": ["us"], "terms": [], "matchScore": 2, "suggestion": true}, + "shop/pet/Petland": {"name": "Petland", "icon": "fas-cat", "imageURL": "https://graph.facebook.com/PetlandUSA/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q17111474", "shop": "pet"}, "addTags": {"brand": "Petland", "brand:wikidata": "Q17111474", "brand:wikipedia": "en:Petland", "name": "Petland", "shop": "pet"}, "countryCodes": ["us"], "terms": [], "matchScore": 2, "suggestion": true}, + "shop/pet/Petland Discounts": {"name": "Petland Discounts", "icon": "fas-cat", "imageURL": "https://graph.facebook.com/petlanddiscount/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q7178463", "shop": "pet"}, "addTags": {"brand": "Petland Discounts", "brand:wikidata": "Q7178463", "brand:wikipedia": "en:Petland Discounts", "name": "Petland Discounts", "shop": "pet"}, "countryCodes": ["us"], "terms": [], "matchScore": 2, "suggestion": true}, + "shop/pet/Pets Corner": {"name": "Pets Corner", "icon": "fas-cat", "imageURL": "https://graph.facebook.com/petscorner/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q17018476", "shop": "pet"}, "addTags": {"brand": "Pets Corner", "brand:wikidata": "Q17018476", "brand:wikipedia": "en:Pets Corner", "name": "Pets Corner", "shop": "pet"}, "countryCodes": ["gb"], "terms": [], "matchScore": 2, "suggestion": true}, + "shop/pet/Pets at Home": {"name": "Pets at Home", "icon": "fas-cat", "imageURL": "https://graph.facebook.com/petsathomeUK/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q7179258", "shop": "pet"}, "addTags": {"brand": "Pets at Home", "brand:wikidata": "Q7179258", "brand:wikipedia": "en:Pets at Home", "name": "Pets at Home", "shop": "pet"}, "countryCodes": ["gb"], "terms": [], "matchScore": 2, "suggestion": true}, + "shop/pet/Unleashed": {"name": "Unleashed", "icon": "fas-cat", "imageURL": "https://graph.facebook.com/Petco/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q62122874", "shop": "pet"}, "addTags": {"alt_name": "Unleashed by Petco", "brand": "Unleashed", "brand:wikidata": "Q62122874", "name": "Unleashed", "shop": "pet"}, "countryCodes": ["us"], "terms": [], "matchScore": 2, "suggestion": true}, + "shop/pet/Wild Birds Unlimited": {"name": "Wild Birds Unlimited", "icon": "fas-cat", "imageURL": "https://pbs.twimg.com/profile_images/466209950788636672/DHjpTthh_bigger.jpeg", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q8000542", "shop": "pet"}, "addTags": {"brand": "Wild Birds Unlimited", "brand:wikidata": "Q8000542", "brand:wikipedia": "en:Wild Birds Unlimited", "name": "Wild Birds Unlimited", "shop": "pet"}, "countryCodes": ["us"], "terms": [], "matchScore": 2, "suggestion": true}, + "shop/pet/ะ‘ะตั‚ั…ะพะฒะตะฝ": {"name": "ะ‘ะตั‚ั…ะพะฒะตะฝ", "icon": "fas-cat", "imageURL": "https://graph.facebook.com/zoobethowenclub/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q62390798", "shop": "pet"}, "addTags": {"brand": "ะ‘ะตั‚ั…ะพะฒะตะฝ", "brand:wikidata": "Q62390798", "name": "ะ‘ะตั‚ั…ะพะฒะตะฝ", "shop": "pet"}, "countryCodes": ["ru"], "terms": [], "matchScore": 2, "suggestion": true}, + "shop/pet/ะงะตั‚ั‹ั€ะต ะปะฐะฟั‹": {"name": "ะงะตั‚ั‹ั€ะต ะปะฐะฟั‹", "icon": "fas-cat", "imageURL": "https://graph.facebook.com/4laps/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q62390783", "shop": "pet"}, "addTags": {"brand": "ะงะตั‚ั‹ั€ะต ะปะฐะฟั‹", "brand:wikidata": "Q62390783", "name": "ะงะตั‚ั‹ั€ะต ะปะฐะฟั‹", "shop": "pet"}, "countryCodes": ["kz", "ru"], "terms": [], "matchScore": 2, "suggestion": true}, + "shop/pet/ใ‚คใ‚ชใƒณใƒšใƒƒใƒˆ": {"name": "ใ‚คใ‚ชใƒณใƒšใƒƒใƒˆ", "icon": "fas-cat", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q11286064", "shop": "pet"}, "addTags": {"brand": "ใ‚คใ‚ชใƒณใƒšใƒƒใƒˆ", "brand:en": "Aeonpet", "brand:ja": "ใ‚คใ‚ชใƒณใƒšใƒƒใƒˆ", "brand:wikidata": "Q11286064", "brand:wikipedia": "ja:ใ‚คใ‚ชใƒณใƒšใƒƒใƒˆ", "name": "ใ‚คใ‚ชใƒณใƒšใƒƒใƒˆ", "name:en": "Aeonpet", "name:ja": "ใ‚คใ‚ชใƒณใƒšใƒƒใƒˆ", "shop": "pet"}, "countryCodes": ["jp"], "terms": [], "matchScore": 2, "suggestion": true}, "shop/photo/Kamera Express": {"name": "Kamera Express", "icon": "fas-camera-retro", "imageURL": "https://graph.facebook.com/kameraexpress/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q77976400", "shop": "photo"}, "addTags": {"brand": "Kamera Express", "brand:wikidata": "Q77976400", "name": "Kamera Express", "shop": "photo"}, "countryCodes": ["be", "de", "nl"], "terms": [], "matchScore": 2, "suggestion": true}, "shop/photo/Kodak Express": {"name": "Kodak Express", "icon": "fas-camera-retro", "imageURL": "https://graph.facebook.com/kodakexpress/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q6425126", "shop": "photo"}, "addTags": {"brand": "Kodak Express", "brand:wikidata": "Q6425126", "brand:wikipedia": "en:Kodak Express", "name": "Kodak Express", "shop": "photo"}, "terms": ["kodak"], "matchScore": 2, "suggestion": true}, "shop/photo/Max Spielmann": {"name": "Max Spielmann", "icon": "fas-camera-retro", "imageURL": "https://graph.facebook.com/MaxPhotoCentres/picture?type=large", "geometry": ["point", "area"], "tags": {"brand:wikidata": "Q76221051", "shop": "photo"}, "addTags": {"brand": "Max Spielmann", "brand:wikidata": "Q76221051", "name": "Max Spielmann", "shop": "photo"}, "countryCodes": ["gb"], "terms": [], "matchScore": 2, "suggestion": true}, diff --git a/data/presets/presets/disused/_shop.json b/data/presets/presets/disused/_shop.json index d9f8df0ee..cdd3362f2 100644 --- a/data/presets/presets/disused/_shop.json +++ b/data/presets/presets/disused/_shop.json @@ -1,4 +1,5 @@ { + "icon": "fas-store-alt-slash", "fields": [ "disused/shop" ], diff --git a/data/presets/presets/leisure/pitch/table_tennis.json b/data/presets/presets/leisure/pitch/table_tennis.json index 6909e287f..992336b10 100644 --- a/data/presets/presets/leisure/pitch/table_tennis.json +++ b/data/presets/presets/leisure/pitch/table_tennis.json @@ -1,5 +1,5 @@ { - "icon": "maki-tennis", + "icon": "fas-table-tennis", "fields": [ "name", "lit", diff --git a/data/presets/presets/shop/pet.json b/data/presets/presets/shop/pet.json index 322a3dd8f..4bf517353 100644 --- a/data/presets/presets/shop/pet.json +++ b/data/presets/presets/shop/pet.json @@ -1,5 +1,5 @@ { - "icon": "maki-dog-park", + "icon": "fas-cat", "geometry": [ "point", "area" diff --git a/data/taginfo.json b/data/taginfo.json index e1f40110a..c69cd3b01 100644 --- a/data/taginfo.json +++ b/data/taginfo.json @@ -439,7 +439,7 @@ {"key": "cycleway", "value": "asl", "description": "๐Ÿ„ฟ Advanced Stop Line", "object_types": ["node"], "icon_url": "https://cdn.jsdelivr.net/gh/mapbox/maki/icons/bicycle-15.svg"}, {"key": "demolished:building", "description": "๐Ÿ„ฟ Recently Demolished Building (unsearchable)", "object_types": ["area"], "icon_url": "https://cdn.jsdelivr.net/gh/openstreetmap/iD@develop/svg/fontawesome/fas-house-damage.svg"}, {"key": "disused:railway", "description": "๐Ÿ„ฟ Disused Railway Feature (unsearchable), ๐Ÿ„ต Type", "object_types": ["node", "way", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/rail_profile.svg"}, - {"key": "disused:shop", "description": "๐Ÿ„ฟ Disused Shop (unsearchable), ๐Ÿ„ต Type", "object_types": ["node", "area"]}, + {"key": "disused:shop", "description": "๐Ÿ„ฟ Disused Shop (unsearchable), ๐Ÿ„ต Type", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/openstreetmap/iD@develop/svg/fontawesome/fas-store-alt-slash.svg"}, {"key": "emergency", "value": "designated", "description": "๐Ÿ„ฟ Emergency Access Designated (unsearchable)", "object_types": ["way"]}, {"key": "emergency", "value": "destination", "description": "๐Ÿ„ฟ Emergency Access Destination (unsearchable)", "object_types": ["way"]}, {"key": "emergency", "value": "no", "description": "๐Ÿ„ฟ Emergency Access No (unsearchable)", "object_types": ["way"]}, @@ -692,7 +692,7 @@ {"key": "sport", "value": "skateboard", "description": "๐Ÿ„ฟ Skate Park", "object_types": ["area", "node"], "icon_url": "https://cdn.jsdelivr.net/gh/mapbox/maki/icons/skateboard-15.svg"}, {"key": "sport", "value": "soccer", "description": "๐Ÿ„ฟ Soccer Field", "object_types": ["area", "node"], "icon_url": "https://cdn.jsdelivr.net/gh/mapbox/maki/icons/soccer-15.svg"}, {"key": "sport", "value": "softball", "description": "๐Ÿ„ฟ Softball Field", "object_types": ["area", "node"], "icon_url": "https://cdn.jsdelivr.net/gh/mapbox/maki/icons/baseball-15.svg"}, - {"key": "sport", "value": "table_tennis", "description": "๐Ÿ„ฟ Ping Pong Table", "object_types": ["area", "node"], "icon_url": "https://cdn.jsdelivr.net/gh/mapbox/maki/icons/tennis-15.svg"}, + {"key": "sport", "value": "table_tennis", "description": "๐Ÿ„ฟ Ping Pong Table", "object_types": ["area", "node"], "icon_url": "https://cdn.jsdelivr.net/gh/openstreetmap/iD@develop/svg/fontawesome/fas-table-tennis.svg"}, {"key": "sport", "value": "tennis", "description": "๐Ÿ„ฟ Tennis Court", "object_types": ["area", "node"], "icon_url": "https://cdn.jsdelivr.net/gh/mapbox/maki/icons/tennis-15.svg"}, {"key": "sport", "value": "volleyball", "description": "๐Ÿ„ฟ Volleyball Court", "object_types": ["area", "node"], "icon_url": "https://cdn.jsdelivr.net/gh/mapbox/maki/icons/volleyball-15.svg"}, {"key": "leisure", "value": "playground", "description": "๐Ÿ„ฟ Playground", "object_types": ["area", "node"], "icon_url": "https://cdn.jsdelivr.net/gh/mapbox/maki/icons/playground-15.svg"}, @@ -1109,7 +1109,7 @@ {"key": "shop", "value": "pawnbroker", "description": "๐Ÿ„ฟ Pawn Shop", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/money_hand.svg"}, {"key": "shop", "value": "perfumery", "description": "๐Ÿ„ฟ Perfume Store", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/perfume.svg"}, {"key": "shop", "value": "pet_grooming", "description": "๐Ÿ„ฟ Pet Grooming Store", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/pet_grooming.svg"}, - {"key": "shop", "value": "pet", "description": "๐Ÿ„ฟ Pet Store", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/mapbox/maki/icons/dog-park-15.svg"}, + {"key": "shop", "value": "pet", "description": "๐Ÿ„ฟ Pet Store", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/openstreetmap/iD@develop/svg/fontawesome/fas-cat.svg"}, {"key": "shop", "value": "photo", "description": "๐Ÿ„ฟ Photography Store", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/openstreetmap/iD@develop/svg/fontawesome/fas-camera-retro.svg"}, {"key": "shop", "value": "pottery", "description": "๐Ÿ„ฟ Pottery Store", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/vase.svg"}, {"key": "shop", "value": "printer_ink", "description": "๐Ÿ„ฟ Printer Ink Store", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/openstreetmap/iD@develop/svg/fontawesome/fas-print.svg"}, diff --git a/package.json b/package.json index 31ef429e8..673df7ea8 100644 --- a/package.json +++ b/package.json @@ -65,10 +65,10 @@ "which-polygon": "2.2.0" }, "devDependencies": { - "@fortawesome/fontawesome-svg-core": "^1.2.26", - "@fortawesome/free-brands-svg-icons": "~5.12.0", - "@fortawesome/free-regular-svg-icons": "~5.12.0", - "@fortawesome/free-solid-svg-icons": "~5.12.0", + "@fortawesome/fontawesome-svg-core": "^1.2.28", + "@fortawesome/free-brands-svg-icons": "~5.13.0", + "@fortawesome/free-regular-svg-icons": "~5.13.0", + "@fortawesome/free-solid-svg-icons": "~5.13.0", "@ideditor/temaki": "~3.23.0", "@mapbox/maki": "^6.0.0", "@rollup/plugin-buble": "^0.21.0", diff --git a/svg/fontawesome/fas-cat.svg b/svg/fontawesome/fas-cat.svg new file mode 100644 index 000000000..b33d8601a --- /dev/null +++ b/svg/fontawesome/fas-cat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/svg/fontawesome/fas-store-alt-slash.svg b/svg/fontawesome/fas-store-alt-slash.svg new file mode 100644 index 000000000..3e722a0dd --- /dev/null +++ b/svg/fontawesome/fas-store-alt-slash.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/svg/fontawesome/fas-table-tennis.svg b/svg/fontawesome/fas-table-tennis.svg new file mode 100644 index 000000000..ebfa36a43 --- /dev/null +++ b/svg/fontawesome/fas-table-tennis.svg @@ -0,0 +1 @@ + \ No newline at end of file From 4ff1b48b862df1b57440710acf8c499c4ec16738 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Mon, 18 May 2020 11:09:58 -0400 Subject: [PATCH 51/67] Deprecate `contents` and `content=unknown` --- data/deprecated.json | 7 +++++++ data/taginfo.json | 2 ++ 2 files changed, 9 insertions(+) diff --git a/data/deprecated.json b/data/deprecated.json index fe483061f..300f7a0ec 100644 --- a/data/deprecated.json +++ b/data/deprecated.json @@ -283,6 +283,13 @@ "old": {"company": "consulting"}, "replace": {"office": "consulting"} }, + { + "old": {"content": "unknown"} + }, + { + "old": {"contents": "*"}, + "replace": {"content": "$1"} + }, { "old": {"craft": "catering"}, "replace": {"craft": "caterer"} diff --git a/data/taginfo.json b/data/taginfo.json index c69cd3b01..70e587aab 100644 --- a/data/taginfo.json +++ b/data/taginfo.json @@ -2046,6 +2046,8 @@ {"key": "camp_site", "value": "camp_pitch", "description": "๐Ÿ„ณ โžœ tourism=camp_pitch"}, {"key": "color", "description": "๐Ÿ„ณ โžœ colour=*"}, {"key": "company", "value": "consulting", "description": "๐Ÿ„ณ โžœ office=consulting"}, + {"key": "content", "value": "unknown", "description": "๐Ÿ„ณ"}, + {"key": "contents", "description": "๐Ÿ„ณ โžœ content=*"}, {"key": "craft", "value": "catering", "description": "๐Ÿ„ณ โžœ craft=caterer"}, {"key": "craft", "value": "glass", "description": "๐Ÿ„ณ โžœ craft=glaziery"}, {"key": "craft", "value": "jeweler", "description": "๐Ÿ„ณ โžœ shop=jewelry"}, From c8b80457c52ab8d96ee1ca6af914aeadf03235ba Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Mon, 18 May 2020 11:14:46 -0400 Subject: [PATCH 52/67] Add terms to shop=collector preset (re: #7588) --- data/presets.yaml | 2 +- data/presets/presets.json | 2 +- data/presets/presets/shop/collector.json | 2 ++ dist/locales/en.json | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/data/presets.yaml b/data/presets.yaml index 486a48154..4acbacd61 100644 --- a/data/presets.yaml +++ b/data/presets.yaml @@ -7812,7 +7812,7 @@ en: shop/collector: # shop=collector name: Collectibles Shop - # 'terms: antiques,coins,collection,collectors,comics,dolls,figurines,stamps,thrift' + # 'terms: antiques,coins,collection,collectors,comics,dolls,figurines,numismatics,philately,stamps,thrift' terms: '' shop/computer: # shop=computer diff --git a/data/presets/presets.json b/data/presets/presets.json index 5bbfe45de..49c22b4d3 100644 --- a/data/presets/presets.json +++ b/data/presets/presets.json @@ -1083,7 +1083,7 @@ "shop/clothes/underwear": {"icon": "maki-clothing-store", "geometry": ["point", "area"], "tags": {"shop": "clothes", "clothes": "underwear"}, "reference": {"key": "clothes", "value": "underwear"}, "terms": ["boutique", "bras", "brassieres", "briefs", "boxers", "fashion", "lingerie", "panties", "slips", "socks", "stockings", "underclothes", "undergarments", "underpants", "undies"], "name": "Underwear Store"}, "shop/clothes/wedding": {"icon": "temaki-gown", "geometry": ["point", "area"], "tags": {"shop": "clothes", "clothes": "wedding"}, "reference": {"key": "clothes", "value": "wedding"}, "terms": ["boutique", "bridal", "bride", "bridegroom", "bridesmaid", "groom", "groomsman", "tuxedo", "wedding dress", "wedding gown"], "name": "Wedding Clothes Store"}, "shop/coffee": {"icon": "temaki-coffee", "geometry": ["point", "area"], "tags": {"shop": "coffee"}, "name": "Coffee Store"}, - "shop/collector": {"icon": "fas-th", "fields": ["name", "collector", "{shop}"], "geometry": ["point", "area"], "terms": ["antiques", "coins", "collection", "collectors", "comics", "dolls", "figurines", "stamps", "thrift"], "tags": {"shop": "collector"}, "name": "Collectibles Shop"}, + "shop/collector": {"icon": "fas-th", "fields": ["name", "collector", "{shop}"], "geometry": ["point", "area"], "terms": ["antiques", "coins", "collection", "collectors", "comics", "dolls", "figurines", "numismatics", "philately", "stamps", "thrift"], "tags": {"shop": "collector"}, "name": "Collectibles Shop"}, "shop/computer": {"icon": "fas-laptop", "geometry": ["point", "area"], "tags": {"shop": "computer"}, "terms": ["desktop", "laptop", "hardware", "operating system", "software"], "name": "Computer Store"}, "shop/confectionery": {"icon": "maki-confectionery", "geometry": ["point", "area"], "terms": ["sweet"], "tags": {"shop": "confectionery"}, "name": "Candy Store"}, "shop/convenience": {"icon": "fas-shopping-basket", "geometry": ["point", "area"], "tags": {"shop": "convenience"}, "name": "Convenience Store"}, diff --git a/data/presets/presets/shop/collector.json b/data/presets/presets/shop/collector.json index 91f457825..a1f4ac465 100644 --- a/data/presets/presets/shop/collector.json +++ b/data/presets/presets/shop/collector.json @@ -17,6 +17,8 @@ "comics", "dolls", "figurines", + "numismatics", + "philately", "stamps", "thrift" ], diff --git a/dist/locales/en.json b/dist/locales/en.json index 3be643e82..5c5bb65dd 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -9267,7 +9267,7 @@ }, "shop/collector": { "name": "Collectibles Shop", - "terms": "antiques,coins,collection,collectors,comics,dolls,figurines,stamps,thrift" + "terms": "antiques,coins,collection,collectors,comics,dolls,figurines,numismatics,philately,stamps,thrift" }, "shop/computer": { "name": "Computer Store", From 1142d57321a0fd39a9bc0874eb41f4bd6f20bf8b Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Mon, 18 May 2020 11:22:46 -0400 Subject: [PATCH 53/67] Remove Both/All option from yield sign direction field (close #7581) --- data/presets.yaml | 8 ++++++++ data/presets/fields.json | 1 + data/presets/fields/direction_vertex_dual.json | 11 +++++++++++ data/presets/presets.json | 2 +- data/presets/presets/highway/give_way.json | 2 +- dist/locales/en.json | 7 +++++++ 6 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 data/presets/fields/direction_vertex_dual.json diff --git a/data/presets.yaml b/data/presets.yaml index 4acbacd61..ab4c4f1a9 100644 --- a/data/presets.yaml +++ b/data/presets.yaml @@ -745,6 +745,14 @@ en: both: Both / All # direction=forward forward: Forward + direction_vertex_dual: + # direction=* + label: Direction Affected + options: + # direction=backward + backward: Backward + # direction=forward + forward: Forward dispensing: # dispensing=* label: Dispenses Prescriptions diff --git a/data/presets/fields.json b/data/presets/fields.json index 9c27cd0ee..89b03e550 100644 --- a/data/presets/fields.json +++ b/data/presets/fields.json @@ -123,6 +123,7 @@ "diplomatic/services": {"key": "diplomatic:services:", "type": "multiCombo", "label": "Services"}, "direction_cardinal": {"key": "direction", "type": "combo", "label": "Direction", "strings": {"options": {"N": "North", "E": "East", "S": "South", "W": "West", "NE": "Northeast", "SE": "Southeast", "SW": "Southwest", "NW": "Northwest", "NNE": "North-northeast", "ENE": "East-northeast", "ESE": "East-southeast", "SSE": "South-southeast", "SSW": "South-southwest", "WSW": "West-southwest", "WNW": "West-northwest", "NNW": "North-northwest"}}}, "direction_clock": {"key": "direction", "type": "combo", "label": "Direction", "strings": {"options": {"clockwise": "Clockwise", "anticlockwise": "Counterclockwise"}}}, + "direction_vertex_dual": {"key": "direction", "type": "combo", "label": "Direction Affected", "strings": {"options": {"forward": "Forward", "backward": "Backward"}}}, "direction_vertex": {"key": "direction", "type": "combo", "label": "Direction Affected", "strings": {"options": {"forward": "Forward", "backward": "Backward", "both": "Both / All"}}}, "direction": {"key": "direction", "type": "number", "label": "Direction (Degrees Clockwise)", "placeholder": "45, 90, 180, 270"}, "dispensing": {"key": "dispensing", "type": "check", "label": "Dispenses Prescriptions", "default": "yes"}, diff --git a/data/presets/fields/direction_vertex_dual.json b/data/presets/fields/direction_vertex_dual.json new file mode 100644 index 000000000..ee4f70052 --- /dev/null +++ b/data/presets/fields/direction_vertex_dual.json @@ -0,0 +1,11 @@ +{ + "key": "direction", + "type": "combo", + "label": "Direction Affected", + "strings": { + "options": { + "forward": "Forward", + "backward": "Backward" + } + } +} diff --git a/data/presets/presets.json b/data/presets/presets.json index 49c22b4d3..1419040fa 100644 --- a/data/presets/presets.json +++ b/data/presets/presets.json @@ -527,7 +527,7 @@ "highway/footway/sidewalk": {"icon": "temaki-pedestrian", "geometry": ["line"], "tags": {"footway": "sidewalk"}, "addTags": {"highway": "footway", "footway": "sidewalk"}, "reference": {"key": "footway", "value": "sidewalk"}, "terms": ["pavement", "sidepath"], "name": "Sidewalk"}, "highway/footway/unmarked-raised": {"icon": "temaki-pedestrian", "fields": ["crossing", "access", "surface", "tactile_paving", "crossing/island"], "geometry": ["line"], "tags": {"footway": "crossing", "crossing": "unmarked", "traffic_calming": "table"}, "addTags": {"highway": "footway", "footway": "crossing", "crossing": "unmarked", "traffic_calming": "table"}, "reference": {"key": "traffic_calming", "value": "table"}, "terms": ["flat top", "hump", "speed", "slow"], "name": "Unmarked Crossing (Raised)"}, "highway/footway/unmarked": {"icon": "temaki-pedestrian", "fields": ["crossing", "access", "surface", "tactile_paving", "crossing/island"], "geometry": ["line"], "tags": {"footway": "crossing", "crossing": "unmarked"}, "addTags": {"highway": "footway", "footway": "crossing", "crossing": "unmarked"}, "reference": {"key": "footway", "value": "crossing"}, "terms": ["unmarked foot path crossing", "unmarked crosswalk", "unmarked pedestrian crossing"], "name": "Unmarked Crossing"}, - "highway/give_way": {"icon": "temaki-yield", "fields": ["direction_vertex"], "geometry": ["vertex"], "tags": {"highway": "give_way"}, "terms": ["give way", "yield", "sign"], "name": "Yield Sign"}, + "highway/give_way": {"icon": "temaki-yield", "fields": ["direction_vertex_dual"], "geometry": ["vertex"], "tags": {"highway": "give_way"}, "terms": ["give way", "yield", "sign"], "name": "Yield Sign"}, "highway/living_street": {"icon": "iD-highway-living-street", "fields": ["name", "oneway", "maxspeed", "lanes", "surface", "structure", "access"], "moreFields": ["covered", "cycleway", "flood_prone", "junction_line", "lit", "maxheight", "maxweight_bridge", "oneway/bicycle", "smoothness", "trolley_wire", "width"], "geometry": ["line"], "tags": {"highway": "living_street"}, "name": "Living Street"}, "highway/milestone": {"icon": "temaki-milestone", "geometry": ["point", "vertex"], "fields": ["distance", "direction_vertex"], "tags": {"highway": "milestone"}, "terms": ["mile marker", "mile post", "mile stone", "mileage marker", "milemarker", "milepost"], "name": "Highway Milestone"}, "highway/mini_roundabout": {"icon": "maki-circle-stroked", "geometry": ["vertex"], "terms": ["traffic circle"], "tags": {"highway": "mini_roundabout"}, "fields": ["direction_clock"], "name": "Mini-Roundabout"}, diff --git a/data/presets/presets/highway/give_way.json b/data/presets/presets/highway/give_way.json index 5716e0dee..e6001a406 100644 --- a/data/presets/presets/highway/give_way.json +++ b/data/presets/presets/highway/give_way.json @@ -1,7 +1,7 @@ { "icon": "temaki-yield", "fields": [ - "direction_vertex" + "direction_vertex_dual" ], "geometry": [ "vertex" diff --git a/dist/locales/en.json b/dist/locales/en.json index 5c5bb65dd..c894fa7e1 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -3249,6 +3249,13 @@ "anticlockwise": "Counterclockwise" } }, + "direction_vertex_dual": { + "label": "Direction Affected", + "options": { + "forward": "Forward", + "backward": "Backward" + } + }, "direction_vertex": { "label": "Direction Affected", "options": { From cd9c42109147c7c3b232530a39aac670bbdf84df Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Mon, 18 May 2020 13:39:52 -0400 Subject: [PATCH 54/67] Add Rooftop parking type field option (close #7578) Deprecate various parking tag values --- data/deprecated.json | 24 ++++++++++++++++++++++++ data/presets.yaml | 2 ++ data/presets/fields.json | 2 +- data/presets/fields/parking.json | 7 ++++--- data/taginfo.json | 11 +++++++++-- dist/locales/en.json | 7 ++++--- 6 files changed, 44 insertions(+), 9 deletions(-) diff --git a/data/deprecated.json b/data/deprecated.json index 300f7a0ec..775044c11 100644 --- a/data/deprecated.json +++ b/data/deprecated.json @@ -918,6 +918,30 @@ { "old": {"operator:type": "unknown"} }, + { + "old": {"parking": "covered"}, + "replace": {"covered": "yes"} + }, + { + "old": {"parking": "customers"}, + "replace": {"access": "customers"} + }, + { + "old": {"parking": "entrance"}, + "replace": {"amenity": "parking_entrance"} + }, + { + "old": {"parking": "park_and_ride"}, + "replace": {"park_ride": "yes"} + }, + { + "old": {"parking": "private"}, + "replace": {"access": "private"} + }, + { + "old": {"parking": "street"}, + "replace": {"parking": "lane"} + }, { "old": {"place_name": "*"}, "replace": {"name": "$1"} diff --git a/data/presets.yaml b/data/presets.yaml index ab4c4f1a9..27544ccb2 100644 --- a/data/presets.yaml +++ b/data/presets.yaml @@ -1657,6 +1657,8 @@ en: lane: Roadside Lane # parking=multi-storey multi-storey: Multilevel + # parking=rooftop + rooftop: Rooftop # parking=sheds sheds: Sheds # parking=surface diff --git a/data/presets/fields.json b/data/presets/fields.json index 89b03e550..169908e8f 100644 --- a/data/presets/fields.json +++ b/data/presets/fields.json @@ -299,7 +299,7 @@ "par": {"key": "par", "type": "number", "minValue": 1, "label": "Par", "placeholder": "3, 4, 5..."}, "park_ride": {"key": "park_ride", "type": "check", "label": "Park and Ride"}, "parking_space": {"key": "parking_space", "type": "combo", "label": "Type"}, - "parking": {"key": "parking", "type": "combo", "label": "Type", "strings": {"options": {"surface": "Surface", "multi-storey": "Multilevel", "underground": "Underground", "sheds": "Sheds", "carports": "Carports", "garage_boxes": "Garage Boxes", "lane": "Roadside Lane"}}}, + "parking": {"key": "parking", "type": "combo", "label": "Type", "strings": {"options": {"surface": "Surface", "underground": "Underground", "multi-storey": "Multilevel", "lane": "Roadside Lane", "carports": "Carports", "garage_boxes": "Garage Boxes", "rooftop": "Rooftop", "sheds": "Sheds"}}}, "payment_multi_fee": {"key": "payment:", "type": "multiCombo", "label": "Payment Types", "prerequisiteTag": {"key": "fee", "valueNot": "no"}}, "payment_multi": {"key": "payment:", "type": "multiCombo", "label": "Payment Types"}, "phases": {"key": "phases", "type": "number", "minValue": 1, "label": "Phases", "placeholder": "1, 2, 3..."}, diff --git a/data/presets/fields/parking.json b/data/presets/fields/parking.json index d0ff081d2..a0925767b 100644 --- a/data/presets/fields/parking.json +++ b/data/presets/fields/parking.json @@ -5,12 +5,13 @@ "strings": { "options": { "surface": "Surface", - "multi-storey": "Multilevel", "underground": "Underground", - "sheds": "Sheds", + "multi-storey": "Multilevel", + "lane": "Roadside Lane", "carports": "Carports", "garage_boxes": "Garage Boxes", - "lane": "Roadside Lane" + "rooftop": "Rooftop", + "sheds": "Sheds" } } } diff --git a/data/taginfo.json b/data/taginfo.json index 70e587aab..c11d95687 100644 --- a/data/taginfo.json +++ b/data/taginfo.json @@ -1686,10 +1686,11 @@ {"key": "park_ride", "description": "๐Ÿ„ต Park and Ride"}, {"key": "parking_space", "description": "๐Ÿ„ต Type"}, {"key": "parking", "value": "surface", "description": "๐Ÿ„ต Type"}, - {"key": "parking", "value": "sheds", "description": "๐Ÿ„ต Type"}, + {"key": "parking", "value": "lane", "description": "๐Ÿ„ต Type"}, {"key": "parking", "value": "carports", "description": "๐Ÿ„ต Type"}, {"key": "parking", "value": "garage_boxes", "description": "๐Ÿ„ต Type"}, - {"key": "parking", "value": "lane", "description": "๐Ÿ„ต Type"}, + {"key": "parking", "value": "rooftop", "description": "๐Ÿ„ต Type"}, + {"key": "parking", "value": "sheds", "description": "๐Ÿ„ต Type"}, {"key": "payment:", "description": "๐Ÿ„ต Payment Types"}, {"key": "phases", "description": "๐Ÿ„ต Phases"}, {"key": "phone", "description": "๐Ÿ„ต Telephone"}, @@ -2189,6 +2190,12 @@ {"key": "operator:type", "value": "Public", "description": "๐Ÿ„ณ โžœ operator:type=public"}, {"key": "operator:type", "value": "Publico", "description": "๐Ÿ„ณ โžœ operator:type=public"}, {"key": "operator:type", "value": "unknown", "description": "๐Ÿ„ณ"}, + {"key": "parking", "value": "covered", "description": "๐Ÿ„ณ โžœ covered=yes"}, + {"key": "parking", "value": "customers", "description": "๐Ÿ„ณ โžœ access=customers"}, + {"key": "parking", "value": "entrance", "description": "๐Ÿ„ณ โžœ amenity=parking_entrance"}, + {"key": "parking", "value": "park_and_ride", "description": "๐Ÿ„ณ โžœ park_ride=yes"}, + {"key": "parking", "value": "private", "description": "๐Ÿ„ณ โžœ access=private"}, + {"key": "parking", "value": "street", "description": "๐Ÿ„ณ โžœ parking=lane"}, {"key": "place_name", "description": "๐Ÿ„ณ โžœ name=*"}, {"key": "pole", "value": "transition", "description": "๐Ÿ„ณ โžœ location:transition=yes"}, {"key": "postcode", "description": "๐Ÿ„ณ โžœ addr:postcode=*"}, diff --git a/dist/locales/en.json b/dist/locales/en.json index c894fa7e1..2efd8074a 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -4042,12 +4042,13 @@ "label": "Type", "options": { "surface": "Surface", - "multi-storey": "Multilevel", "underground": "Underground", - "sheds": "Sheds", + "multi-storey": "Multilevel", + "lane": "Roadside Lane", "carports": "Carports", "garage_boxes": "Garage Boxes", - "lane": "Roadside Lane" + "rooftop": "Rooftop", + "sheds": "Sheds" } }, "payment_multi_fee": { From 7b09b6c0dcd193af04926727b820f346a1d86ca8 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Mon, 18 May 2020 17:14:50 -0400 Subject: [PATCH 55/67] Enable low-zoom display of focused feature when resolving conflicts (close #7330) --- modules/modes/save.js | 10 ++++++++-- modules/renderer/map.js | 11 +++++++++-- modules/ui/conflicts.js | 10 ++++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/modules/modes/save.js b/modules/modes/save.js index 9acc5ac0f..e58326a81 100644 --- a/modules/modes/save.js +++ b/modules/modes/save.js @@ -16,6 +16,7 @@ export function modeSave(context) { var commit = uiCommit(context) .on('cancel', cancel); + var _conflictsUi; // uiConflicts var _location; var _success; @@ -65,7 +66,7 @@ export function modeSave(context) { .classed('active', true) .classed('inactive', false); - var ui = uiConflicts(context) + _conflictsUi = uiConflicts(context) .conflictList(conflicts) .origChanges(origChanges) .on('cancel', function() { @@ -86,7 +87,7 @@ export function modeSave(context) { uploader.processResolvedConflicts(changeset); }); - selection.call(ui); + selection.call(_conflictsUi); } @@ -199,6 +200,11 @@ export function modeSave(context) { } + mode.selectedIDs = function() { + return _conflictsUi ? _conflictsUi.shownEntityIds() : []; + }; + + mode.enter = function() { // Show sidebar context.ui().sidebar.expand(); diff --git a/modules/renderer/map.js b/modules/renderer/map.js index 44af8309c..36e8e3d6e 100644 --- a/modules/renderer/map.js +++ b/modules/renderer/map.js @@ -320,6 +320,7 @@ export function rendererMap(context) { var data; var set; var filter; + var applyFeatureLayerFilters = true; if (map.isInWideSelection()) { data = []; @@ -329,6 +330,8 @@ export function rendererMap(context) { }); fullRedraw = true; filter = utilFunctor(true); + // selected features should always be visible, so we can skip filtering + applyFeatureLayerFilters = false; } else if (difference) { var complete = difference.complete(map.extent()); @@ -356,7 +359,11 @@ export function rendererMap(context) { } } - data = features.filter(data, graph); + if (applyFeatureLayerFilters) { + data = features.filter(data, graph); + } else { + context.features().resetStats(); + } if (mode && mode.id === 'select') { // update selected vertices - the user might have just double-clicked a way, @@ -1022,7 +1029,7 @@ export function rendererMap(context) { map.isInWideSelection = function() { - return !map.withinEditableZoom() && context.mode() && context.mode().id === 'select'; + return !map.withinEditableZoom() && context.selectedIDs().length; }; diff --git a/modules/ui/conflicts.js b/modules/ui/conflicts.js index 2a91c357e..1cd2134cf 100644 --- a/modules/ui/conflicts.js +++ b/modules/ui/conflicts.js @@ -25,6 +25,7 @@ export function uiConflicts(context) { var keybinding = utilKeybinding('conflicts'); var _origChanges; var _conflictList; + var _shownConflictIndex; function keybindingOn() { @@ -145,6 +146,7 @@ export function uiConflicts(context) { function showConflict(selection, index) { index = utilWrap(index, _conflictList.length); + _shownConflictIndex = index; var parent = d3_select(selection.node().parentNode); @@ -343,5 +345,13 @@ export function uiConflicts(context) { }; + conflicts.shownEntityIds = function() { + if (_conflictList && typeof _shownConflictIndex === 'number') { + return [_conflictList[_shownConflictIndex].id]; + } + return []; + }; + + return utilRebind(conflicts, dispatch, 'on'); } From 02f57eb23a5aa2ff74f9d3922583008f39d20e40 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Tue, 19 May 2020 10:26:53 -0400 Subject: [PATCH 56/67] Don't add default field values when upgrading to a replacement preset (close #7613) --- modules/validations/outdated_tags.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/validations/outdated_tags.js b/modules/validations/outdated_tags.js index c162d17d8..9074b4d3e 100644 --- a/modules/validations/outdated_tags.js +++ b/modules/validations/outdated_tags.js @@ -63,7 +63,7 @@ export function validationOutdatedTags() { // upgrade preset.. if (preset.replacement) { const newPreset = presetManager.item(preset.replacement); - graph = actionChangePreset(entity.id, preset, newPreset)(graph); + graph = actionChangePreset(entity.id, preset, newPreset, true /* skip field defaults */)(graph); entity = graph.entity(entity.id); preset = newPreset; } From ec73a88e268aea0155c0f0ad0bc81ff28c2e2a30 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Tue, 19 May 2020 11:11:50 -0400 Subject: [PATCH 57/67] Don't show tooltips when the mouse has buttons pressed --- modules/ui/popover.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/ui/popover.js b/modules/ui/popover.js index 8481e2709..4ae6c2b97 100644 --- a/modules/ui/popover.js +++ b/modules/ui/popover.js @@ -169,6 +169,10 @@ export function uiPopover(klass) { return; } } + + // don't show if buttons are pressed, e.g. during click and drag of map + if (d3_event.buttons !== 0) return; + show.apply(this, arguments); }); anchor.on(_pointerPrefix + 'leave.popover', function() { From c4f5dbbc4c500afac4795a69d0d5277f25547330 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Tue, 19 May 2020 11:13:03 -0400 Subject: [PATCH 58/67] Don't register spacebar-to-select unless the pointer is over the map --- modules/behavior/select.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/modules/behavior/select.js b/modules/behavior/select.js index aa1e82a23..1dce63853 100644 --- a/modules/behavior/select.js +++ b/modules/behavior/select.js @@ -30,6 +30,15 @@ export function behaviorSelect(context) { } + function mapContains(event) { + var rect = context.container().select('.main-map').node().getBoundingClientRect(); + return event.clientX >= rect.left && + event.clientX <= rect.right && + event.clientY >= rect.top && + event.clientY <= rect.bottom; + } + + function keydown() { if (d3_event.keyCode === 32) { @@ -158,7 +167,7 @@ export function behaviorSelect(context) { var p2 = point(_lastPointerEvent); var dist = geoVecLength(_p1, p2); _p1 = null; - if (dist > _tolerancePx) { + if (dist > _tolerancePx || !mapContains(_lastPointerEvent)) { resetProperties(); return; } @@ -263,6 +272,7 @@ export function behaviorSelect(context) { d3_select(window) .on('keydown.select', keydown) .on('keyup.select', keyup) + .on(_pointerPrefix + 'move.select', pointermove, true) .on('contextmenu.select-window', function() { // Edge and IE really like to show the contextmenu on the // menubar when user presses a keyboard menu button @@ -275,7 +285,6 @@ export function behaviorSelect(context) { selection .on(_pointerPrefix + 'down.select', pointerdown) - .on(_pointerPrefix + 'move.select', pointermove) .on('contextmenu.select', contextmenu); if (d3_event && d3_event.shiftKey) { @@ -292,11 +301,11 @@ export function behaviorSelect(context) { .on('keydown.select', null) .on('keyup.select', null) .on('contextmenu.select-window', null) + .on(_pointerPrefix + 'move.select', null, true) .on(_pointerPrefix + 'up.select', null, true); selection .on(_pointerPrefix + 'down.select', null) - .on(_pointerPrefix + 'move.select', null) .on('contextmenu.select', null); context.surface() From 311566328eb5b5737ca9ba9fa0c792d3bb675fb5 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Tue, 19 May 2020 12:08:14 -0400 Subject: [PATCH 59/67] Enable nudging the selection via shift+arrow keys and shift+command+arrow keys to nudge more (close #7186) No longer nudge the viewport with shift+arrow keys --- data/core.yaml | 2 ++ data/shortcuts.json | 12 ++++++++++++ dist/locales/en.json | 2 ++ modules/modes/select.js | 29 +++++++++++++++++++++++++++++ modules/ui/init.js | 9 +++++---- 5 files changed, 50 insertions(+), 4 deletions(-) diff --git a/data/core.yaml b/data/core.yaml index 491e05cc5..12688876c 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -2053,6 +2053,8 @@ en: split: "Split a line into two at the selected node" reverse: "Reverse selected features" move: "Move selected features" + nudge: "Nudge selected features" + nudge_more: "Nudge selected features by a lot" rotate: "Rotate selected features" orthogonalize: "Square corners of a line or area" straighten: "Straighten a line or points" diff --git a/data/shortcuts.json b/data/shortcuts.json index fec11cba0..7620679d9 100644 --- a/data/shortcuts.json +++ b/data/shortcuts.json @@ -269,6 +269,18 @@ "shortcuts": ["operations.move.key"], "text": "shortcuts.editing.operations.move" }, + { + "modifiers": ["โ‡ง"], + "shortcuts": ["โ†“", "โ†‘", "โ†", "โ†’"], + "text": "shortcuts.editing.operations.nudge", + "separator": "," + }, + { + "modifiers": ["โŒ˜", "โ‡ง"], + "shortcuts": ["โ†“", "โ†‘", "โ†", "โ†’"], + "text": "shortcuts.editing.operations.nudge_more", + "separator": "," + }, { "shortcuts": ["operations.rotate.key"], "text": "shortcuts.editing.operations.rotate" diff --git a/dist/locales/en.json b/dist/locales/en.json index 302e16402..06ab7bf57 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -2528,6 +2528,8 @@ "split": "Split a line into two at the selected node", "reverse": "Reverse selected features", "move": "Move selected features", + "nudge": "Nudge selected features", + "nudge_more": "Nudge selected features by a lot", "rotate": "Rotate selected features", "orthogonalize": "Square corners of a line or area", "straighten": "Straighten a line or points", diff --git a/modules/modes/select.js b/modules/modes/select.js index 11572c71c..7cf00981b 100644 --- a/modules/modes/select.js +++ b/modules/modes/select.js @@ -4,6 +4,7 @@ import { t } from '../core/localizer'; import { actionAddMidpoint } from '../actions/add_midpoint'; import { actionDeleteRelation } from '../actions/delete_relation'; +import { actionMove } from '../actions/move'; import { behaviorBreathe } from '../behavior/breathe'; import { behaviorHover } from '../behavior/hover'; @@ -11,6 +12,8 @@ import { behaviorLasso } from '../behavior/lasso'; import { behaviorPaste } from '../behavior/paste'; import { behaviorSelect } from '../behavior/select'; +import { operationMove } from '../operations/move'; + import { geoExtent, geoChooseEdge } from '../geo'; import { modeBrowse } from './browse'; import { modeDragNode } from './drag_node'; @@ -212,6 +215,14 @@ export function modeSelect(context, selectedIDs) { .on([']', 'pgdown'], nextVertex) .on(['{', uiCmd('โŒ˜['), 'home'], firstVertex) .on(['}', uiCmd('โŒ˜]'), 'end'], lastVertex) + .on(uiCmd('โ‡งโ†'), nudgeSelection([-10, 0])) + .on(uiCmd('โ‡งโ†‘'), nudgeSelection([0, -10])) + .on(uiCmd('โ‡งโ†’'), nudgeSelection([10, 0])) + .on(uiCmd('โ‡งโ†“'), nudgeSelection([0, 10])) + .on(uiCmd('โ‡งโŒ˜โ†'), nudgeSelection([-100, 0])) + .on(uiCmd('โ‡งโŒ˜โ†‘'), nudgeSelection([0, -100])) + .on(uiCmd('โ‡งโŒ˜โ†’'), nudgeSelection([100, 0])) + .on(uiCmd('โ‡งโŒ˜โ†“'), nudgeSelection([0, 100])) .on(['\\', 'pause'], nextParent) .on('โŽ‹', esc, true); @@ -258,6 +269,24 @@ export function modeSelect(context, selectedIDs) { } + function nudgeSelection(delta) { + return function() { + d3_event.stopImmediatePropagation(); + + var moveOp = operationMove(context, selectedIDs); + if (moveOp.disabled()) { + context.ui().flash + .duration(4000) + .iconName('#iD-operation-' + moveOp.id) + .iconClass('operation disabled') + .text(moveOp.tooltip)(); + } else { + context.perform(actionMove(selectedIDs, delta, context.projection), moveOp.annotation()); + } + }; + } + + function didDoubleUp(loc) { if (!context.map().withinEditableZoom()) return; diff --git a/modules/ui/init.js b/modules/ui/init.js index 0e9eaabb8..a9bed76e1 100644 --- a/modules/ui/init.js +++ b/modules/ui/init.js @@ -319,10 +319,10 @@ export function uiInit(context) { .on('โ†‘', pan([0, panPixels])) .on('โ†’', pan([-panPixels, 0])) .on('โ†“', pan([0, -panPixels])) - .on(['โ‡งโ†', uiCmd('โŒ˜โ†')], pan([map.dimensions()[0], 0])) - .on(['โ‡งโ†‘', uiCmd('โŒ˜โ†‘')], pan([0, map.dimensions()[1]])) - .on(['โ‡งโ†’', uiCmd('โŒ˜โ†’')], pan([-map.dimensions()[0], 0])) - .on(['โ‡งโ†“', uiCmd('โŒ˜โ†“')], pan([0, -map.dimensions()[1]])) + .on(uiCmd('โŒ˜โ†'), pan([map.dimensions()[0], 0])) + .on(uiCmd('โŒ˜โ†‘'), pan([0, map.dimensions()[1]])) + .on(uiCmd('โŒ˜โ†’'), pan([-map.dimensions()[0], 0])) + .on(uiCmd('โŒ˜โ†“'), pan([0, -map.dimensions()[1]])) .on(uiCmd('โŒ˜' + t('background.key')), function quickSwitch() { if (d3_event) { d3_event.stopImmediatePropagation(); @@ -399,6 +399,7 @@ export function uiInit(context) { function pan(d) { return function() { + if (d3_event.shiftKey) return; if (context.container().select('.combobox').size()) return; d3_event.preventDefault(); context.map().pan(d, 100); From a0ae1bf1de529cde15f8c22f7f7ec816e29e0362 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Tue, 19 May 2020 12:11:03 -0400 Subject: [PATCH 60/67] Prevent nudging the selection during low-zoom editing --- modules/modes/select.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/modes/select.js b/modules/modes/select.js index 7cf00981b..d23704a93 100644 --- a/modules/modes/select.js +++ b/modules/modes/select.js @@ -271,7 +271,8 @@ export function modeSelect(context, selectedIDs) { function nudgeSelection(delta) { return function() { - d3_event.stopImmediatePropagation(); + // prevent nudging during low zoom selection + if (!context.map().withinEditableZoom()) return; var moveOp = operationMove(context, selectedIDs); if (moveOp.disabled()) { From de90cc041041e0a03e1b09a47be11ef7c7998640 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Tue, 19 May 2020 13:21:16 -0400 Subject: [PATCH 61/67] Fix code tests --- test/spec/behavior/select.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/spec/behavior/select.js b/test/spec/behavior/select.js index ea7365ab0..df7dfc9c8 100644 --- a/test/spec/behavior/select.js +++ b/test/spec/behavior/select.js @@ -48,8 +48,8 @@ describe('iD.behaviorSelect', function() { specify('click on entity selects the entity', function(done) { var el = context.surface().selectAll('.' + a.id).node(); - happen.mousedown(el); - happen.mouseup(el); + happen.mousedown(el, { clientX: 100, clientY: 100 }); + happen.mouseup(el, { clientX: 100, clientY: 100 }); window.setTimeout(function() { expect(context.selectedIDs()).to.eql([a.id]); done(); @@ -59,8 +59,8 @@ describe('iD.behaviorSelect', function() { specify('click on empty space clears the selection', function(done) { context.enter(iD.modeSelect(context, [a.id])); var el = context.surface().node(); - happen.mousedown(el); - happen.mouseup(el); + happen.mousedown(el, { clientX: 100, clientY: 100 }); + happen.mouseup(el, { clientX: 100, clientY: 100 }); window.setTimeout(function() { expect(context.mode().id).to.eql('browse'); done(); @@ -70,8 +70,8 @@ describe('iD.behaviorSelect', function() { specify('shift-click on unselected entity adds it to the selection', function(done) { context.enter(iD.modeSelect(context, [a.id])); var el = context.surface().selectAll('.' + b.id).node(); - happen.mousedown(el, { shiftKey: true }); - happen.mouseup(el, { shiftKey: true }); + happen.mousedown(el, { clientX: 100, clientY: 100, shiftKey: true }); + happen.mouseup(el, { clientX: 100, clientY: 100, shiftKey: true }); window.setTimeout(function() { expect(context.selectedIDs()).to.eql([a.id, b.id]); done(); @@ -81,8 +81,8 @@ describe('iD.behaviorSelect', function() { specify('shift-click on selected entity removes it from the selection', function(done) { context.enter(iD.modeSelect(context, [a.id, b.id])); var el = context.surface().selectAll('.' + b.id).node(); - happen.mousedown(el, { shiftKey: true }); - happen.mouseup(el, { shiftKey: true }); + happen.mousedown(el, { clientX: 100, clientY: 100, shiftKey: true }); + happen.mouseup(el, { clientX: 100, clientY: 100, shiftKey: true }); window.setTimeout(function() { expect(context.selectedIDs()).to.eql([a.id]); done(); @@ -92,8 +92,8 @@ describe('iD.behaviorSelect', function() { specify('shift-click on last selected entity clears the selection', function(done) { context.enter(iD.modeSelect(context, [a.id])); var el = context.surface().selectAll('.' + a.id).node(); - happen.mousedown(el, { shiftKey: true }); - happen.mouseup(el, { shiftKey: true }); + happen.mousedown(el, { clientX: 100, clientY: 100, shiftKey: true }); + happen.mouseup(el, { clientX: 100, clientY: 100, shiftKey: true }); window.setTimeout(function() { expect(context.mode().id).to.eql('browse'); done(); @@ -103,8 +103,8 @@ describe('iD.behaviorSelect', function() { specify('shift-click on empty space leaves the selection unchanged', function(done) { context.enter(iD.modeSelect(context, [a.id])); var el = context.surface().node(); - happen.mousedown(el, { shiftKey: true }); - happen.mouseup(el, { shiftKey: true }); + happen.mousedown(el, { clientX: 100, clientY: 100, shiftKey: true }); + happen.mouseup(el, { clientX: 100, clientY: 100, shiftKey: true }); window.setTimeout(function() { expect(context.selectedIDs()).to.eql([a.id]); done(); From 2acd1112940a7af6d9bd2b8625785482308ee2d0 Mon Sep 17 00:00:00 2001 From: Eric Christensen Date: Tue, 19 May 2020 13:39:12 -0400 Subject: [PATCH 62/67] Update lighthouse.json Not all lighthouses are light_major as some are no longer active. --- data/presets/presets/man_made/lighthouse.json | 8 -------- 1 file changed, 8 deletions(-) diff --git a/data/presets/presets/man_made/lighthouse.json b/data/presets/presets/man_made/lighthouse.json index 25e28612e..bc77953f7 100644 --- a/data/presets/presets/man_made/lighthouse.json +++ b/data/presets/presets/man_made/lighthouse.json @@ -22,13 +22,5 @@ "tags": { "man_made": "lighthouse" }, - "addTags": { - "man_made": "lighthouse", - "seamark:type": "light_major" - }, - "removeTags": { - "man_made": "lighthouse", - "seamark:type": "*" - }, "name": "Lighthouse" } From e4de64fa9189ca90e851d0188942462da35ca38f Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Tue, 19 May 2020 14:15:10 -0400 Subject: [PATCH 63/67] Add derived data for #7621 --- data/presets/presets.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/presets/presets.json b/data/presets/presets.json index 1419040fa..a358b87ea 100644 --- a/data/presets/presets.json +++ b/data/presets/presets.json @@ -751,7 +751,7 @@ "man_made/flagpole": {"icon": "maki-embassy", "fields": ["operator", "flag/type", "country_flag", "lit", "height"], "moreFields": ["manufacturer", "material"], "geometry": ["point", "vertex"], "tags": {"man_made": "flagpole"}, "name": "Flagpole"}, "man_made/gasometer": {"icon": "temaki-storage_tank", "fields": ["content", "building_area"], "geometry": ["point", "area"], "terms": ["gas holder"], "tags": {"man_made": "gasometer"}, "name": "Gasometer"}, "man_made/groyne": {"fields": ["material", "seamark/type"], "geometry": ["line", "area"], "tags": {"man_made": "groyne"}, "name": "Groin"}, - "man_made/lighthouse": {"icon": "maki-lighthouse", "fields": ["name", "operator", "building_area", "height"], "moreFields": ["address", "email", "fax", "gnis/feature_id", "phone", "seamark/type", "website"], "geometry": ["point", "area"], "tags": {"man_made": "lighthouse"}, "addTags": {"man_made": "lighthouse", "seamark:type": "light_major"}, "removeTags": {"man_made": "lighthouse", "seamark:type": "*"}, "name": "Lighthouse"}, + "man_made/lighthouse": {"icon": "maki-lighthouse", "fields": ["name", "operator", "building_area", "height"], "moreFields": ["address", "email", "fax", "gnis/feature_id", "phone", "seamark/type", "website"], "geometry": ["point", "area"], "tags": {"man_made": "lighthouse"}, "name": "Lighthouse"}, "man_made/manhole": {"icon": "temaki-manhole", "fields": ["manhole", "operator", "label", "ref"], "geometry": ["point", "vertex"], "tags": {"manhole": "*"}, "addTags": {"man_made": "manhole", "manhole": "*"}, "terms": ["cover", "hole", "sewer", "sewage", "telecom"], "name": "Manhole"}, "man_made/manhole/drain": {"icon": "temaki-manhole", "fields": ["operator", "ref"], "geometry": ["point", "vertex"], "tags": {"manhole": "drain"}, "addTags": {"man_made": "manhole", "manhole": "drain"}, "terms": ["cover", "drain", "hole", "rain", "sewer", "sewage", "storm"], "name": "Storm Drain"}, "man_made/manhole/gas": {"icon": "temaki-gas_manhole", "fields": ["operator", "ref"], "geometry": ["point", "vertex"], "tags": {"manhole": "gas"}, "addTags": {"man_made": "manhole", "manhole": "gas"}, "terms": ["cover", "gas", "heat", "hole", "utility"], "name": "Gas Utility Manhole"}, From f3b25551366b7698dce485e6a1f51369815d5e18 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Tue, 19 May 2020 14:36:07 -0400 Subject: [PATCH 64/67] Add amenity=give_box preset and deprecate amenity=givebox (close #7574) --- data/deprecated.json | 4 ++ data/presets.yaml | 5 +++ data/presets/presets.json | 1 + data/presets/presets/amenity/give_box.json | 44 ++++++++++++++++++++++ data/taginfo.json | 2 + dist/locales/en.json | 4 ++ svg/fontawesome/fas-box-open.svg | 1 + 7 files changed, 61 insertions(+) create mode 100644 data/presets/presets/amenity/give_box.json create mode 100644 svg/fontawesome/fas-box-open.svg diff --git a/data/deprecated.json b/data/deprecated.json index 775044c11..5c8b09985 100644 --- a/data/deprecated.json +++ b/data/deprecated.json @@ -95,6 +95,10 @@ "old": {"amenity": "garages"}, "replace": {"landuse": "garages"} }, + { + "old": {"amenity": "givebox"}, + "replace": {"amenity": "give_box"} + }, { "old": {"amenity": "gym"}, "replace": {"leisure": "fitness_centre"} diff --git a/data/presets.yaml b/data/presets.yaml index 27544ccb2..d159a1f99 100644 --- a/data/presets.yaml +++ b/data/presets.yaml @@ -3324,6 +3324,11 @@ en: name: Gambling Hall # 'terms: betting,bingo,blackjack,casino,craps,gamble,gambling,keno,lottery,pachinko,poker,roulette,slot machines,slots' terms: '' + amenity/give_box: + # amenity=give_box + name: Free Box + # 'terms: donations,free box,free table,freebox,give box,give shelf,givebox,library,share shelf' + terms: '' amenity/grave_yard: # amenity=grave_yard name: Graveyard diff --git a/data/presets/presets.json b/data/presets/presets.json index a358b87ea..01ecd86a8 100644 --- a/data/presets/presets.json +++ b/data/presets/presets.json @@ -126,6 +126,7 @@ "amenity/fountain": {"icon": "temaki-fountain", "fields": ["name", "operator", "fountain", "drinking_water", "height", "lit"], "moreFields": ["covered", "indoor", "level", "manufacturer"], "geometry": ["point", "area"], "tags": {"amenity": "fountain"}, "terms": ["basin", "water"], "name": "Fountain"}, "amenity/fuel": {"icon": "maki-fuel", "fields": ["name", "brand", "operator", "address", "fuel_multi", "self_service"], "moreFields": ["brand", "building", "email", "fax", "gnis/feature_id", "opening_hours", "opening_hours/covid19", "payment_multi", "phone", "ref/vatin", "website", "wheelchair"], "geometry": ["point", "area"], "terms": ["petrol", "fuel", "gasoline", "propane", "diesel", "lng", "cng", "biodiesel"], "tags": {"amenity": "fuel"}, "name": "Gas Station"}, "amenity/gambling": {"icon": "fas-coins", "fields": ["{amenity/casino}"], "moreFields": ["{amenity/casino}"], "geometry": ["point", "area"], "terms": ["betting", "bingo", "blackjack", "casino", "craps", "gamble", "gambling", "keno", "lottery", "pachinko", "poker", "roulette", "slot machines", "slots"], "tags": {"amenity": "gambling"}, "name": "Gambling Hall"}, + "amenity/give_box": {"icon": "fas-box-open", "fields": ["name", "operator", "opening_hours", "opening_hours/covid19", "access_simple", "website"], "moreFields": ["address", "brand", "capacity", "covered", "email", "indoor", "level", "lit", "location", "phone", "ref", "wheelchair"], "geometry": ["point", "area"], "terms": ["donations", "free box", "free table", "freebox", "give box", "give shelf", "givebox", "library", "share shelf"], "tags": {"amenity": "give_box"}, "name": "Free Box"}, "amenity/grave_yard": {"icon": "maki-cemetery", "fields": ["religion", "address"], "moreFields": ["email", "fax", "gnis/feature_id", "phone", "website"], "geometry": ["point", "area"], "tags": {"amenity": "grave_yard"}, "terms": ["burial ground", "cemetary", "cemetery", "churchyard", "columbarium", "grave yard", "graveyard", "mausoleum", "tomb"], "name": "Graveyard"}, "amenity/grit_bin": {"icon": "fas-box", "fields": ["operator", "access_simple", "material", "collection_times"], "moreFields": ["colour", "height", "lit"], "geometry": ["point", "vertex"], "tags": {"amenity": "grit_bin"}, "terms": ["salt", "sand"], "name": "Grit Bin"}, "amenity/hospital": {"icon": "maki-hospital", "fields": ["name", "operator", "operator/type", "healthcare/speciality", "address", "emergency"], "moreFields": ["email", "fax", "gnis/feature_id", "internet_access", "internet_access/fee", "internet_access/ssid", "phone", "website", "wheelchair"], "geometry": ["area", "point"], "terms": ["clinic", "doctor", "emergency room", "health", "infirmary", "institution", "sanatorium", "sanitarium", "sick", "surgery", "ward"], "tags": {"amenity": "hospital"}, "addTags": {"amenity": "hospital", "healthcare": "hospital"}, "reference": {"key": "amenity", "value": "hospital"}, "name": "Hospital Grounds"}, diff --git a/data/presets/presets/amenity/give_box.json b/data/presets/presets/amenity/give_box.json new file mode 100644 index 000000000..e28e3b81e --- /dev/null +++ b/data/presets/presets/amenity/give_box.json @@ -0,0 +1,44 @@ +{ + "icon": "fas-box-open", + "fields": [ + "name", + "operator", + "opening_hours", + "opening_hours/covid19", + "access_simple", + "website" + ], + "moreFields": [ + "address", + "brand", + "capacity", + "covered", + "email", + "indoor", + "level", + "lit", + "location", + "phone", + "ref", + "wheelchair" + ], + "geometry": [ + "point", + "area" + ], + "terms": [ + "donations", + "free box", + "free table", + "freebox", + "give box", + "give shelf", + "givebox", + "library", + "share shelf" + ], + "tags": { + "amenity": "give_box" + }, + "name": "Free Box" +} diff --git a/data/taginfo.json b/data/taginfo.json index c11d95687..c0c325449 100644 --- a/data/taginfo.json +++ b/data/taginfo.json @@ -129,6 +129,7 @@ {"key": "amenity", "value": "fountain", "description": "๐Ÿ„ฟ Fountain", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/fountain.svg"}, {"key": "amenity", "value": "fuel", "description": "๐Ÿ„ฟ Gas Station", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/mapbox/maki/icons/fuel-15.svg"}, {"key": "amenity", "value": "gambling", "description": "๐Ÿ„ฟ Gambling Hall", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/openstreetmap/iD@develop/svg/fontawesome/fas-coins.svg"}, + {"key": "amenity", "value": "give_box", "description": "๐Ÿ„ฟ Free Box", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/openstreetmap/iD@develop/svg/fontawesome/fas-box-open.svg"}, {"key": "amenity", "value": "grave_yard", "description": "๐Ÿ„ฟ Graveyard", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/mapbox/maki/icons/cemetery-15.svg"}, {"key": "amenity", "value": "grit_bin", "description": "๐Ÿ„ฟ Grit Bin", "object_types": ["node"], "icon_url": "https://cdn.jsdelivr.net/gh/openstreetmap/iD@develop/svg/fontawesome/fas-box.svg"}, {"key": "amenity", "value": "hospital", "description": "๐Ÿ„ฟ Hospital Grounds", "object_types": ["area", "node"], "icon_url": "https://cdn.jsdelivr.net/gh/mapbox/maki/icons/hospital-15.svg"}, @@ -2004,6 +2005,7 @@ {"key": "amenity", "value": "firepit", "description": "๐Ÿ„ณ โžœ leisure=firepit"}, {"key": "amenity", "value": "garage", "description": "๐Ÿ„ณ โžœ landuse=garages"}, {"key": "amenity", "value": "garages", "description": "๐Ÿ„ณ โžœ landuse=garages"}, + {"key": "amenity", "value": "givebox", "description": "๐Ÿ„ณ โžœ amenity=give_box"}, {"key": "amenity", "value": "gym", "description": "๐Ÿ„ณ โžœ leisure=fitness_centre"}, {"key": "amenity", "value": "hotel", "description": "๐Ÿ„ณ โžœ tourism=hotel"}, {"key": "amenity", "value": "kiosk", "description": "๐Ÿ„ณ โžœ shop=kiosk"}, diff --git a/dist/locales/en.json b/dist/locales/en.json index 06ab7bf57..79475cdbd 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -5495,6 +5495,10 @@ "name": "Gambling Hall", "terms": "betting,bingo,blackjack,casino,craps,gamble,gambling,keno,lottery,pachinko,poker,roulette,slot machines,slots" }, + "amenity/give_box": { + "name": "Free Box", + "terms": "donations,free box,free table,freebox,give box,give shelf,givebox,library,share shelf" + }, "amenity/grave_yard": { "name": "Graveyard", "terms": "burial ground,cemetary,cemetery,churchyard,columbarium,grave yard,graveyard,mausoleum,tomb" diff --git a/svg/fontawesome/fas-box-open.svg b/svg/fontawesome/fas-box-open.svg new file mode 100644 index 000000000..73fe87e91 --- /dev/null +++ b/svg/fontawesome/fas-box-open.svg @@ -0,0 +1 @@ + \ No newline at end of file From 267707d552beb8457228b6b2f18cf784dc7c7de4 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Tue, 19 May 2020 14:40:15 -0400 Subject: [PATCH 65/67] Update public bookcase fields --- data/presets/presets.json | 2 +- data/presets/presets/amenity/public_bookcase.json | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/data/presets/presets.json b/data/presets/presets.json index 01ecd86a8..ceef41792 100644 --- a/data/presets/presets.json +++ b/data/presets/presets.json @@ -184,7 +184,7 @@ "amenity/pub/lgbtq": {"icon": "maki-beer", "geometry": ["point", "area"], "tags": {"amenity": "pub", "lgbtq": "primary"}, "terms": ["gay pub", "lesbian pub", "lgbtq pub", "lgbt pub", "lgb pub"], "name": "LGBTQ+ Pub"}, "amenity/pub/microbrewery": {"icon": "maki-beer", "geometry": ["point", "area"], "tags": {"amenity": "pub", "microbrewery": "yes"}, "reference": {"key": "microbrewery"}, "terms": ["alcohol", "drink", "dive", "beer", "bier", "booze", "craft brewery", "microbrewery", "small batch brewery"], "name": "Brewpub"}, "amenity/public_bath": {"icon": "maki-water", "fields": ["name", "bath/type", "bath/open_air", "bath/sand_bath", "address", "building_area", "fee", "charge_fee"], "moreFields": ["email", "fax", "gnis/feature_id", "internet_access", "internet_access/fee", "internet_access/ssid", "opening_hours", "opening_hours/covid19", "payment_multi_fee", "phone", "website", "wheelchair"], "geometry": ["point", "area"], "tags": {"amenity": "public_bath"}, "terms": ["onsen", "foot bath", "hot springs"], "name": "Public Bath"}, - "amenity/public_bookcase": {"icon": "maki-library", "fields": ["name", "public_bookcase/type", "operator", "opening_hours", "opening_hours/covid19", "capacity", "website", "lit"], "moreFields": ["access_simple", "address", "brand", "email", "level", "location", "phone", "website", "wheelchair"], "geometry": ["point", "area"], "terms": ["library", "bookcrossing"], "tags": {"amenity": "public_bookcase"}, "name": "Public Bookcase"}, + "amenity/public_bookcase": {"icon": "maki-library", "fields": ["name", "public_bookcase/type", "operator", "opening_hours", "opening_hours/covid19", "capacity", "website", "lit"], "moreFields": ["access_simple", "address", "brand", "covered", "email", "indoor", "level", "location", "phone", "ref", "wheelchair"], "geometry": ["point", "area"], "terms": ["library", "bookcrossing"], "tags": {"amenity": "public_bookcase"}, "name": "Public Bookcase"}, "amenity/ranger_station": {"icon": "maki-ranger-station", "fields": ["name", "operator", "address", "building_area", "opening_hours", "opening_hours/covid19"], "moreFields": ["email", "fax", "gnis/feature_id", "internet_access", "internet_access/fee", "internet_access/ssid", "phone", "website", "wheelchair"], "geometry": ["point", "area"], "terms": ["visitor center", "visitor centre", "permit center", "permit centre", "backcountry office", "warden office", "warden center"], "tags": {"amenity": "ranger_station"}, "name": "Ranger Station"}, "amenity/recycling_centre": {"icon": "maki-recycling", "fields": ["name", "operator", "operator/type", "address", "building", "opening_hours", "opening_hours/covid19", "recycling_accepts"], "moreFields": ["charge_fee", "email", "fax", "fee", "payment_multi_fee", "phone", "website", "wheelchair"], "geometry": ["point", "area"], "terms": ["bottle", "can", "dump", "glass", "garbage", "rubbish", "scrap", "trash"], "tags": {"amenity": "recycling", "recycling_type": "centre"}, "reference": {"key": "recycling_type", "value": "*"}, "name": "Recycling Center"}, "amenity/recycling_container": {"icon": "maki-recycling", "fields": ["operator", "recycling_accepts", "opening_hours", "opening_hours/covid19", "collection_times"], "moreFields": ["colour", "covered", "indoor", "level", "manufacturer", "material", "ref"], "geometry": ["point", "area"], "terms": ["bin", "can", "bottle", "glass", "garbage", "rubbish", "scrap", "trash"], "tags": {"amenity": "recycling", "recycling_type": "container"}, "reference": {"key": "amenity", "value": "recycling"}, "name": "Recycling Container"}, diff --git a/data/presets/presets/amenity/public_bookcase.json b/data/presets/presets/amenity/public_bookcase.json index 4dea479d7..afa3c691c 100644 --- a/data/presets/presets/amenity/public_bookcase.json +++ b/data/presets/presets/amenity/public_bookcase.json @@ -14,11 +14,13 @@ "access_simple", "address", "brand", + "covered", "email", + "indoor", "level", "location", "phone", - "website", + "ref", "wheelchair" ], "geometry": [ From 45153637791e3a420c453bdd6ff40d74c10cc410 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Tue, 19 May 2020 15:41:38 -0400 Subject: [PATCH 66/67] Fix issue where merging adjacent points in a loop could disconnect the loop from itself (close #7553) --- modules/actions/connect.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/modules/actions/connect.js b/modules/actions/connect.js index 581766245..0746317ad 100644 --- a/modules/actions/connect.js +++ b/modules/actions/connect.js @@ -1,4 +1,5 @@ import { actionDeleteNode } from './delete_node'; +import { actionDeleteWay } from './delete_way'; import { utilArrayUniq } from '../util'; @@ -36,9 +37,7 @@ export function actionConnect(nodeIDs) { parents = graph.parentWays(node); for (j = 0; j < parents.length; j++) { - if (!parents[j].areAdjacent(node.id, survivor.id)) { - graph = graph.replace(parents[j].replaceNode(node.id, survivor.id)); - } + graph = graph.replace(parents[j].replaceNode(node.id, survivor.id)); } parents = graph.parentRelations(node); @@ -52,6 +51,14 @@ export function actionConnect(nodeIDs) { graph = graph.replace(survivor); + // find and delete any degenerate ways created by connecting adjacent vertices + parents = graph.parentWays(survivor); + for (i = 0; i < parents.length; i++) { + if (parents[i].isDegenerate()) { + graph = actionDeleteWay(parents[i].id)(graph); + } + } + return graph; }; From 95f8e36a2accb0341b565a913941d7e373e90bf3 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Tue, 19 May 2020 17:24:47 -0400 Subject: [PATCH 67/67] Add a small tolerance to node dragging so selecting them no longer requires zero movement (close #1981) --- modules/behavior/drag.js | 36 ++++++++++++++++++++++-------------- modules/behavior/select.js | 2 +- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/modules/behavior/drag.js b/modules/behavior/drag.js index 3d222c3d4..86eb4d27f 100644 --- a/modules/behavior/drag.js +++ b/modules/behavior/drag.js @@ -7,6 +7,7 @@ import { selection as d3_selection } from 'd3-selection'; +import { geoVecLength } from '../geo'; import { osmNote } from '../osm'; import { utilRebind } from '../util/rebind'; import { utilFastMouse, utilPrefixCSSProperty, utilPrefixDOMProperty } from '../util'; @@ -29,6 +30,11 @@ import { utilFastMouse, utilPrefixCSSProperty, utilPrefixDOMProperty } from '../ export function behaviorDrag() { var dispatch = d3_dispatch('start', 'move', 'end'); + + // see also behaviorSelect + var _tolerancePx = 1; // keep this low to facilitate pixel-perfect micromapping + var _penTolerancePx = 4; // styluses can be touchy so require greater movement - #1981 + var _origin = null; var _selector = ''; var _event; @@ -93,26 +99,28 @@ export function behaviorDrag() { if (_pointerId !== (d3_event.pointerId || 'mouse')) return; var p = pointerLocGetter(d3_event); - var dx = p[0] - startOrigin[0]; - var dy = p[1] - startOrigin[1]; - if (dx === 0 && dy === 0) - return; + if (!started) { + var dist = geoVecLength(startOrigin, p); + var tolerance = d3_event.pointerType === 'pen' ? _penTolerancePx : _tolerancePx; + // don't start until the drag has actually moved somewhat + if (dist < tolerance) return; + + started = true; + _event({ type: 'start' }); + } startOrigin = p; d3_event.stopPropagation(); d3_event.preventDefault(); - if (!started) { - started = true; - _event({ type: 'start' }); - } else { - _event({ - type: 'move', - point: [p[0] + offset[0], p[1] + offset[1]], - delta: [dx, dy] - }); - } + var dx = p[0] - startOrigin[0]; + var dy = p[1] - startOrigin[1]; + _event({ + type: 'move', + point: [p[0] + offset[0], p[1] + offset[1]], + delta: [dx, dy] + }); } diff --git a/modules/behavior/select.js b/modules/behavior/select.js index 1dce63853..58c84054a 100644 --- a/modules/behavior/select.js +++ b/modules/behavior/select.js @@ -11,7 +11,7 @@ import { utilFastMouse } from '../util/util'; export function behaviorSelect(context) { - var _tolerancePx = 4; + var _tolerancePx = 4; // see also behaviorDrag var _lastPointerEvent = null; var _showMenu = false; var _p1 = null;