diff --git a/ACCESSIBILITY.md b/ACCESSIBILITY.md index be2299f0e..b0cdab658 100644 --- a/ACCESSIBILITY.md +++ b/ACCESSIBILITY.md @@ -7,14 +7,16 @@ fail to serve some proportion of mappers. Broadly speaking, iD should strive to follow [universal design](https://en.wikipedia.org/wiki/Universal_design) principles. This is a living document that details the usability of iD across a number of dimensions, -with the intent of identifying and addressing problem areas. +with the intent of identifying and addressing problem areas. Since there are always more +factors to consider, no part of this document should be considered "complete". Symbols used in this document: - ✅ Full support -- 🟩 Full support assumed but unverified +- 🟩 Full support assumed, but not sufficiently tested - 🟠 Partial support - ❌ No appreciable support +- 🤷 Unknown support, none is assumed ## Browser Compatibility @@ -26,18 +28,78 @@ should fallback gracefully without breaking other aspects of the app. This table covers high-level compatibility, with individual features to be detailed elsewhere in this document. -| | Browser | Notes | -|---|---|---| -| ✅ | Chrome | | -| ✅* | Firefox | *Minor known issues ([#7132]) | -| ✅ | Safari | | -| 🟩 | Opera | Infrequently tested | -| 🟩 | Edge | Infrequently tested | -| 🟠 | Internet Explorer | Infrequently tested. IE has been discontinued, but IE 11 is still maintained. iD polyfills ES6 features on IE 11, with varying success. | -| 🟠 | Mobile browsers | iD has not yet been fully optimized for mobile devices, but some editing is usually possible. | +| | | Browser | Notes | Issues | +|---|---|---|---|---| +| ✅ | ![chrome logo] | Chrome | | +| ✅* | ![firefox logo] | Firefox | \*Minor known issues | [#7132] | +| ✅ | ![safari logo] | Safari | | +| 🟩 | ![opera logo] | Opera | | +| 🟩 | ![edge logo] | Edge | | +| 🟠 | ![ie logo] | Internet Explorer | IE has been discontinued, but IE 11 is still maintained. iD polyfills ES6 features on IE 11, with varying success. | +| 🟩 | 🌐 | Others | iD should run without issue on any desktop browser implementing modern web standards. | +| 🟠 | 📱 | Mobile browsers | iD has not yet been fully optimized for mobile devices, but some editing is usually possible. | + +[safari logo]: https://upload.wikimedia.org/wikipedia/commons/thumb/5/52/Safari_browser_logo.svg/30px-Safari_browser_logo.svg.png +[opera logo]: https://upload.wikimedia.org/wikipedia/commons/thumb/5/5c/Opera_browser_logo_2013_vector.svg/30px-Opera_browser_logo_2013_vector.svg.png +[chrome logo]: https://upload.wikimedia.org/wikipedia/commons/thumb/a/a5/Google_Chrome_icon_%28September_2014%29.svg/30px-Google_Chrome_icon_%28September_2014%29.svg.png +[firefox logo]: https://upload.wikimedia.org/wikipedia/commons/thumb/a/a0/Firefox_logo%2C_2019.svg/30px-Firefox_logo%2C_2019.svg.png +[edge logo]: https://upload.wikimedia.org/wikipedia/commons/thumb/b/b8/Microsoft_Edge_logo_%282015%E2%80%932019%29.svg/30px-Microsoft_Edge_logo_%282015%E2%80%932019%29.svg.png +[ie logo]: https://upload.wikimedia.org/wikipedia/commons/thumb/1/18/Internet_Explorer_10%2B11_logo.svg/30px-Internet_Explorer_10%2B11_logo.svg.png [#7132]: https://github.com/openstreetmap/iD/issues/7132 +## Input Device Support + +iD has traditionally assumed the mapper will be interacting via a mouse and keyboard, +but realistically people want or need to use various other [input devices](https://en.wikipedia.org/wiki/Input_device). + +iD relies on modern [pointer events](https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events) for some interactions, so +some devices may see degraded functionality on older browsers. + +### Setups + +The following table lists iD's usability for different setups. A setup is where +a mapper is using only the device(s) given in the row. + +A setup with "full support" offers functionality equivalent to that of the +highest-functioning setup (generally mouse and keyboard). +Certain functions may be accessed differently on different setups, +such as opening the edit menu via long-pressing instead of right-clicking. + +| | | Input Setup | Notes | +|---|---|---|---| +| ✅ | 🖱⌨️ | [Mouse](https://en.wikipedia.org/wiki/Computer_mouse) + [keyboard](https://en.wikipedia.org/wiki/Computer_keyboard) | iD's original input paradigm. Any mouse-like device such as a [trackpad](https://en.wikipedia.org/wiki/Touchpad), [trackball](https://en.wikipedia.org/wiki/Trackball), or [pointing stick](https://en.wikipedia.org/wiki/Pointing_stick) is grouped into "mouse" for this table | +| ❌ | ⌨️ | Keyboard only | Not all elements can necessarily be keyed to. Key traps may exists. Geometry editing isn't possible | +| 🟠 | 🖱 | Mouse only | The primary [mouse button](https://en.wikipedia.org/wiki/Mouse_button) (e.g. left click) alone is sufficient. Multiselection and disabling of node-snapping aren't possible | +| 🟠 | 🖐 | [Multi-touch](https://en.wikipedia.org/wiki/Multi-touch) on a [touchscreen](https://en.wikipedia.org/wiki/Touchscreen) | Move and rotate aren't possible | +| 🟠 | ✍️ | [Stylus](https://en.wikipedia.org/wiki/Stylus_(computing)) on a touchscreen | Move, rotate, and multiselection aren't possible | +| 🤷 | ✍️🔲 | Stylus on a [graphics tablet](https://en.wikipedia.org/wiki/Graphics_tablet) | | +| 🤷 | 🎮 | [Gamepad](https://en.wikipedia.org/wiki/Gamepad) | | +| 🤷 | 🗣 | [Voice](https://en.wikipedia.org/wiki/Voice_user_interface) | Tools like [Voice Control on macOS](https://support.apple.com/en-us/HT210539) and [Windows Speech Recognition](https://en.wikipedia.org/wiki/Windows_Speech_Recognition) allow navigating webpages with voice commands to some degree | +| 🤷 | 🔘 | [Switch](https://en.wikipedia.org/wiki/Switch_access) | Tools like [Switch Control on macOS](https://support.apple.com/en-us/HT202865) can theoretically replicate mouse and keyboard interactions in most apps | + +### Devices + +This table details iD's support for specific classes of input devices. + +"Full support" for a device means that iD reasonably handles its entire range of input on supported platforms. But unlike the "setups" table above, a given device is not necessarily expected to perform all of iD's functions. + +It's impractical to ensure every single input device works as expected, so the table only reflects the support status to the best of our knowledge. + +| | Input Device | Notes | Issues | +|---|---|---|---| +| ✅ | Single-button mouse | E.g. [Chester Mouse](https://duckduckgo.com/?q=chester+one+button+mouse&iar=images&iax=images&ia=images). Primary click (e.g. left-click) can be used for all pointer interactions. A long-click on map features opens the edit menu | +| ✅ | Multi-button mouse | Secondary click (e.g. right-click) can be used on map features to open the edit menu. Middle click, etc., are not needed by iD but are passed through to the browser | +| ✅ | Multi-touch mouse | E.g. [Magic Mouse](https://en.wikipedia.org/wiki/Magic_Mouse). 2D scrolling in the map is treated as panning, not zooming | +| 🟠 | Vertical [scroll wheel](https://en.wikipedia.org/wiki/Scroll_wheel) | Should zoom the map in and out | [#5550](https://github.com/openstreetmap/iD/issues/5550) | +| ❌ | Horizontal scroll wheel | Currently does nothing in the map | [#7134](https://github.com/openstreetmap/iD/issues/7134) | +| 🤷 | Scroll ball | E.g. in [Apple Mighty Mouse](https://en.wikipedia.org/wiki/Apple_Mighty_Mouse) | +| 🟩 | Trackball | | +| 🟩 | Trackpad | | +| ✅ | Multi-touch trackpad | E.g. [Magic Trackpad](https://en.wikipedia.org/wiki/Magic_Trackpad). Pinch-to-zoom and scroll-to-pan are supported in the map | +| 🟩 | Pointing stick | | +| ✅ | Keyboard | | + ## Language Support English is the language of tags and relation roles in the OpenStreetMap database. @@ -56,7 +118,7 @@ for more info. | ❌ | Base language fallback | E.g. if `pt_BR` is incomplete, `pt` should be tried before `en` | | ❌ | Custom fallback language | If the preferred language is incomplete, a user-specified one should be tried before `en` (e.g. `kk` → `ru`) | | ✅ | Locale URL parameters | `locale` and `rtl` can be used to manually set iD's locale preferences. See the [API](API.md#url-parameters) | -| 🟩 | Right-to-left layouts | Infrequently tested. Used for languages like Hebrew and Arabic | +| 🟩 | Right-to-left layouts | Used for languages like Hebrew and Arabic | ### Translatability @@ -64,22 +126,22 @@ The following table details which interface elements can adapt to the mapper's language preferences. This doesn't account for whether they've actually been translated to one or more languages. -| | Interface Element | Notes | -|---|---|---| -| ✅ | Labels and descriptions | | -| ✅ | Help docs and walkthrough | | -| ✅ | Letter hotkeys | E.g. S for Straighten makes sense in English, but not every language | -| ✅ | Preset names and search terms | | -| 🟠 | Fields | Combo fields may show raw tag values. The Wikipedia field lists Wiki names in their native languages | -| ❌ | Tags | OpenStreetMap tags are English-only as a limitation of the database | -| ❌ | Relation member roles | OpenStreetMap roles are also limited to English | -| ✅ | Imagery metadata | | -| 🟠 | Locator overlay | This layer shows feature labels in their local languages | -| ✅ | OSM community index | | -| ✅ | iD validation issues | | -| ✅ | KeepRight issues | | -| ✅ | ImproveOSM issues | | -| ✅ | Osmose issues | Translated strings are [provided by Osmose](https://www.transifex.com/openstreetmap-france/osmose/) itself, not iD | +| | Interface Element | Notes | Issues | +|---|---|---|---| +| ✅ | Labels and descriptions | | | +| ✅ | Help docs and walkthrough | | | +| ✅ | Letter hotkeys | E.g. S for Straighten makes sense in English, but not every language | | +| ✅ | Preset names and search terms | | | +| 🟠 | Fields | Combo fields may show raw tag values. The Wikipedia field lists Wiki names in their native languages | [#2708](https://github.com/openstreetmap/iD/issues/2708) | +| ❌ | Tags | OpenStreetMap tags are English-only as a limitation of the database | | +| ❌ | Relation member roles | OpenStreetMap roles are also limited to English | | +| ✅ | Imagery metadata | | | +| 🟠 | Locator overlay | This layer shows feature labels in their local languages | [#7737](https://github.com/openstreetmap/iD/issues/7737) | +| ✅ | OSM community index | | | +| ✅ | iD validation issues | | | +| ✅ | KeepRight issues | | | +| ✅ | ImproveOSM issues | | | +| ✅ | Osmose issues | Translated strings are [provided by Osmose](https://www.transifex.com/openstreetmap-france/osmose/) itself, not iD | | ### Language Coverage diff --git a/css/80_app.css b/css/80_app.css index 3af905565..f7c0e2235 100644 --- a/css/80_app.css +++ b/css/80_app.css @@ -2583,21 +2583,9 @@ img.tag-reference-wiki-image { } .form-field-input-member > input.member-role { - border-radius: 0 0 0 4px; -} -.ideditor[dir='rtl'] .form-field-input-member > input.member-role { - border-radius: 0 0 4px 0; -} - -.member-incomplete .form-field-input-member > input.member-role, -.ideditor[dir='rtl'] .member-incomplete .form-field-input-member > input.member-role { border-radius: 0 0 4px 4px; } -.member-incomplete .member-delete { - display: none; -} - .member-row-new .member-entity-input { flex: 1 1 100%; border-radius: 4px 4px 0 0; diff --git a/data/core.yaml b/data/core.yaml index a6067f9ba..531983c0b 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -315,6 +315,7 @@ en: vertex: Moved a node in a way. line: Moved a line. area: Moved an area. + relation: Moved a relation. multiple: Moved multiple features. incomplete_relation: single: This feature can't be moved because it hasn't been fully downloaded. @@ -371,6 +372,7 @@ en: line: Rotated a line. area: Rotated an area. multiple: Rotated multiple features. + relation: Rotated a relation. incomplete_relation: single: This feature can't be rotated because it hasn't been fully downloaded. multiple: These features can't be rotated because they haven't been fully downloaded. diff --git a/data/deprecated.json b/data/deprecated.json index 6644ca349..84f806ded 100644 --- a/data/deprecated.json +++ b/data/deprecated.json @@ -247,6 +247,10 @@ "old": {"building": "household"}, "replace": {"building": "house"} }, + { + "old": {"building": "pavillion"}, + "replace": {"building": "pavilion"} + }, { "old": {"building:color": "*"}, "replace": {"building:colour": "$1"} diff --git a/data/presets.yaml b/data/presets.yaml index 34f91a879..251bb7530 100644 --- a/data/presets.yaml +++ b/data/presets.yaml @@ -4664,8 +4664,9 @@ en: terms: '' craft/agricultural_engines: # craft=agricultural_engines - name: Argricultural Engines Mechanic - terms: '' + name: Agricultural Engines Mechanic + # 'terms: combines,farm equipment,harvesters,tractors' + terms: '' craft/basket_maker: # craft=basket_maker name: Basket Maker diff --git a/data/presets/presets.json b/data/presets/presets.json index 1b31714e1..1d116f123 100644 --- a/data/presets/presets.json +++ b/data/presets/presets.json @@ -395,7 +395,7 @@ "craft": {"icon": "temaki-tools", "fields": ["name", "craft", "operator", "address", "building_area", "opening_hours", "opening_hours/covid19"], "moreFields": ["air_conditioning", "building/levels_building", "ele", "email", "fax", "gnis/feature_id", "height_building", "internet_access", "internet_access/fee", "internet_access/ssid", "level", "phone", "product", "ref/vatin", "website", "wheelchair"], "geometry": ["point", "area"], "tags": {"craft": "*"}, "terms": [], "name": "Craft"}, "craft/locksmith": {"icon": "maki-marker-stroked", "geometry": ["point", "area"], "tags": {"craft": "locksmith"}, "reference": {"key": "shop", "value": "locksmith"}, "name": "Locksmith", "searchable": false}, "craft/tailor": {"icon": "temaki-needle_and_spool", "geometry": ["point", "area"], "tags": {"craft": "tailor"}, "reference": {"key": "shop", "value": "tailor"}, "name": "Tailor", "searchable": false}, - "craft/agricultural_engines": {"icon": "temaki-tools", "geometry": ["point", "area"], "tags": {"craft": "agricultural_engines"}, "name": "Argricultural Engines Mechanic"}, + "craft/agricultural_engines": {"icon": "temaki-tools", "geometry": ["point", "area"], "tags": {"craft": "agricultural_engines"}, "terms": ["combines", "farm equipment", "harvesters", "tractors"], "name": "Agricultural Engines Mechanic"}, "craft/basket_maker": {"icon": "temaki-vase", "geometry": ["point", "area"], "tags": {"craft": "basket_maker"}, "name": "Basket Maker"}, "craft/beekeeper": {"icon": "maki-farm", "geometry": ["point", "area"], "tags": {"craft": "beekeeper"}, "name": "Beekeeper"}, "craft/blacksmith": {"icon": "temaki-anvil_and_hammer", "geometry": ["point", "area"], "tags": {"craft": "blacksmith"}, "name": "Blacksmith"}, diff --git a/data/presets/presets/craft/agricultural_engines.json b/data/presets/presets/craft/agricultural_engines.json index b06944e4c..a6f3b2468 100644 --- a/data/presets/presets/craft/agricultural_engines.json +++ b/data/presets/presets/craft/agricultural_engines.json @@ -7,5 +7,11 @@ "tags": { "craft": "agricultural_engines" }, - "name": "Argricultural Engines Mechanic" + "terms": [ + "combines", + "farm equipment", + "harvesters", + "tractors" + ], + "name": "Agricultural Engines Mechanic" } diff --git a/data/taginfo.json b/data/taginfo.json index 6e5636fe9..5b94a2cdf 100644 --- a/data/taginfo.json +++ b/data/taginfo.json @@ -391,7 +391,7 @@ {"key": "craft", "description": "🄿 Craft, 🄵 Type", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/tools.svg"}, {"key": "craft", "value": "locksmith", "description": "🄿 Locksmith (unsearchable)", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/mapbox/maki/icons/marker-stroked-15.svg"}, {"key": "craft", "value": "tailor", "description": "🄿 Tailor (unsearchable)", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/needle_and_spool.svg"}, - {"key": "craft", "value": "agricultural_engines", "description": "🄿 Argricultural Engines Mechanic", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/tools.svg"}, + {"key": "craft", "value": "agricultural_engines", "description": "🄿 Agricultural Engines Mechanic", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/tools.svg"}, {"key": "craft", "value": "basket_maker", "description": "🄿 Basket Maker", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/vase.svg"}, {"key": "craft", "value": "beekeeper", "description": "🄿 Beekeeper", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/mapbox/maki/icons/farm-15.svg"}, {"key": "craft", "value": "blacksmith", "description": "🄿 Blacksmith", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/anvil_and_hammer.svg"}, @@ -2060,6 +2060,7 @@ {"key": "building", "value": "family_house", "description": "🄳 ➜ building=house"}, {"key": "building", "value": "home", "description": "🄳 ➜ building=house"}, {"key": "building", "value": "household", "description": "🄳 ➜ building=house"}, + {"key": "building", "value": "pavillion", "description": "🄳 ➜ building=pavilion"}, {"key": "building:color", "description": "🄳 ➜ building:colour=*"}, {"key": "building:height", "description": "🄳 ➜ height=*"}, {"key": "building:material", "value": "Brick", "description": "🄳 ➜ building:material=brick"}, diff --git a/dist/locales/en.json b/dist/locales/en.json index 852a7f701..296083846 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -411,6 +411,7 @@ "vertex": "Moved a node in a way.", "line": "Moved a line.", "area": "Moved an area.", + "relation": "Moved a relation.", "multiple": "Moved multiple features." }, "incomplete_relation": { @@ -486,7 +487,8 @@ "annotation": { "line": "Rotated a line.", "area": "Rotated an area.", - "multiple": "Rotated multiple features." + "multiple": "Rotated multiple features.", + "relation": "Rotated a relation." }, "incomplete_relation": { "single": "This feature can't be rotated because it hasn't been fully downloaded.", @@ -6711,8 +6713,8 @@ "name": "Tailor" }, "craft/agricultural_engines": { - "name": "Argricultural Engines Mechanic", - "terms": "" + "name": "Agricultural Engines Mechanic", + "terms": "combines,farm equipment,harvesters,tractors" }, "craft/basket_maker": { "name": "Basket Maker", diff --git a/modules/actions/extract.js b/modules/actions/extract.js index 4a4ebba05..82fb1ccf0 100644 --- a/modules/actions/extract.js +++ b/modules/actions/extract.js @@ -45,7 +45,10 @@ export function actionExtract(entityID) { var keysToRetain = ['area']; var buildingKeysToRetain = ['architect', 'building', 'height', 'layer']; - var centroid = d3_geoCentroid(entity.asGeoJSON(graph)); + var extractedLoc = d3_geoCentroid(entity.asGeoJSON(graph)); + if (!extractedLoc || !isFinite(extractedLoc[0]) || !isFinite(extractedLoc[1])) { + extractedLoc = entity.extent(graph).center(); + } var isBuilding = entity.tags.building && entity.tags.building !== 'no'; @@ -87,7 +90,7 @@ export function actionExtract(entityID) { entityTags.area = 'yes'; } - var replacement = osmNode({ loc: centroid, tags: pointTags }); + var replacement = osmNode({ loc: extractedLoc, tags: pointTags }); graph = graph.replace(replacement); extractedNodeID = replacement.id; diff --git a/modules/actions/merge_polygon.js b/modules/actions/merge_polygon.js index 5c0aee36f..8d5829401 100644 --- a/modules/actions/merge_polygon.js +++ b/modules/actions/merge_polygon.js @@ -1,6 +1,6 @@ import { geoPolygonContainsPolygon } from '../geo'; import { osmJoinWays, osmRelation } from '../osm'; -import { utilArrayGroupBy, utilObjectOmit } from '../util'; +import { utilArrayGroupBy, utilArrayIntersection, utilObjectOmit } from '../util'; export function actionMergePolygon(ids, newRelationId) { @@ -114,10 +114,35 @@ export function actionMergePolygon(ids, newRelationId) { action.disabled = function(graph) { var entities = groupEntities(graph); if (entities.other.length > 0 || - entities.closedWay.length + entities.multipolygon.length < 2) + entities.closedWay.length + entities.multipolygon.length < 2) { return 'not_eligible'; - if (!entities.multipolygon.every(function(r) { return r.isComplete(graph); })) + } + if (!entities.multipolygon.every(function(r) { return r.isComplete(graph); })) { return 'incomplete_relation'; + } + + if (!entities.multipolygon.length) { + var sharedMultipolygons = []; + entities.closedWay.forEach(function(way, i) { + if (i === 0) { + sharedMultipolygons = graph.parentMultipolygons(way); + } else { + sharedMultipolygons = utilArrayIntersection(sharedMultipolygons, graph.parentMultipolygons(way)); + } + }); + sharedMultipolygons = sharedMultipolygons.filter(function(relation) { + return relation.members.length === entities.closedWay.length; + }); + if (sharedMultipolygons.length) { + // don't create a new multipolygon if it'd be redundant + return 'not_eligible'; + } + } else if (entities.closedWay.some(function(way) { + return utilArrayIntersection(graph.parentMultipolygons(way), entities.multipolygon).length; + })) { + // don't add a way to a multipolygon again if it's already a member + return 'not_eligible'; + } }; diff --git a/modules/operations/extract.js b/modules/operations/extract.js index 8e080bbe3..bcd33bc6e 100644 --- a/modules/operations/extract.js +++ b/modules/operations/extract.js @@ -21,10 +21,9 @@ export function operationExtract(context, selectedIDs) { if (entity.type === 'node' && graph.parentWays(entity).length === 0) return; - var geometry = graph.geometry(entityID); - if (geometry === 'area' || geometry === 'line') { + if (entity.type !== 'node') { var preset = presetManager.match(entity, graph); - // only allow extraction from ways/multipolygons if the preset supports points + // only allow extraction from ways/relations if the preset supports points if (preset.geometry.indexOf('point') === -1) return; } diff --git a/modules/operations/merge.js b/modules/operations/merge.js index ea7a2bbc1..fc0d0d50d 100644 --- a/modules/operations/merge.js +++ b/modules/operations/merge.js @@ -14,19 +14,24 @@ export function operationMerge(context, selectedIDs) { var _action = getAction(); function getAction() { + // prefer a non-disabled action first var join = actionJoin(selectedIDs); - if (join.disabled(context.graph()) !== 'not_eligible') { - return join; - } + if (!join.disabled(context.graph())) return join; + var merge = actionMerge(selectedIDs); - if (merge.disabled(context.graph()) !== 'not_eligible') { - return merge; - } + if (!merge.disabled(context.graph())) return merge; + var mergePolygon = actionMergePolygon(selectedIDs); - if (mergePolygon.disabled(context.graph()) !== 'not_eligible') { - return mergePolygon; - } + if (!mergePolygon.disabled(context.graph())) return mergePolygon; + var mergeNodes = actionMergeNodes(selectedIDs); + if (!mergeNodes.disabled(context.graph())) return mergeNodes; + + // otherwise prefer an action with an interesting disabled reason + if (join.disabled(context.graph()) !== 'not_eligible') return join; + if (merge.disabled(context.graph()) !== 'not_eligible') return merge; + if (mergePolygon.disabled(context.graph()) !== 'not_eligible') return mergePolygon; + return mergeNodes; } diff --git a/modules/ui/edit_menu.js b/modules/ui/edit_menu.js index c5a95f0cc..d436d64b2 100644 --- a/modules/ui/edit_menu.js +++ b/modules/ui/edit_menu.js @@ -48,7 +48,16 @@ export function uiEditMenu(context) { var showLabels = isTouchMenu; var buttonHeight = showLabels ? 32 : 34; - var menuWidth = showLabels ? 172 : 44; + var menuWidth; + if (showLabels) { + // Get a general idea of the width based on the length of the label + menuWidth = 52 + Math.min(120, 6 * Math.max.apply(Math, ops.map(function(op) { + return op.title.length; + }))); + } else { + menuWidth = 44; + } + var menuLeft = displayOnLeft(viewport); offset[0] = menuLeft ? -1 * (_menuSideMargin + menuWidth) : _menuSideMargin; diff --git a/modules/ui/init.js b/modules/ui/init.js index 797e779bb..2c01c273d 100644 --- a/modules/ui/init.js +++ b/modules/ui/init.js @@ -59,6 +59,9 @@ export function uiInit(context) { container .on('click.ui', function() { + // we're only concerned with the primary mouse button + if (d3_event.button !== 0) return; + if (!d3_event.composedPath) return; // some targets have default click events we don't want to override diff --git a/modules/ui/sections/raw_member_editor.js b/modules/ui/sections/raw_member_editor.js index f99f01438..5e263a87d 100644 --- a/modules/ui/sections/raw_member_editor.js +++ b/modules/ui/sections/raw_member_editor.js @@ -189,6 +189,13 @@ export function uiSectionRawMemberEditor(context) { .attr('class', 'member-entity-name') .text(function(d) { return utilDisplayName(d.member); }); + label + .append('button') + .attr('tabindex', -1) + .attr('title', t('icons.remove')) + .attr('class', 'remove member-delete') + .call(svgIcon('#iD-operation-delete')); + label .append('button') .attr('class', 'member-zoom') @@ -235,13 +242,6 @@ export function uiSectionRawMemberEditor(context) { .attr('placeholder', t('inspector.role')) .call(utilNoAuto); - wrapEnter - .append('button') - .attr('tabindex', -1) - .attr('title', t('icons.remove')) - .attr('class', 'remove form-field-button member-delete') - .call(svgIcon('#iD-operation-delete')); - if (taginfo) { wrapEnter.each(bindTypeahead); } diff --git a/modules/ui/sections/raw_membership_editor.js b/modules/ui/sections/raw_membership_editor.js index 476603c01..3d3da9fb7 100644 --- a/modules/ui/sections/raw_membership_editor.js +++ b/modules/ui/sections/raw_membership_editor.js @@ -61,6 +61,16 @@ export function uiSectionRawMembershipEditor(context) { context.enter(modeSelect(context, [d.relation.id])); } + function zoomToRelation(d) { + d3_event.preventDefault(); + + var entity = context.entity(d.relation.id); + context.map().zoomToEase(entity); + + // highlight the relation in case it wasn't previously on-screen + utilHighlightEntities([d.relation.id], true, context); + } + function changeRole(d) { if (d === 0) return; // called on newrow (shoudn't happen) @@ -236,14 +246,16 @@ export function uiSectionRawMembershipEditor(context) { .attr('class', 'field-label') .attr('for', function(d) { return d.domId; - }) + }); + + var labelLink = labelEnter .append('span') .attr('class', 'label-text') .append('a') .attr('href', '#') .on('click', selectRelation); - labelEnter + labelLink .append('span') .attr('class', 'member-entity-type') .text(function(d) { @@ -251,11 +263,25 @@ export function uiSectionRawMembershipEditor(context) { return (matched && matched.name()) || t('inspector.relation'); }); - labelEnter + labelLink .append('span') .attr('class', 'member-entity-name') .text(function(d) { return utilDisplayName(d.relation); }); + labelEnter + .append('button') + .attr('tabindex', -1) + .attr('class', 'remove member-delete') + .call(svgIcon('#iD-operation-delete')) + .on('click', deleteMembership); + + labelEnter + .append('button') + .attr('class', 'member-zoom') + .attr('title', t('icons.zoom_to')) + .call(svgIcon('#iD-icon-framed-dot', 'monochrome')) + .on('click', zoomToRelation); + var wrapEnter = itemsEnter .append('div') .attr('class', 'form-field-input-wrap form-field-input-member'); @@ -273,13 +299,6 @@ export function uiSectionRawMembershipEditor(context) { .on('blur', changeRole) .on('change', changeRole); - wrapEnter - .append('button') - .attr('tabindex', -1) - .attr('class', 'remove form-field-button member-delete') - .call(svgIcon('#iD-operation-delete')) - .on('click', deleteMembership); - if (taginfo) { wrapEnter.each(bindTypeahead); }