diff --git a/ACCESSIBILITY.md b/ACCESSIBILITY.md index 8182f52b2..d2d9eb6f2 100644 --- a/ACCESSIBILITY.md +++ b/ACCESSIBILITY.md @@ -110,7 +110,7 @@ such as opening the edit menu via long-pressing instead of right-clicking. | | Icon | Input Setup | Notes | Issues | |---|---|---|---|---| | ✅ | 🖱⌨️ | [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 | iD hasn't been optimized for keyboard-only navigation, but some is possible. Geometry editing isn't possible | [#7770](https://github.com/openstreetmap/iD/issues/7770) | +| 🟠 | ⌨️ | Keyboard only | iD hasn't been optimized for keyboard-only navigation, but some is possible. Geometry editing isn't possible | [#8004](https://github.com/openstreetmap/iD/issues/8004) | | 🟠 | 🖱 | 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) | Moving and rotating selections isn't possible | [#7599](https://github.com/openstreetmap/iD/issues/7599) | | 🟠 | ✍️ | [Stylus](https://en.wikipedia.org/wiki/Stylus_(computing)) on a touchscreen | Moving and rotating selections isn't possible, nor is selecting multiple features | diff --git a/css/80_app.css b/css/80_app.css index d4e99ea4d..a0ebcd792 100644 --- a/css/80_app.css +++ b/css/80_app.css @@ -98,7 +98,14 @@ h4, h5 { padding-bottom: 10px; } -:focus { +button:focus, +textarea:focus, +input[type=text]:focus, +input[type=search]:focus, +input[type=number]:focus, +input[type=url]:focus, +input[type=tel]:focus, +input[type=email]:focus { outline-color: transparent; outline-style: none; } @@ -127,6 +134,9 @@ a:visited, a:active { color: #7092ff; } +a:focus { + color: #597be7; +} @media (hover: hover) { a:hover { color: #597be7; @@ -305,7 +315,6 @@ button { text-align: center; border: 0; background: #fff; - font-weight: bold; color: #333; font-size: 12px; display: inline-block; @@ -361,6 +370,7 @@ button.disabled { button.action { background: #7092ff; color: #fff; + font-weight: bold; } button.action:focus, button.action:active { @@ -368,6 +378,7 @@ button.action:active { } button.secondary-action { background: #ececec; + font-weight: bold; } button.secondary-action:focus, button.secondary-action:active { @@ -553,6 +564,7 @@ button.bar-button { min-width: 30px; white-space: nowrap; display: flex; + font-weight: bold; } button.bar-button .icon { flex: 0 0 20px; @@ -760,11 +772,6 @@ button.add-note svg.icon { width: 16px; height: 16px; margin-top: -3px; - margin-left: -3px; -} -.ideditor[dir='rtl'] .hide-toggle .icon.pre-text { - margin-left: 0; - margin-right: -3px; } a:visited.hide-toggle, @@ -772,7 +779,7 @@ a.hide-toggle { display: inline-block; font-size: 14px; font-weight: bold; - padding-bottom: 5px; + margin-bottom: 5px; } @@ -939,35 +946,22 @@ a.hide-toggle { } .no-results-item { padding: 10px; + font-weight: bold; } .geocode-item { width: 100%; max-width: 200px; - background-color: #ccc; margin: 30px auto; - padding: 5px; min-height: 40px; } -.ideditor[dir='rtl'] .geocode-item { - left: -25%; -} -.geocode-item:active { - background-color: #aaa; -} -@media (hover: hover) { - .geocode-item:hover { - background-color: #aaa; - } -} - .feature-list-item { background-color: #fff; - font-weight: bold; display: flex; } -.feature-list-item:active { +.feature-list-item:active, +.feature-list-item:focus { background-color: #ececec; } @media (hover: hover) { @@ -1005,8 +999,10 @@ a.hide-toggle { } .feature-list-item .entity-type { color: #7092ff; + font-weight: bold; } -.feature-list-item:active .entity-type { +.feature-list-item:active .entity-type, +.feature-list-item:focus .entity-type { color: #597be7; } @media (hover: hover) { @@ -1015,7 +1011,6 @@ a.hide-toggle { } } .feature-list-item .entity-name { - font-weight: normal; color: #666; padding-left: 10px; } @@ -1236,8 +1231,8 @@ a.hide-toggle { .preset-list-button .label-inner .namepart { text-overflow: ellipsis; } -.preset-list-button .label-inner .namepart:nth-child(2) { - font-weight: normal; +.preset-list-button .label-inner .namepart:nth-child(1) { + font-weight: bold; } .preset-list-button:focus .label, @@ -1269,7 +1264,8 @@ a.hide-toggle { .ideditor[dir='rtl'] .preset-list-button-wrap:not(.category) button:last-child { border-radius: 4px 0 0 4px; } -.preset-list-button-wrap button.tag-reference-button:active { +.preset-list-button-wrap button.tag-reference-button:active, +.preset-list-button-wrap button.tag-reference-button:hover { background: #f1f1f1; } @media (hover: hover) { @@ -1433,7 +1429,8 @@ a.hide-toggle { border-left: none; border-right: 1px solid #ccc; } -.field-label button:active { +.field-label button:active, +.field-label button:focus { background: #f1f1f1; } @media (hover: hover) { @@ -1502,7 +1499,8 @@ a.hide-toggle { border-left-width: 1px; border-right-width: 0; } -.form-field-button:active { +.form-field-button:active, +.form-field-button:focus { background-color: #f1f1f1; } @media (hover: hover) { @@ -1780,6 +1778,7 @@ a.hide-toggle { border: 1px solid #ccd5e3; border-radius: 2px; padding: 0px 8px; + color: inherit; } .ideditor[dir='ltr'] .form-field-input-check > .reverser { padding-right: 2px; @@ -1790,7 +1789,8 @@ a.hide-toggle { .form-field-input-check > .reverser.hide { display: none; } -.form-field-input-check:active { +.form-field-input-check:active, +.form-field-input-check:focus { background: #f1f1f1; } @media (hover: hover) { @@ -1830,7 +1830,8 @@ a.hide-toggle { .form-field-input-radio > label:last-child { border-radius: 0 0 4px 4px; } -.form-field-input-radio > label:active { +.form-field-input-radio > label:active, +.form-field-input-radio > label:focus { background-color: #ececec; } @media (hover: hover) { @@ -2182,7 +2183,8 @@ div.combobox { } .combobox a.selected, -.combobox a:active { +.combobox a:active, +.combobox a:focus { background: #ececec; } @media (hover: hover) { @@ -2267,7 +2269,8 @@ div.combobox { color: #7092ff; border-bottom: 2px solid; } -.field-help-nav-item:active { +.field-help-nav-item:active, +.field-help-nav-item:focus { color: #597be7; background-color: #efefef; } @@ -2365,7 +2368,7 @@ div.combobox { .raw-tag-options { display: flex; flex-flow: row nowrap; - flex-direction: row-reverse; + justify-content: flex-end; margin-top: -28px; } button.raw-tag-option { @@ -2488,7 +2491,8 @@ button.raw-tag-option svg.icon { border-right-width: 0; } -.tag-row button:active { +.tag-row button:active, +.tag-row button:focus { background: #f1f1f1; } @media (hover: hover) { @@ -2989,7 +2993,8 @@ div.full-screen > button, div.full-screen > button.active { height: 40px; background: transparent; } -div.full-screen > button:active { +div.full-screen > button:active, +div.full-screen > button:focus { background-color: rgba(0, 0, 0, .8); } @media (hover: hover) { @@ -3182,6 +3187,8 @@ div.full-screen > button:active { display: flex; flex-flow: row nowrap; cursor: pointer; + text-align: initial; + background: none; } .issue-text .issue-icon { @@ -3242,7 +3249,7 @@ button.autofix.action.active { .autofix-all { display: flex; flex-flow: row nowrap; - flex-direction: row-reverse; + justify-content: flex-end; margin-top: -25px; padding-bottom: 5px; } @@ -3268,7 +3275,7 @@ button.autofix.action.active { .warnings-list .issue.severity-warning .issue-label, .issue.severity-warning .issue-fix-list, -.ideditor.mode-save .warning-section { +.warning-section { background: #ffc; } @@ -3280,26 +3287,30 @@ button.autofix.action.active { color: #f90; } -.issue.severity-warning .issue-fix-item.actionable, +.issue.severity-warning .issue-fix-item button.actionable, .issue-container.active .issue.severity-warning .issue-info-button { color: #b15500; fill: #b15500; } .warnings-list .issue.severity-warning .issue-label:active, -.issue.severity-warning .issue-fix-item.actionable:active { +.warnings-list .issue.severity-warning .issue-label:focus, +.issue.severity-warning .issue-fix-item button.actionable:active, +.issue.severity-warning .issue-fix-item button.actionable:focus { background: #ff8; } -.issue.severity-warning .issue-fix-item.actionable:active, -.issue-container.active .issue.severity-warning .issue-info-button:active { +.issue.severity-warning .issue-fix-item button.actionable:active, +.issue.severity-warning .issue-fix-item button.actionable:focus, +.issue-container.active .issue.severity-warning .issue-info-button:active, +.issue-container.active .issue.severity-warning .issue-info-button:focus { color: #7f3d00; fill: #7f3d00; } @media (hover: hover) { .warnings-list .issue.severity-warning .issue-label:hover, - .issue.severity-warning .issue-fix-item.actionable:hover { + .issue.severity-warning .issue-fix-item button.actionable:hover { background: #ff8; } - .issue.severity-warning .issue-fix-item.actionable:hover, + .issue.severity-warning .issue-fix-item button.actionable:hover, .issue-container.active .issue.severity-warning .issue-info-button:hover { color: #7f3d00; fill: #7f3d00; @@ -3317,7 +3328,7 @@ button.autofix.action.active { .errors-list .issue.severity-error .issue-label, .issue.severity-error .issue-fix-list, -.ideditor.mode-save .error-section { +.error-section { background: #ffd6d6; } @@ -3325,7 +3336,7 @@ button.autofix.action.active { background: #ffc6c6; } -.issue.severity-error .issue-fix-item.actionable, +.issue.severity-error .issue-fix-item button.actionable, .issue-container.active .issue.severity-error .issue-info-button { color: #b91201; fill: #b91201; @@ -3334,20 +3345,24 @@ button.autofix.action.active { color: #dd1400; } .errors-list .issue.severity-error .issue-label:active, -.issue.severity-error .issue-fix-item.actionable:active { +.errors-list .issue.severity-error .issue-label:focus, +.issue.severity-error .issue-fix-item button.actionable:active, +.issue.severity-error .issue-fix-item button.actionable:focus { background: #ffb6b6; } -.issue.severity-error .issue-fix-item.actionable:active, -.issue-container.active .issue.severity-error .issue-info-button:active { +.issue.severity-error .issue-fix-item button.actionable:active, +.issue.severity-error .issue-fix-item button.actionable:focus, +.issue-container.active .issue.severity-error .issue-info-button:active, +.issue-container.active .issue.severity-error .issue-info-button:focus { color: #840c00; fill: #840c00; } @media (hover: hover) { .errors-list .issue.severity-error .issue-label:hover, - .issue.severity-error .issue-fix-item.actionable:hover { + .issue.severity-error .issue-fix-item button.actionable:hover { background: #ffb6b6; } - .issue.severity-error .issue-fix-item.actionable:hover, + .issue.severity-error .issue-fix-item button.actionable:hover, .issue-container.active .issue.severity-error .issue-info-button:hover { color: #840c00; fill: #840c00; @@ -3391,7 +3406,7 @@ button.autofix.action.active { .section-footer { display: flex; flex-flow: row nowrap; - flex-direction: row-reverse; + justify-content: flex-end; height: 30px; } .section-footer a { @@ -3426,7 +3441,9 @@ input.square-degrees-input { background: #f6f6f6; } .section-entity-issues .issue-container:not(.active) .issue-text:active, -.section-entity-issues .issue-container:not(.active) .issue-info-button:active { +.section-entity-issues .issue-container:not(.active) .issue-text:focus, +.section-entity-issues .issue-container:not(.active) .issue-info-button:active, +.section-entity-issues .issue-container:not(.active) .issue-info-button:focus { background: #f1f1f1; } @media (hover: hover) { @@ -3443,7 +3460,7 @@ input.square-degrees-input { padding-left: 10px; } -.section-entity-issues .issue-container.active .issue-label .issue-text { +.section-entity-issues .issue-container.active .issue-label button.issue-text { font-weight: bold; } .section-entity-issues .issue-container:not(:last-of-type) { @@ -3465,27 +3482,31 @@ input.square-degrees-input { display: none; } -li.issue-fix-item { +li.issue-fix-item button { padding: 2px 10px 2px 20px; + background: transparent; + width: 100%; + text-align: initial; } -.ideditor[dir='rtl'] li.issue-fix-item { +.ideditor[dir='rtl'] li.issue-fix-item button { padding: 2px 20px 2px 10px; } -li.issue-fix-item:first-of-type { +li.issue-fix-item:first-of-type button { padding-top: 5px; } -li.issue-fix-item:last-of-type { +li.issue-fix-item:last-of-type button { padding-bottom: 5px; } -li.issue-fix-item .fix-message { +li.issue-fix-item button .fix-message { padding: 0 10px; + vertical-align: middle; } -li.issue-fix-item.actionable { +li.issue-fix-item button.actionable { cursor: pointer; } -li.issue-fix-item:not(.actionable) .fix-icon { +li.issue-fix-item button:not(.actionable) .fix-icon { color: #555; fill: #555; } @@ -3797,6 +3818,8 @@ li.issue-fix-item:not(.actionable) .fix-icon { .help-pane .toc li a { border-bottom: 0; } +.help-pane .toc li a:focus, +.help-pane .nav a:focus, .help-pane .toc li a:active, .help-pane .nav a:active { background: #ececec; @@ -3935,7 +3958,7 @@ li.issue-fix-item:not(.actionable) .fix-icon { .inspector-hover .section-entity-issues .issue-container.active .issue-label { border-bottom: 0; } -.inspector-hover .section-entity-issues .issue-container.active .issue-label .issue-text { +.inspector-hover .section-entity-issues .issue-container.active .issue-label button.issue-text { font-weight: normal; } @@ -4185,7 +4208,8 @@ img.tile-debug { ------------------------------------------------------- */ .info-panels { display: flex; - flex-flow: row-reverse wrap-reverse; + flex-flow: row wrap-reverse; + justify-content: flex-end; width: 100%; z-index: 1; -ms-user-select: element; @@ -4237,6 +4261,7 @@ img.tile-debug { .ideditor[dir='rtl'] .panel-title button.close { float: left; } +.panel-title button.close:focus, .panel-title button.close:active { color: #fff; } @@ -4368,6 +4393,7 @@ img.tile-debug { .attribution-wrap .attribution a:visited { color: #ccf; } +.attribution-wrap .attribution a:focus, .attribution-wrap .attribution a:hover { color: #aaf; } @@ -4464,52 +4490,35 @@ img.tile-debug { /* Footer - About, Source Switcher ------------------------------------------------------- */ .map-footer-bar .info-block { - flex: 1 1 auto; + flex: 1 1 100%; + overflow: hidden; } .map-footer-list { - text-align: right; - margin-right: 10px; display: flex; - flex-direction: row-reverse; - flex-wrap: wrap; + flex-flow: row nowrap; position: relative; height: 100%; -} -.ideditor[dir='rtl'] .map-footer-list { - text-align: left; - margin-left: 10px; - margin-right: 0; + justify-content: flex-end; } .map-footer-list li { - border-left: 1px solid rgba(255,255,255,.5); - padding: 5px 0 5px 5px; - margin-left: 5px; height: 100%; display: flex; align-items: center; + white-space: nowrap; + padding: 5px; } -.ideditor[dir='rtl'] .map-footer-list li { - border-left: none; +.ideditor[dir='ltr'] .map-footer-list li:not(:last-child) { border-right: 1px solid rgba(255,255,255,.5); - margin-left: 0; - margin-right: 5px; - padding: 5px 5px 5px 0; +} +.ideditor[dir='rtl'] .map-footer-list li:not(:last-child) { + border-left: 1px solid rgba(255,255,255,.5); } .map-footer-list li:empty { display: none; } -.map-footer-list li:last-child { - border-left: 0; - margin-left: 0; - padding-left: 0; -} -.ideditor[dir='rtl'] .map-footer-list li:last-child { - border-right: none; -} - .map-footer-list a.chip { padding: 1px 4px 1px 4px; border-radius: 2px; @@ -4579,6 +4588,7 @@ img.tile-debug { color: #ccc; pointer-events: all; } +.api-status a:focus, .api-status a:active { color: inherit; } @@ -4727,7 +4737,6 @@ img.tile-debug { display: flex; } .modal-actions button { - font-weight: normal; color: #7092ff; border-bottom: 1px solid #ccc; border-radius: 0; @@ -4881,29 +4890,30 @@ img.tile-debug { } .modal-shortcuts .tabs-bar { - text-align: center; padding-bottom: 5px; - font-size: 16px; - font-weight: bold; + text-align: center; } -.modal-shortcuts .tab { +.modal-shortcuts a.tab { display: inline-block; padding: 5px 10px; margin: 0 5px; cursor: pointer; color: #666; + font-size: 16px; + font-weight: bold; } -.modal-shortcuts .tab.active { +.modal-shortcuts a.tab.active { color: #7092ff; border-bottom: 2px solid; } -.modal-shortcuts .tab:active { +.modal-shortcuts a.tab:focus, +.modal-shortcuts a.tab:active { color: #597be7; background-color: #efefef; } @media (hover: hover) { - .modal-shortcuts .tab:hover { + .modal-shortcuts a.tab:hover { color: #597be7; background-color: #efefef; } @@ -4984,20 +4994,20 @@ img.tile-debug { /* Save Mode ------------------------------------------------------- */ -.ideditor.mode-save a.user-info { +a.user-info { display: inline-block; } -.ideditor.mode-save .commit-form { +.commit-form { margin-bottom: 0; } -.ideditor.mode-save .user-info img { +.user-info img { float: left; } .note-save .field-warning, -.ideditor.mode-save .field-warning { +.field-warning { background: #ffb; border: 1px solid #ccc; border-radius: 4px; @@ -5005,62 +5015,45 @@ img.tile-debug { } .note-save .field-warning:empty, -.ideditor.mode-save .field-warning:empty { +.field-warning:empty { display: none; } -.ideditor.mode-save .field-warning, -.ideditor.mode-save .changeset-info, -.ideditor.mode-save .request-review, -.ideditor.mode-save .commit-info { +.field-warning, +.changeset-info, +.request-review, +.commit-info { margin-bottom: 10px; } -.ideditor.mode-save .request-review label { +.request-review label { cursor: pointer; } -.ideditor.mode-save .changeset-list { +.changeset-list { border: 1px solid #ccc; border-radius: 4px; background: #fff; margin-bottom: 10px; + overflow: hidden; } -.ideditor.mode-save .warning-section .changeset-list button { - border-left: 1px solid #ccc; -} - -.ideditor.mode-save .changeset-list li { - position: relative; - border-top: 1px solid #ccc; +.changeset-list li button { padding: 5px 10px; - cursor: pointer; + width: 100%; + border-radius: 0; + text-align: initial; } -.ideditor.mode-save .changeset-list li:active { - background-color: #ececec; +.changeset-list li { + border-top: 1px solid #ccc; } -@media (hover: hover) { - .ideditor.mode-save .changeset-list li:hover { - background-color: #ececec; - } +.changeset-list li:first-child { + border-top: 0; } - -.ideditor.mode-save .changeset-list .alert { +.changeset-list .alert { opacity: 0.5; } -.changeset-list li span.count { - font-size: 10px; - color: #555; -} - -.changeset-list li span.count:before { content: '('; } - -.changeset-list li span.count:after { content: ')'; } - -.changeset-list li:first-child { border-top: 0;} - /* Conflict resolution ------------------------------------------------------- */ @@ -5074,7 +5067,7 @@ img.tile-debug { padding: 20px; } -.ideditor.mode-save button.conflicts-button { +button.conflicts-button { float: left; } @@ -5128,6 +5121,7 @@ img.tile-debug { padding: 20px 5px; font-size: 150%; border-radius: 8px; + font-weight: bold; } .notice .zoom-to:focus, @@ -5489,6 +5483,7 @@ li.hide + li.version .badge .tooltip .popover-arrow { .intro-nav-wrap button.chapter { flex: 1 1 100%; padding: 0px 5px; + font-weight: bold; } .intro-nav-wrap button.chapter.next { diff --git a/data/core.yaml b/data/core.yaml index 3a7b49899..3c6b030fd 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -101,8 +101,9 @@ en: multiple: Make these features circular. key: O annotation: - single: Made a feature circular. - multiple: Made features circular. + feature: + one: Made a feature circular. + other: "Made {n} features circular." multiple_blockers: multiple: These can't be made circular for multiple reasons. not_closed: @@ -132,11 +133,11 @@ en: key: Q annotation: corner: - single: Squared a corner. - multiple: Squared several corners. + one: Squared a corner. + other: "Squared {n} corners." feature: - single: Squared the corners of a feature. - multiple: Squared the corners of several features. + one: Squared the corners of a feature. + other: "Squared the corners of {n} features." multiple_blockers: multiple: These can't be squared for multiple reasons. end_vertex: @@ -165,9 +166,12 @@ en: lines: Straighten these lines. key: S annotation: - points: Straightened several points. - line: Straightened a line. - lines: Straightened several lines. + point: + one: Straightened a point. + other: "Straightened {n} points." + line: + one: Straightened a line. + other: "Straightened {n} lines." too_bendy: single: This can't be straightened because it bends too much. multiple: These can't be straightened because they bend too much. @@ -316,7 +320,9 @@ en: line: Moved a line. area: Moved an area. relation: Moved a relation. - multiple: Moved multiple features. + feature: + one: "Moved a feature." + other: "Moved {n} features." incomplete_relation: single: This feature can't be moved because it hasn't been fully downloaded. multiple: These features can't be moved because they haven't been fully downloaded. @@ -345,11 +351,13 @@ en: short: Y annotation: long: - single: Flipped a feature across its long axis. - multiple: Flipped multiple features across their long axis. + feature: + one: Flipped a feature across its long axis. + other: "Flipped {n} features across their long axis." short: - single: Flipped a feature across its short axis. - multiple: Flipped multiple features across their short axis. + feature: + one: Flipped a feature across its short axis. + other: "Flipped {n} features across their short axis." incomplete_relation: single: This feature can't be flipped because it hasn't been fully downloaded. multiple: These features can't be flipped because they haven't been fully downloaded. @@ -371,8 +379,10 @@ en: annotation: line: Rotated a line. area: Rotated an area. - multiple: Rotated multiple features. relation: Rotated a relation. + feature: + one: Rotated a feature. + other: "Rotated {n} features." 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. @@ -395,11 +405,37 @@ en: features: Flip the directions of these features. key: V annotation: - point: Reversed a point. - points: Reversed multiple points. - line: Reversed a line. - lines: Reversed multiple lines. - features: Reversed multiple features. + point: + one: Reversed a point. + other: "Reversed {n} points." + line: + one: Reversed a line. + other: "Reversed {n} lines." + feature: + one: Reversed a feature. + other: "Reversed {n} features." + scale: + annotation: + down: + feature: + one: Scaled down a feature. + other: "Scaled down {n} features." + up: + feature: + one: Scaled up a feature. + other: "Scaled up {n} features." + too_small: + single: This feature can't be scaled down because it would become too small. + multiple: These features can't be scaled down because they would become too small. + too_large: + single: This feature can't be scaled because not enough of it is currently visible. + multiple: These features can't be scaled because not enough of them are currently visible. + connected_to_hidden: + single: This feature can't be scaled because it is connected to a hidden feature. + multiple: These features can't be scaled because some are connected to hidden features. + not_downloaded: + single: This feature can't be scaled because parts of it have not yet been downloaded. + multiple: These features can't be scaled because parts of them have not yet been downloaded. split: title: Split description: @@ -2172,6 +2208,8 @@ en: move: "Move selected features" nudge: "Nudge selected features" nudge_more: "Nudge selected features by a lot" + scale: "Scale selected features" + scale_more: "Scale 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/deprecated.json b/data/deprecated.json index bce2e0f6d..ff2d07d51 100644 --- a/data/deprecated.json +++ b/data/deprecated.json @@ -227,6 +227,10 @@ "old": {"barrier": "wood_fence"}, "replace": {"barrier": "fence", "fence_type": "wood"} }, + { + "old": {"amenity": "bench", "capacity": "*"}, + "replace": {"amenity": "bench", "seats": "$1"} + }, { "old": {"bicycle:oneway": "*"}, "replace": {"oneway:bicycle": "$1"} diff --git a/data/presets.yaml b/data/presets.yaml index f389f06e7..a31134c65 100644 --- a/data/presets.yaml +++ b/data/presets.yaml @@ -3203,6 +3203,11 @@ en: name: Biergarten # 'terms: beer,bier,booze' terms: '' + amenity/binoculars: + # amenity=binoculars + name: Mounted Binoculars + # 'terms: observation viewer,optical ranger,spotting scope,sight,spyglass,telescope,tower viewer,viewfinder,viewing stand' + terms: '' amenity/boat_rental: # amenity=boat_rental name: Boat Rental @@ -8354,7 +8359,7 @@ en: shop/music: # shop=music name: Music Store - # 'terms: tape cassettes,CDs,compact discs,vinyl records' + # 'terms: tape cassettes,CDs,compact discs,vinyl records,CD store,casette,casette store' terms: '' shop/musical_instrument: # shop=musical_instrument diff --git a/data/presets/presets.json b/data/presets/presets.json index df70e888a..c8902b860 100644 --- a/data/presets/presets.json +++ b/data/presets/presets.json @@ -79,9 +79,10 @@ "amenity/bicycle_rental": {"icon": "temaki-bicycle_rental", "fields": ["capacity", "network", "operator", "operator/type", "fee", "payment_multi_fee"], "moreFields": ["address", "covered", "email", "fax", "level", "opening_hours", "opening_hours/covid19", "phone", "website", "wheelchair"], "geometry": ["point", "vertex", "area"], "terms": ["bike", "bicycle", "bikeshare", "bike share", "bicycle share", "cycle dock", "cycle hub", "cycleshare", "cycling"], "tags": {"amenity": "bicycle_rental"}, "name": "Bicycle Rental"}, "amenity/bicycle_repair_station": {"icon": "temaki-bicycle_repair", "fields": ["operator", "brand", "opening_hours", "opening_hours/covid19", "fee", "payment_multi_fee", "charge_fee", "service/bicycle"], "moreFields": ["colour", "covered", "indoor", "level", "manufacturer"], "geometry": ["point", "vertex"], "terms": ["bike chain", "bike multitool", "bike repair", "bike tools", "cycle pump", "cycle repair", "cycling"], "tags": {"amenity": "bicycle_repair_station"}, "name": "Bicycle Repair Tool Stand"}, "amenity/biergarten": {"icon": "fas-beer", "fields": ["name", "address", "opening_hours", "opening_hours/covid19", "outdoor_seating", "brewery"], "moreFields": ["{amenity/bar}", "building_area", "cuisine"], "geometry": ["point", "area"], "tags": {"amenity": "biergarten"}, "terms": ["beer", "bier", "booze"], "name": "Biergarten"}, + "amenity/binoculars": {"icon": "temaki-binoculars", "fields": ["operator", "access_simple", "fee", "payment_multi_fee", "charge_fee", "direction", "height", "ele_node"], "moreFields": ["colour", "covered", "indoor", "lit", "manufacturer", "ref"], "geometry": ["point"], "terms": ["observation viewer", "optical ranger", "spotting scope", "sight", "spyglass", "telescope", "tower viewer", "viewfinder", "viewing stand"], "tags": {"amenity": "binoculars"}, "name": "Mounted Binoculars"}, "amenity/boat_rental": {"icon": "temaki-boat_rental", "fields": ["name", "operator", "operator/type", "opening_hours", "opening_hours/covid19", "fee", "payment_multi_fee", "charge_fee"], "moreFields": ["access_simple", "address", "email", "fax", "phone", "website", "wheelchair"], "geometry": ["point", "area"], "tags": {"amenity": "boat_rental"}, "name": "Boat Rental"}, "amenity/bureau_de_change": {"icon": "temaki-money_hand", "fields": ["name", "operator", "payment_multi", "currency_multi", "address", "building_area"], "moreFields": ["email", "fax", "level", "opening_hours", "opening_hours/covid19", "phone", "website", "wheelchair"], "geometry": ["point", "area"], "terms": ["bureau de change", "money changer"], "tags": {"amenity": "bureau_de_change"}, "name": "Currency Exchange"}, - "amenity/cafe": {"icon": "maki-cafe", "fields": ["name", "cuisine", "address", "building_area", "opening_hours", "opening_hours/covid19", "outdoor_seating", "internet_access", "internet_access/fee", "internet_access/ssid"], "moreFields": ["air_conditioning", "bar", "brand", "capacity", "delivery", "diet_multi", "email", "fax", "gnis/feature_id", "level", "min_age", "not/name", "payment_multi", "phone", "ref/vatin", "reservation", "smoking", "takeaway", "website", "wheelchair"], "geometry": ["point", "area"], "terms": ["bistro", "coffee", "tea"], "tags": {"amenity": "cafe"}, "name": "Cafe"}, + "amenity/cafe": {"icon": "maki-cafe", "fields": ["name", "cuisine", "address", "building_area", "opening_hours", "opening_hours/covid19", "outdoor_seating", "internet_access", "internet_access/fee", "internet_access/ssid", "phone", "website"], "moreFields": ["air_conditioning", "bar", "brand", "capacity", "delivery", "diet_multi", "email", "fax", "gnis/feature_id", "level", "min_age", "not/name", "payment_multi", "ref/vatin", "reservation", "smoking", "takeaway", "wheelchair"], "geometry": ["point", "area"], "terms": ["bistro", "coffee", "tea"], "tags": {"amenity": "cafe"}, "name": "Cafe"}, "amenity/cafe/bubble_tea": {"icon": "temaki-bubble_tea", "geometry": ["point", "area"], "tags": {"amenity": "cafe", "cuisine": "bubble_tea"}, "reference": {"key": "cuisine", "value": "bubble_tea"}, "terms": ["boba", "bubble milk tea", "pearl milk tea", "taiwanese tea drink", "tapioca", "pearl tea", "boba drink"], "name": "Bubble Tea Cafe"}, "amenity/cafe/coffee_shop": {"icon": "temaki-hot_drink_cup", "geometry": ["point", "area"], "tags": {"amenity": "cafe", "cuisine": "coffee_shop"}, "reference": {"key": "cuisine", "value": "coffee_shop"}, "terms": ["americano", "brew", "cafe", "café", "caffe", "caffè", "cappuccino", "cocoa", "coffee shop", "drip", "espresso", "hot drinks", "latte", "macchiato", "tea"], "name": "Coffeehouse"}, "amenity/car_pooling": {"icon": "temaki-car_pool", "fields": ["name", "operator", "operator/type", "capacity", "address", "opening_hours", "opening_hours/covid19", "lit"], "moreFields": ["email", "fax", "phone", "website", "wheelchair"], "geometry": ["point", "area"], "tags": {"amenity": "car_pooling"}, "terms": ["car sharing", "carpooling", "lift sharing", "ride sharing"], "name": "Car Pooling Station"}, @@ -113,7 +114,7 @@ "amenity/driving_school": {"icon": "maki-car", "fields": ["name", "operator", "operator/type", "address", "building_area", "opening_hours", "opening_hours/covid19"], "moreFields": ["email", "fax", "gnis/feature_id", "level", "payment_multi", "phone", "website", "wheelchair"], "geometry": ["point", "area"], "tags": {"amenity": "driving_school"}, "name": "Driving School"}, "amenity/events_venue": {"icon": "fas-users", "fields": ["name", "operator", "building_area", "address", "website", "internet_access", "internet_access/fee", "internet_access/ssid"], "moreFields": ["air_conditioning", "email", "fax", "gnis/feature_id", "level", "min_age", "phone", "smoking", "wheelchair"], "geometry": ["point", "area"], "tags": {"amenity": "events_venue"}, "terms": ["banquet hall", "baptism", "Bar Mitzvah", "Bat Mitzvah", "birthdays", "celebrations", "conferences", "confirmation", "meetings", "parties", "party", "quinceañera", "reunions", "weddings"], "name": "Events Venue"}, "amenity/exhibition_centre": {"icon": "fas-user-tie", "fields": ["name", "operator", "operator/type", "building_area", "address", "website", "internet_access", "internet_access/fee", "internet_access/ssid"], "moreFields": ["air_conditioning", "email", "fax", "gnis/feature_id", "not/name", "phone", "smoking", "wheelchair"], "geometry": ["point", "area"], "tags": {"amenity": "exhibition_centre"}, "terms": ["exhibition center", "fair", "exhibition", "exposition", "trade fair", "trade show", "trade exhibition", "expo", "tradeshow"], "name": "Exposition Center"}, - "amenity/fast_food": {"icon": "maki-fast-food", "fields": ["name", "cuisine", "operator", "address", "building_area", "opening_hours", "opening_hours/covid19", "drive_through"], "moreFields": ["air_conditioning", "brand", "capacity", "delivery", "diet_multi", "email", "fax", "gnis/feature_id", "internet_access", "internet_access/fee", "internet_access/ssid", "level", "opening_hours", "opening_hours/covid19", "outdoor_seating", "payment_multi", "phone", "ref/vatin", "smoking", "takeaway", "website", "wheelchair"], "geometry": ["point", "area"], "tags": {"amenity": "fast_food"}, "terms": ["restaurant", "takeaway"], "name": "Fast Food"}, + "amenity/fast_food": {"icon": "maki-fast-food", "fields": ["name", "cuisine", "operator", "address", "building_area", "opening_hours", "opening_hours/covid19", "drive_through", "phone", "website"], "moreFields": ["air_conditioning", "brand", "capacity", "delivery", "diet_multi", "email", "fax", "gnis/feature_id", "internet_access", "internet_access/fee", "internet_access/ssid", "level", "opening_hours", "opening_hours/covid19", "outdoor_seating", "payment_multi", "ref/vatin", "smoking", "takeaway", "wheelchair"], "geometry": ["point", "area"], "tags": {"amenity": "fast_food"}, "terms": ["restaurant", "takeaway"], "name": "Fast Food"}, "amenity/fast_food/ice_cream": {"icon": "fas-ice-cream", "geometry": ["point", "area"], "tags": {"amenity": "fast_food", "cuisine": "ice_cream"}, "reference": {"key": "cuisine", "value": "ice_cream"}, "name": "Ice Cream Fast Food", "searchable": false}, "amenity/fast_food/burger": {"icon": "maki-fast-food", "geometry": ["point", "area"], "terms": ["breakfast", "dine", "dining", "dinner", "drive-in", "eat", "grill", "lunch", "table"], "tags": {"amenity": "fast_food", "cuisine": "burger"}, "reference": {"key": "cuisine", "value": "burger"}, "name": "Burger Fast Food"}, "amenity/fast_food/chicken": {"icon": "fas-drumstick-bite", "geometry": ["point", "area"], "terms": ["breakfast", "canteen", "dine", "dining", "dinner", "drive-in", "eat", "grill", "lunch", "table"], "tags": {"amenity": "fast_food", "cuisine": "chicken"}, "reference": {"key": "cuisine", "value": "chicken"}, "name": "Chicken Fast Food"}, @@ -136,7 +137,7 @@ "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"}, "amenity/hunting_stand": {"icon": "temaki-binoculars", "fields": ["access_simple", "lockable"], "geometry": ["point", "vertex", "area"], "terms": ["game", "gun", "lookout", "rifle", "shoot*", "wild", "watch"], "tags": {"amenity": "hunting_stand"}, "name": "Hunting Stand"}, "amenity/ice_cream": {"icon": "fas-ice-cream", "fields": ["name", "address", "building_area", "opening_hours", "opening_hours/covid19", "outdoor_seating"], "moreFields": ["delivery", "diet_multi", "drive_through", "email", "fax", "gnis/feature_id", "internet_access", "internet_access/fee", "internet_access/ssid", "level", "payment_multi", "phone", "takeaway", "website", "wheelchair"], "geometry": ["point", "area"], "terms": ["gelato", "sorbet", "sherbet", "frozen", "yogurt"], "tags": {"amenity": "ice_cream"}, "name": "Ice Cream Shop"}, - "amenity/internet_cafe": {"icon": "temaki-antenna", "fields": ["name", "operator", "operator/type", "address", "building_area", "internet_access", "internet_access/fee", "internet_access/ssid"], "moreFields": ["air_conditioning", "email", "fax", "gnis/feature_id", "level", "min_age", "opening_hours", "opening_hours/covid19", "outdoor_seating", "payment_multi", "phone", "ref/vatin", "smoking", "website", "wheelchair"], "geometry": ["point", "area"], "terms": ["cybercafe", "taxiphone", "teleboutique", "coffee", "cafe", "net", "lanhouse"], "tags": {"amenity": "internet_cafe"}, "name": "Internet Cafe"}, + "amenity/internet_cafe": {"icon": "temaki-antenna", "fields": ["name", "operator", "operator/type", "address", "building_area", "internet_access", "internet_access/fee", "internet_access/ssid", "phone", "website"], "moreFields": ["air_conditioning", "email", "fax", "gnis/feature_id", "level", "min_age", "opening_hours", "opening_hours/covid19", "outdoor_seating", "payment_multi", "ref/vatin", "smoking", "wheelchair"], "geometry": ["point", "area"], "terms": ["cybercafe", "taxiphone", "teleboutique", "coffee", "cafe", "net", "lanhouse"], "tags": {"amenity": "internet_cafe"}, "name": "Internet Cafe"}, "amenity/karaoke_box": {"icon": "maki-karaoke", "fields": ["name", "operator", "address", "building_area", "opening_hours", "opening_hours/covid19", "website"], "moreFields": ["air_conditioning", "email", "fax", "gnis/feature_id", "level", "min_age", "payment_multi", "phone", "ref/vatin", "smoking", "wheelchair"], "geometry": ["point", "area"], "terms": ["karaoke club", "karaoke room", "karaoke television", "KTV"], "tags": {"amenity": "karaoke_box"}, "name": "Karaoke Box"}, "amenity/kindergarten": {"icon": "temaki-school", "fields": ["name", "operator", "operator/type", "address", "phone", "preschool"], "moreFields": ["email", "fax", "gnis/feature_id", "internet_access", "internet_access/fee", "internet_access/ssid", "level", "max_age", "min_age", "not/name", "opening_hours", "opening_hours/covid19", "payment_multi", "website", "wheelchair"], "geometry": ["area", "point"], "terms": ["kindergarden", "pre-school"], "tags": {"amenity": "kindergarten"}, "name": "Preschool / Kindergarten Grounds"}, "amenity/kneipp_water_cure": {"icon": "maki-hospital", "fields": ["kneipp_water_cure_multi", "opening_hours", "opening_hours/covid19", "fee"], "geometry": ["point", "area"], "terms": [], "tags": {"amenity": "kneipp_water_cure"}, "reference": {"key": "amenity", "value": "kneipp_water_cure"}, "name": "Kneipp Water Cure"}, @@ -394,9 +395,9 @@ "building/transportation": {"icon": "maki-building", "fields": ["{building}", "smoking"], "geometry": ["area"], "tags": {"building": "transportation"}, "matchScore": 0.5, "name": "Transportation Building"}, "building/university": {"icon": "fas-school", "moreFields": ["{building}", "polling_station"], "geometry": ["area"], "terms": ["college"], "tags": {"building": "university"}, "matchScore": 0.5, "name": "University Building"}, "building/warehouse": {"icon": "maki-warehouse", "geometry": ["area"], "tags": {"building": "warehouse"}, "matchScore": 0.5, "name": "Warehouse"}, - "club": {"icon": "fas-handshake", "fields": ["name", "club", "operator", "address", "building_area", "opening_hours", "opening_hours/covid19"], "moreFields": ["access_simple", "building/levels_building", "email", "fax", "gnis/feature_id", "height_building", "internet_access", "internet_access/fee", "internet_access/ssid", "level", "max_age", "min_age", "phone", "website", "wheelchair"], "geometry": ["point", "area"], "tags": {"club": "*"}, "terms": ["social"], "name": "Club"}, + "club": {"icon": "fas-handshake", "fields": ["name", "club", "operator", "address", "building_area", "opening_hours", "opening_hours/covid19", "phone", "website"], "moreFields": ["access_simple", "building/levels_building", "email", "fax", "gnis/feature_id", "height_building", "internet_access", "internet_access/fee", "internet_access/ssid", "level", "max_age", "min_age", "phone", "website", "wheelchair"], "geometry": ["point", "area"], "tags": {"club": "*"}, "terms": ["social"], "name": "Club"}, "club/sport": {"icon": "maki-pitch", "fields": ["name", "sport", "{club}"], "geometry": ["point", "area"], "tags": {"club": "sport"}, "terms": ["athletics club", "sporting club", "sports association", "sports society"], "name": "Sports Club"}, - "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": {"icon": "temaki-tools", "fields": ["name", "craft", "operator", "address", "building_area", "opening_hours", "opening_hours/covid19", "phone", "website"], "moreFields": ["air_conditioning", "building/levels_building", "ele", "email", "fax", "gnis/feature_id", "height_building", "internet_access", "internet_access/fee", "internet_access/ssid", "level", "product", "ref/vatin", "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"}, "terms": ["combines", "farm equipment", "harvesters", "tractors"], "name": "Agricultural Engines Mechanic"}, @@ -488,7 +489,7 @@ "golf/rough": {"icon": "maki-golf", "fields": ["name"], "geometry": ["area"], "tags": {"golf": "rough"}, "addTags": {"golf": "rough", "landuse": "grass"}, "name": "Rough"}, "golf/tee": {"icon": "maki-golf", "fields": ["name"], "geometry": ["area"], "tags": {"golf": "tee"}, "addTags": {"golf": "tee", "landuse": "grass"}, "terms": ["teeing ground"], "name": "Tee Box"}, "golf/water_hazard": {"icon": "maki-golf", "fields": ["name"], "geometry": ["area"], "tags": {"golf": "water_hazard"}, "addTags": {"golf": "water_hazard", "natural": "water"}, "name": "Water Hazard"}, - "healthcare": {"icon": "maki-hospital", "fields": ["name", "healthcare", "operator", "healthcare/speciality", "address", "building_area"], "moreFields": ["brand", "building/levels_building", "email", "fax", "gnis/feature_id", "height_building", "level", "opening_hours", "opening_hours/covid19", "payment_multi", "phone", "website", "wheelchair"], "geometry": ["point", "area"], "tags": {"healthcare": "*"}, "terms": ["clinic", "doctor", "disease", "health", "institution", "sick", "surgery", "wellness"], "name": "Healthcare Facility"}, + "healthcare": {"icon": "maki-hospital", "fields": ["name", "healthcare", "operator", "healthcare/speciality", "address", "building_area", "phone", "website"], "moreFields": ["brand", "building/levels_building", "email", "fax", "gnis/feature_id", "height_building", "level", "opening_hours", "opening_hours/covid19", "payment_multi", "wheelchair"], "geometry": ["point", "area"], "tags": {"healthcare": "*"}, "terms": ["clinic", "doctor", "disease", "health", "institution", "sick", "surgery", "wellness"], "name": "Healthcare Facility"}, "healthcare/alternative": {"icon": "maki-hospital", "geometry": ["point", "area"], "terms": ["acupuncture", "anthroposophical", "applied kinesiology", "aromatherapy", "ayurveda", "herbalism", "homeopathy", "hydrotherapy", "hypnosis", "naturopathy", "osteopathy", "reflexology", "reiki", "shiatsu", "traditional", "tuina", "unani"], "tags": {"healthcare": "alternative"}, "name": "Alternative Medicine"}, "healthcare/alternative/chiropractic": {"icon": "maki-hospital", "geometry": ["point", "area"], "terms": ["back", "pain", "spine"], "tags": {"healthcare": "alternative", "healthcare:speciality": "chiropractic"}, "name": "Chiropractor"}, "healthcare/audiologist": {"icon": "maki-hospital", "geometry": ["point", "area"], "terms": ["ear", "hearing", "sound"], "tags": {"healthcare": "audiologist"}, "name": "Audiologist"}, @@ -700,7 +701,7 @@ "leisure/nature_reserve": {"icon": "maki-park", "geometry": ["area", "point"], "fields": ["name", "operator", "address", "opening_hours", "opening_hours/covid19"], "moreFields": ["dog", "email", "fax", "gnis/feature_id", "phone", "website"], "tags": {"leisure": "nature_reserve"}, "terms": ["protected", "wildlife"], "name": "Nature Reserve"}, "leisure/outdoor_seating": {"icon": "maki-picnic-site", "geometry": ["point", "area"], "fields": ["name", "operator"], "moreFields": ["level"], "terms": ["al fresco", "beer garden", "dining", "cafe", "restaurant", "pub", "bar", "patio"], "tags": {"leisure": "outdoor_seating"}, "name": "Outdoor Seating Area"}, "leisure/park": {"icon": "temaki-tree_and_bench", "fields": ["name", "operator", "address", "opening_hours", "opening_hours/covid19"], "moreFields": ["dog", "email", "fax", "gnis/feature_id", "phone", "smoking", "website"], "geometry": ["area", "point"], "terms": ["esplanade", "estate", "forest", "garden", "grass", "green", "grounds", "lawn", "lot", "meadow", "parkland", "place", "playground", "plaza", "pleasure garden", "recreation area", "square", "tract", "village green", "woodland"], "tags": {"leisure": "park"}, "name": "Park"}, - "leisure/picnic_table": {"icon": "maki-picnic-site", "fields": ["material", "lit", "bench"], "moreFields": ["level"], "geometry": ["point"], "tags": {"leisure": "picnic_table"}, "terms": ["bench"], "name": "Picnic Table"}, + "leisure/picnic_table": {"icon": "maki-picnic-site", "fields": ["material", "lit", "bench", "colour"], "moreFields": ["height", "level", "manufacturer", "operator"], "geometry": ["point"], "tags": {"leisure": "picnic_table"}, "terms": ["bench"], "name": "Picnic Table"}, "leisure/picnic_table/chess": {"icon": "fas-chess-pawn", "geometry": ["point"], "tags": {"leisure": "picnic_table", "sport": "chess"}, "reference": {"key": "sport", "value": "chess"}, "terms": ["bench", "chess board", "checkerboard", "checkers", "chequerboard", "game table"], "name": "Chess Table"}, "leisure/pitch": {"icon": "maki-pitch", "fields": ["name", "sport", "access_simple", "surface", "lit"], "moreFields": ["address", "charge_fee", "covered", "fee", "gnis/feature_id", "indoor", "payment_multi_fee"], "geometry": ["area", "point"], "tags": {"leisure": "pitch"}, "terms": ["field"], "name": "Sport Pitch"}, "leisure/pitch/american_football": {"icon": "maki-american-football", "geometry": ["area", "point"], "tags": {"leisure": "pitch", "sport": "american_football"}, "reference": {"key": "sport", "value": "american_football"}, "terms": ["football", "gridiron"], "name": "American Football Field"}, @@ -752,7 +753,7 @@ "man_made/breakwater": {"fields": ["material", "seamark/type"], "geometry": ["line", "area"], "tags": {"man_made": "breakwater"}, "name": "Breakwater"}, "man_made/bridge": {"icon": "maki-bridge", "fields": ["name", "bridge_combo", "layer", "maxweight"], "moreFields": ["gnis/feature_id", "manufacturer", "material", "seamark/type"], "geometry": ["area"], "tags": {"man_made": "bridge"}, "addTags": {"man_made": "bridge", "layer": "1"}, "removeTags": {"man_made": "bridge", "layer": "*"}, "reference": {"key": "man_made", "value": "bridge"}, "name": "Bridge Area", "matchScore": 0.85}, "man_made/bunker_silo": {"icon": "temaki-bunker_silo", "fields": ["content"], "moreFields": ["gnis/feature_id"], "geometry": ["point", "area"], "terms": ["Silage", "Storage"], "tags": {"man_made": "bunker_silo"}, "name": "Bunker Silo"}, - "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/cairn": {"icon": "temaki-cairn", "fields": ["height", "ele", "ele_node", "check_date"], "moreFields": ["colour", "material", "name", "operator", "ref"], "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"}, @@ -954,7 +955,7 @@ "playground/swing": {"icon": "maki-playground", "fields": ["capacity", "baby_seat", "wheelchair", "blind"], "geometry": ["point"], "tags": {"playground": "swing"}, "name": "Swing"}, "playground/zipwire": {"icon": "maki-playground", "geometry": ["point", "line"], "tags": {"playground": "zipwire"}, "terms": ["zipline", "zip wire", "zipwire"], "name": "Play Zip Line"}, "point": {"fields": ["name"], "geometry": ["vertex", "point"], "tags": {}, "terms": ["node", "other", "vertex", "vertices"], "name": "Point", "matchScore": 0.1}, - "polling_station": {"icon": "fas-vote-yea", "fields": ["name", "ref", "operator", "address", "opening_hours", "opening_hours/covid19", "building_area"], "moreFields": ["air_conditioning", "email", "fax", "internet_access", "internet_access/fee", "internet_access/ssid", "level", "phone", "website", "wheelchair"], "geometry": ["point", "area"], "terms": ["ballot box", "ballot drop", "democracy", "elections", "polling place", "vote", "voting booth", "voting machine"], "tags": {"polling_station": "*"}, "matchScore": 0.75, "name": "Temporary Polling Place"}, + "polling_station": {"icon": "fas-vote-yea", "fields": ["name", "ref", "operator", "address", "opening_hours", "opening_hours/covid19", "building_area", "phone", "website"], "moreFields": ["air_conditioning", "email", "fax", "internet_access", "internet_access/fee", "internet_access/ssid", "level", "wheelchair"], "geometry": ["point", "area"], "terms": ["ballot box", "ballot drop", "democracy", "elections", "polling place", "vote", "voting booth", "voting machine"], "tags": {"polling_station": "*"}, "matchScore": 0.75, "name": "Temporary Polling Place"}, "power/cable": {"icon": "temaki-cable", "fields": ["name", "ref", "operator", "voltage", "location", "layer"], "geometry": ["line"], "tags": {"power": "cable"}, "searchable": false, "name": "Power Cable"}, "power/cable/underground": {"icon": "temaki-cable", "geometry": ["line"], "tags": {"power": "cable", "location": "underground"}, "addTags": {"power": "cable", "location": "underground", "layer": "-1"}, "name": "Underground Power Cable"}, "power/generator": {"icon": "temaki-power", "fields": ["ref", "operator", "generator/source", "generator/method", "generator/type", "generator/output/electricity"], "moreFields": ["colour", "height", "level", "manufacturer", "material"], "geometry": ["point", "vertex", "area"], "terms": ["hydro", "solar", "turbine", "wind"], "tags": {"power": "generator"}, "name": "Power Generator"}, @@ -1057,7 +1058,7 @@ "seamark/buoy_lateral/green": {"icon": "temaki-buoy", "geometry": ["point", "vertex"], "terms": ["lateral buoy", "buoy lateral", "cevni", "channel marker", "iala", "lateral mark"], "tags": {"seamark:type": "buoy_lateral", "seamark:buoy_lateral:colour": "green"}, "name": "Green Buoy"}, "seamark/buoy_lateral/red": {"icon": "temaki-buoy", "geometry": ["point", "vertex"], "terms": ["lateral buoy", "buoy lateral", "cevni", "channel marker", "iala", "lateral mark"], "tags": {"seamark:type": "buoy_lateral", "seamark:buoy_lateral:colour": "red"}, "name": "Red Buoy"}, "seamark/mooring": {"icon": "temaki-buoy", "fields": ["ref", "operator", "seamark/mooring/category", "seamark/type"], "geometry": ["point"], "terms": ["dolphin", "pile", "bollard", "buoy", "post"], "tags": {"seamark:type": "mooring"}, "name": "Mooring"}, - "shop": {"icon": "maki-shop", "fields": ["name", "shop", "operator", "address", "building_area", "opening_hours", "opening_hours/covid19", "payment_multi"], "moreFields": ["air_conditioning", "brand", "building/levels_building", "currency_multi", "ele", "email", "fax", "gnis/feature_id", "height_building", "internet_access", "internet_access/fee", "internet_access/ssid", "level", "not/name", "phone", "ref/vatin", "second_hand", "stroller", "website", "wheelchair"], "geometry": ["point", "area"], "tags": {"shop": "*"}, "terms": [], "name": "Shop"}, + "shop": {"icon": "maki-shop", "fields": ["name", "shop", "operator", "address", "building_area", "opening_hours", "opening_hours/covid19", "payment_multi", "phone", "website"], "moreFields": ["air_conditioning", "brand", "building/levels_building", "currency_multi", "ele", "email", "fax", "gnis/feature_id", "height_building", "internet_access", "internet_access/fee", "internet_access/ssid", "level", "not/name", "ref/vatin", "second_hand", "stroller", "wheelchair"], "geometry": ["point", "area"], "tags": {"shop": "*"}, "terms": [], "name": "Shop"}, "shop/boutique": {"icon": "maki-shop", "fields": ["name", "clothes", "{shop}"], "geometry": ["point", "area"], "tags": {"shop": "boutique"}, "searchable": false, "name": "Boutique"}, "shop/fashion": {"icon": "maki-shop", "fields": ["name", "clothes", "{shop}"], "geometry": ["point", "area"], "tags": {"shop": "fashion"}, "searchable": false, "name": "Fashion Store"}, "shop/vacant": {"icon": "maki-shop", "fields": ["name", "address", "building_area"], "geometry": ["point", "area"], "tags": {"shop": "vacant"}, "name": "Vacant Shop", "searchable": false}, @@ -1169,7 +1170,7 @@ "shop/money_lender": {"icon": "temaki-money_hand", "fields": ["{shop}", "currency_multi"], "geometry": ["point", "area"], "tags": {"shop": "money_lender"}, "name": "Money Lender"}, "shop/motorcycle_repair": {"icon": "temaki-motorcycle_repair", "fields": ["{shop}", "service/vehicle"], "geometry": ["point", "area"], "terms": ["auto", "bike", "garage", "motorcycle", "repair", "service"], "tags": {"shop": "motorcycle_repair"}, "name": "Motorcycle Repair Shop"}, "shop/motorcycle": {"icon": "fas-motorcycle", "fields": ["name", "brand", "{shop}"], "geometry": ["point", "area"], "terms": ["bike"], "tags": {"shop": "motorcycle"}, "name": "Motorcycle Dealership"}, - "shop/music": {"icon": "fas-compact-disc", "geometry": ["point", "area"], "terms": ["tape cassettes", "CDs", "compact discs", "vinyl records"], "tags": {"shop": "music"}, "name": "Music Store"}, + "shop/music": {"icon": "fas-compact-disc", "geometry": ["point", "area"], "terms": ["tape cassettes", "CDs", "compact discs", "vinyl records", "CD store", "casette", "casette store"], "tags": {"shop": "music"}, "name": "Music Store"}, "shop/musical_instrument": {"icon": "fas-guitar", "geometry": ["point", "area"], "terms": ["guitar"], "tags": {"shop": "musical_instrument"}, "name": "Musical Instrument Store"}, "shop/newsagent": {"icon": "fas-newspaper", "geometry": ["point", "area"], "tags": {"shop": "newsagent"}, "name": "Newspaper/Magazine Shop"}, "shop/nutrition_supplements": {"icon": "fas-pills", "geometry": ["point", "area"], "terms": ["health", "supplement", "vitamin"], "tags": {"shop": "nutrition_supplements"}, "name": "Nutrition Supplements Store"}, @@ -1235,7 +1236,7 @@ "tourism/apartment": {"icon": "maki-lodging", "fields": ["name", "operator", "address", "building_area", "rooms", "internet_access", "internet_access/fee"], "moreFields": ["building/levels_building", "email", "fax", "height_building", "internet_access/ssid", "level", "payment_multi", "phone", "reservation", "smoking", "website", "wheelchair"], "geometry": ["point", "area"], "tags": {"tourism": "apartment"}, "terms": ["bnb", "holiday rental", "lodging", "overnight accommodations", "vacation rental"], "name": "Guest Apartment / Condo"}, "tourism/aquarium": {"icon": "maki-aquarium", "fields": ["name", "operator", "address", "building_area", "opening_hours", "opening_hours/covid19"], "moreFields": ["charge_fee", "email", "fax", "fee", "gnis/feature_id", "internet_access", "internet_access/fee", "internet_access/ssid", "payment_multi_fee", "phone", "ref/vatin", "smoking", "website", "wheelchair"], "geometry": ["point", "area"], "terms": ["fish", "sea", "water"], "tags": {"tourism": "aquarium"}, "name": "Aquarium"}, "tourism/artwork": {"icon": "maki-art-gallery", "fields": ["name", "artwork_type", "artist"], "moreFields": ["level", "material", "website"], "geometry": ["point", "vertex", "line", "area"], "tags": {"tourism": "artwork"}, "terms": ["mural", "sculpture", "statue"], "name": "Artwork"}, - "tourism/artwork/bust": {"icon": "fas-user-alt", "fields": ["name", "artist", "material"], "geometry": ["point", "vertex"], "tags": {"tourism": "artwork", "artwork_type": "bust"}, "reference": {"key": "artwork_type", "value": "bust"}, "terms": ["figure"], "name": "Bust"}, + "tourism/artwork/bust": {"icon": "fas-user-alt", "fields": ["name", "artist", "material", "inscription"], "geometry": ["point", "vertex"], "tags": {"tourism": "artwork", "artwork_type": "bust"}, "reference": {"key": "artwork_type", "value": "bust"}, "terms": ["figure"], "name": "Bust"}, "tourism/artwork/graffiti": {"icon": "maki-art-gallery", "fields": ["name", "artist"], "geometry": ["point", "vertex", "line", "area"], "tags": {"tourism": "artwork", "artwork_type": "graffiti"}, "reference": {"key": "artwork_type", "value": "graffiti"}, "terms": ["Street Artwork", "Guerilla Artwork", "Graffiti Artwork"], "name": "Graffiti"}, "tourism/artwork/installation": {"icon": "temaki-sculpture", "fields": ["name", "artist"], "geometry": ["point", "vertex", "line", "area"], "tags": {"tourism": "artwork", "artwork_type": "installation"}, "reference": {"key": "artwork_type", "value": "installation"}, "terms": ["interactive art", "intervention art", "modern art"], "name": "Art Installation"}, "tourism/artwork/mural": {"icon": "maki-art-gallery", "fields": ["name", "artist"], "geometry": ["point", "vertex", "line", "area"], "tags": {"tourism": "artwork", "artwork_type": "mural"}, "reference": {"key": "artwork_type", "value": "mural"}, "terms": ["fresco", "wall painting"], "name": "Mural"}, diff --git a/data/presets/presets/amenity/binoculars.json b/data/presets/presets/amenity/binoculars.json new file mode 100644 index 000000000..1d619e382 --- /dev/null +++ b/data/presets/presets/amenity/binoculars.json @@ -0,0 +1,39 @@ +{ + "icon": "temaki-binoculars", + "fields": [ + "operator", + "access_simple", + "fee", + "payment_multi_fee", + "charge_fee", + "direction", + "height", + "ele_node" + ], + "moreFields": [ + "colour", + "covered", + "indoor", + "lit", + "manufacturer", + "ref" + ], + "geometry": [ + "point" + ], + "terms": [ + "observation viewer", + "optical ranger", + "spotting scope", + "sight", + "spyglass", + "telescope", + "tower viewer", + "viewfinder", + "viewing stand" + ], + "tags": { + "amenity": "binoculars" + }, + "name": "Mounted Binoculars" +} diff --git a/data/presets/presets/amenity/cafe.json b/data/presets/presets/amenity/cafe.json index a858cc0a7..ffe3042b3 100644 --- a/data/presets/presets/amenity/cafe.json +++ b/data/presets/presets/amenity/cafe.json @@ -10,7 +10,9 @@ "outdoor_seating", "internet_access", "internet_access/fee", - "internet_access/ssid" + "internet_access/ssid", + "phone", + "website" ], "moreFields": [ "air_conditioning", @@ -26,12 +28,10 @@ "min_age", "not/name", "payment_multi", - "phone", "ref/vatin", "reservation", "smoking", "takeaway", - "website", "wheelchair" ], "geometry": [ diff --git a/data/presets/presets/amenity/fast_food.json b/data/presets/presets/amenity/fast_food.json index 1657b9503..2ef3e64c1 100644 --- a/data/presets/presets/amenity/fast_food.json +++ b/data/presets/presets/amenity/fast_food.json @@ -8,7 +8,9 @@ "building_area", "opening_hours", "opening_hours/covid19", - "drive_through" + "drive_through", + "phone", + "website" ], "moreFields": [ "air_conditioning", @@ -27,11 +29,9 @@ "opening_hours/covid19", "outdoor_seating", "payment_multi", - "phone", "ref/vatin", "smoking", "takeaway", - "website", "wheelchair" ], "geometry": [ diff --git a/data/presets/presets/amenity/internet_cafe.json b/data/presets/presets/amenity/internet_cafe.json index f60a9bc84..db74faffa 100644 --- a/data/presets/presets/amenity/internet_cafe.json +++ b/data/presets/presets/amenity/internet_cafe.json @@ -8,7 +8,9 @@ "building_area", "internet_access", "internet_access/fee", - "internet_access/ssid" + "internet_access/ssid", + "phone", + "website" ], "moreFields": [ "air_conditioning", @@ -21,10 +23,8 @@ "opening_hours/covid19", "outdoor_seating", "payment_multi", - "phone", "ref/vatin", "smoking", - "website", "wheelchair" ], "geometry": [ diff --git a/data/presets/presets/club.json b/data/presets/presets/club.json index 44e7ffb77..f612d9c58 100644 --- a/data/presets/presets/club.json +++ b/data/presets/presets/club.json @@ -7,7 +7,9 @@ "address", "building_area", "opening_hours", - "opening_hours/covid19" + "opening_hours/covid19", + "phone", + "website" ], "moreFields": [ "access_simple", diff --git a/data/presets/presets/craft.json b/data/presets/presets/craft.json index 1144319b2..62e139d11 100644 --- a/data/presets/presets/craft.json +++ b/data/presets/presets/craft.json @@ -7,7 +7,9 @@ "address", "building_area", "opening_hours", - "opening_hours/covid19" + "opening_hours/covid19", + "phone", + "website" ], "moreFields": [ "air_conditioning", @@ -21,10 +23,8 @@ "internet_access/fee", "internet_access/ssid", "level", - "phone", "product", "ref/vatin", - "website", "wheelchair" ], "geometry": [ diff --git a/data/presets/presets/healthcare.json b/data/presets/presets/healthcare.json index 451e62680..a3db274c1 100644 --- a/data/presets/presets/healthcare.json +++ b/data/presets/presets/healthcare.json @@ -6,7 +6,9 @@ "operator", "healthcare/speciality", "address", - "building_area" + "building_area", + "phone", + "website" ], "moreFields": [ "brand", @@ -19,8 +21,6 @@ "opening_hours", "opening_hours/covid19", "payment_multi", - "phone", - "website", "wheelchair" ], "geometry": [ diff --git a/data/presets/presets/leisure/picnic_table.json b/data/presets/presets/leisure/picnic_table.json index 24ad415f4..b11416b01 100644 --- a/data/presets/presets/leisure/picnic_table.json +++ b/data/presets/presets/leisure/picnic_table.json @@ -3,10 +3,14 @@ "fields": [ "material", "lit", - "bench" + "bench", + "colour" ], "moreFields": [ - "level" + "height", + "level", + "manufacturer", + "operator" ], "geometry": [ "point" diff --git a/data/presets/presets/man_made/cairn.json b/data/presets/presets/man_made/cairn.json index e4fa7a670..d09c9326f 100644 --- a/data/presets/presets/man_made/cairn.json +++ b/data/presets/presets/man_made/cairn.json @@ -1,5 +1,18 @@ { "icon": "temaki-cairn", + "fields": [ + "height", + "ele", + "ele_node", + "check_date" + ], + "moreFields": [ + "colour", + "material", + "name", + "operator", + "ref" + ], "geometry": [ "point", "area" diff --git a/data/presets/presets/polling_station.json b/data/presets/presets/polling_station.json index 7972ad50d..8070894fd 100644 --- a/data/presets/presets/polling_station.json +++ b/data/presets/presets/polling_station.json @@ -7,7 +7,9 @@ "address", "opening_hours", "opening_hours/covid19", - "building_area" + "building_area", + "phone", + "website" ], "moreFields": [ "air_conditioning", @@ -17,8 +19,6 @@ "internet_access/fee", "internet_access/ssid", "level", - "phone", - "website", "wheelchair" ], "geometry": [ diff --git a/data/presets/presets/shop.json b/data/presets/presets/shop.json index 685cd8c39..b821da2b1 100644 --- a/data/presets/presets/shop.json +++ b/data/presets/presets/shop.json @@ -8,7 +8,9 @@ "building_area", "opening_hours", "opening_hours/covid19", - "payment_multi" + "payment_multi", + "phone", + "website" ], "moreFields": [ "air_conditioning", @@ -25,11 +27,9 @@ "internet_access/ssid", "level", "not/name", - "phone", "ref/vatin", "second_hand", "stroller", - "website", "wheelchair" ], "geometry": [ diff --git a/data/presets/presets/shop/music.json b/data/presets/presets/shop/music.json index c4bf643e4..0be8aed88 100644 --- a/data/presets/presets/shop/music.json +++ b/data/presets/presets/shop/music.json @@ -8,7 +8,10 @@ "tape cassettes", "CDs", "compact discs", - "vinyl records" + "vinyl records", + "CD store", + "casette", + "casette store" ], "tags": { "shop": "music" diff --git a/data/presets/presets/tourism/artwork/bust.json b/data/presets/presets/tourism/artwork/bust.json index e45c5db9c..253dcd313 100644 --- a/data/presets/presets/tourism/artwork/bust.json +++ b/data/presets/presets/tourism/artwork/bust.json @@ -3,7 +3,8 @@ "fields": [ "name", "artist", - "material" + "material", + "inscription" ], "geometry": [ "point", diff --git a/data/shortcuts.json b/data/shortcuts.json index 6b910899b..2997ed427 100644 --- a/data/shortcuts.json +++ b/data/shortcuts.json @@ -281,6 +281,18 @@ "text": "shortcuts.editing.operations.nudge_more", "separator": "," }, + { + "modifiers": ["⇧"], + "shortcuts": ["+", "-"], + "text": "shortcuts.editing.operations.scale", + "separator": "," + }, + { + "modifiers": ["⌥", "⇧"], + "shortcuts": ["+", "-"], + "text": "shortcuts.editing.operations.scale_more", + "separator": "," + }, { "shortcuts": ["operations.rotate.key"], "text": "shortcuts.editing.operations.rotate" diff --git a/data/taginfo.json b/data/taginfo.json index d83d820b0..ed863dbef 100644 --- a/data/taginfo.json +++ b/data/taginfo.json @@ -83,6 +83,7 @@ {"key": "amenity", "value": "bicycle_rental", "description": "🄿 Bicycle Rental", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/bicycle_rental.svg"}, {"key": "amenity", "value": "bicycle_repair_station", "description": "🄿 Bicycle Repair Tool Stand", "object_types": ["node"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/bicycle_repair.svg"}, {"key": "amenity", "value": "biergarten", "description": "🄿 Biergarten", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/openstreetmap/iD@develop/svg/fontawesome/fas-beer.svg"}, + {"key": "amenity", "value": "binoculars", "description": "🄿 Mounted Binoculars", "object_types": ["node"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/binoculars.svg"}, {"key": "amenity", "value": "boat_rental", "description": "🄿 Boat Rental", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/boat_rental.svg"}, {"key": "amenity", "value": "bureau_de_change", "description": "🄿 Currency Exchange", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/ideditor/temaki/icons/money_hand.svg"}, {"key": "amenity", "value": "cafe", "description": "🄿 Cafe", "object_types": ["node", "area"], "icon_url": "https://cdn.jsdelivr.net/gh/mapbox/maki/icons/cafe-15.svg"}, diff --git a/dist/locales/en.json b/dist/locales/en.json index 4f2c141ca..a9ec33916 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -131,8 +131,10 @@ }, "key": "O", "annotation": { - "single": "Made a feature circular.", - "multiple": "Made features circular." + "feature": { + "one": "Made a feature circular.", + "other": "Made {n} features circular." + } }, "multiple_blockers": { "multiple": "These can't be made circular for multiple reasons." @@ -173,12 +175,12 @@ "key": "Q", "annotation": { "corner": { - "single": "Squared a corner.", - "multiple": "Squared several corners." + "one": "Squared a corner.", + "other": "Squared {n} corners." }, "feature": { - "single": "Squared the corners of a feature.", - "multiple": "Squared the corners of several features." + "one": "Squared the corners of a feature.", + "other": "Squared the corners of {n} features." } }, "multiple_blockers": { @@ -218,9 +220,14 @@ }, "key": "S", "annotation": { - "points": "Straightened several points.", - "line": "Straightened a line.", - "lines": "Straightened several lines." + "point": { + "one": "Straightened a point.", + "other": "Straightened {n} points." + }, + "line": { + "one": "Straightened a line.", + "other": "Straightened {n} lines." + } }, "too_bendy": { "single": "This can't be straightened because it bends too much.", @@ -414,7 +421,10 @@ "line": "Moved a line.", "area": "Moved an area.", "relation": "Moved a relation.", - "multiple": "Moved multiple features." + "feature": { + "one": "Moved a feature.", + "other": "Moved {n} features." + } }, "incomplete_relation": { "single": "This feature can't be moved because it hasn't been fully downloaded.", @@ -454,12 +464,16 @@ }, "annotation": { "long": { - "single": "Flipped a feature across its long axis.", - "multiple": "Flipped multiple features across their long axis." + "feature": { + "one": "Flipped a feature across its long axis.", + "other": "Flipped {n} features across their long axis." + } }, "short": { - "single": "Flipped a feature across its short axis.", - "multiple": "Flipped multiple features across their short axis." + "feature": { + "one": "Flipped a feature across its short axis.", + "other": "Flipped {n} features across their short axis." + } } }, "incomplete_relation": { @@ -489,8 +503,11 @@ "annotation": { "line": "Rotated a line.", "area": "Rotated an area.", - "multiple": "Rotated multiple features.", - "relation": "Rotated a relation." + "relation": "Rotated a relation.", + "feature": { + "one": "Rotated a feature.", + "other": "Rotated {n} features." + } }, "incomplete_relation": { "single": "This feature can't be rotated because it hasn't been fully downloaded.", @@ -520,11 +537,50 @@ }, "key": "V", "annotation": { - "point": "Reversed a point.", - "points": "Reversed multiple points.", - "line": "Reversed a line.", - "lines": "Reversed multiple lines.", - "features": "Reversed multiple features." + "point": { + "one": "Reversed a point.", + "other": "Reversed {n} points." + }, + "line": { + "one": "Reversed a line.", + "other": "Reversed {n} lines." + }, + "feature": { + "one": "Reversed a feature.", + "other": "Reversed {n} features." + } + } + }, + "scale": { + "annotation": { + "down": { + "feature": { + "one": "Scaled down a feature.", + "other": "Scaled down {n} features." + } + }, + "up": { + "feature": { + "one": "Scaled up a feature.", + "other": "Scaled up {n} features." + } + } + }, + "too_small": { + "single": "This feature can't be scaled down because it would become too small.", + "multiple": "These features can't be scaled down because they would become too small." + }, + "too_large": { + "single": "This feature can't be scaled because not enough of it is currently visible.", + "multiple": "These features can't be scaled because not enough of them are currently visible." + }, + "connected_to_hidden": { + "single": "This feature can't be scaled because it is connected to a hidden feature.", + "multiple": "These features can't be scaled because some are connected to hidden features." + }, + "not_downloaded": { + "single": "This feature can't be scaled because parts of it have not yet been downloaded.", + "multiple": "These features can't be scaled because parts of them have not yet been downloaded." } }, "split": { @@ -2674,6 +2730,8 @@ "move": "Move selected features", "nudge": "Nudge selected features", "nudge_more": "Nudge selected features by a lot", + "scale": "Scale selected features", + "scale_more": "Scale selected features by a lot", "rotate": "Rotate selected features", "orthogonalize": "Square corners of a line or area", "straighten": "Straighten a line or points", @@ -5549,6 +5607,10 @@ "name": "Biergarten", "terms": "beer,bier,booze" }, + "amenity/binoculars": { + "name": "Mounted Binoculars", + "terms": "observation viewer,optical ranger,spotting scope,sight,spyglass,telescope,tower viewer,viewfinder,viewing stand" + }, "amenity/boat_rental": { "name": "Boat Rental", "terms": "" @@ -9862,7 +9924,7 @@ }, "shop/music": { "name": "Music Store", - "terms": "tape cassettes,CDs,compact discs,vinyl records" + "terms": "tape cassettes,CDs,compact discs,vinyl records,CD store,casette,casette store" }, "shop/musical_instrument": { "name": "Musical Instrument Store", diff --git a/modules/actions/index.js b/modules/actions/index.js index dfdd1c7e6..ca8fe5f3d 100644 --- a/modules/actions/index.js +++ b/modules/actions/index.js @@ -30,6 +30,7 @@ export { actionRestrictTurn } from './restrict_turn'; export { actionReverse } from './reverse'; export { actionRevert } from './revert'; export { actionRotate } from './rotate'; +export { actionScale } from './scale'; export { actionSplit } from './split'; export { actionStraightenNodes } from './straighten_nodes'; export { actionStraightenWay } from './straighten_way'; diff --git a/modules/actions/scale.js b/modules/actions/scale.js new file mode 100644 index 000000000..c95f0224f --- /dev/null +++ b/modules/actions/scale.js @@ -0,0 +1,24 @@ +import { utilGetAllNodes } from '../util'; + +export function actionScale(ids, pivotLoc, scaleFactor, projection) { + return function(graph) { + return graph.update(function(graph) { + let point, radial; + + utilGetAllNodes(ids, graph).forEach(function(node) { + + point = projection(node.loc); + radial = [ + point[0] - pivotLoc[0], + point[1] - pivotLoc[1] + ]; + point = [ + pivotLoc[0] + (scaleFactor * radial[0]), + pivotLoc[1] + (scaleFactor * radial[1]) + ]; + + graph = graph.replace(node.move(projection.invert(point))); + }); + }); + }; +} diff --git a/modules/modes/move.js b/modules/modes/move.js index 6a6a7e97a..56db93c4d 100644 --- a/modules/modes/move.js +++ b/modules/modes/move.js @@ -39,7 +39,7 @@ export function modeMove(context, entityIDs, baseGraph) { ]; var annotation = entityIDs.length === 1 ? t('operations.move.annotation.' + context.graph().geometry(entityIDs[0])) : - t('operations.move.annotation.multiple'); + t('operations.move.annotation.feature', { n: entityIDs.length }); var _prevGraph; var _cache; diff --git a/modules/modes/rotate.js b/modules/modes/rotate.js index d22bf8eee..b11e300de 100644 --- a/modules/modes/rotate.js +++ b/modules/modes/rotate.js @@ -43,7 +43,7 @@ export function modeRotate(context, entityIDs) { ]; var annotation = entityIDs.length === 1 ? t('operations.rotate.annotation.' + context.graph().geometry(entityIDs[0])) : - t('operations.rotate.annotation.multiple'); + t('operations.rotate.annotation.feature', { n: entityIDs.length }); var _prevGraph; var _prevAngle; diff --git a/modules/modes/select.js b/modules/modes/select.js index b6c06cd7c..2d0c633bd 100644 --- a/modules/modes/select.js +++ b/modules/modes/select.js @@ -5,6 +5,7 @@ import { t } from '../core/localizer'; import { actionAddMidpoint } from '../actions/add_midpoint'; import { actionDeleteRelation } from '../actions/delete_relation'; import { actionMove } from '../actions/move'; +import { actionScale } from '../actions/scale'; import { behaviorBreathe } from '../behavior/breathe'; import { behaviorHover } from '../behavior/hover'; @@ -14,7 +15,7 @@ import { behaviorSelect } from '../behavior/select'; import { operationMove } from '../operations/move'; -import { geoExtent, geoChooseEdge } from '../geo'; +import { geoExtent, geoChooseEdge, geoMetersToLat, geoMetersToLon } from '../geo'; import { modeBrowse } from './browse'; import { modeDragNode } from './drag_node'; import { modeDragNote } from './drag_note'; @@ -23,7 +24,7 @@ import * as Operations from '../operations/index'; import { uiCmd } from '../ui/cmd'; import { utilArrayIntersection, utilDeepMemberSelector, utilEntityOrDeepMemberSelector, - utilEntitySelector, utilKeybinding + utilEntitySelector, utilKeybinding, utilTotalExtent, utilGetAllNodes } from '../util'; @@ -243,6 +244,10 @@ export function modeSelect(context, selectedIDs) { .on(uiCmd('⇧⌥↑'), nudgeSelection([0, -100])) .on(uiCmd('⇧⌥→'), nudgeSelection([100, 0])) .on(uiCmd('⇧⌥↓'), nudgeSelection([0, 100])) + .on(utilKeybinding.plusKeys.map((key) => uiCmd('⇧' + key)), scaleSelection(1.05)) + .on(utilKeybinding.plusKeys.map((key) => uiCmd('⇧⌥' + key)), scaleSelection(Math.pow(1.05, 5))) + .on(utilKeybinding.minusKeys.map((key) => uiCmd('⇧' + key)), scaleSelection(1/1.05)) + .on(utilKeybinding.minusKeys.map((key) => uiCmd('⇧⌥' + key)), scaleSelection(1/Math.pow(1.05, 5))) .on(['\\', 'pause'], nextParent) .on('⎋', esc, true); @@ -303,6 +308,82 @@ export function modeSelect(context, selectedIDs) { .text(moveOp.tooltip)(); } else { context.perform(actionMove(selectedIDs, delta, context.projection), moveOp.annotation()); + context.validator().validate(); + } + }; + } + + function scaleSelection(factor) { + return function() { + // prevent scaling during low zoom selection + if (!context.map().withinEditableZoom()) return; + + let nodes = utilGetAllNodes(selectedIDs, context.graph()); + + let isUp = factor > 1; + + // can only scale if multiple nodes are selected + if (nodes.length <= 1) return; + + let extent = utilTotalExtent(selectedIDs, context.graph()); + + // These disabled checks would normally be handled by an operation + // object, but we don't want an actual scale operation at this point. + function scalingDisabled() { + + if (tooSmall()) { + return 'too_small'; + } else if (extent.percentContainedIn(context.map().extent()) < 0.8) { + return 'too_large'; + } else if (someMissing() || selectedIDs.some(incompleteRelation)) { + return 'not_downloaded'; + } else if (selectedIDs.some(context.hasHiddenConnections)) { + return 'connected_to_hidden'; + } + + return false; + + function tooSmall() { + if (isUp) return false; + let dLon = Math.abs(extent[1][0] - extent[0][0]); + let dLat = Math.abs(extent[1][1] - extent[0][1]); + return dLon < geoMetersToLon(1, extent[1][1]) && + dLat < geoMetersToLat(1); + } + + function someMissing() { + if (context.inIntro()) return false; + let osm = context.connection(); + if (osm) { + let missing = nodes.filter(function(n) { return !osm.isDataLoaded(n.loc); }); + if (missing.length) { + missing.forEach(function(loc) { context.loadTileAtLoc(loc); }); + return true; + } + } + return false; + } + + function incompleteRelation(id) { + let entity = context.entity(id); + return entity.type === 'relation' && !entity.isComplete(context.graph()); + } + } + + const disabled = scalingDisabled(); + + if (disabled) { + let multi = (selectedIDs.length === 1 ? 'single' : 'multiple'); + context.ui().flash + .duration(4000) + .iconName('#iD-icon-no') + .iconClass('operation disabled') + .text(t('operations.scale.' + disabled + '.' + multi))(); + } else { + const pivot = context.projection(extent.center()); + const annotation = t('operations.scale.annotation.' + (isUp ? 'up' : 'down') + '.feature', { n: selectedIDs.length }); + context.perform(actionScale(selectedIDs, pivot, factor, context.projection), annotation); + context.validator().validate(); } }; } diff --git a/modules/operations/circularize.js b/modules/operations/circularize.js index 27fe1bdf3..a08ef0ccd 100644 --- a/modules/operations/circularize.js +++ b/modules/operations/circularize.js @@ -102,7 +102,7 @@ export function operationCircularize(context, selectedIDs) { operation.annotation = function() { - return t('operations.circularize.annotation.' + _amount); + return t('operations.circularize.annotation.feature', { n: _actions.length }); }; diff --git a/modules/operations/move.js b/modules/operations/move.js index 68af877ca..c65b78e68 100644 --- a/modules/operations/move.js +++ b/modules/operations/move.js @@ -67,7 +67,7 @@ export function operationMove(context, selectedIDs) { operation.annotation = function() { return selectedIDs.length === 1 ? t('operations.move.annotation.' + context.graph().geometry(selectedIDs[0])) : - t('operations.move.annotation.multiple'); + t('operations.move.annotation.feature', { n: selectedIDs.length }); }; diff --git a/modules/operations/orthogonalize.js b/modules/operations/orthogonalize.js index 1a683b166..157dc280f 100644 --- a/modules/operations/orthogonalize.js +++ b/modules/operations/orthogonalize.js @@ -125,7 +125,7 @@ export function operationOrthogonalize(context, selectedIDs) { operation.annotation = function() { - return t('operations.orthogonalize.annotation.' + _type + '.' + _amount); + return t('operations.orthogonalize.annotation.' + _type, { n: _actions.length }); }; diff --git a/modules/operations/reflect.js b/modules/operations/reflect.js index 9be80cfb9..2a0e17271 100644 --- a/modules/operations/reflect.js +++ b/modules/operations/reflect.js @@ -83,7 +83,7 @@ export function operationReflect(context, selectedIDs, axis) { operation.annotation = function() { - return t('operations.reflect.annotation.' + axis + '.' + multi); + return t('operations.reflect.annotation.' + axis + '.feature', { n: selectedIDs.length }); }; diff --git a/modules/operations/reverse.js b/modules/operations/reverse.js index b21e6de75..772e4736b 100644 --- a/modules/operations/reverse.js +++ b/modules/operations/reverse.js @@ -41,9 +41,9 @@ export function operationReverse(context, selectedIDs) { var entity = context.hasEntity(act.entityID()); return entity && entity.type === 'node'; }).length; - var typeID = nodeActionCount === 0 ? 'line' : (nodeActionCount === acts.length ? 'point' : 'features'); - if (typeID !== 'features' && acts.length > 1) typeID += 's'; - return typeID; + if (nodeActionCount === 0) return 'line'; + if (nodeActionCount === acts.length) return 'point'; + return 'feature'; } @@ -63,7 +63,8 @@ export function operationReverse(context, selectedIDs) { operation.annotation = function() { - return t('operations.reverse.annotation.' + reverseTypeID()); + var acts = actions(); + return t('operations.reverse.annotation.' + reverseTypeID(), { n: acts.length }); }; diff --git a/modules/operations/rotate.js b/modules/operations/rotate.js index c7883c2cf..072808d7c 100644 --- a/modules/operations/rotate.js +++ b/modules/operations/rotate.js @@ -67,7 +67,7 @@ export function operationRotate(context, selectedIDs) { operation.annotation = function() { return selectedIDs.length === 1 ? t('operations.rotate.annotation.' + context.graph().geometry(selectedIDs[0])) : - t('operations.rotate.annotation.multiple'); + t('operations.rotate.annotation.feature', { n: selectedIDs.length }); }; diff --git a/modules/operations/straighten.js b/modules/operations/straighten.js index b3e6b3894..e44baf817 100644 --- a/modules/operations/straighten.js +++ b/modules/operations/straighten.js @@ -20,7 +20,7 @@ export function operationStraighten(context, selectedIDs) { function chooseAction() { // straighten selected nodes if (_wayIDs.length === 0 && _nodeIDs.length > 2) { - _geometry = 'points'; + _geometry = 'point'; return actionStraightenNodes(_nodeIDs, context.projection); // straighten selected ways (possibly between range of 2 selected nodes) @@ -67,7 +67,7 @@ export function operationStraighten(context, selectedIDs) { _extent = utilTotalExtent(_nodeIDs, context.graph()); } - _geometry = _wayIDs.length === 1 ? 'line' : 'lines'; + _geometry = 'line'; return actionStraightenWay(selectedIDs, context.projection); } @@ -125,12 +125,12 @@ export function operationStraighten(context, selectedIDs) { var disable = operation.disabled(); return disable ? t('operations.straighten.' + disable + '.' + _amount) : - t('operations.straighten.description.' + _geometry); + t('operations.straighten.description.' + _geometry + (_wayIDs.length === 1 ? '' : 's')); }; operation.annotation = function() { - return t('operations.straighten.annotation.' + _geometry); + return t('operations.straighten.annotation.' + _geometry, { n: _wayIDs.length ? _wayIDs.length : _nodeIDs.length }); }; diff --git a/modules/ui/account.js b/modules/ui/account.js index 77969b37b..6ca578175 100644 --- a/modules/ui/account.js +++ b/modules/ui/account.js @@ -30,22 +30,22 @@ export function uiAccount(context) { .classed('hide', false); // Link - userLink.append('a') + var userLinkA = userLink.append('a') .attr('href', osm.userURL(details.display_name)) .attr('target', '_blank'); // Add thumbnail or dont if (details.image_url) { - userLink.append('img') + userLinkA.append('img') .attr('class', 'icon pre-text user-icon') .attr('src', details.image_url); } else { - userLink + userLinkA .call(svgIcon('#iD-icon-avatar', 'pre-text light')); } // Add user name - userLink.append('span') + userLinkA.append('span') .attr('class', 'label') .html(details.display_name); @@ -62,14 +62,15 @@ export function uiAccount(context) { return function(selection) { - selection.append('li') - .attr('class', 'logoutLink') - .classed('hide', true); selection.append('li') .attr('class', 'userLink') .classed('hide', true); + selection.append('li') + .attr('class', 'logoutLink') + .classed('hide', true); + if (osm) { osm.on('change.account', function() { update(selection); }); update(selection); diff --git a/modules/ui/changeset_editor.js b/modules/ui/changeset_editor.js index 192e4aab5..63b2d84f5 100644 --- a/modules/ui/changeset_editor.js +++ b/modules/ui/changeset_editor.js @@ -104,7 +104,6 @@ export function uiChangesetEditor(context) { commentEnter .append('a') .attr('target', '_blank') - .attr('tabindex', -1) .call(svgIcon('#iD-icon-alert', 'inline')) .attr('href', t('commit.google_warning_link')) .append('span') diff --git a/modules/ui/commit.js b/modules/ui/commit.js index eab57a373..c34bc6706 100644 --- a/modules/ui/commit.js +++ b/modules/ui/commit.js @@ -388,6 +388,12 @@ export function uiCommit(context) { .on('click.save', function() { if (!d3_select(this).classed('disabled')) { this.blur(); // avoid keeping focus on the button - #4641 + + for (var key in context.changeset.tags) { + // remove any empty keys before upload + if (!key) delete context.changeset.tags[key]; + } + context.uploader().save(context.changeset); } }); @@ -547,14 +553,12 @@ export function uiCommit(context) { k = context.cleanTagKey(k); if (readOnlyTags.indexOf(k) !== -1) return; - if (k !== '' && v !== undefined) { - if (onInput) { - tags[k] = v; - } else { - tags[k] = context.cleanTagValue(v); - } - } else { + if (v === undefined) { delete tags[k]; + } else if (onInput) { + tags[k] = v; + } else { + tags[k] = context.cleanTagValue(v); } }); diff --git a/modules/ui/commit_warnings.js b/modules/ui/commit_warnings.js index 9e62a415e..576ee8b2a 100644 --- a/modules/ui/commit_warnings.js +++ b/modules/ui/commit_warnings.js @@ -47,28 +47,8 @@ export function uiCommitWarnings(context) { .append('li') .attr('class', issueItem); - itemsEnter - .call(svgIcon('#iD-icon-alert', 'pre-text')); - - itemsEnter - .append('strong') - .attr('class', 'issue-message'); - - itemsEnter.filter(function(d) { return d.tooltip; }) - .call(uiTooltip() - .title(function(d) { return d.tooltip; }) - .placement('top') - ); - - items = itemsEnter - .merge(items); - - items.selectAll('.issue-message') - .html(function(d) { - return d.message(context); - }); - - items + var buttons = itemsEnter + .append('button') .on('mouseover', function(d) { if (d.entityIds) { context.surface().selectAll( @@ -86,6 +66,27 @@ export function uiCommitWarnings(context) { .on('click', function(d) { context.validator().focusIssue(d); }); + + buttons + .call(svgIcon('#iD-icon-alert', 'pre-text')); + + buttons + .append('strong') + .attr('class', 'issue-message'); + + buttons.filter(function(d) { return d.tooltip; }) + .call(uiTooltip() + .title(function(d) { return d.tooltip; }) + .placement('top') + ); + + items = itemsEnter + .merge(items); + + items.selectAll('.issue-message') + .html(function(d) { + return d.message(context); + }); } } diff --git a/modules/ui/feature_info.js b/modules/ui/feature_info.js index 845412f39..f4fecc933 100644 --- a/modules/ui/feature_info.js +++ b/modules/ui/feature_info.js @@ -28,7 +28,6 @@ export function uiFeatureInfo(context) { selection.append('a') .attr('class', 'chip') .attr('href', '#') - .attr('tabindex', -1) .html(t('feature_info.hidden_warning', { count: count })) .call(tooltipBehavior) .on('click', function() { diff --git a/modules/ui/feature_list.js b/modules/ui/feature_list.js index bb1d405dd..aa99d75a7 100644 --- a/modules/ui/feature_list.js +++ b/modules/ui/feature_list.js @@ -89,7 +89,9 @@ export function uiFeatureList(context) { function keypress() { var q = search.property('value'), items = list.selectAll('.feature-list-item'); - if (d3_event.keyCode === 13 && q.length && items.size()) { // return + if (d3_event.keyCode === 13 && // ↩ Return + q.length && + items.size()) { click(items.datum()); } } @@ -261,7 +263,7 @@ export function uiFeatureList(context) { .data([0]) .enter() .append('button') - .attr('class', 'geocode-item') + .attr('class', 'geocode-item secondary-action') .on('click', geocoderSearch) .append('div') .attr('class', 'label') diff --git a/modules/ui/field.js b/modules/ui/field.js index d25df54a5..d6bd10629 100644 --- a/modules/ui/field.js +++ b/modules/ui/field.js @@ -143,7 +143,6 @@ export function uiField(context, presetField, entityIDs, options) { .append('button') .attr('class', 'remove-icon') .attr('title', t('icons.remove', { html: false })) - .attr('tabindex', -1) .call(svgIcon('#iD-operation-delete')); } @@ -152,7 +151,6 @@ export function uiField(context, presetField, entityIDs, options) { .append('button') .attr('class', 'modified-icon') .attr('title', t('icons.undo', { html: false })) - .attr('tabindex', -1) .call(svgIcon((localizer.textDirection() === 'rtl') ? '#iD-icon-redo' : '#iD-icon-undo')); } } diff --git a/modules/ui/field_help.js b/modules/ui/field_help.js index b81aa686b..e1b6ab5ab 100644 --- a/modules/ui/field_help.js +++ b/modules/ui/field_help.js @@ -149,7 +149,6 @@ export function uiFieldHelp(context, fieldName) { button.enter() .append('button') .attr('class', 'field-help-button') - .attr('tabindex', -1) .call(svgIcon('#iD-icon-help')) .merge(button) .on('click', function () { diff --git a/modules/ui/fields/check.js b/modules/ui/fields/check.js index d44e8ecba..f33beca99 100644 --- a/modules/ui/fields/check.js +++ b/modules/ui/fields/check.js @@ -113,7 +113,7 @@ export function uiFieldCheck(field, context) { if (field.type === 'onewayCheck') { enter - .append('a') + .append('button') .attr('class', 'reverser' + (reverserHidden() ? ' hide' : '')) .attr('href', '#') .append('span') @@ -163,7 +163,7 @@ export function uiFieldCheck(field, context) { } return graph; }, - t('operations.reverse.annotation') + t('operations.reverse.annotation.line', { n: 1 }) ); // must manually revalidate since no 'change' event was called diff --git a/modules/ui/fields/input.js b/modules/ui/fields/input.js index fe2494e3d..fdcaa6d19 100644 --- a/modules/ui/fields/input.js +++ b/modules/ui/fields/input.js @@ -80,7 +80,6 @@ export function uiFieldText(field, context) { buttons.enter() .append('button') - .attr('tabindex', -1) .attr('class', function(d) { var which = (d === 1 ? 'increment' : 'decrement'); return 'form-field-button ' + which; @@ -106,7 +105,6 @@ export function uiFieldText(field, context) { outlinkButton.enter() .append('button') - .attr('tabindex', -1) .call(svgIcon('#iD-icon-out-link')) .attr('class', 'form-field-button foreign-id-permalink') .attr('title', function() { diff --git a/modules/ui/fields/localized.js b/modules/ui/fields/localized.js index d390cc1b4..2b112381c 100644 --- a/modules/ui/fields/localized.js +++ b/modules/ui/fields/localized.js @@ -219,7 +219,6 @@ export function uiFieldLocalized(field, context) { translateButton = translateButton.enter() .append('button') .attr('class', 'localized-add form-field-button') - .attr('tabindex', -1) .call(svgIcon('#iD-icon-plus')) .merge(translateButton); diff --git a/modules/ui/fields/wikidata.js b/modules/ui/fields/wikidata.js index 46b7dca66..6e7ae8c59 100644 --- a/modules/ui/fields/wikidata.js +++ b/modules/ui/fields/wikidata.js @@ -94,7 +94,6 @@ export function uiFieldWikidata(field, context) { .append('button') .attr('class', 'form-field-button wiki-link') .attr('title', t('icons.view_on', { domain: 'wikidata.org', html: false })) - .attr('tabindex', -1) .call(svgIcon('#iD-icon-out-link')) .on('click', function() { d3_event.preventDefault(); @@ -131,7 +130,6 @@ export function uiFieldWikidata(field, context) { .append('button') .attr('class', 'form-field-button') .attr('title', t('icons.copy', { html: false })) - .attr('tabindex', -1) .call(svgIcon('#iD-operation-copy')) .on('click', function() { d3_event.preventDefault(); diff --git a/modules/ui/fields/wikipedia.js b/modules/ui/fields/wikipedia.js index 8142bf6f5..dd6ce17a8 100644 --- a/modules/ui/fields/wikipedia.js +++ b/modules/ui/fields/wikipedia.js @@ -128,7 +128,6 @@ export function uiFieldWikipedia(field, context) { link = link.enter() .append('button') .attr('class', 'form-field-button wiki-link') - .attr('tabindex', -1) .attr('title', t('icons.view_on', { domain: 'wikipedia.org', html: false })) .call(svgIcon('#iD-icon-out-link')) .merge(link); diff --git a/modules/ui/full_screen.js b/modules/ui/full_screen.js index 2589fc1f3..02ecba26b 100644 --- a/modules/ui/full_screen.js +++ b/modules/ui/full_screen.js @@ -65,7 +65,6 @@ export function uiFullScreen(context) { // button = selection.append('button') // .attr('title', t('full_screen', { html: false })) - // .attr('tabindex', -1) // .on('click', fullScreen) // .call(tooltip); diff --git a/modules/ui/improveOSM_comments.js b/modules/ui/improveOSM_comments.js index 1714942f0..888ce7541 100644 --- a/modules/ui/improveOSM_comments.js +++ b/modules/ui/improveOSM_comments.js @@ -52,7 +52,6 @@ export function uiImproveOsmComments() { .append('a') .attr('class', 'comment-author-link') .attr('href', osm.userURL(d.username)) - .attr('tabindex', -1) .attr('target', '_blank'); } selection diff --git a/modules/ui/init.js b/modules/ui/init.js index 1cfb03b59..123ddbb19 100644 --- a/modules/ui/init.js +++ b/modules/ui/init.js @@ -269,15 +269,30 @@ export function uiInit(context) { .append('ul') .attr('class', 'map-footer-list'); - if (!context.embed()) { + aboutList + .append('li') + .attr('class', 'user-list') + .call(uiContributors(context)); + + var apiConnections = context.apiConnections(); + if (apiConnections && apiConnections.length > 1) { aboutList - .call(uiAccount(context)); + .append('li') + .attr('class', 'source-switch') + .call(uiSourceSwitch(context) + .keys(apiConnections) + ); } aboutList .append('li') - .attr('class', 'version') - .call(uiVersion(context)); + .attr('class', 'issues-info') + .call(uiIssuesInfo(context)); + + aboutList + .append('li') + .attr('class', 'feature-warning') + .call(uiFeatureInfo(context)); var issueLinks = aboutList .append('li'); @@ -298,33 +313,14 @@ export function uiInit(context) { aboutList .append('li') - .attr('class', 'feature-warning') - .attr('tabindex', -1) - .call(uiFeatureInfo(context)); + .attr('class', 'version') + .call(uiVersion(context)); - aboutList - .append('li') - .attr('class', 'issues-info') - .attr('tabindex', -1) - .call(uiIssuesInfo(context)); - - var apiConnections = context.apiConnections(); - if (apiConnections && apiConnections.length > 1) { + if (!context.embed()) { aboutList - .append('li') - .attr('class', 'source-switch') - .attr('tabindex', -1) - .call(uiSourceSwitch(context) - .keys(apiConnections) - ); + .call(uiAccount(context)); } - aboutList - .append('li') - .attr('class', 'user-list') - .attr('tabindex', -1) - .call(uiContributors(context)); - // Setup map dimensions and move map to initial center/zoom. // This should happen after .main-content and toolbars exist. diff --git a/modules/ui/intro/building.js b/modules/ui/intro/building.js index 989c70ee1..1fc26ce23 100644 --- a/modules/ui/intro/building.js +++ b/modules/ui/intro/building.js @@ -391,7 +391,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.orthogonalize.annotation.feature.single')) { + if (context.history().undoAnnotation() === t('operations.orthogonalize.annotation.feature', { n: 1 })) { continueTo(doneSquare); } else { continueTo(retryClickSquare); @@ -721,7 +721,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.single')) { + if (context.history().undoAnnotation() === t('operations.circularize.annotation.feature', { n: 1 })) { continueTo(play); } else { continueTo(retryClickCircle); diff --git a/modules/ui/issues_info.js b/modules/ui/issues_info.js index 4d1d0619c..ed3c680ff 100644 --- a/modules/ui/issues_info.js +++ b/modules/ui/issues_info.js @@ -56,7 +56,6 @@ export function uiIssuesInfo(context) { return 'chip ' + d.id + '-count'; }) .attr('href', '#') - .attr('tabindex', -1) .each(function(d) { var chipSelection = d3_select(this); diff --git a/modules/ui/note_comments.js b/modules/ui/note_comments.js index 08e0f32e6..970bbfcc6 100644 --- a/modules/ui/note_comments.js +++ b/modules/ui/note_comments.js @@ -51,7 +51,6 @@ export function uiNoteComments() { .append('a') .attr('class', 'comment-author-link') .attr('href', osm.userURL(d.user)) - .attr('tabindex', -1) .attr('target', '_blank'); } selection diff --git a/modules/ui/note_editor.js b/modules/ui/note_editor.js index 9e1f5a63f..3315b0aac 100644 --- a/modules/ui/note_editor.js +++ b/modules/ui/note_editor.js @@ -177,7 +177,8 @@ export function uiNoteEditor(context) { // fast submit if user presses cmd+enter function keydown() { - if (!(d3_event.keyCode === 13 && d3_event.metaKey)) return; + if (!(d3_event.keyCode === 13 && // ↩ Return + d3_event.metaKey)) return; var osm = services.osm; if (!osm) return; @@ -304,7 +305,6 @@ export function uiNoteEditor(context) { .attr('class', 'user-info') .html(user.display_name) .attr('href', osm.userURL(user.display_name)) - .attr('tabindex', -1) .attr('target', '_blank'); prose diff --git a/modules/ui/panels/history.js b/modules/ui/panels/history.js index a3c22a404..0ac7a075c 100644 --- a/modules/ui/panels/history.js +++ b/modules/ui/panels/history.js @@ -40,7 +40,6 @@ export function uiPanelHistory(context) { .attr('class', 'user-osm-link') .attr('href', osm.userURL(userName)) .attr('target', '_blank') - .attr('tabindex', -1) .html('OSM'); } @@ -77,7 +76,6 @@ export function uiPanelHistory(context) { .attr('class', 'changeset-osm-link') .attr('href', osm.changesetURL(changeset)) .attr('target', '_blank') - .attr('tabindex', -1) .html('OSM'); } @@ -86,7 +84,6 @@ export function uiPanelHistory(context) { .attr('class', 'changeset-osmcha-link') .attr('href', 'https://osmcha.org/changesets/' + changeset) .attr('target', '_blank') - .attr('tabindex', -1) .html('OSMCha'); links @@ -94,7 +91,6 @@ export function uiPanelHistory(context) { .attr('class', 'changeset-achavi-link') .attr('href', 'https://overpass-api.de/achavi/?changeset=' + changeset) .attr('target', '_blank') - .attr('tabindex', -1) .html('Achavi'); } @@ -169,7 +165,6 @@ export function uiPanelHistory(context) { .append('a') .attr('class', 'view-history-on-osm') .attr('target', '_blank') - .attr('tabindex', -1) .attr('href', osm.noteURL(note)) .call(svgIcon('#iD-icon-out-link', 'inline')) .append('span') @@ -196,7 +191,6 @@ export function uiPanelHistory(context) { .attr('class', 'view-history-on-osm') .attr('href', osm.historyURL(entity)) .attr('target', '_blank') - .attr('tabindex', -1) .attr('title', t('info_panels.history.link_text', { html: false })) .html('OSM'); } diff --git a/modules/ui/panes/help.js b/modules/ui/panes/help.js index 4fd2f6356..3aff48741 100644 --- a/modules/ui/panes/help.js +++ b/modules/ui/panes/help.js @@ -1,4 +1,6 @@ +import { event as d3_event } from 'd3-selection'; + import marked from 'marked'; import { svgIcon } from '../../svg/icon'; import { uiIntro } from '../intro/intro'; @@ -263,6 +265,7 @@ export function uiPaneHelp(context) { helpPane.renderContent = function(content) { function clickHelp(d, i) { + if (d3_event) d3_event.preventDefault(); var rtl = (localizer.textDirection() === 'rtl'); content.property('scrollTop', 0); helpPane.selection().select('.pane-heading h2').html(d.title); @@ -286,8 +289,10 @@ export function uiPaneHelp(context) { if (i < docs.length - 1) { var nextLink = selection .append('a') + .attr('href', '#') .attr('class', 'next') .on('click', function() { + d3_event.preventDefault(); clickHelp(docs[i + 1], i + 1); }); @@ -303,8 +308,10 @@ export function uiPaneHelp(context) { if (i > 0) { var prevLink = selection .append('a') + .attr('href', '#') .attr('class', 'previous') .on('click', function() { + d3_event.preventDefault(); clickHelp(docs[i - 1], i - 1); }); @@ -318,6 +325,7 @@ export function uiPaneHelp(context) { function clickWalkthrough() { + d3_event.preventDefault(); if (context.inIntro()) return; context.container().call(uiIntro(context)); context.ui().togglePanes(); @@ -325,6 +333,7 @@ export function uiPaneHelp(context) { function clickShortcuts() { + d3_event.preventDefault(); context.container().call(uiShortcuts(context), true); } @@ -337,6 +346,7 @@ export function uiPaneHelp(context) { .enter() .append('li') .append('a') + .attr('href', '#') .html(function(d) { return d.title; }) .on('click', clickHelp); @@ -349,6 +359,7 @@ export function uiPaneHelp(context) { .placement('top') ) .append('a') + .attr('href', '#') .on('click', clickShortcuts); shortcuts @@ -359,6 +370,7 @@ export function uiPaneHelp(context) { .append('li') .attr('class', 'walkthrough') .append('a') + .attr('href', '#') .on('click', clickWalkthrough); walkthrough diff --git a/modules/ui/popover.js b/modules/ui/popover.js index bf5915570..42ee586ee 100644 --- a/modules/ui/popover.js +++ b/modules/ui/popover.js @@ -189,6 +189,7 @@ export function uiPopover(klass) { .on('click.popover', toggle); popoverSelection + // This attribute lets the popover take focus .attr('tabindex', 0) .on('blur.popover', function() { anchor.each(function() { diff --git a/modules/ui/preset_list.js b/modules/ui/preset_list.js index 4cee38deb..50a8592ab 100644 --- a/modules/ui/preset_list.js +++ b/modules/ui/preset_list.js @@ -85,7 +85,8 @@ export function uiPresetList(context) { function keypress() { // enter var value = search.property('value'); - if (d3_event.keyCode === 13 && value.length) { + if (d3_event.keyCode === 13 && // ↩ Return + value.length) { list.selectAll('.preset-list-item:first-child') .each(function(d) { d.choose.call(this); }); } @@ -132,6 +133,12 @@ export function uiPresetList(context) { if (_autofocus) { search.node().focus(); + + // Safari 14 doesn't always like to focus immediately, + // so try again on the next pass + setTimeout(function() { + search.node().focus(); + }, 0); } var listWrap = selection diff --git a/modules/ui/sections/background_list.js b/modules/ui/sections/background_list.js index f9cc79ae3..b5b1b657c 100644 --- a/modules/ui/sections/background_list.js +++ b/modules/ui/sections/background_list.js @@ -164,10 +164,18 @@ export function uiSectionBackgroundList(context) { function drawListItems(layerList, type, change, filter) { var sources = context.background() .sources(context.map().extent(), context.map().zoom(), true) - .filter(filter); + .filter(filter) + .sort(function(a, b) { + return a.best() && !b.best() ? -1 + : b.best() && !a.best() ? 1 + : d3_descending(a.area(), b.area()) || d3_ascending(a.name(), b.name()) || 0; + }); var layerLinks = layerList.selectAll('li') - .data(sources, function(d) { return d.name(); }); + // We have to be a bit inefficient about reordering the list since + // arrow key navigation of radio values likes to work in the order + // they were added, not the display document order. + .data(sources, function(d, i) { return d.id + '---' + i; }); layerLinks.exit() .remove(); @@ -183,7 +191,10 @@ export function uiSectionBackgroundList(context) { label .append('input') .attr('type', type) - .attr('name', 'layers') + .attr('name', 'background-layer') + .attr('value', function(d) { + return d.id; + }) .on('change', change); label @@ -210,19 +221,8 @@ export function uiSectionBackgroundList(context) { .append('span') .html('★'); - - layerList.selectAll('li') - .sort(sortSources); - layerList .call(updateLayerSelections); - - - function sortSources(a, b) { - return a.best() && !b.best() ? -1 - : b.best() && !a.best() ? 1 - : d3_descending(a.area(), b.area()) || d3_ascending(a.name(), b.name()) || 0; - } } function updateLayerSelections(selection) { @@ -244,12 +244,10 @@ export function uiSectionBackgroundList(context) { return editCustom(); } - d3_event.preventDefault(); var previousBackground = context.background().baseLayerSource(); prefs('background-last-used-toggle', previousBackground.id); prefs('background-last-used', d.id); context.background().baseLayerSource(d); - document.activeElement.blur(); } diff --git a/modules/ui/sections/changes.js b/modules/ui/sections/changes.js index 9bb317e4f..b07daecc1 100644 --- a/modules/ui/sections/changes.js +++ b/modules/ui/sections/changes.js @@ -59,18 +59,24 @@ export function uiSectionChanges(context) { .append('li') .attr('class', 'change-item'); - itemsEnter + var buttons = itemsEnter + .append('button') + .on('mouseover', mouseover) + .on('mouseout', mouseout) + .on('click', click); + + buttons .each(function(d) { d3_select(this) .call(svgIcon('#iD-icon-' + d.entity.geometry(d.graph), 'pre-text ' + d.changeType)); }); - itemsEnter + buttons .append('span') .attr('class', 'change-type') .html(function(d) { return t('commit.' + d.changeType) + ' '; }); - itemsEnter + buttons .append('strong') .attr('class', 'entity-type') .html(function(d) { @@ -78,7 +84,7 @@ export function uiSectionChanges(context) { return (matched && matched.name()) || utilDisplayType(d.entity.id); }); - itemsEnter + buttons .append('span') .attr('class', 'entity-name') .html(function(d) { @@ -90,19 +96,9 @@ export function uiSectionChanges(context) { return string += ' ' + name; }); - itemsEnter - .style('opacity', 0) - .transition() - .style('opacity', 1); - items = itemsEnter .merge(items); - items - .on('mouseover', mouseover) - .on('mouseout', mouseout) - .on('click', click); - // Download changeset link var changeset = new osmChangeset().update({ id: undefined }); diff --git a/modules/ui/sections/data_layers.js b/modules/ui/sections/data_layers.js index ac7185a16..76c38f8e7 100644 --- a/modules/ui/sections/data_layers.js +++ b/modules/ui/sections/data_layers.js @@ -229,7 +229,6 @@ export function uiSectionDataLayers(context) { .attr('class', 'vectortile-footer') .append('a') .attr('target', '_blank') - .attr('tabindex', -1) .call(svgIcon('#iD-icon-out-link', 'inline')) .attr('href', 'https://github.com/osmus/detroit-mapping-challenge') .append('span') @@ -330,6 +329,7 @@ export function uiSectionDataLayers(context) { liEnter .append('button') + .attr('class', 'open-data-options') .call(uiTooltip() .title(t('settings.custom_data.tooltip')) .placement((localizer.textDirection() === 'rtl') ? 'right' : 'left') @@ -339,16 +339,19 @@ export function uiSectionDataLayers(context) { liEnter .append('button') + .attr('class', 'zoom-to-data') .call(uiTooltip() .title(t('map_data.layers.custom.zoom')) .placement((localizer.textDirection() === 'rtl') ? 'right' : 'left') ) .on('click', function() { + if (d3_select(this).classed('disabled')) return; + d3_event.preventDefault(); d3_event.stopPropagation(); dataLayer.fitZoom(); }) - .call(svgIcon('#iD-icon-framed-dot')); + .call(svgIcon('#iD-icon-framed-dot', 'monochrome')); // Update ul = ul @@ -361,6 +364,9 @@ export function uiSectionDataLayers(context) { .selectAll('input') .property('disabled', !hasData) .property('checked', showsData); + + ul.selectAll('button.zoom-to-data') + .classed('disabled', !hasData); } function editCustom() { diff --git a/modules/ui/sections/entity_issues.js b/modules/ui/sections/entity_issues.js index dbfd0e4a6..835de9978 100644 --- a/modules/ui/sections/entity_issues.js +++ b/modules/ui/sections/entity_issues.js @@ -79,7 +79,11 @@ export function uiSectionEntityIssues(context) { var labelsEnter = itemsEnter .append('div') - .attr('class', 'issue-label') + .attr('class', 'issue-label'); + + var textEnter = labelsEnter + .append('button') + .attr('class', 'issue-text') .on('click', function(d) { makeActiveIssue(d.id); // expand only the clicked item @@ -91,17 +95,11 @@ export function uiSectionEntityIssues(context) { } }); - var textEnter = labelsEnter - .append('span') - .attr('class', 'issue-text'); - textEnter - .append('span') - .attr('class', 'issue-icon') .each(function(d) { var iconName = '#iD-icon-' + (d.severity === 'warning' ? 'alert' : 'error'); d3_select(this) - .call(svgIcon(iconName)); + .call(svgIcon(iconName, 'issue-icon')); }); textEnter @@ -113,7 +111,6 @@ export function uiSectionEntityIssues(context) { .append('button') .attr('class', 'issue-info-button') .attr('title', t('icons.information', { html: false })) - .attr('tabindex', -1) .call(svgIcon('#iD-icon-inspect')); infoButton @@ -189,10 +186,13 @@ export function uiSectionEntityIssues(context) { var fixesEnter = fixes.enter() .append('li') - .attr('class', 'issue-fix-item') + .attr('class', 'issue-fix-item'); + + var buttons = fixesEnter + .append('button') .on('click', function(d) { // not all fixes are actionable - if (!d3_select(this).classed('actionable') || !d.onClick) return; + if (d3_select(this).attr('disabled') || !d.onClick) return; // Don't run another fix for this issue within a second of running one // (Necessary for "Select a feature type" fix. Most fixes should only ever run once) @@ -221,26 +221,28 @@ export function uiSectionEntityIssues(context) { utilHighlightEntities(d.entityIds, false, context); }); - fixesEnter - .append('span') - .attr('class', 'fix-icon') + buttons .each(function(d) { var iconName = d.icon || 'iD-icon-wrench'; if (iconName.startsWith('maki')) { iconName += '-15'; } - d3_select(this).call(svgIcon('#' + iconName)); + d3_select(this).call(svgIcon('#' + iconName, 'fix-icon')); }); - fixesEnter + buttons .append('span') .attr('class', 'fix-message') .html(function(d) { return d.title; }); fixesEnter.merge(fixes) + .selectAll('button') .classed('actionable', function(d) { return d.onClick; }) + .attr('disabled', function(d) { + return d.onClick ? null : 'true'; + }) .attr('title', function(d) { if (d.disabledReason) { return d.disabledReason; diff --git a/modules/ui/sections/map_features.js b/modules/ui/sections/map_features.js index 7dd77058f..78e5f8e31 100644 --- a/modules/ui/sections/map_features.js +++ b/modules/ui/sections/map_features.js @@ -32,18 +32,18 @@ export function uiSectionMapFeatures(context) { .append('a') .attr('class', 'feature-list-link') .attr('href', '#') - .html(t('issues.enable_all')) + .html(t('issues.disable_all')) .on('click', function() { - context.features().enableAll(); + context.features().disableAll(); }); footer .append('a') .attr('class', 'feature-list-link') .attr('href', '#') - .html(t('issues.disable_all')) + .html(t('issues.enable_all')) .on('click', function() { - context.features().disableAll(); + context.features().enableAll(); }); // Update diff --git a/modules/ui/sections/preset_fields.js b/modules/ui/sections/preset_fields.js index a8061e22f..06dc9245e 100644 --- a/modules/ui/sections/preset_fields.js +++ b/modules/ui/sections/preset_fields.js @@ -127,7 +127,9 @@ export function uiSectionPresetFields(context) { selection.selectAll('.wrap-form-field input') .on('keydown', function() { // if user presses enter, and combobox is not active, accept edits.. - if (d3_event.keyCode === 13 && context.container().select('.combobox').empty()) { + if (d3_event.keyCode === 13 && // ↩ Return + context.container().select('.combobox').empty()) { + context.enter(modeBrowse(context)); } }); diff --git a/modules/ui/sections/raw_member_editor.js b/modules/ui/sections/raw_member_editor.js index 7ae500faf..d40735007 100644 --- a/modules/ui/sections/raw_member_editor.js +++ b/modules/ui/sections/raw_member_editor.js @@ -199,7 +199,6 @@ export function uiSectionRawMemberEditor(context) { label .append('button') - .attr('tabindex', -1) .attr('title', t('icons.remove', { html: false })) .attr('class', 'remove member-delete') .call(svgIcon('#iD-operation-delete')); @@ -230,7 +229,6 @@ export function uiSectionRawMemberEditor(context) { .append('button') .attr('class', 'member-download') .attr('title', t('icons.download', { html: false })) - .attr('tabindex', -1) .call(svgIcon('#iD-icon-load')) .on('click', downloadMember); } diff --git a/modules/ui/sections/raw_membership_editor.js b/modules/ui/sections/raw_membership_editor.js index b2cc26c59..9060d722f 100644 --- a/modules/ui/sections/raw_membership_editor.js +++ b/modules/ui/sections/raw_membership_editor.js @@ -274,7 +274,6 @@ export function uiSectionRawMembershipEditor(context) { labelEnter .append('button') - .attr('tabindex', -1) .attr('class', 'remove member-delete') .call(svgIcon('#iD-operation-delete')) .on('click', deleteMembership); @@ -333,7 +332,6 @@ export function uiSectionRawMembershipEditor(context) { newLabelEnter .append('button') - .attr('tabindex', -1) .attr('class', 'remove member-delete') .call(svgIcon('#iD-operation-delete')) .on('click', function() { diff --git a/modules/ui/sections/raw_tag_editor.js b/modules/ui/sections/raw_tag_editor.js index 956f7c890..27438b043 100644 --- a/modules/ui/sections/raw_tag_editor.js +++ b/modules/ui/sections/raw_tag_editor.js @@ -25,8 +25,8 @@ export function uiSectionRawTagEditor(id, context) { var taginfo = services.taginfo; var dispatch = d3_dispatch('change'); var availableViews = [ - { id: 'text', icon: '#fas-i-cursor' }, - { id: 'list', icon: '#fas-th-list' } + { id: 'list', icon: '#fas-th-list' }, + { id: 'text', icon: '#fas-i-cursor' } ]; var _tagView = (prefs('raw-tag-editor-view') || 'list'); // 'list, 'text' @@ -204,7 +204,6 @@ export function uiSectionRawTagEditor(id, context) { innerWrap .append('button') - .attr('tabindex', -1) .attr('class', 'form-field-button remove') .attr('title', t('icons.remove', { html: false })) .call(svgIcon('#iD-operation-delete')); diff --git a/modules/ui/sections/validation_rules.js b/modules/ui/sections/validation_rules.js index 25b0230d1..96f2ec9f6 100644 --- a/modules/ui/sections/validation_rules.js +++ b/modules/ui/sections/validation_rules.js @@ -46,18 +46,18 @@ export function uiSectionValidationRules(context) { .append('a') .attr('class', 'issue-rules-link') .attr('href', '#') - .html(t('issues.enable_all')) + .html(t('issues.disable_all')) .on('click', function() { - context.validator().disableRules([]); + context.validator().disableRules(_ruleKeys); }); ruleLinks .append('a') .attr('class', 'issue-rules-link') .attr('href', '#') - .html(t('issues.disable_all')) + .html(t('issues.enable_all')) .on('click', function() { - context.validator().disableRules(_ruleKeys); + context.validator().disableRules([]); }); @@ -144,7 +144,7 @@ export function uiSectionValidationRules(context) { this.select(); }) .on('keyup', function () { - if (d3_event.keyCode === 13) { // enter + if (d3_event.keyCode === 13) { // ↩ Return this.blur(); this.select(); } diff --git a/modules/ui/shortcuts.js b/modules/ui/shortcuts.js index 5e7d0b33c..13cc3d490 100644 --- a/modules/ui/shortcuts.js +++ b/modules/ui/shortcuts.js @@ -1,4 +1,4 @@ -import { select as d3_select } from 'd3-selection'; +import { event as d3_event, select as d3_select } from 'd3-selection'; import { fileFetcher } from '../core/file_fetcher'; import { t } from '../core/localizer'; @@ -74,9 +74,11 @@ export function uiShortcuts(context) { var tabsEnter = tabs .enter() - .append('div') + .append('a') .attr('class', 'tab') + .attr('href', '#') .on('click', function (d, i) { + d3_event.preventDefault(); _activeTab = i; render(selection, dataShortcuts); }); diff --git a/modules/ui/success.js b/modules/ui/success.js index e2c5a62da..f07671361 100644 --- a/modules/ui/success.js +++ b/modules/ui/success.js @@ -100,7 +100,6 @@ export function uiSuccess(context) { .append('a') .attr('class', 'link-out') .attr('target', '_blank') - .attr('tabindex', -1) .attr('href', t('success.help_link_url')) .call(svgIcon('#iD-icon-out-link', 'inline')) .append('span') @@ -221,7 +220,6 @@ export function uiSuccess(context) { .append('a') .attr('class', 'link-out') .attr('target', '_blank') - .attr('tabindex', -1) .call(svgIcon('#iD-icon-out-link', 'inline')) .attr('href', 'https://github.com/osmlab/osm-community-index/issues') .append('span') diff --git a/modules/ui/tag_reference.js b/modules/ui/tag_reference.js index a81761291..41e7dc025 100644 --- a/modules/ui/tag_reference.js +++ b/modules/ui/tag_reference.js @@ -68,7 +68,6 @@ export function uiTagReference(what) { .append('a') .attr('class', 'tag-reference-edit') .attr('target', '_blank') - .attr('tabindex', -1) .attr('title', t('inspector.edit_reference', { html: false })) .attr('href', docs.editURL) .call(svgIcon('#iD-icon-edit', 'inline')); @@ -78,7 +77,6 @@ export function uiTagReference(what) { .append('a') .attr('class', 'tag-reference-link') .attr('target', '_blank') - .attr('tabindex', -1) .attr('href', docs.wiki.url) .call(svgIcon('#iD-icon-out-link', 'inline')) .append('span') @@ -91,7 +89,6 @@ export function uiTagReference(what) { .append('a') .attr('class', 'tag-reference-comment-link') .attr('target', '_blank') - .attr('tabindex', -1) .call(svgIcon('#iD-icon-out-link', 'inline')) .attr('href', t('commit.about_changeset_comments_link')) .append('span') @@ -152,9 +149,8 @@ export function uiTagReference(what) { _button = _button.enter() .append('button') - .attr('class', 'tag-reference-button ' + klass) + .attr('class', 'tag-reference-button ' + (klass || '')) .attr('title', t('icons.information', { html: false })) - .attr('tabindex', -1) .call(svgIcon('#iD-icon-' + (iconName || 'inspect'))) .merge(_button); diff --git a/modules/ui/tools/notes.js b/modules/ui/tools/notes.js index 49c750ab3..02c48a5b0 100644 --- a/modules/ui/tools/notes.js +++ b/modules/ui/tools/notes.js @@ -73,7 +73,6 @@ export function uiToolNotes(context) { // enter var buttonsEnter = buttons.enter() .append('button') - .attr('tabindex', -1) .attr('class', function(d) { return d.id + ' add-button bar-button'; }) .on('click.notes', function(d) { if (!enabled(d)) return; diff --git a/modules/ui/zoom.js b/modules/ui/zoom.js index bf3e0fb56..856149963 100644 --- a/modules/ui/zoom.js +++ b/modules/ui/zoom.js @@ -7,6 +7,7 @@ import { t, localizer } from '../core/localizer'; import { svgIcon } from '../svg/icon'; import { uiCmd } from './cmd'; import { uiTooltip } from './tooltip'; +import { utilKeybinding } from '../util/keybinding'; export function uiZoom(context) { @@ -34,21 +35,25 @@ export function uiZoom(context) { }]; function zoomIn() { + if (d3_event.shiftKey) return; d3_event.preventDefault(); context.map().zoomIn(); } function zoomOut() { + if (d3_event.shiftKey) return; d3_event.preventDefault(); context.map().zoomOut(); } function zoomInFurther() { + if (d3_event.shiftKey) return; d3_event.preventDefault(); context.map().zoomInFurther(); } function zoomOutFurther() { + if (d3_event.shiftKey) return; d3_event.preventDefault(); context.map().zoomOutFurther(); } @@ -95,12 +100,12 @@ export function uiZoom(context) { .call(svgIcon('#' + d.icon, 'light')); }); - ['plus', 'ffplus', '=', 'ffequals'].forEach(function(key) { + utilKeybinding.plusKeys.forEach(function(key) { context.keybinding().on([key], zoomIn); context.keybinding().on([uiCmd('⌥' + key)], zoomInFurther); }); - ['_', '-', 'ffminus', 'dash'].forEach(function(key) { + utilKeybinding.minusKeys.forEach(function(key) { context.keybinding().on([key], zoomOut); context.keybinding().on([uiCmd('⌥' + key)], zoomOutFurther); }); diff --git a/modules/util/keybinding.js b/modules/util/keybinding.js index 02a55e89a..34a112a33 100644 --- a/modules/util/keybinding.js +++ b/modules/util/keybinding.js @@ -28,17 +28,22 @@ export function utilKeybinding(namespace) { if (matches(binding, true)) { binding.callback(); didMatch = true; + + // match a max of one binding per event + break; } } - // then unshifted keybindings if (didMatch) return; + + // then unshifted keybindings for (i = 0; i < bindings.length; i++) { binding = bindings[i]; if (binding.event.modifiers.shiftKey) continue; // shift if (!!binding.capture !== isCapturing) continue; if (matches(binding, false)) { binding.callback(); + break; } } @@ -214,13 +219,16 @@ utilKeybinding.modifierProperties = { 91: 'metaKey' }; +utilKeybinding.plusKeys = ['plus', 'ffplus', '=', 'ffequals', '≠', '±']; +utilKeybinding.minusKeys = ['_', '-', 'ffminus', 'dash', '–', '—']; + utilKeybinding.keys = { // Backspace key, on Mac: ⌫ (Backspace) '⌫': 'Backspace', backspace: 'Backspace', // Tab Key, on Mac: ⇥ (Tab), on Windows ⇥⇥ '⇥': 'Tab', '⇆': 'Tab', tab: 'Tab', // Return key, ↩ - '↩': 'Enter', 'return': 'Enter', enter: 'Enter', '⌅': 'Enter', + '↩': 'Enter', '↵': 'Enter', '⏎': 'Enter', 'return': 'Enter', enter: 'Enter', '⌅': 'Enter', // Pause/Break key 'pause': 'Pause', 'pause-break': 'Pause', // Caps Lock key, ⇪ @@ -321,7 +329,7 @@ utilKeybinding.keyCodes = { // Tab Key, on Mac: ⇥ (Tab), on Windows ⇥⇥ '⇥': 9, '⇆': 9, tab: 9, // Return key, ↩ - '↩': 13, 'return': 13, enter: 13, '⌅': 13, + '↩': 13, '↵': 13, '⏎': 13, 'return': 13, enter: 13, '⌅': 13, // Pause/Break key 'pause': 19, 'pause-break': 19, // Caps Lock key, ⇪ diff --git a/modules/validations/impossible_oneway.js b/modules/validations/impossible_oneway.js index 289c83ee9..32bf927de 100644 --- a/modules/validations/impossible_oneway.js +++ b/modules/validations/impossible_oneway.js @@ -180,7 +180,7 @@ export function validationImpossibleOneway() { entityIds: [way.id], onClick: function(context) { var id = this.issue.entityIds[0]; - context.perform(actionReverse(id), t('operations.reverse.annotation')); + context.perform(actionReverse(id), t('operations.reverse.annotation.line', { n: 1 })); } })); } diff --git a/modules/validations/mismatched_geometry.js b/modules/validations/mismatched_geometry.js index 91d52db4c..ce6367a02 100644 --- a/modules/validations/mismatched_geometry.js +++ b/modules/validations/mismatched_geometry.js @@ -208,7 +208,7 @@ export function validationMismatchedGeometry() { var action = actionExtract(entityId); context.perform( action, - t('operations.extract.annotation.single') + t('operations.extract.annotation', { n: 1 }) ); // re-enter mode to trigger updates context.enter(modeSelect(context, [action.getExtractedNodeID()])); diff --git a/modules/validations/unsquare_way.js b/modules/validations/unsquare_way.js index d6babc2ff..40b045c8f 100644 --- a/modules/validations/unsquare_way.js +++ b/modules/validations/unsquare_way.js @@ -67,7 +67,7 @@ export function validationUnsquareWay(context) { // use same degree threshold as for detection var autoAction = actionOrthogonalize(entity.id, context.projection, undefined, degreeThreshold); autoAction.transitionable = false; // when autofixing, do it instantly - autoArgs = [autoAction, t('operations.orthogonalize.annotation.feature.single')]; + autoArgs = [autoAction, t('operations.orthogonalize.annotation.feature', { n: 1 })]; } return [new validationIssue({ @@ -92,7 +92,7 @@ export function validationUnsquareWay(context) { // use same degree threshold as for detection context.perform( actionOrthogonalize(entityId, context.projection, undefined, degreeThreshold), - t('operations.orthogonalize.annotation.feature.single') + t('operations.orthogonalize.annotation.feature', { n: 1 }) ); // run after the squaring transition (currently 150ms) window.setTimeout(function() { completionHandler(); }, 175); diff --git a/scripts/build_data.js b/scripts/build_data.js index e272b92c7..24ef65988 100644 --- a/scripts/build_data.js +++ b/scripts/build_data.js @@ -683,8 +683,7 @@ function validateCategoryPresets(categories, presets) { function validatePresetFields(presets, fields) { const betweenBracketsRegex = /([^{]*?)(?=\})/; - const maxFieldsBeforeError = 12; - const maxFieldsBeforeWarning = 8; + const maxFieldsBeforeError = 10; for (let presetID in presets) { let preset = presets[presetID]; @@ -711,22 +710,37 @@ function validatePresetFields(presets, fields) { if (!preset[fieldsKey]) continue; // no fields are referenced, okay for (let fieldIndex in preset[fieldsKey]) { - let field = preset[fieldsKey][fieldIndex]; - if (fields[field] !== undefined) continue; // field found, okay + let fieldID = preset[fieldsKey][fieldIndex]; + let field = fields[fieldID]; + if (field) { + if (field.geometry) { + let sharedGeometry = field.geometry.filter(value => preset.geometry.includes(value)); + if (!sharedGeometry.length) { + console.error('The preset "' + presetID + '" (' + preset.name + ') will never display the field "' + fieldID + '" since they don\'t share geometry types.'); + console.log(''); + process.exit(1); + } + } - let regexResult = betweenBracketsRegex.exec(field); - if (regexResult) { - let foreignPresetID = regexResult[0]; - if (presets[foreignPresetID] === undefined) { - console.error('Unknown preset "' + foreignPresetID + '" referenced in "' + fieldsKey + '" array of preset "' + presetID + '" (' + preset.name + ')'); + } else { + // no field found with this ID... + + let regexResult = betweenBracketsRegex.exec(fieldID); + if (regexResult) { + let foreignPresetID = regexResult[0]; + if (presets[foreignPresetID] === undefined) { + console.error('Unknown preset "' + foreignPresetID + '" referenced in "' + fieldsKey + '" array of preset "' + presetID + '" (' + preset.name + ')'); + console.log(''); + process.exit(1); + } + } else { + console.error('Unknown preset field "' + fieldID + '" in "' + fieldsKey + '" array of preset "' + presetID + '" (' + preset.name + ')'); console.log(''); process.exit(1); } - } else { - console.error('Unknown preset field "' + field + '" in "' + fieldsKey + '" array of preset "' + presetID + '" (' + preset.name + ')'); - console.log(''); - process.exit(1); } + + } } @@ -734,23 +748,20 @@ function validatePresetFields(presets, fields) { // since `moreFields` is available, check that `fields` doesn't get too cluttered let fieldCount = preset.fields.length; - if (fieldCount > maxFieldsBeforeWarning) { - // Fields with `prerequisiteTag` probably won't show up initially, + if (fieldCount > maxFieldsBeforeError) { + // Fields with `prerequisiteTag` or `geometry` may not always be shown, // so don't count them against the limits. - const fieldsWithoutPrerequisites = preset.fields.filter(fieldID => { - if (fields[fieldID] && fields[fieldID].prerequisiteTag) return false; + const alwaysShownFields = preset.fields.filter(fieldID => { + if (fields[fieldID] && fields[fieldID].prerequisiteTag || fields[fieldID].geometry) return false; return true; }); - fieldCount = fieldsWithoutPrerequisites.length; + fieldCount = alwaysShownFields.length; } if (fieldCount > maxFieldsBeforeError) { console.error(fieldCount + ' values in "fields" of "' + preset.name + '" (' + presetID + '). Limit: ' + maxFieldsBeforeError + '. Please move lower-priority fields to "moreFields".'); console.log(''); process.exit(1); } - else if (fieldCount > maxFieldsBeforeWarning) { - console.log('Warning: ' + fieldCount + ' values in "fields" of "' + preset.name + '" (' + presetID + '). Recommended: ' + maxFieldsBeforeWarning + ' or fewer. Consider moving lower-priority fields to "moreFields".'); - } } } }