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);
}