diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 38d58fa61..7c106eb6c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -72,6 +72,21 @@ Transiflex, will automatically detect the change. Use `make` to build the translations with the local changes. `make translate` can be used to pull the latest translations from Transifex. +## Contributing Documentation + +Documentation is maintained as a series of [Markdown](http://daringfireball.net/projects/markdown/) +documents in the `data/doc/` path. The first line of each page of documentation +should be of the form + + # GPS + +This will be used for navigation and as its title in iD. Documentation is +shown in alphabetical order, so most documentation is prefixed with `02-` and +so on in order to keep it in a certain order. + +To add a new page of documentation, simply create a new Markdown file in +`data/doc` in the same format as the rest. + ## Javascript We use the [Airbnb style for Javascript](https://github.com/airbnb/javascript) with diff --git a/Makefile b/Makefile index f99342a98..8b7691e77 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ all: \ iD.js \ iD.min.js -DATA_FILES = $(shell find data -type f -name '*.json') +DATA_FILES = $(shell find data -type f -name '*.json' -o -name '*.md') data/data.js: $(DATA_FILES) node build.js @@ -18,6 +18,7 @@ data/data.js: $(DATA_FILES) js/lib/d3.v3.js \ js/lib/d3.combobox.js \ js/lib/d3.geo.tile.js \ + js/lib/d3.jsonp.js \ js/lib/d3.keybinding.js \ js/lib/d3.one.js \ js/lib/d3.size.js \ diff --git a/build.js b/build.js index 27bec4be2..fd5f5f35f 100644 --- a/build.js +++ b/build.js @@ -2,13 +2,18 @@ var fs = require('fs'), path = require('path'), glob = require('glob'), YAML = require('js-yaml'), + marked = require('marked'), _ = require('./js/lib/lodash'), jsonschema = require('jsonschema'), fieldSchema = require('./data/presets/schema/field.json'), presetSchema = require('./data/presets/schema/preset.json'); +function readtxt(f) { + return fs.readFileSync(f, 'utf8'); +} + function read(f) { - return JSON.parse(fs.readFileSync(f)); + return JSON.parse(readtxt(f)); } function r(f) { @@ -19,6 +24,10 @@ function rp(f) { return r('presets/' + f); } +function stringify(o) { + return JSON.stringify(o, null, 4); +} + function validate(file, instance, schema) { var result = jsonschema.validate(instance, schema); if (result.length) { @@ -39,59 +48,81 @@ var translations = { presets: {} }; -var fields = {}; -glob.sync(__dirname + '/data/presets/fields/*.json').forEach(function(file) { - var field = read(file), - id = path.basename(file, '.json'); +function generateDocumentation() { + var docs = []; + glob.sync(__dirname + '/data/doc/*.md').forEach(function(file) { + var text = readtxt(file), + title = text.split('\n')[0] + .replace('#', '').trim(); + docs.push({ + html: marked(text.split('\n').slice(1).join('\n')), + title: title + }); + }); + fs.writeFileSync('data/doc.json', stringify(docs)); +} - validate(file, field, fieldSchema); +function generateFields() { + var fields = {}; + glob.sync(__dirname + '/data/presets/fields/*.json').forEach(function(file) { + var field = read(file), + id = path.basename(file, '.json'); - translations.fields[id] = {label: field.label}; - if (field.strings) { - for (var i in field.strings) { - translations.fields[id][i] = field.strings[i]; + validate(file, field, fieldSchema); + + translations.fields[id] = {label: field.label}; + if (field.strings) { + for (var i in field.strings) { + translations.fields[id][i] = field.strings[i]; + } } - } - fields[id] = field; -}); -fs.writeFileSync('data/presets/fields.json', JSON.stringify(fields, null, 4)); + fields[id] = field; + }); + fs.writeFileSync('data/presets/fields.json', stringify(fields)); +} -var presets = {}; -glob.sync(__dirname + '/data/presets/presets/**/*.json').forEach(function(file) { - var preset = read(file), - id = file.match(/presets\/presets\/([^.]*)\.json/)[1]; +function generatePresets() { + var presets = {}; + glob.sync(__dirname + '/data/presets/presets/**/*.json').forEach(function(file) { + var preset = read(file), + id = file.match(/presets\/presets\/([^.]*)\.json/)[1]; - validate(file, preset, presetSchema); + validate(file, preset, presetSchema); - translations.presets[id] = { - name: preset.name, - terms: (preset.terms || []).join(',') - }; + translations.presets[id] = { + name: preset.name, + terms: (preset.terms || []).join(',') + }; - presets[id] = preset; -}); -fs.writeFileSync('data/presets/presets.json', JSON.stringify(presets, null, 4)); + presets[id] = preset; + }); + fs.writeFileSync('data/presets/presets.json', stringify(presets)); + fs.writeFileSync('data/presets.yaml', YAML.dump({en: {presets: translations}})); +} -fs.writeFileSync('data/presets.yaml', YAML.dump({en: {presets: translations}})); +generateDocumentation(); +generateFields(); +generatePresets(); -fs.writeFileSync('data/data.js', 'iD.data = ' + JSON.stringify({ +fs.writeFileSync('data/data.js', 'iD.data = ' + stringify({ deprecated: r('deprecated.json'), discarded: r('discarded.json'), keys: r('keys.json'), imagery: r('imagery.json'), + doc: r('doc.json'), presets: { presets: rp('presets.json'), defaults: rp('defaults.json'), categories: rp('categories.json'), fields: rp('fields.json') } -}, null, 4) + ';'); +}) + ';'); // Push changes from data/core.yaml into data/locales.js var core = YAML.load(fs.readFileSync('data/core.yaml', 'utf8')); var presets = YAML.load(fs.readFileSync('data/presets.yaml', 'utf8')); var intro = YAML.load(fs.readFileSync('data/intro.yaml', 'utf8')); var en = _.merge(_.merge(core, presets), intro); -var out = 'locale.en = ' + JSON.stringify(en.en, null, 4) + ';'; +var out = 'locale.en = ' + stringify(en.en) + ';'; fs.writeFileSync('data/locales.js', fs.readFileSync('data/locales.js', 'utf8').replace(/locale.en =[^;]*;/, out)); diff --git a/combobox.html b/combobox.html index 72c7ea538..c24384cb9 100644 --- a/combobox.html +++ b/combobox.html @@ -70,7 +70,6 @@ - diff --git a/css/app.css b/css/app.css index 2bb72b993..87fb899b1 100644 --- a/css/app.css +++ b/css/app.css @@ -62,7 +62,7 @@ h2 { font-size: 25px; line-height: 1.25; font-weight: bold; - margin-bottom: 10px; + margin-bottom: 20px; } h3:last-child, @@ -71,11 +71,9 @@ h4:last-child { margin-bottom: 0;} h3 { font-size: 16px; - line-height: 1.3333; + line-height: 1.25; font-weight: bold; margin-bottom: 10px; - text-overflow: ellipsis; - white-space: nowrap; } h4 { @@ -96,10 +94,15 @@ h5 { } p { + font-size: 12px; margin:0; padding:0; } +p:last-child { + padding-bottom: 0; +} + em { font-style: italic; } @@ -147,6 +150,7 @@ input[type=email] { background-color: white; border:1px solid #ccc; padding:5px 10px; + height:30px; width: 100%; border-radius:4px; -webkit-transition: all 100ms; @@ -159,11 +163,6 @@ input:focus { background-color: #F1F1F1; } -input[type=text] { - padding:5px 10px; - height:30px; -} - input.major { width: 100%; padding:5px 10px; @@ -258,6 +257,11 @@ ul.link-list li:last-child { color: #333; } +.fillL3 { + background: #f1f1f1; + color: #333; +} + .fillD { background:rgba(0,0,0,.8); color: #6C6C6C; @@ -310,7 +314,6 @@ a.hide { right: 0; } - /* Buttons */ button { @@ -393,10 +396,10 @@ button.action:hover { background: #597BE7; } -button.delete { +button.cancel { background-color: #ff7070; } -button.delete:hover { +button.cancel:hover { background-color: #ef5454; } @@ -500,8 +503,11 @@ button[disabled] .label { .icon.warning { background-position: -380px 0px;} .icon.back { background-position: -420px 0px;} .icon.forward { background-position: -440px 0px;} +.icon.help { background-position: -460px 0px;} +.icon.inspect.light { background-position: -220px -20px;} .icon.geocode.light { background-position: -280px -20px;} +.icon.help.light { background-position: -460px -20px;} .fillD .icon.avatar { background-position: -320px -20px;} .fillD .icon.nearby { background-position: -340px -20px;} @@ -513,7 +519,6 @@ button[disabled] .icon.add-area { background-position: -60px -40px;} button.disabled .icon.undo { background-position: -80px -40px;} button.disabled .icon.redo { background-position: -100px -40px;} button[disabled] .apply.icon { background-position: -120px -40px;} -button[disabled] .save.icon { background-position: -140px -40px;} button[disabled] .close.icon { background-position: -160px -40px;} button[disabled] .delete.icon { background-position: -180px -40px;} button[disabled] .icon.remove { background-position: -200px -40px;} @@ -569,18 +574,22 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} border-radius: 0; } - /* Header for modals / panes ------------------------------------------------------- */ .header { border-bottom: 1px solid #ccc; z-index: 2; + height: 60px; position: relative; } .header h3 { + margin-right: 40px; margin-bottom: 0; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; } .modal > button, @@ -593,10 +602,11 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} overflow: hidden; position: absolute; right: 0; + top: 0; } .modal > button { - height: 61px; + height: 59px; z-index: 3; } @@ -627,10 +637,6 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} top: 120px; } -.inspector-body::-webkit-scrollbar { - background: #fff; -} - .inspector-inner { padding: 20px; position: relative; @@ -639,15 +645,11 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} .inspector-wrap .header button.preset-reset { border-right: 1px solid #CCC; position: relative; - width: 60px; } .inspector-wrap .header button.preset-reset > div { height: 100%; padding: 20px 0; - -webkit-transition: opacity 200ms; - -moz-transition: opacity 200ms; - transition: opacity 200ms; } .inspector-wrap .header button.preset-reset .col12:last-child { @@ -669,12 +671,6 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} padding: 0; } -.pane:last-child .header h3 { - position: absolute; - left: 60px; - right: 40px; -} - .inspector-toggle { color:#fff; width: 100%; @@ -715,25 +711,6 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} background: #ececec; } -.grid-entry:hover .preset-help { - display: block; - border-radius: 0; -} - -.grid-entry .preset-help { - display: none; - position: absolute; - bottom: 0; - right: 0; - height: 30px; - width: 30px; - background: rgba(0,0,0,.5); -} - -.grid-entry .preset-help:hover { - background: rgba(0,0,0,.9); -} - .grid-entry > .icon { position: absolute; top: 30px;left: 0px; right: 0px; @@ -814,23 +791,24 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} } .subgrid { - width: -webkit-calc(100% - 10px); - width: calc(100% - 10px); + width: 100%; + width: -webkit-calc(100% + 10px); + width: calc(100% + 10px); + margin-left: -10px; overflow: hidden; } .subgrid .preset-grid { - background: #eee; padding: 10px 0px 0px 10px; + border: 1px solid #CCC; margin-top: 0px; - border: 0; - border-radius: 4px; + border-radius: 8px; } .subgrid .arrow { border: solid rgba(0, 0, 0, 0); border-width: 10px; - border-bottom-color: #eee; + border-bottom-color: #CCC; width: 0; height: 0; margin-left: 33.3333%; @@ -861,6 +839,25 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} color: #222; } +.grid-entry:hover .tag-reference-button { + opacity: 1; + border-radius: 0; +} + +.grid-entry .tag-reference-button { + opacity: 0; + position: absolute; + bottom: 0; + right: 0; + width: 20px; + height: 30px; + background: rgba(0,0,0,.5); +} + +.grid-entry .tag-reference-button:hover { + background: rgba(0,0,0,.9); +} + /* Preset icon colors */ .inspector-body-line .icon.feature-marker-stroked { @@ -900,69 +897,104 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} /* preset form basics */ -.preset-field.inspector-inner { - padding-bottom: 0; +.tag-wrap .preset-icon-wrap { + border-bottom: 1px solid #CCC; } -.tag-wrap .grid-button-wrap { - padding: 0; - height: 110px; -} - -.tag-wrap .grid-button-wrap .grid-entry { - border-top: 0; - background: #eef0ff; -} - -.tag-wrap .grid-button-wrap .grid-entry .label { - background: #eef0ff; -} - -.preset-field h4 .modified-icon { - opacity: 0.2; - display: none; - pointer-events: all; -} -.preset-field h4 .modified-icon:hover { - opacity: 0.5; -} -.preset-field.modified h4 .modified-icon { - display: inline-block; -} - -.preset-field h4[for*="input-"] { - border: 1px solid #cfcfcf; - padding: 5px 10px; - background: #f6f6f6; +.tag-wrap .preset-icon-wrap::after { + content: ""; position: absolute; - left: 20px; - right: 20px; + height: 0; + width: 0; + bottom: 0; + left: 0; + right: 0; + margin: auto; + border: solid rgba(0, 0, 0, 0); + border-width: 10px; + border-bottom-color: #CCC; +} + +.tag-wrap .preset-icon-wrap div { + height: 80px; + width: 33.3333%; + width: -webkit-calc(33.3333% - 10px); + width: calc(33.3333% - 10px); + margin: auto; + border-radius: 4px; + border: 1px solid #CCC; + position: relative; +} + +.tag-wrap .preset-icon-wrap .preset-icon { + position: absolute; + top: 30px; + left: 0px; + right: 0px; + margin: auto; +} + +.tag-wrap .preset-icon-wrap .preset-icon.line { + top: 15px; +} + +.inspector-preset .form-field { + padding-left: 20px; + padding-right: 20px; +} + +.form-label { + position: relative; + font-weight: bold; + border: 1px solid #cfcfcf; + padding: 5px 0px 5px 10px; + background: #f6f6f6; + display: block; border-radius: 4px 4px 0 0; - pointer-events: none; - z-index: 2; } -.preset-field h4 + input, -h4 + .input-wrap-position input, -h4 + .preset-input input:first-child { - padding-top: 35px; - height: 60px; +.form-label button { + pointer-events: all; + height: 29px; + margin-top: -5px; + border-left: 1px solid #CCC; + border-radius: 0; + opacity: .5; } -.preset-field h4 + textarea { - padding-top: 35px; - height: 100px; +.form-label .modified-icon { + border-right: 0; + opacity: 0; } -.preset-field h4[for="input-building:levels"], -.preset-field h4[for="input-ele"], -.preset-field.checkselect h4 { - right: 50%; +.modified .form-label .modified-icon { + opacity: .5; } -.preset-field-name h4 + input { - padding-top: 35px; - height: 70px; +.form-label button.tag-reference-button { + border-top-right-radius: 3px; +} + +.form-field > input, +.form-field > textarea, +.form-field .preset-input-wrap { + border: 1px solid #CCC; + border-top: 0; + border-radius: 0 0 4px 4px; +} + +.form-field textarea { + height: 65px; +} + +.form-field-levels, +.form-field-elevation, +.form-field.checkselect { + width: 60%; +} + +.form-field-name input { + height: 35px; font-size: 18px; font-weight: bold; } @@ -989,21 +1021,16 @@ button.preset-add-field { padding: 0 10px; } -.view-on-osm { - padding: 20px; -} - /* preset form numbers */ input[type=number] { position: relative; - width: 50%; padding-right: 65px; } .spin-control { width: 60px; - height: 30px; + height: 29px; border-left: 1px solid #CCC; display: inline-block; margin-left: -60px; @@ -1016,13 +1043,14 @@ input[type=number] { float: left; height: 100%; width: 50%; - border: 1px solid #CCC; + border-left: 1px solid #CCC; + border-right: 1px solid #CCC; border-radius: 0; border-left: 0; } -.spin-control button.descend { - border-bottom-right-radius: 4px; +.spin-control button.decrement { + border-bottom-right-radius: 3px; } .spin-control button.decrement::after, @@ -1048,14 +1076,11 @@ input[type=number] { /* preset form checkbox */ -.checkselect label { +.checkselect label:last-of-type { display: block; - padding: 35px 5px 5px 5px; - border-radius: 4px; - margin-right: 50%; + padding: 5px; box-sizing: border-box; color: #999; - border: 1px solid #CCC; } .checkselect label:hover { @@ -1081,11 +1106,8 @@ input[type=number] { .radio-wrap { display: block; - padding: 30px 0 0 0; - border-radius: 4px; box-sizing: border-box; color: #999; - border: 1px solid #CCC; overflow: hidden; } @@ -1106,25 +1128,29 @@ input[type=number] { /* Preset form address */ -.preset-field .addr-housename { - border-bottom: none; - border-radius: 4px 4px 0 0; +.form-field .addr-housename { + border: none; } -.preset-field .addr-number { +.form-field .addr-number { width: 20%; + border-left: none; border-right: none; border-bottom: none; border-radius: 0; } -.preset-field .addr-street { +.form-field .addr-street { width: 80%; - border-radius: 0; + border-right: none; border-bottom: none; + border-radius: 0; } -.preset-field .addr-city { +.form-field .addr-city { + border-left: none; + border-right: none; + border-bottom: none; border-radius: 0 0 4px 4px; } @@ -1209,7 +1235,7 @@ div.combobox { .tag-row { width: 100%; position: relative; - height: 30px; + clear: both; } .tag-row input { @@ -1228,7 +1254,6 @@ div.combobox { border-right: 1px solid #CCC; } - .tag-row:first-child input.key { border-top: 1px solid #CCC; border-top-left-radius: 4px; @@ -1255,6 +1280,10 @@ div.combobox { left: -20px } +.tag-row div.tag-help { + display: hidden; +} + .tag-row:hover input.value, .tag-row:hover input.key { border-radius: 0; @@ -1282,6 +1311,20 @@ div.combobox { display: none; } +/* Tag reference */ + +.tag-help { + overflow: hidden; +} + +img.wiki-image { + float: left; + max-width: 33.3333%; + margin-right: 20px; + max-height: 200px; + border-radius: 4px; +} + /* Map Controls */ .map-control { @@ -1523,10 +1566,68 @@ div.combobox { } .geolocate-control button { +} + +/* Help */ + +.help-control { + top: 270px; +} + +.help-control button { border-radius: 0 0 4px 0; border-bottom: 0; } +.help-wrap { + position: absolute; + top:60px; + bottom: 30px; + padding: 20px 20px 20px 50px; + left: 0; + overflow-y: scroll; +} + +.help-wrap p { + font-size: 15px; + margin-bottom: 20px; +} + +.help-wrap .left-content .body p code { + padding:2px 4px; + background:#eee; +} + +.help-wrap .toc { + /* This is two columns, 41.66666 x .4 = 16.6666 */ + width:40%; + float:right; + margin-left: 20px; + margin-bottom: 20px; + padding-left: 5px +} + +.help-wrap .toc li a { + display: block; + font-weight: bold; + border: 1px solid #CCC; + border-bottom: 0px; + padding: 5px; +} + +.help-wrap .toc li a.selected { + background: #eef0ff; +} + +.help-wrap .toc li:first-child a { + border-radius: 4px 4px 0 0; +} + +.help-wrap .toc li:last-child a { + border-bottom: 1px solid #CCC; + border-radius: 0 0 4px 4px +} + /* Map ------------------------------------------------------- */ @@ -1597,6 +1698,20 @@ div.combobox { color:#fff; } +/* Attribution overlay */ +.attribution { + position: absolute; + bottom: 35px; + right:10px; + color:#888; + font-size:10px; +} + +.source-image { + height:20px; + vertical-align:top; +} + .user-list a:not(:last-child):after { content: ', '; } @@ -1798,7 +1913,7 @@ div.typeahead a:first-child { ------------------------------------------------------- */ .modal a.success-action { - height: 180px; + height: 170px; border-bottom: 1px solid #CCC; text-align: center; -webkit-transition: all 200ms; @@ -1820,7 +1935,7 @@ a.success-action:before { height: 100px; width: 100px; margin: auto; - margin-bottom: 20px; + margin-bottom: 10px; background:transparent url(../img/sprite.png) no-repeat 0px -220px; } @@ -2091,13 +2206,24 @@ a.success-action.twitter:before { @media only screen and (max-height: 840px) { } +@media only screen and (-webkit-min-device-pixel-ratio: 1.5), + only screen and (-o-min-device-pixel-ratio: 3/2), + only screen and (min--moz-device-pixel-ratio: 1.5), + only screen and (min-device-pixel-ratio: 1.5) { + .map-control .icon, + .button-wrap .icon { + background-image: url(../img/sprite2x.png); + background-size: 500px 320px; + } +} + /* Scrollbars ----------------------------------------------------- */ ::-webkit-scrollbar { height: 20px; overflow: visible; width: 10px; - background: #EBEBEB; + background: white; border-left: 1px solid #DDD; } ::-webkit-scrollbar-button { @@ -2114,10 +2240,8 @@ a.success-action.twitter:before { } ::-webkit-scrollbar-track:hover { background-color: rgba(0,0,0,.05); - box-shadow: inset 1px 0 0 rgba(0,0,0,.1); } ::-webkit-scrollbar-track:horizontal:hover { - box-shadow: inset 0 1px 0 rgba(0,0,0,.1) } ::-webkit-scrollbar-track:active { background-color: rgba(0,0,0,.05); diff --git a/css/map.css b/css/map.css index 02989ea77..e8032f733 100644 --- a/css/map.css +++ b/css/map.css @@ -856,11 +856,11 @@ text.point { cursor: url(../img/cursor-select-remove.png), pointer; } -.point:active, -.vertex:active, -.line:active, -.area:active, -.midpoint:active, +#map .point:active, +#map .vertex:active, +#map .line:active, +#map .area:active, +#map .midpoint:active, .mode-select .selected { cursor: url(../img/cursor-select-acting.png), pointer; } diff --git a/data/core.yaml b/data/core.yaml index 8f40b5ac7..6f38a68ad 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -107,7 +107,7 @@ en: nothing_to_redo: Nothing to redo. just_edited: "You just edited OpenStreetMap!" browser_notice: "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map." - view_on_osm: View on OSM + view_on_osm: "View on OSM →" zoom_in_edit: zoom in to edit the map logout: logout report_a_bug: report a bug @@ -123,8 +123,8 @@ en: deleted: Deleted created: Created contributors: - list: "Viewing contributions by {users}" - truncated_list: "Viewing contributions by {users} and {count} others" + list: "Contributed by {users}" + truncated_list: "Contributed by {users} and {count} others" geocoder: title: Find a place placeholder: Find a place @@ -136,7 +136,7 @@ en: no_documentation_key: There is no documentation available for this key show_more: Show More new_tag: New tag - view_on_osm: View on OSM + view_on_osm: View on OSM → editing_feature: "Editing {feature}" additional: Additional tags choose: Select feature type @@ -150,7 +150,8 @@ en: fix_misalignment: Fix misalignment reset: reset restore: - description: "You have unsaved changes from a previous editing session. Do you wish to restore these changes?" + heading: You have unsaved changes + description: "Do you wish to restore changes from a previous editing session?" restore: Restore reset: Reset save: @@ -167,6 +168,7 @@ en: start: Start Editing source_switch: live: live + lose_changes: "You have unsaved changes. Switching the map server will discard them. Are you sure you want to switch servers?" dev: dev tag_reference: description: Description @@ -182,8 +184,8 @@ en: zoom: in: Zoom In out: Zoom Out - imagery: - provided_by: "Imagery provided by {source}" gpx: local_layer: "Local GPX file" drag_drop: "Drag and drop a .gpx file on the page" + help: + title: "Help" diff --git a/data/data_dev.js b/data/data_dev.js index 3445f943a..6968c7d6a 100644 --- a/data/data_dev.js +++ b/data/data_dev.js @@ -12,7 +12,8 @@ iD.data = { path + 'data/presets/presets.json', path + 'data/presets/defaults.json', path + 'data/presets/categories.json', - path + 'data/presets/fields.json'], d3.json, function (err, data) { + path + 'data/presets/fields.json', + path + 'data/doc.json'], d3.json, function (err, data) { iD.data = { deprecated: data[0], @@ -24,7 +25,8 @@ iD.data = { defaults: data[5], categories: data[6], fields: data[7] - } + }, + doc: data[8] }; callback(); diff --git a/data/doc.json b/data/doc.json new file mode 100644 index 000000000..b00025eaf --- /dev/null +++ b/data/doc.json @@ -0,0 +1,34 @@ +[ + { + "html": "

This is an editor for OpenStreetMap, the\nfree and editable map of the world. You can use it to fix and update\ndata in your area, making an open-source and open-data map of the world\nbetter for everyone.

\n

Edits that you make on this map will be visible to everyone who uses\nOpenStreetMap. In order to make an edit, you'll need a\nfree OpenStreetMap account.

\n

iD Editor is a collaborative project with source\ncode available on GitHub.

\n", + "title": "Help" + }, + { + "html": "

This editor is designed to work primarily online, and you're accessing\nit through a website right now.

\n

Selecting Features

\n

To select a map feature, like a road or point of interest, simply single-click\non it on the map. This will highlight the selected feature, show a panel of\ndetails, and also show a menu of things you can do with the feature.

\n

Multiple features can be selected by holding the 'Shift' key, clicking,\nand dragging on the map. This will select all features within the box\nthat's drawn, and you can do certain 'batch operations' on all features.

\n

Saving Edits

\n

When you create changes, like editing roads, buildings, and places, these are\nstored locally until you save them to the server. Don't worry if you make\na mistake - you can undo changes by clicking the undo button, and redo\nchanges by clicking the redo button.

\n

Click 'Save' to finish a group of edits - for instance, if you've completed\nan area of town and would like to start on a new area. You'll have a chance\nto review what you've done, and the editor supplies helpful suggestions\nand warnings if something doesn't seem right about the changes.

\n

Clicking 'Save' again, on the new dialog, will post the changes\nto OpenStreetMap.org, where they are visible\nto all other users and available for others to build and improve upon.

\n

If you can't finish your edits in one sitting, you can leave the editor\nwindow and come back (on the same browser and computer), and the\neditor application will offer to restore your work.

\n", + "title": "Editing & Saving" + }, + { + "html": "

You can create, fix, and delete roads with this editor. Roads can be all\nkinds: paths, highways, trails, cycleways, and more - any often-crossed\nsegment should be mappable.

\n

Selecting

\n

Click on a road to select it. An outline should become visible, along\nwith a small tools menu on the map and a sidebar showing more information\nabout the road.

\n

Modifying

\n

Often you'll see roads that aren't aligned to the imagery behind them\nor a GPS track.

\n

First click on the road you want to change. This will highlight it and show\n'control points along it' that you can drag to better locations. If\nyou want to add new control points for more detail, double-click a part\nof the road without a point, and one will be added.

\n

If the road connects to another road, but doesn't properly connect on\nthe map, you can drag one of its control points onto the other road in\norder to join them. Having roads connect is important for the map\nand essential for providing driving directions.

\n

You can also click the 'Move' tool or type M to move the entire road at\none time, and then click again to save that movement.

\n

Deleting

\n

If a road is entirely incorrect - you can see that it doesn't exist in satellite\nimagery and ideally have confirmed locally that it's not present - you can delete\nit, which removes it from the map. Be cautious when deleting features -\nlike any other edit, the results are seen by everyone and satellite imagery\nis often out of date, so the road could simply be newly built.

\n

You can delete a road by clicking on it to select it, then clicking the\ntrash can icon or pressing the 'Delete' key.

\n

Creating

\n

Found somewhere there should be a road but there isn't? Click the 'Line'\nicon in the top-left of the editor or press the key '2' to start drawing\na line.

\n

Click on the start of the road on the map to start drawing. If the road\nconnects to another road, first, click on the place where they connect.

\n

Then click on points along the road so that it follows the right path, according\nto satellite imagery or GPS. When you're done drawing the road, double-click\nor press 'Return' or 'Enter' on your keyboard.

\n", + "title": "Roads" + }, + { + "html": "

GPS data is the most trusted source of data for OpenStreetMap. This editor\nsupports local traces - .gpx files on your local computer. You can collect\nthis kind of GPS trace with a number of smartphone applications as well as\npersonal GPS hardware.

\n

For information on how to perform a GPS survey, read\nSurveying with a GPS.

\n

To use a GPX track for mapping, drag and drop the GPX file onto the map map\neditor. If it's recognized, it will be added to the map as a bright green\nline. Click on the 'Background Settings' menu on the left side to enable,\ndisable, or zoom to this new GPX-powered layer.

\n

The GPX track isn't directly uploaded to OpenStreetMap - the best way to\nuse it is to draw on the map, using it as a guide for the new features that\nyou add.

\n", + "title": "GPS" + }, + { + "html": "

Aerial imagery is an important resource for mapping. A combination of\nairplane flyovers, satellite views, and freely-compiled sources are available\nin the editor under the 'Background Settings' menu on the left.

\n

By default a Bing Maps satellite layer is\npresented in the editor, but as you pan and zoom the map to new geographical\nareas, new sources will become available. Some countries, like the United\nStates, France, and Denmark have very high-resolution, high-quality imagery\navailable for smaller geographical coverages.

\n

Imagery is sometimes offset from the map data because of a mistake on the\nimagery provider's side - so if you see many roads shifted from the background,\ndon't immediately go to move them all. You can set an offset for imagery\nby clicking 'Fix alignment' at the bottom of the bottom of the Background\nSettings UI.

\n", + "title": "Imagery" + }, + { + "html": "

Addresses are some of the most useful information for the map.

\n

Although addresses are often represented as parts of streets, in OpenStreetMap\nthey're recorded as attributes of buildings and places along streets.

\n

You can add address information to places mapped as building outlines as well\nas well as those mapped as single points. The optimal source of address\ndata is from an on-the-ground survey or personal knowledge - as with any\nother feature, copying from commercial sources like Google Maps is strictly\nforbidden.

\n", + "title": "Addresses" + }, + { + "html": "

The inspector is the user interface element on the right-hand side of the\npage that appears when an element is selected and allows to edit its details.

\n

Selecting a Preset

\n

The inspector has two modes: the first allows you to pick a preset, or\npredetermined selection of forms and tags.

\n

Click the 'i' in the bottom-right-hand corner of a preset option to learn\nmore about it. Click a preset to choose it.

\n

Using Forms and Editing Tags

\n

The second allows you to edit the\nattributes of a map element using those forms and tags.

\n

Below the forms you see, you can click icons to add more easy-to-use forms,\nlike Wikipedia information, wheelchair\naccess, and more.

\n

At the bottom of the inspector, click 'Additional tags' to add arbitrary\nother tags to the element. Taginfo is a\ngreat resource for learn more about popular tag combinations.

\n

Changes you make in the inspector are automatically applied to the map.\nYou can undo them at any time by clicking the Undo button.

\n

Closing the Inspector

\n

You can close the inspector by either clicking the close button in the top-right,\npressing the 'Escape' key, or clicking on the map.

\n", + "title": "Using the Inspector" + }, + { + "html": "

OpenStreetMap is the world's largest database of buildings. You can create\nand improve this database.

\n

Selecting

\n

You can select a building by clicking on its border. This will highlight the\nbuilding and open a small tools menu and a sidebar showing more information\nabout the building.

\n

Modifying

\n

Sometimes buildings are incorrectly placed or have incorrect tags.

\n

To move an entire building, select it, then click the 'Move' tool. Move your\nmouse to shift the building, and click when it's correctly placed.

\n

To fix the specific shape of a building, click and drag the points that form\nits border into better places.

\n

Creating

\n

One of the main questions around adding buildings to the map is that\nOpenStreetMap records buildings both as shapes and points. The rule of thumb\nis to map a building as a shape whenever possible, and map companies, homes,\namenities, and other things that operate out of buildings as points placed\nwithin the building shape.

\n

Start drawing a building as a shape by clicking the 'Area' button in the top\nleft of the interface, and end it either by pressing 'Return' on your keyboard\nor clicking on the first point drawn to close the shape.

\n

Deleting

\n

If a building is entirely incorrect - you can see that it doesn't exist in satellite\nimagery and ideally have confirmed locally that it's not present - you can delete\nit, which removes it from the map. Be cautious when deleting features -\nlike any other edit, the results are seen by everyone and satellite imagery\nis often out of date, so the road could simply be newly built.

\n

You can delete a building by clicking on it to select it, then clicking the\ntrash can icon or pressing the 'Delete' key.

\n", + "title": "Buildings" + } +] \ No newline at end of file diff --git a/data/doc/00-help.md b/data/doc/00-help.md new file mode 100644 index 000000000..60d1c976f --- /dev/null +++ b/data/doc/00-help.md @@ -0,0 +1,13 @@ +# Help + +This is an editor for [OpenStreetMap](http://www.openstreetmap.org/), the +free and editable map of the world. You can use it to fix and update +data in your area, making an open-source and open-data map of the world +better for everyone. + +Edits that you make on this map will be visible to everyone who uses +OpenStreetMap. In order to make an edit, you'll need a +[free OpenStreetMap account](https://www.openstreetmap.org/user/new). + +[iD Editor](http://ideditor.com/) is a collaborative project with [source +code available on GitHub](https://github.com/systemed/iD). diff --git a/data/doc/01-editing-saving.md b/data/doc/01-editing-saving.md new file mode 100644 index 000000000..5f9a8c38a --- /dev/null +++ b/data/doc/01-editing-saving.md @@ -0,0 +1,34 @@ +# Editing & Saving + +This editor is designed to work primarily online, and you're accessing +it through a website right now. + +### Selecting Features + +To select a map feature, like a road or point of interest, simply single-click +on it on the map. This will highlight the selected feature, show a panel of +details, and also show a menu of things you can do with the feature. + +Multiple features can be selected by holding the 'Shift' key, clicking, +and dragging on the map. This will select all features within the box +that's drawn, and you can do certain 'batch operations' on all features. + +### Saving Edits + +When you create changes, like editing roads, buildings, and places, these are +stored locally until you save them to the server. Don't worry if you make +a mistake - you can undo changes by clicking the undo button, and redo +changes by clicking the redo button. + +Click 'Save' to finish a group of edits - for instance, if you've completed +an area of town and would like to start on a new area. You'll have a chance +to review what you've done, and the editor supplies helpful suggestions +and warnings if something doesn't seem right about the changes. + +Clicking 'Save' again, on the new dialog, will post the changes +to [OpenStreetMap.org](http://www.openstreetmap.org/), where they are visible +to all other users and available for others to build and improve upon. + +If you can't finish your edits in one sitting, you can leave the editor +window and come back (on the same browser and computer), and the +editor application will offer to restore your work. diff --git a/data/doc/03-road.md b/data/doc/03-road.md new file mode 100644 index 000000000..2f59a46e2 --- /dev/null +++ b/data/doc/03-road.md @@ -0,0 +1,53 @@ +# Roads + +You can create, fix, and delete roads with this editor. Roads can be all +kinds: paths, highways, trails, cycleways, and more - any often-crossed +segment should be mappable. + +### Selecting + +Click on a road to select it. An outline should become visible, along +with a small tools menu on the map and a sidebar showing more information +about the road. + +### Modifying + +Often you'll see roads that aren't aligned to the imagery behind them +or a GPS track. + +First click on the road you want to change. This will highlight it and show +'control points along it' that you can drag to better locations. If +you want to add new control points for more detail, double-click a part +of the road without a point, and one will be added. + +If the road connects to another road, but doesn't properly connect on +the map, you can drag one of its control points onto the other road in +order to join them. Having roads connect is important for the map +and essential for providing driving directions. + +You can also click the 'Move' tool or type `M` to move the entire road at +one time, and then click again to save that movement. + +### Deleting + +If a road is entirely incorrect - you can see that it doesn't exist in satellite +imagery and ideally have confirmed locally that it's not present - you can delete +it, which removes it from the map. Be cautious when deleting features - +like any other edit, the results are seen by everyone and satellite imagery +is often out of date, so the road could simply be newly built. + +You can delete a road by clicking on it to select it, then clicking the +trash can icon or pressing the 'Delete' key. + +### Creating + +Found somewhere there should be a road but there isn't? Click the 'Line' +icon in the top-left of the editor or press the key '2' to start drawing +a line. + +Click on the start of the road on the map to start drawing. If the road +connects to another road, first, click on the place where they connect. + +Then click on points along the road so that it follows the right path, according +to satellite imagery or GPS. When you're done drawing the road, double-click +or press 'Return' or 'Enter' on your keyboard. diff --git a/data/doc/04-gps.md b/data/doc/04-gps.md new file mode 100644 index 000000000..4bb48f276 --- /dev/null +++ b/data/doc/04-gps.md @@ -0,0 +1,18 @@ +# GPS + +GPS data is the most trusted source of data for OpenStreetMap. This editor +supports local traces - `.gpx` files on your local computer. You can collect +this kind of GPS trace with a number of smartphone applications as well as +personal GPS hardware. + +For information on how to perform a GPS survey, read +[Surveying with a GPS](http://learnosm.org/en/beginner/using-gps/). + +To use a GPX track for mapping, drag and drop the GPX file onto the map map +editor. If it's recognized, it will be added to the map as a bright green +line. Click on the 'Background Settings' menu on the left side to enable, +disable, or zoom to this new GPX-powered layer. + +The GPX track isn't directly uploaded to OpenStreetMap - the best way to +use it is to draw on the map, using it as a guide for the new features that +you add. diff --git a/data/doc/05-imagery.md b/data/doc/05-imagery.md new file mode 100644 index 000000000..71ae7bccd --- /dev/null +++ b/data/doc/05-imagery.md @@ -0,0 +1,17 @@ +# Imagery + +Aerial imagery is an important resource for mapping. A combination of +airplane flyovers, satellite views, and freely-compiled sources are available +in the editor under the 'Background Settings' menu on the left. + +By default a [Bing Maps](http://www.bing.com/maps/) satellite layer is +presented in the editor, but as you pan and zoom the map to new geographical +areas, new sources will become available. Some countries, like the United +States, France, and Denmark have very high-resolution, high-quality imagery +available for smaller geographical coverages. + +Imagery is sometimes offset from the map data because of a mistake on the +imagery provider's side - so if you see many roads shifted from the background, +don't immediately go to move them all. You can set an offset for imagery +by clicking 'Fix alignment' at the bottom of the bottom of the Background +Settings UI. diff --git a/data/doc/06-addresses.md b/data/doc/06-addresses.md new file mode 100644 index 000000000..0250e8cd3 --- /dev/null +++ b/data/doc/06-addresses.md @@ -0,0 +1,12 @@ +# Addresses + +Addresses are some of the most useful information for the map. + +Although addresses are often represented as parts of streets, in OpenStreetMap +they're recorded as attributes of buildings and places along streets. + +You can add address information to places mapped as building outlines as well +as well as those mapped as single points. The optimal source of address +data is from an on-the-ground survey or personal knowledge - as with any +other feature, copying from commercial sources like Google Maps is strictly +forbidden. diff --git a/data/doc/07-inspector.md b/data/doc/07-inspector.md new file mode 100644 index 000000000..687468e72 --- /dev/null +++ b/data/doc/07-inspector.md @@ -0,0 +1,33 @@ +# Using the Inspector + +The inspector is the user interface element on the right-hand side of the +page that appears when an element is selected and allows to edit its details. + +### Selecting a Preset + +The inspector has two modes: the first allows you to pick a preset, or +predetermined selection of forms and tags. + +Click the 'i' in the bottom-right-hand corner of a preset option to learn +more about it. Click a preset to choose it. + +### Using Forms and Editing Tags + +The second allows you to edit the +attributes of a map element using those forms and tags. + +Below the forms you see, you can click icons to add more easy-to-use forms, +like [Wikipedia](http://www.wikipedia.org/) information, wheelchair +access, and more. + +At the bottom of the inspector, click 'Additional tags' to add arbitrary +other tags to the element. [Taginfo](http://taginfo.openstreetmap.org/) is a +great resource for learn more about popular tag combinations. + +Changes you make in the inspector are automatically applied to the map. +You can undo them at any time by clicking the Undo button. + +### Closing the Inspector + +You can close the inspector by either clicking the close button in the top-right, +pressing the 'Escape' key, or clicking on the map. diff --git a/data/doc/building.md b/data/doc/building.md new file mode 100644 index 000000000..5a634de0d --- /dev/null +++ b/data/doc/building.md @@ -0,0 +1,43 @@ +# Buildings + +OpenStreetMap is the world's largest database of buildings. You can create +and improve this database. + +### Selecting + +You can select a building by clicking on its border. This will highlight the +building and open a small tools menu and a sidebar showing more information +about the building. + +### Modifying + +Sometimes buildings are incorrectly placed or have incorrect tags. + +To move an entire building, select it, then click the 'Move' tool. Move your +mouse to shift the building, and click when it's correctly placed. + +To fix the specific shape of a building, click and drag the points that form +its border into better places. + +### Creating + +One of the main questions around adding buildings to the map is that +OpenStreetMap records buildings both as shapes and points. The rule of thumb +is to _map a building as a shape whenever possible_, and map companies, homes, +amenities, and other things that operate out of buildings as points placed +within the building shape. + +Start drawing a building as a shape by clicking the 'Area' button in the top +left of the interface, and end it either by pressing 'Return' on your keyboard +or clicking on the first point drawn to close the shape. + +### Deleting + +If a building is entirely incorrect - you can see that it doesn't exist in satellite +imagery and ideally have confirmed locally that it's not present - you can delete +it, which removes it from the map. Be cautious when deleting features - +like any other edit, the results are seen by everyone and satellite imagery +is often out of date, so the road could simply be newly built. + +You can delete a building by clicking on it to select it, then clicking the +trash can icon or pressing the 'Delete' key. diff --git a/data/locales.js b/data/locales.js index 57e88fb46..eebbd3daf 100644 --- a/data/locales.js +++ b/data/locales.js @@ -139,7 +139,7 @@ locale.en = { "nothing_to_redo": "Nothing to redo.", "just_edited": "You just edited OpenStreetMap!", "browser_notice": "This editor is supported in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. Please upgrade your browser or use Potlatch 2 to edit the map.", - "view_on_osm": "View on OSM", + "view_on_osm": "View on OSM →", "zoom_in_edit": "zoom in to edit the map", "logout": "logout", "report_a_bug": "report a bug", @@ -156,8 +156,8 @@ locale.en = { "created": "Created" }, "contributors": { - "list": "Viewing contributions by {users}", - "truncated_list": "Viewing contributions by {users} and {count} others" + "list": "Contributed by {users}", + "truncated_list": "Contributed by {users} and {count} others" }, "geocoder": { "title": "Find a place", @@ -172,7 +172,7 @@ locale.en = { "no_documentation_key": "There is no documentation available for this key", "show_more": "Show More", "new_tag": "New tag", - "view_on_osm": "View on OSM", + "view_on_osm": "View on OSM →", "editing_feature": "Editing {feature}", "additional": "Additional tags", "choose": "Select feature type", @@ -188,7 +188,8 @@ locale.en = { "reset": "reset" }, "restore": { - "description": "You have unsaved changes from a previous editing session. Do you wish to restore these changes?", + "heading": "You have unsaved changes", + "description": "Do you wish to restore changes from a previous editing session?", "restore": "Restore", "reset": "Reset" }, @@ -208,6 +209,7 @@ locale.en = { }, "source_switch": { "live": "live", + "lose_changes": "You have unsaved changes. Switching the map server will discard them. Are you sure you want to switch servers?", "dev": "dev" }, "tag_reference": { @@ -227,13 +229,13 @@ locale.en = { "in": "Zoom In", "out": "Zoom Out" }, - "imagery": { - "provided_by": "Imagery provided by {source}" - }, "gpx": { "local_layer": "Local GPX file", "drag_drop": "Drag and drop a .gpx file on the page" }, + "help": { + "title": "Help" + }, "presets": { "fields": { "access": { @@ -1624,6 +1626,7 @@ locale.zh = { "commit": { "title": "保存更改", "description_placeholder": "简要说明你的贡献", + "message_label": "提交说明", "upload_explanation": "{user}你上传的更新将会显示在所有使用OpenStreetMap数据的地图上。", "save": "保存", "cancel": "取消", @@ -1649,10 +1652,7 @@ locale.zh = { "no_documentation_key": "没有关于此键的文档", "show_more": "显示更多", "new_tag": "新建标签", - "edit_tags": "编辑标签", - "okay": "确定", "view_on_osm": "在OSM上查看", - "name": "名称", "editing_feature": "编辑{feature}", "additional": "附加标签", "choose": "选择对象的类型", @@ -1821,6 +1821,9 @@ locale.zh = { "maxspeed": { "label": "限速" }, + "name": { + "label": "名称" + }, "natural": { "label": "自然" }, @@ -1836,6 +1839,9 @@ locale.zh = { "oneway": { "label": "单行" }, + "oneway_yes": { + "label": "单行" + }, "opening_hours": { "label": "小时" }, @@ -2051,6 +2057,9 @@ locale.zh = { "barrier/bollard": { "name": "短柱" }, + "barrier/cattle_grid": { + "name": "家畜栅栏" + }, "barrier/city_wall": { "name": "城墙" }, @@ -2091,7 +2100,7 @@ locale.zh = { "name": "公路" }, "highway/bridleway": { - "name": "台阶", + "name": "马道", "terms": "楼梯" }, "highway/bus_stop": { @@ -2110,18 +2119,30 @@ locale.zh = { "highway/motorway": { "name": "高速公路" }, + "highway/motorway_link": { + "name": "高速公路匝道" + }, "highway/path": { "name": "路" }, "highway/primary": { "name": "主要道路" }, + "highway/primary_link": { + "name": "主要道路匝道" + }, "highway/residential": { "name": "住宅区道路" }, + "highway/road": { + "name": "未知道路" + }, "highway/secondary": { "name": "次要道路" }, + "highway/secondary_link": { + "name": "次要道路匝道" + }, "highway/service": { "name": "辅助道路" }, @@ -2132,6 +2153,9 @@ locale.zh = { "highway/tertiary": { "name": "三级道路" }, + "highway/tertiary_link": { + "name": "三级道路匝道" + }, "highway/track": { "name": "小路" }, @@ -2142,6 +2166,9 @@ locale.zh = { "highway/trunk": { "name": "干线道路" }, + "highway/trunk_link": { + "name": "干线道路匝道" + }, "highway/turning_circle": { "name": "环岛" }, @@ -2154,6 +2181,9 @@ locale.zh = { "historic/archaeological_site": { "name": "考古遗址" }, + "historic/boundary_stone": { + "name": "界桩" + }, "historic/castle": { "name": "城堡" }, @@ -2166,6 +2196,9 @@ locale.zh = { "historic/ruins": { "name": "废墟" }, + "historic/wayside_cross": { + "name": "路边的十字架" + }, "historic/wayside_shrine": { "name": "路边的神社" }, @@ -2352,7 +2385,7 @@ locale.zh = { "name": "村庄" }, "power": { - "name": "动力" + "name": "电力设施" }, "power/generator": { "name": "发电厂" @@ -2415,11 +2448,14 @@ locale.zh = { "shop/beverages": { "name": "饮料店" }, + "shop/bicycle": { + "name": "自行车店" + }, "shop/books": { "name": "书店" }, "shop/boutique": { - "name": "精品" + "name": "精品店" }, "shop/butcher": { "name": "肉贩" @@ -2434,7 +2470,7 @@ locale.zh = { "name": "汽车修理店" }, "shop/chemist": { - "name": "化学家" + "name": "药房" }, "shop/clothes": { "name": "服装店" @@ -2461,7 +2497,10 @@ locale.zh = { "name": "干洗店" }, "shop/electronics": { - "name": "鱼贩子" + "name": "家电店" + }, + "shop/fishmonger": { + "name": "鱼贩" }, "shop/florist": { "name": "花店" @@ -2470,7 +2509,7 @@ locale.zh = { "name": "家具店" }, "shop/garden_centre": { - "name": "花园中心" + "name": "花店" }, "shop/gift": { "name": "礼品店" @@ -2488,10 +2527,10 @@ locale.zh = { "name": "音响店" }, "shop/jewelry": { - "name": "珠宝商" + "name": "珠宝店" }, "shop/kiosk": { - "name": "亭" + "name": "报刊亭" }, "shop/laundry": { "name": "洗衣店" @@ -2512,7 +2551,7 @@ locale.zh = { "name": "书报" }, "shop/optician": { - "name": "配镜师" + "name": "眼镜店" }, "shop/outdoor": { "name": "户外店" @@ -2581,7 +2620,7 @@ locale.zh = { "name": "旅馆" }, "tourism/information": { - "name": "信息" + "name": "信息板" }, "tourism/motel": { "name": "汽车旅馆" @@ -2804,10 +2843,8 @@ locale.zh_TW = { "no_documentation_key": "這個鍵值沒有可用的文檔", "show_more": "顯示更多", "new_tag": "新的標籤", - "edit_tags": "編輯標籤", - "okay": "確定", "view_on_osm": "在OSM上顯示", - "name": "名稱", + "editing_feature": "正在編輯 {feature}", "additional": "附加的標籤", "choose": "選擇功能種類", "results": "{search} 的 {n} 個結果", @@ -2862,6 +2899,10 @@ locale.zh_TW = { "imagery": { "provided_by": "影像由 {source} 提供" }, + "gpx": { + "local_layer": "本機GPX檔案", + "drag_drop": "拖放一個.gpx格式的檔案到本頁" + }, "presets": { "fields": { "access": { @@ -3152,10 +3193,12 @@ locale.zh_TW = { "terms": "穆斯林,清真寺" }, "amenity/police": { - "name": "警察局" + "name": "警察局", + "terms": "徽章,警官,警官,警官,警官,男童軍,警官,警官,警官,警官,警官,軍團,警車,偵探,警官,警官,部隊,警官,憲兵,刑警,警官, 法律,執法,警官,警官,警官,警官,警察" }, "amenity/post_box": { - "name": "郵箱" + "name": "郵箱", + "terms": "信箱,信箱,郵箱,郵箱,郵筒,郵箱" }, "amenity/post_office": { "name": "郵政局" @@ -3701,8 +3744,6 @@ locale.da = { "no_documentation_combination": "Der er ingen dokumentation for denne tag kombination", "no_documentation_key": "Der er ingen dokumentation tilgængelig for denne nøgle", "new_tag": "Nyt tag", - "edit_tags": "Ret tags", - "okay": "Ok", "view_on_osm": "Vis på OSM" }, "background": { @@ -3912,10 +3953,7 @@ locale.nl = { "no_documentation_combination": "Voor deze tag is geen documentatie beschikbaar.", "no_documentation_key": "Voor deze sleutel is geen documentatie beschikbaar", "new_tag": "Nieuwe tag", - "edit_tags": "Tags aanpassen", - "okay": "OK", "view_on_osm": "Bekijk op OSM", - "name": "Name", "additional": "Additional tags", "choose": "What are you adding?", "results": "{n} results for {search}" @@ -4138,10 +4176,7 @@ locale.fr = { "no_documentation_key": "Aucune documentation n'est disponible pour cette clé", "show_more": "Plus d'infornations", "new_tag": "Nouveau tag", - "edit_tags": "Editer les tags", - "okay": "Okay", "view_on_osm": "Visualiser sur OSM", - "name": "Nom", "editing_feature": "Édition de {feature}", "additional": "Tags complémentaires", "choose": "Que souhaitez vous ajouter?", @@ -4660,6 +4695,7 @@ locale.de = { "rotate": { "title": "Drehen", "description": "Dieses Objekt um seinen Mittelpunkt drehen.", + "key": "R", "annotation": { "line": "Linie gedreht.", "area": "Fläche gedreht." @@ -4714,13 +4750,13 @@ locale.de = { "no_documentation_key": "Für dises Schlüsselwort ist keine Dokumentation verfügbar", "show_more": "Zeige mehr", "new_tag": "Neues Attribut", - "edit_tags": "Attribute bearbeiten", - "okay": "OK", "view_on_osm": "auf OpenStreetMap ansehen", - "name": "Name", + "editing_feature": "In Bearbeitung {feature}", "additional": "Weitere Merkmale", + "choose": "Eigenschafts-Typ auswählen", "results": "{n} Resultate für {search}", - "reference": "In der OpenSteetMap Wiki anschauen →" + "reference": "In der OpenSteetMap Wiki anschauen →", + "back_tooltip": "Eigenschafts-Typ ändern" }, "background": { "title": "Hintergrund", @@ -4737,6 +4773,7 @@ locale.de = { "save": { "title": "Speichern", "help": "Speichere Änderungen auf OpenStreetMap, um diese für andere Nutzer sichtbar zu machen.", + "no_changes": "Keine zu speichernden Änderungen.", "error": "Beim Speichern ist ein Fehler aufgetreten", "uploading": "Änderungen werden zu OpenStreetMap hochgeladen.", "unsaved_changes": "Ungespeicherte Änderungen vorhanden" @@ -5267,10 +5304,7 @@ locale.it = { "no_documentation_key": "Non c'è documentazione per questa chiave", "show_more": "Mostra di più", "new_tag": "Nuovo Tag", - "edit_tags": "Modifica i tag", - "okay": "Ok", "view_on_osm": "Mostra su OSM", - "name": "Nome", "additional": "Tag aggiuntivi", "choose": "Seleziona il tipo di caratteristica", "results": "{n} risultati per {search}", @@ -6129,7 +6163,6 @@ locale.ja = { "no_documentation_combination": "このタグの組み合わせに関する説明文はありません", "no_documentation_key": "このキーに対する説明文はありません", "new_tag": "新規タグ", - "edit_tags": "タグ編集", "view_on_osm": "詳細情報確認" }, "background": { @@ -6345,10 +6378,7 @@ locale.lv = { "no_documentation_combination": "Šai apzīmējumu kombinācijai nav piejama dokumentācija", "no_documentation_key": "Šai vērtībai nav piejama dokumentācija", "new_tag": "Jauns apzīmējums", - "edit_tags": "Labot apzīmējumus", - "okay": "Labi", "view_on_osm": "Apskatīt OSM", - "name": "Name", "additional": "Papildus apzīmējumi", "choose": "Izvēlieties objekta tipu", "results": "Atrasti {n} rezultāti meklējot {search}", @@ -6698,8 +6728,6 @@ locale.pl = { "no_documentation_combination": "Nie ma dokumentacji dla tej kombinacji tagu.", "no_documentation_key": "Nie ma dokumentacji dla tego klucza", "new_tag": "Nowy tag", - "edit_tags": "Edytuj tagi", - "okay": "Okej", "view_on_osm": "Zobacz w OSM" }, "background": { @@ -6918,14 +6946,14 @@ locale.pt = { "inspector": { "no_documentation_combination": "Não há documentação disponível para esta combinação de tags", "no_documentation_key": "Não há documentação disponível para esta tecla", + "show_more": "Mostrar Mais", "new_tag": "Nova tag", - "edit_tags": "Editar tags", - "okay": "OK", "view_on_osm": "Ver em OSM", - "name": "Nome", + "editing_feature": "Editando {feature}", "additional": "Tags adicionais", "choose": "O que está a adicionar?", - "results": "{n} resultados para {search}" + "results": "{n} resultados para {search}", + "reference": "Ver na Wiki do OpenStreetMap" }, "background": { "title": "Fundo", @@ -6935,6 +6963,7 @@ locale.pt = { "reset": "reiniciar" }, "restore": { + "heading": "Tem alterações por guardar", "description": "Tem alterações por guardar de uma prévia sessão de edição. Deseja restaurar estas alterações?", "restore": "Restaurar", "reset": "Descartar" @@ -6942,6 +6971,7 @@ locale.pt = { "save": { "title": "Guardar", "help": "Guardar alterações no OpenStreetMap, tornando-as visíveis a outros utilizadores.", + "no_changes": "Não há alterações para guardar.", "error": "Um erro ocorreu ao tentar guardar", "uploading": "Enviando alterações para OpenStreetMap.", "unsaved_changes": "Tem alterações por guardar" @@ -6952,6 +6982,7 @@ locale.pt = { }, "source_switch": { "live": "ao vivo", + "lose_changes": "Tem alterações por guardar. Mudando o servidor de mapas irá perdê-las. Tem a certeza que deseja mudar de servidores?", "dev": "dev" }, "tag_reference": { @@ -6970,6 +7001,403 @@ locale.pt = { "zoom": { "in": "Aproximar", "out": "Afastar" + }, + "imagery": { + "provided_by": "Imagens disponibilizadas por {source}" + }, + "gpx": { + "local_layer": "Ficheiro GPX local", + "drag_drop": "Arraste um ficheiro .gpx para a página" + }, + "help": { + "title": "Ajuda" + }, + "presets": { + "fields": { + "access": { + "label": "Acesso" + }, + "address": { + "label": "Morada", + "placeholders": { + "housename": "Nome de casa", + "number": "123", + "street": "Rua", + "city": "Cidade" + } + }, + "aeroway": { + "label": "Tipo" + }, + "amenity": { + "label": "Tipo" + }, + "atm": { + "label": "MB" + }, + "bicycle_parking": { + "label": "Tipo" + }, + "building": { + "label": "Edifício" + }, + "building_area": { + "label": "Edifício" + }, + "building_yes": { + "label": "Edifício" + }, + "capacity": { + "label": "Capacidade" + }, + "construction": { + "label": "Tipo" + }, + "crossing": { + "label": "Tipo" + }, + "cuisine": { + "label": "Cozinha" + }, + "denomination": { + "label": "Denominação" + }, + "denotation": { + "label": "Denotação" + }, + "elevation": { + "label": "Elevação" + }, + "emergency": { + "label": "Emergência" + }, + "entrance": { + "label": "Tipo" + }, + "fax": { + "label": "Fax" + }, + "fee": { + "label": "Tarifa" + }, + "highway": { + "label": "Tipo" + }, + "historic": { + "label": "Tipo" + }, + "internet_access": { + "label": "Acesso à Internet", + "options": { + "wlan": "Wifi" + } + }, + "maxspeed": { + "label": "Limite de Velocidade" + }, + "natural": { + "label": "Natural" + }, + "network": { + "label": "Rede" + }, + "note": { + "label": "Nota" + }, + "office": { + "label": "Tipo" + }, + "oneway": { + "label": "Sentido Único" + }, + "opening_hours": { + "label": "Horas" + }, + "operator": { + "label": "Operador" + }, + "phone": { + "label": "Telefone" + }, + "place": { + "label": "Tipo" + }, + "railway": { + "label": "Tipo" + }, + "religion": { + "label": "Religião", + "options": { + "christian": "Cristão", + "muslim": "Muçulmano", + "buddhist": "Budista", + "jewish": "Judeu" + } + }, + "shelter": { + "label": "Abrigo" + }, + "shop": { + "label": "Tipo" + }, + "source": { + "label": "Fonte" + }, + "sport": { + "label": "Desporto" + }, + "surface": { + "label": "Superfície" + }, + "tourism": { + "label": "Tipo" + }, + "water": { + "label": "Tipo" + }, + "waterway": { + "label": "Tipo" + }, + "website": { + "label": "Website" + }, + "wetland": { + "label": "Tipo" + }, + "wikipedia": { + "label": "Wikipedia" + }, + "wood": { + "label": "Tipo" + } + }, + "presets": { + "aeroway/aerodrome": { + "name": "Aeroporto" + }, + "amenity": { + "name": "Amenidade" + }, + "amenity/bank": { + "name": "Banco" + }, + "amenity/bar": { + "name": "Bar" + }, + "amenity/bench": { + "name": "Banco" + }, + "amenity/bicycle_parking": { + "name": "Parque de Bicicletas" + }, + "amenity/bicycle_rental": { + "name": "Aluguer de Bicicletas" + }, + "amenity/cafe": { + "name": "Café" + }, + "amenity/cinema": { + "name": "Cinema" + }, + "amenity/fire_station": { + "name": "Quartel de Bombeiros" + }, + "amenity/grave_yard": { + "name": "Cemitério" + }, + "amenity/hospital": { + "name": "Hospital" + }, + "amenity/library": { + "name": "Biblioteca" + }, + "amenity/parking": { + "name": "Estacionamento" + }, + "amenity/pharmacy": { + "name": "Farmácia" + }, + "amenity/place_of_worship": { + "name": "Local de Oração" + }, + "amenity/place_of_worship/christian": { + "name": "Igreja" + }, + "amenity/place_of_worship/jewish": { + "name": "Sinagoga" + }, + "amenity/place_of_worship/muslim": { + "name": "Mesquita" + }, + "amenity/police": { + "name": "Polícia" + }, + "amenity/post_box": { + "name": "Caixa de Correio" + }, + "amenity/post_office": { + "name": "Estação de Correios" + }, + "amenity/pub": { + "name": "Bar" + }, + "amenity/restaurant": { + "name": "Restaurante" + }, + "amenity/school": { + "name": "Escola" + }, + "amenity/telephone": { + "name": "Telefone" + }, + "amenity/toilets": { + "name": "Casas de Banho" + }, + "amenity/townhall": { + "name": "Câmara Municipal" + }, + "amenity/university": { + "name": "Universidade" + }, + "building": { + "name": "Edifício" + }, + "entrance": { + "name": "Entrada" + }, + "highway": { + "name": "Autoestrada" + }, + "highway/bus_stop": { + "name": "Paragem de Autocarro" + }, + "highway/crossing": { + "name": "Passadeira" + }, + "highway/cycleway": { + "name": "Ciclovia" + }, + "highway/primary": { + "name": "Estrada Principal" + }, + "highway/residential": { + "name": "Estrada Residencial" + }, + "highway/secondary": { + "name": "Estrada Secundária" + }, + "highway/service": { + "name": "Estrada de Serviço" + }, + "highway/steps": { + "name": "Passos" + }, + "highway/track": { + "name": "Pista" + }, + "landuse/cemetery": { + "name": "Cemitério" + }, + "landuse/commercial": { + "name": "Comercial" + }, + "landuse/construction": { + "name": "Construção" + }, + "landuse/farm": { + "name": "Quinta" + }, + "landuse/farmyard": { + "name": "Quintal" + }, + "landuse/forest": { + "name": "Floresta" + }, + "landuse/grass": { + "name": "Relva" + }, + "landuse/industrial": { + "name": "Industrial" + }, + "leisure/golf_course": { + "name": "Campo de Golf" + }, + "leisure/park": { + "name": "Parque" + }, + "leisure/pitch": { + "name": "Campo de Desporto" + }, + "leisure/pitch/tennis": { + "name": "Campo de Ténis" + }, + "man_made/water_tower": { + "name": "Torre de Água" + }, + "natural": { + "name": "Natural" + }, + "natural/bay": { + "name": "Baía" + }, + "natural/beach": { + "name": "Praia" + }, + "natural/cliff": { + "name": "Penhasco" + }, + "natural/coastline": { + "name": "Linha Costeira" + }, + "natural/water": { + "name": "Água" + }, + "natural/water/lake": { + "name": "Lago" + }, + "place/island": { + "name": "Ilha" + }, + "place/locality": { + "name": "Localidade" + }, + "place/village": { + "name": "Aldeia" + }, + "railway/subway": { + "name": "Metro" + }, + "railway/subway_entrance": { + "name": "Entrada de Metro" + }, + "shop": { + "name": "Loja" + }, + "shop/butcher": { + "name": "Talho" + }, + "shop/supermarket": { + "name": "Supermercado" + }, + "tourism": { + "name": "Turismo" + }, + "tourism/camp_site": { + "name": "Parque de Campismo" + }, + "tourism/hotel": { + "name": "Hotal" + }, + "tourism/museum": { + "name": "Musei" + }, + "waterway/canal": { + "name": "Canal" + }, + "waterway/river": { + "name": "Rio" + } + } } }; locale.ru = { @@ -7143,10 +7571,7 @@ locale.ru = { "no_documentation_combination": "Для этой комбинации ключа и значения нет описания", "no_documentation_key": "Для этого ключа описания нет", "new_tag": "Новый тег", - "edit_tags": "Править теги", - "okay": "Готово", "view_on_osm": "Посмотреть в OSM", - "name": "Name", "additional": "Additional tags", "choose": "What are you adding?", "results": "{n} results for {search}", @@ -7361,8 +7786,6 @@ locale.es = { "no_documentation_combination": "No hay documentación disponible para esta combinación de etiquetas", "no_documentation_key": "No hay documentación disponible para esta tecla", "new_tag": "Nueva etiqueta", - "edit_tags": "Editar etiquetas", - "okay": "OK", "view_on_osm": "Ver en OSM" }, "background": { @@ -7582,10 +8005,7 @@ locale.sv = { "no_documentation_combination": "Der er ingen dokumentation for denne tag kombination", "no_documentation_key": "Det finns inget dokumentation för denna nyckel.", "new_tag": "Ny tagg", - "edit_tags": "Redigera taggar", - "okay": "Ok", "view_on_osm": "Visa på OSM", - "name": "Namn", "additional": "Fler taggar", "choose": "Vad lägger du till?", "results": "{n} sökresult för {search}", @@ -7799,10 +8219,7 @@ locale.tr = { "no_documentation_combination": "Bu etiket kombinasyonu için dökümantasyon bulunmamaktadır.", "no_documentation_key": "Bu anahtar için dökümantasyon bulunmamaktadır.", "new_tag": "Yeni Etiket", - "edit_tags": "Etiketleri güncelle", - "okay": "Tamam", "view_on_osm": "OSM üzerinde gör", - "name": "İsim", "additional": "Ekstra etiketler", "choose": "Neyi ekliyorsunuz?", "results": "{n} results for {search}" @@ -8025,10 +8442,7 @@ locale.uk = { "no_documentation_key": "Для цього теґа немає документації", "show_more": "Ще", "new_tag": "Новий теґ", - "edit_tags": "Редагувати теґи", - "okay": "Готово", "view_on_osm": "Подивтись в ОСМ", - "name": "Name", "editing_feature": "Властивості {feature}", "additional": "Додаткові теґи", "choose": "Виберіть тип об’єкту", @@ -8096,7 +8510,7 @@ locale.uk = { "label": "Адреса", "placeholders": { "housename": "Назвабудинку", - "number": "123", + "number": "Номер", "street": "Вулиця", "city": "Місто" } @@ -8128,6 +8542,9 @@ locale.uk = { "capacity": { "label": "Міськість" }, + "collection_times": { + "label": "Час виїмки пошти" + }, "construction": { "label": "Тип" }, @@ -8205,6 +8622,9 @@ locale.uk = { "office": { "label": "Тип" }, + "oneway": { + "label": "Односторонній рух" + }, "opening_hours": { "label": "Години" }, @@ -8340,6 +8760,9 @@ locale.uk = { "amenity/fire_station": { "name": "Пожежна станція" }, + "amenity/fuel": { + "name": "Заправка" + }, "amenity/grave_yard": { "name": "Цвинтар" }, @@ -8803,6 +9226,9 @@ locale.uk = { "shop/butcher": { "name": "М’ясна лавка" }, + "shop/car": { + "name": "Автосалон" + }, "shop/car_parts": { "name": "Автозапчастини" }, @@ -8902,6 +9328,9 @@ locale.uk = { "shop/sports": { "name": "Спорттовари" }, + "shop/stationery": { + "name": "Канцтовари" + }, "shop/supermarket": { "name": "Супермаркет" }, @@ -8914,6 +9343,9 @@ locale.uk = { "shop/tyres": { "name": "Колеса та шини" }, + "shop/vacant": { + "name": "Здається в оренду" + }, "shop/variety_store": { "name": "Універсам" }, @@ -9149,6 +9581,7 @@ locale.vi = { "commit": { "title": "Lưu các Thay đổi", "description_placeholder": "Tóm lược các đóng góp của bạn", + "message_label": "Tóm lược sửa đổi", "upload_explanation": "Các thay đổi bạn thực hiện dưới tên {user} sẽ xuất hiện trên tất cả các bản đồ sử dụng dữ liệu OpenStreetMap.", "save": "Lưu", "cancel": "Hủy bỏ", @@ -9174,10 +9607,7 @@ locale.vi = { "no_documentation_key": "Không có tài liệu về chìa khóa này", "show_more": "Xem thêm", "new_tag": "Thẻ mới", - "edit_tags": "Sửa đổi các thẻ", - "okay": "OK", "view_on_osm": "Xem tại OSM", - "name": "Tên", "editing_feature": "Đang sửa {feature}", "additional": "Các thẻ nâng cao", "choose": "Chọn loại đối tượng", @@ -9193,6 +9623,7 @@ locale.vi = { "reset": "đặt lại" }, "restore": { + "heading": "Bạn có thay đổi chưa lưu", "description": "Bạn có thay đổi chưa lưu từ một phiên làm việc trước đây. Bạn có muốn khôi phục các thay đổi này không?", "restore": "Khôi phục", "reset": "Đặt lại" @@ -9211,6 +9642,7 @@ locale.vi = { }, "source_switch": { "live": "thật", + "lose_changes": "Bạn có các thay đổi chưa lưu. Các thay đổi này sẽ bị mất khi bạn đổi máy chủ bản đồ. Bạn có chắc chắn muốn đổi máy chủ?", "dev": "thử" }, "tag_reference": { @@ -9237,6 +9669,9 @@ locale.vi = { "local_layer": "Tập tin GPX địa phương", "drag_drop": "Kéo thả một tập tin .gpx vào trang" }, + "help": { + "title": "Trợ giúp" + }, "presets": { "fields": { "access": { @@ -9346,6 +9781,9 @@ locale.vi = { "maxspeed": { "label": "Tốc độ Tối đa" }, + "name": { + "label": "Tên" + }, "natural": { "label": "Thiên nhiên" }, @@ -9361,6 +9799,9 @@ locale.vi = { "oneway": { "label": "Một chiều" }, + "oneway_yes": { + "label": "Một chiều" + }, "opening_hours": { "label": "Giờ Mở cửa" }, @@ -9661,18 +10102,33 @@ locale.vi = { "highway/motorway": { "name": "Đường Cao tốc" }, + "highway/motorway_link": { + "name": "Nhánh Ra vào Đường Cao tốc", + "terms": "đường nhánh,đoạn nhánh,đường nhánh rẽ,đoạn nhánh rẽ,đường nhánh chuyển đường,nhánh chuyển đường,lối ra vào,lối ra,lối vào,nhánh ra,nhánh vào,đường nối" + }, "highway/path": { "name": "Lối" }, "highway/primary": { "name": "Đường Chính" }, + "highway/primary_link": { + "name": "Nhánh Ra vào Đường Chính", + "terms": "đường nhánh,đoạn nhánh,đường nhánh rẽ,đoạn nhánh rẽ,đường nhánh chuyển đường,nhánh chuyển đường,lối ra vào,lối ra,lối vào,nhánh ra,nhánh vào,đường nối" + }, "highway/residential": { "name": "Ngõ Dân cư" }, + "highway/road": { + "name": "Đường Nói chung" + }, "highway/secondary": { "name": "Đường Lớn" }, + "highway/secondary_link": { + "name": "Nhánh Ra vào Đường Lớn", + "terms": "đường nhánh,đoạn nhánh,đường nhánh rẽ,đoạn nhánh rẽ,đường nhánh chuyển đường,nhánh chuyển đường,lối ra vào,lối ra,lối vào,nhánh ra,nhánh vào,đường nối" + }, "highway/service": { "name": "Ngách" }, @@ -9683,6 +10139,10 @@ locale.vi = { "highway/tertiary": { "name": "Phố" }, + "highway/tertiary_link": { + "name": "Nhánh Ra vào Phố", + "terms": "đường nhánh,đoạn nhánh,đường nhánh rẽ,đoạn nhánh rẽ,đường nhánh chuyển đường,nhánh chuyển đường,lối ra vào,lối ra,lối vào,nhánh ra,nhánh vào,đường nối" + }, "highway/track": { "name": "Đường mòn" }, @@ -9693,6 +10153,10 @@ locale.vi = { "highway/trunk": { "name": "Xa lộ" }, + "highway/trunk_link": { + "name": "Nhánh Ra vào Xa lộ", + "terms": "đường nhánh,đoạn nhánh,đường nhánh rẽ,đoạn nhánh rẽ,đường nhánh chuyển đường,nhánh chuyển đường,lối ra vào,lối ra,lối vào,nhánh ra,nhánh vào,đường nối" + }, "highway/turning_circle": { "name": "Cuối đường Vòng tròn" }, diff --git a/data/presets/fields.json b/data/presets/fields.json index bf31d2fe1..841321562 100644 --- a/data/presets/fields.json +++ b/data/presets/fields.json @@ -372,6 +372,7 @@ "key": "website", "type": "url", "icon": "website", + "placeholder": "http://example.com/", "universal": true, "label": "Website" }, diff --git a/data/presets/fields/website.json b/data/presets/fields/website.json index 90e1cd1d1..1fcfaa6cf 100644 --- a/data/presets/fields/website.json +++ b/data/presets/fields/website.json @@ -2,6 +2,7 @@ "key": "website", "type": "url", "icon": "website", + "placeholder": "http://example.com/", "universal": true, "label": "Website" -} \ No newline at end of file +} diff --git a/data/presets/schema/field.json b/data/presets/schema/field.json index f852288c5..253ab79ff 100644 --- a/data/presets/schema/field.json +++ b/data/presets/schema/field.json @@ -59,9 +59,12 @@ "icon": { "type": "string" }, + "placeholder": { + "type": "string" + }, "strings": { "type": "object" } }, "additionalProperties": false -} \ No newline at end of file +} diff --git a/img/bing.png b/img/bing.png deleted file mode 100644 index a5a5c38dd..000000000 Binary files a/img/bing.png and /dev/null differ diff --git a/img/bing_maps.png b/img/bing_maps.png new file mode 100644 index 000000000..bbc4b1dfa Binary files /dev/null and b/img/bing_maps.png differ diff --git a/img/source/sprite.svg b/img/source/sprite.svg index 32ee46f77..52a403fae 100644 --- a/img/source/sprite.svg +++ b/img/source/sprite.svg @@ -9,7 +9,7 @@ xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="460" + width="500" height="320" id="svg12393" version="1.1" @@ -39,8 +39,8 @@ inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="1" - inkscape:cx="237.92081" - inkscape:cy="230.70164" + inkscape:cx="319.99979" + inkscape:cy="247.06899" inkscape:document-units="px" inkscape:current-layer="layer12" showgrid="false" @@ -53,10 +53,11 @@ fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0" - showguides="false" + showguides="true" inkscape:guide-bbox="true" inkscape:snap-bbox="true" - inkscape:snap-nodes="false"> + inkscape:snap-nodes="true" + inkscape:snap-global="true"> + + + @@ -201,7 +214,7 @@ image/svg+xml - + @@ -214,8 +227,8 @@ @@ -351,14 +364,9 @@ id="path3769" inkscape:connector-curvature="0" sodipodi:nodetypes="ccccccccc" /> - + + + + + + + + + + diff --git a/img/sprite.png b/img/sprite.png index 746c18725..a82be2edf 100644 Binary files a/img/sprite.png and b/img/sprite.png differ diff --git a/img/sprite2x.png b/img/sprite2x.png new file mode 100644 index 000000000..a82be2edf Binary files /dev/null and b/img/sprite2x.png differ diff --git a/index.html b/index.html index b4c04fd2f..c682f017b 100644 --- a/index.html +++ b/index.html @@ -23,6 +23,7 @@ + @@ -73,6 +74,7 @@ + @@ -81,7 +83,6 @@ - diff --git a/js/id/actions/orthogonalize.js b/js/id/actions/orthogonalize.js index 41fca79e3..9b12714f0 100644 --- a/js/id/actions/orthogonalize.js +++ b/js/id/actions/orthogonalize.js @@ -6,34 +6,54 @@ iD.actions.Orthogonalize = function(wayId, projection) { var action = function(graph) { var way = graph.entity(wayId), nodes = graph.childNodes(way), - points = nodes.map(function(n) { return projection(n.loc); }), - best, i, j; + corner = {i: 0, dotp: 1}, + points, i, j, score, motions; - var score = squareness(); - for (i = 0; i < 1000; i++) { - var motions = points.map(stepMap); - for (j = 0; j < motions.length; j++) { - points[j] = addPoints(points[j],motions[j]); - } - var newScore = squareness(); - if (newScore < score) { - best = _.clone(points); - score = newScore; - } - if (score < 1.0e-8) { - break; - } - } - points = best; + if (nodes.length === 4) { + points = _.uniq(nodes).map(function(n) { return projection(n.loc); }); - for (i = 0; i < points.length - 1; i++) { - graph = graph.replace(graph.entity(nodes[i].id) - .move(projection.invert(points[i]))); + for (i = 0; i < 1000; i++) { + motions = points.map(calcMotion); + points[corner.i] = addPoints(points[corner.i],motions[corner.i]); + score = corner.dotp; + if (score < 1.0e-8) { + break; + } + } + + graph = graph.replace(graph.entity(nodes[corner.i].id) + .move(projection.invert(points[corner.i]))); + } else { + var best; + points = nodes.map(function(n) { return projection(n.loc); }); + score = squareness(); + + for (i = 0; i < 1000; i++) { + motions = points.map(calcMotion); + for (j = 0; j < motions.length; j++) { + points[j] = addPoints(points[j],motions[j]); + } + var newScore = squareness(); + if (newScore < score) { + best = _.clone(points); + score = newScore; + } + if (score < 1.0e-8) { + break; + } + } + + points = best; + + for (i = 0; i < points.length - 1; i++) { + graph = graph.replace(graph.entity(nodes[i].id) + .move(projection.invert(points[i]))); + } } return graph; - function stepMap(b, i, array) { + function calcMotion(b, i, array) { var a = array[(i - 1 + array.length) % array.length], c = array[(i + 1) % array.length], p = subtractPoints(a, b), @@ -44,9 +64,15 @@ iD.actions.Orthogonalize = function(wayId, projection) { q = normalizePoint(q, 1.0); var dotp = p[0] * q[0] + p[1] * q[1]; + // nasty hack to deal with almost-straight segments (angle is closer to 180 than to 90/270). - if (dotp < -0.707106781186547) { - dotp += 1.0; + if (array.length > 3) { + if (dotp < -0.707106781186547) { + dotp += 1.0; + } + } else if (Math.abs(dotp) < corner.dotp) { + corner.i = i; + corner.dotp = Math.abs(dotp); } return normalizePoint(addPoints(p, q), 0.1 * dotp * scale); @@ -86,7 +112,7 @@ iD.actions.Orthogonalize = function(wayId, projection) { return [a[0] + b[0], a[1] + b[1]]; } - function normalizePoint(point, thickness) { + function normalizePoint(point, scale) { var vector = [0, 0]; var length = Math.sqrt(point[0] * point[0] + point[1] * point[1]); if (length !== 0) { @@ -94,8 +120,8 @@ iD.actions.Orthogonalize = function(wayId, projection) { vector[1] = point[1] / length; } - vector[0] *= thickness; - vector[1] *= thickness; + vector[0] *= scale; + vector[1] *= scale; return vector; } diff --git a/js/id/id.js b/js/id/id.js index d6696ca02..8048175e9 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -102,7 +102,13 @@ window.iD = function () { context.zoomOut = map.zoomOut; /* Background */ - var backgroundSources = iD.data.imagery.map(iD.BackgroundSource.template); + var backgroundSources = iD.data.imagery.map(function(source) { + if (source.sourcetag === 'Bing') { + return iD.BackgroundSource.Bing(source, context.background().dispatch); + } else { + return iD.BackgroundSource.template(source); + } + }); backgroundSources.push(iD.BackgroundSource.Custom); context.backgroundSources = function() { diff --git a/js/id/operations/disconnect.js b/js/id/operations/disconnect.js index e64653626..48149bb03 100644 --- a/js/id/operations/disconnect.js +++ b/js/id/operations/disconnect.js @@ -4,6 +4,7 @@ iD.operations.Disconnect = function(selection, context) { var operation = function() { context.perform(action, t('operations.disconnect.annotation')); + context.enter(iD.modes.Browse(context)); }; operation.available = function() { diff --git a/js/id/operations/orthogonalize.js b/js/id/operations/orthogonalize.js index 482c4ba5b..2e5844398 100644 --- a/js/id/operations/orthogonalize.js +++ b/js/id/operations/orthogonalize.js @@ -10,7 +10,7 @@ iD.operations.Orthogonalize = function(selection, context) { operation.available = function() { return selection.length === 1 && context.entity(entityId).type === 'way' && - _.uniq(context.entity(entityId).nodes).length > 3; + _.uniq(context.entity(entityId).nodes).length > 2; }; operation.enabled = function() { diff --git a/js/id/renderer/background.js b/js/id/renderer/background.js index 9fe8a7378..83ffc31a8 100644 --- a/js/id/renderer/background.js +++ b/js/id/renderer/background.js @@ -168,14 +168,17 @@ iD.Background = function() { } } + background.dispatch = d3.dispatch('change'); + background.source = function(_) { if (!arguments.length) return source; source = _; cache = {}; tile.scaleExtent((source.data && source.data.scaleExtent) || [1, 20]); setHash(source); + background.dispatch.change(); return background; }; - return background; + return d3.rebind(background, background.dispatch, 'on'); }; diff --git a/js/id/renderer/background_source.js b/js/id/renderer/background_source.js index 3ddc76477..46c93821f 100644 --- a/js/id/renderer/background_source.js +++ b/js/id/renderer/background_source.js @@ -29,12 +29,55 @@ iD.BackgroundSource.template = function(data) { } generator.data = data; + generator.copyrightNotices = function() {}; return generator; }; +iD.BackgroundSource.Bing = function(data, dispatch) { + // http://msdn.microsoft.com/en-us/library/ff701716.aspx + // http://msdn.microsoft.com/en-us/library/ff701701.aspx + + var bing = iD.BackgroundSource.template(data), + key = 'Arzdiw4nlOJzRwOz__qailc8NiR31Tt51dN2D7cm57NrnceZnCpgOkmJhNpGoppU', // Same as P2 and JOSM + url = 'http://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial?include=ImageryProviders&key=' + + key + '&jsonp={callback}', + providers = []; + + d3.jsonp(url, function(json) { + providers = json.resourceSets[0].resources[0].imageryProviders.map(function(provider) { + return { + attribution: provider.attribution, + areas: provider.coverageAreas.map(function(area) { + return { + zoom: [area.zoomMin, area.zoomMax], + extent: iD.geo.Extent([area.bbox[1], area.bbox[0]], [area.bbox[3], area.bbox[2]]) + }; + }) + }; + }); + dispatch.change(); + }); + + bing.copyrightNotices = function(zoom, extent) { + zoom = Math.min(zoom, 21); + return providers.filter(function(provider) { + return _.any(provider.areas, function(area) { + return extent.intersects(area.extent) && + area.zoom[0] <= zoom && + area.zoom[1] >= zoom; + }); + }).map(function(provider) { + return provider.attribution; + }).join(', '); + }; + + return bing; +}; + iD.BackgroundSource.Custom = function() { - var template = window.prompt('Enter a tile template. Valid tokens are {z}, {x}, {y} for Z/X/Y scheme and {u} for quadtile scheme.'); + var template = window.prompt('Enter a tile template. ' + + 'Valid tokens are {z}, {x}, {y} for Z/X/Y scheme and {u} for quadtile scheme.'); if (!template) return null; return iD.BackgroundSource.template({ template: template, diff --git a/js/id/svg/surface.js b/js/id/svg/surface.js index 15ef0cca3..c37f5d89b 100644 --- a/js/id/svg/surface.js +++ b/js/id/svg/surface.js @@ -5,6 +5,17 @@ iD.svg.Surface = function() { }); } + function autosize(image) { + var img = document.createElement('img'); + img.src = image.attr('xlink:href'); + img.onload = function() { + image.attr({ + width: img.width, + height: img.height + }); + }; + } + function sprites(stylesheetName, selectorRegexp) { var sprites = []; @@ -90,12 +101,9 @@ iD.svg.Surface = function() { .attr('height', function(d) { return d; }); defs.append('image') - .attr({ - id: 'sprite', - width: 460, - height: 320, - 'xlink:href': 'img/sprite.png' - }); + .attr('id', 'sprite') + .attr('xlink:href', 'img/sprite.png') + .call(autosize); defs.selectAll() .data(sprites("app.css", /^\.(icon-operation-[a-z0-9-]+)$/)) @@ -104,20 +112,10 @@ iD.svg.Surface = function() { .attr('transform', function(d) { return "translate(" + d.x + "," + d.y + ")"; }) .attr('xlink:href', '#sprite'); - var image = defs.append('image') - .attr({ - id: 'maki-sprite', - 'xlink:href': 'img/feature-icons.png' - }); - - var img = document.createElement('img'); - img.src = 'img/feature-icons.png'; - img.onload = function() { - image.attr({ - width: img.width, - height: img.height - }); - }; + defs.append('image') + .attr('id', 'maki-sprite') + .attr('xlink:href', 'img/feature-icons.png') + .call(autosize); defs.selectAll() .data(sprites("feature-icons.css", /^\.(feature-[a-z0-9-]+-(12|18))$/)) diff --git a/js/id/svg/vertices.js b/js/id/svg/vertices.js index 0418f63af..2e0982a00 100644 --- a/js/id/svg/vertices.js +++ b/js/id/svg/vertices.js @@ -69,13 +69,13 @@ iD.svg.Vertices = function(projection, context) { groups.select('circle.shadow') .each(center) .attr('r', function(entity) { - return radiuses.shadow[icon(entity) ? 3 : zoom] + return radiuses.shadow[icon(entity) ? 3 : zoom]; }); groups.select('circle.stroke') .each(center) .attr('r', function(entity) { - return radiuses.stroke[icon(entity) ? 3 : zoom] + return radiuses.stroke[icon(entity) ? 3 : zoom]; }); // Each vertex gets either a circle or a use, depending diff --git a/js/id/ui.js b/js/id/ui.js index 158cfa0f4..6924412d4 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -52,6 +52,10 @@ iD.ui = function(context) { .attr('class', 'spinner') .call(iD.ui.Spinner(context)); + container.append('div') + .style('display', 'none') + .attr('class', 'help-wrap fillL col5'); + container.append('div') .attr('class', 'map-control zoombuttons') .call(iD.ui.Zoom(context)); @@ -68,10 +72,19 @@ iD.ui = function(context) { .attr('class', 'map-control geolocate-control') .call(iD.ui.Geolocate(map)); + container.append('div') + .attr('class', 'map-control help-control') + .call(iD.ui.Help(context)); + container.append('div') .style('display', 'none') .attr('class', 'inspector-wrap fr content col4'); + container.append('idv') + .attr('class', 'attribution') + .attr('tabindex', -1) + .call(iD.ui.Attribution(context)); + var about = container.append('div') .attr('class','col12 about-block fillD'); @@ -97,12 +110,6 @@ iD.ui = function(context) { .attr('href', 'https://help.openstreetmap.org/questions/ask/') .text(t('report_a_bug')); - linkList.append('li') - .attr('class', 'attribution') - .attr('tabindex', -1) - .data([context.background().source()]) - .call(iD.ui.Attribution(context)); - linkList.append('li') .attr('class', 'source-switch') .call(iD.ui.SourceSwitch(context)); diff --git a/js/id/ui/attribution.js b/js/id/ui/attribution.js index ec98a84b8..9c4ecb7a7 100644 --- a/js/id/ui/attribution.js +++ b/js/id/ui/attribution.js @@ -1,6 +1,8 @@ iD.ui.Attribution = function(context) { - return function attribution(selection) { - var d = selection.data()[0]; + var selection; + + function update() { + var d = context.background().source(); var provided_by = selection .html('') @@ -9,18 +11,36 @@ iD.ui.Attribution = function(context) { if (!d) return; - var desc = t('imagery.provided_by', { - source: (d.data.sourcetag || d.data.name) - }); + var source = d.data.sourcetag || d.data.name; + if (d.data.logo) { + source = ''; + } if (d.data.terms_url) { provided_by.append('a') - .attr('href', (d.data.terms_url || '')) + .attr('href', d.data.terms_url) .attr('target', '_blank') - .classed('disabled', !d.data.terms_url) - .text(desc); + .html(source); } else { - provided_by.text(desc); + provided_by.text(source); } + + var copyright = d.copyrightNotices(context.map().zoom(), context.map().extent()); + if (copyright) { + provided_by.append('span') + .text(copyright); + } + } + + return function(select) { + selection = select; + + context.background() + .on('change.attribution', update); + + context.map() + .on('move.attribution', _.throttle(update, 400)); + + update(); }; }; diff --git a/js/id/ui/background.js b/js/id/ui/background.js index 7e23e94e5..0595875c2 100644 --- a/js/id/ui/background.js +++ b/js/id/ui/background.js @@ -1,6 +1,5 @@ iD.ui.Background = function(context) { - var event = d3.dispatch('cancel', 'save'), - key = 'b', + var key = 'b', opacities = [1, 0.5, 0], directions = [ ['left', [1, 0]], @@ -57,16 +56,11 @@ iD.ui.Background = function(context) { } } - function selectLayer(d) { + function selectLayer() { content.selectAll('a.layer') .classed('selected', function(d) { return d.data.name === context.background().source().data.name; }); - - context.container() - .select('.attribution') - .data([d]) - .call(iD.ui.Attribution(context)); } function clickSetSource(d) { @@ -85,7 +79,7 @@ iD.ui.Background = function(context) { .imagery_used(d.data.sourcetag || d.data.name); } context.redraw(); - selectLayer(d); + selectLayer(); } function clickGpx(d) { @@ -139,7 +133,7 @@ iD.ui.Background = function(context) { layerLinks.exit() .remove(); - selectLayer(context.background().source()); + selectLayer(); } function clickNudge(d) { @@ -289,5 +283,5 @@ iD.ui.Background = function(context) { .call(keybinding); } - return d3.rebind(background, event, 'on'); + return background; }; diff --git a/js/id/ui/commit.js b/js/id/ui/commit.js index 8bba39cb2..324ce192f 100644 --- a/js/id/ui/commit.js +++ b/js/id/ui/commit.js @@ -36,10 +36,10 @@ iD.ui.Commit = function(context) { // Comment Section var commentSection = body.append('div') - .attr('class', 'modal-section preset-field'); + .attr('class', 'modal-section form-field'); - commentSection.append('h4') - .attr('for','input-commit-note') + commentSection.append('label') + .attr('class','form-label') .text(t('commit.message_label')); var commentField = commentSection diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index d41228ca2..9206ff53c 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -82,6 +82,10 @@ iD.ui.Geocoder = function(context) { gcForm.call(iD.ui.Toggle(show)); if (!show && !resultsList.classed('hide')) { resultsList.call(iD.ui.Toggle(show)); + // remove results so that they lose focus. if the user has + // tabbed into the list, then they will have focus still, + // even if they're hidden. + resultsList.selectAll('span').remove(); } if (show) inputNode.node().focus(); else inputNode.node().blur(); diff --git a/js/id/ui/help.js b/js/id/ui/help.js new file mode 100644 index 000000000..6a4c1af3f --- /dev/null +++ b/js/id/ui/help.js @@ -0,0 +1,109 @@ +iD.ui.Help = function(context) { + + var key = 'h'; + + function help(selection) { + + var shown = false, pane; + + function setup() { + pane = context.container() + .select('.help-wrap') + .html(''); + + var toc = pane.append('ul') + .attr('class', 'toc'); + + function clickHelp(d) { + doctitle.text(d.title); + body.html(d.html); + body.selectAll('a') + .attr('target', '_blank'); + menuItems.classed('selected', function(m) { + return m.title === d.title; + }); + } + + var menuItems = toc.selectAll('li') + .data(iD.data.doc) + .enter() + .append('li') + .append('a') + .text(function(d) { return d.title; }) + .on('click', clickHelp); + + var content = pane.append('div') + .attr('class', 'left-content'), + doctitle = content.append('h2') + .text(t('help.title')), + body = content.append('div') + .attr('class', 'body'); + + clickHelp(iD.data.doc[0]); + } + + function hide() { setVisible(false); } + function toggle() { + if (d3.event) d3.event.preventDefault(); + tooltip.hide(button); + setVisible(!button.classed('active')); + } + + function blockClick() { + pane.on('mousedown.help-inside', function() { + return d3.event.stopPropagation(); + }); + selection.on('mousedown.help-inside', function() { + return d3.event.stopPropagation(); + }); + } + + function setVisible(show) { + if (show !== shown) { + button.classed('active', show); + shown = show; + if (show) { + pane.style('display', 'block') + .style('left', '-500px') + .transition() + .duration(200) + .style('left', '0px') + .each('end', blockClick); + } else { + pane.style('left', '0px') + .transition() + .duration(200) + .style('left', '-500px') + .each('end', function() { + d3.select(this).style('display', 'none'); + }); + pane.on('mousedown.help-inside', null); + } + } + } + + var tooltip = bootstrap.tooltip() + .placement('right') + .html(true) + .title(iD.ui.tooltipHtml(t('help.title'), key)); + + var button = selection.append('button') + .attr('tabindex', -1) + .on('click', toggle) + .call(tooltip); + + button.append('span') + .attr('class', 'icon help light'); + + context.surface().on('mousedown.help-outside', hide); + context.container().on('mousedown.b.help-outside', hide); + + setup(); + + var keybinding = d3.keybinding('help'); + keybinding.on(key, toggle); + d3.select(document).call(keybinding); + } + + return help; +}; diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js index b635b2d31..2f2751148 100644 --- a/js/id/ui/inspector.js +++ b/js/id/ui/inspector.js @@ -2,7 +2,8 @@ iD.ui.Inspector = function(context, entity) { var tagEditor; function changeTags(tags) { - if (!_.isEqual(entity.tags, tags)) { + entity = context.entity(entity.id); + if (entity && !_.isEqual(entity.tags, tags)) { context.perform( iD.actions.ChangeTags(entity.id, tags), t('operations.change_tags.annotation')); diff --git a/js/id/ui/key_reference.js b/js/id/ui/key_reference.js deleted file mode 100644 index c5e2b206f..000000000 --- a/js/id/ui/key_reference.js +++ /dev/null @@ -1,37 +0,0 @@ -iD.ui.keyReference = function(selection) { - selection.each(function() { - - var selection = d3.select(this), - data = selection.datum(), - header = selection.append('div') - .attr('class','modal-section fillL') - .append('h2'), - body = selection.append('div') - .attr('class', 'modal-section fillL2'); - - header.append('span').attr('class', 'icon big icon-pre-text big-' + data.geometry); - header.append('span').text(data.title); - body.append('h3').text('Common Values'); - - var table = body.append('table') - .attr('class', 'tags'), - thead = table.append('thead'); - - thead.append('th').text('Value'); - thead.append('th').text('Description'); - thead.append('th').text('Count'); - - var rows = table.selectAll('tr') - .data(data.data) - .enter() - .append('tr'); - - var cols = rows.selectAll('td') - .data(function(d) { - return [d.value, d.description || "", d.count]; - }) - .enter() - .append('td') - .text(String); - }); -}; diff --git a/js/id/ui/preset.js b/js/id/ui/preset.js index f69517842..308005835 100644 --- a/js/id/ui/preset.js +++ b/js/id/ui/preset.js @@ -1,109 +1,212 @@ -iD.ui.preset = function(context, entity) { - var event = d3.dispatch('change', 'setTags', 'close'), - tags, - keys, - preset, +iD.ui.preset = function(context, entity, preset) { + var original = context.graph().base().entities[entity.id], + event = d3.dispatch('change', 'close'), + fields = [], + tags = {}, formwrap, formbuttonwrap; - function presets(selection) { - selection.html(''); + function UIField(field, show) { + field = _.clone(field); - keys = []; - formwrap = selection.append('div'); + field.input = iD.ui.preset[field.type](field, context) + .on('close', event.close) + .on('change', event.change); - var geometry = entity.geometry(context.graph()), - fields = preset.fields.filter(function(f) { - return f.matchGeometry(geometry); + if (field.type === 'address') { + field.input.entity(entity); + } + + field.keys = field.keys || [field.key]; + + field.show = show; + + field.shown = function() { + return field.id === 'name' || field.show || _.any(field.keys, function(key) { return !!tags[key]; }); + }; + + field.modified = function() { + return _.any(field.keys, function(key) { + return original ? tags[key] !== original.tags[key] : tags[key]; + }); + }; + + return field; + } + + fields.push(UIField(context.presets().field('name'))); + + var geometry = entity.geometry(context.graph()); + preset.fields.forEach(function(field) { + if (field.matchGeometry(geometry)) { + fields.push(UIField(field, true)); + } + }); + + context.presets().universal().forEach(function(field) { + if (fields.indexOf(field) < 0) { + fields.push(UIField(field)); + } + }); + + function fieldKey(field) { + return field.id; + } + + function shown() { + return fields.filter(function(field) { return field.shown(); }); + } + + function notShown() { + return fields.filter(function(field) { return !field.shown(); }); + } + + function show(field) { + field.show = true; + render(); + field.input.focus(); + } + + function revert(field) { + d3.event.stopPropagation(); + d3.event.preventDefault(); + var t = {}; + field.keys.forEach(function(key) { + t[key] = original ? original.tags[key] : undefined; + }); + event.change(t); + } + + function toggleReference(field) { + d3.event.stopPropagation(); + d3.event.preventDefault(); + _.forEach(fields, function(other) { + if (other.id === field.id) { + other.showingReference = !other.showingReference; + } else { + other.showingReference = false; + } + }); + + render(); + } + + function render() { + var selection = formwrap.selectAll('.form-field') + .data(shown(), fieldKey); + + var enter = selection.enter() + .insert('div', '.more-buttons') + .style('opacity', 0) + .attr('class', function(field) { + return 'form-field form-field-' + field.id + ' fillL col12'; }); - fields.unshift(context.presets().field('name')); + enter.transition() + .style('max-height', '0px') + .style('padding-top', '0px') + .style('opacity', '0') + .transition() + .duration(200) + .style('padding-top', '20px') + .style('max-height', '200px') + .style('opacity', '1'); - draw(formwrap, fields); + var label = enter.append('label') + .attr('class', 'form-label') + .attr('for', function(field) { return 'preset-input-' + field.id; }) + .text(function(field) { return field.label(); }); - var wrap = selection.append('div') - .attr('class', 'col12 more-buttons inspector-inner'); + label.append('button') + .attr('class', 'tag-reference-button fr') + .attr('tabindex', -1) + .on('click', toggleReference) + .append('span') + .attr('class', 'icon inspect'); - formbuttonwrap = wrap.append('div') - .attr('class', 'col12 preset-input'); + label.append('button') + .attr('class', 'fr modified-icon') + .attr('tabindex', -1) + .on('click', revert) + .append('div') + .attr('class','icon undo'); - formbuttonwrap.selectAll('button') - .data(context.presets().universal().filter(notInForm)) - .enter() + enter.each(function(field) { + d3.select(this).call(field.input); + }); + + enter.append('div') + .attr('class', 'tag-help'); + + selection + .classed('modified', function(field) { + return field.modified(); + }); + + selection.selectAll('.tag-help') + .style('display', function(field) { + return field.showingReference ? 'block' : 'block'; + }) + .each(function(field) { + if (field.showingReference) { + d3.select(this) + .call(iD.ui.TagReference(entity, {key: field.key})) + .style('max-height', '0px') + .style('padding-top', '0px') + .style('opacity', '0') + .transition() + .duration(200) + .style('padding-top', '20px') + .style('max-height', '200px') + .style('opacity', '1'); + } else { + d3.select(this) + .call(iD.ui.TagReference(entity, {key: field.key})) + .transition() + .duration(200) + .style('max-height', '0px') + .style('padding-top', '0px') + .style('opacity', '0'); + } + }); + + selection.exit() + .remove(); + + var addFields = formbuttonwrap.selectAll('.preset-add-field') + .data(notShown(), fieldKey); + + addFields.enter() .append('button') .attr('class', 'preset-add-field') - .on('click', addForm) + .on('click', show) .call(bootstrap.tooltip() .placement('top') .title(function(d) { return d.label(); })) .append('span') .attr('class', function(d) { return 'icon ' + d.icon; }); - function notInForm(p) { - return preset.fields.indexOf(p) < 0; - } + addFields.exit() + .transition() + .style('opacity', 0) + .remove(); - function addForm(d) { - draw(formwrap, [d]); - - d3.select(this) - .style('opacity', 1) - .transition() - .style('opacity', 0) - .remove(); - - if (!wrap.selectAll('button').node()) { - wrap.remove(); - } - } + return selection; } - function draw(selection, fields) { - var sections = selection.selectAll('div.preset-field') - .data(fields, function(field) { return field.id; }) - .enter() - .append('div') - .style('opacity', 0) - .attr('class', function(field) { - return 'preset-field preset-field-' + field.id + ' fillL inspector-inner col12'; - }); + function presets(selection) { + selection.html(''); - sections.append('h4') - .attr('for', function(d) { return 'input-' + d.key; }) - .text(function(d) { return d.label(); }) - .append('button') - .attr('class', 'fr icon undo modified-icon') - .on('click', function(d) { - var original = context.graph().base().entities[entity.id]; - var t = {}; - (d.keys || [d.key]).forEach(function(key) { - t[key] = original ? original.tags[key] : undefined; - }); - event.change(t); - }); + formwrap = selection; - sections.transition() - .style('opacity', 1); + formbuttonwrap = selection.append('div') + .attr('class', 'col12 more-buttons inspector-inner'); - sections.each(function(field) { - var i = iD.ui.preset[field.type](field, context) - .on('close', event.close) - .on('change', event.change); - - event.on('setTags.' + field.key || field.type, function (tags) { - i.tags(_.clone(tags)); - }); - - if (field.type === 'address') i.entity(entity); - - keys = keys.concat(field.key ? [field.key] : field.keys); - - d3.select(this).call(i); - }); + render(); } presets.rendered = function() { - return keys; + return _.flatten(shown().map(function(field) { return field.keys; })); }; presets.preset = function(_) { @@ -112,27 +215,17 @@ iD.ui.preset = function(context, entity) { return presets; }; - presets.change = function(t) { - tags = t; + presets.change = function(_) { + tags = _; - function haveKey(k) { return k && !!tags[k]; } - - formbuttonwrap.selectAll('button').each(function(p) { - if (haveKey(p.key) || _.any(p.keys, haveKey)) { - draw(formwrap, [p]); - d3.select(this).remove(); + fields.forEach(function(field) { + if (field.shown()) { + field.input.tags(_); } }); - formwrap.selectAll('div.preset-field') - .classed('modified', function(d) { - var original = context.graph().base().entities[entity.id]; - return _.any(d.keys || [d.key], function(key) { - return original ? tags[key] !== original.tags[key] : tags[key]; - }); - }); + render(); - event.setTags(tags); return presets; }; diff --git a/js/id/ui/preset/address.js b/js/id/ui/preset/address.js index b3d947fbe..0abf3efc3 100644 --- a/js/id/ui/preset/address.js +++ b/js/id/ui/preset/address.js @@ -41,15 +41,19 @@ iD.ui.preset.address = function(field, context) { function close() { return iD.behavior.accept().on('accept', event.close); } - housename = selection.append('input') + var wrap = selection.append('div') + .attr('class', 'preset-input-wrap'); + + housename = wrap.append('input') .property('type', 'text') .attr('placeholder', field.t('placeholders.housename')) .attr('class', 'addr-housename') + .attr('id', 'preset-input-' + field.id) .on('blur', change) .on('change', change) .call(close()); - housenumber = selection.append('input') + housenumber = wrap.append('input') .property('type', 'text') .attr('placeholder', field.t('placeholders.number')) .attr('class', 'addr-number') @@ -57,25 +61,21 @@ iD.ui.preset.address = function(field, context) { .on('change', change) .call(close()); - var streetwrap = selection.append('span') - .attr('class', 'input-wrap-position'); - - street = streetwrap.append('input') + street = wrap.append('input') .property('type', 'text') .attr('placeholder', field.t('placeholders.street')) .attr('class', 'addr-street') .on('blur', change) - .on('change', change); + .on('change', change) + .call(d3.combobox().data(getStreets())); - city = selection.append('input') + city = wrap.append('input') .property('type', 'text') .attr('placeholder', field.t('placeholders.city')) .attr('class', 'addr-city') .on('blur', change) .on('change', change) .call(close()); - - streetwrap.call(d3.combobox().data(getStreets())); } function change() { @@ -101,5 +101,9 @@ iD.ui.preset.address = function(field, context) { return address; }; + address.focus = function() { + housename.node().focus(); + }; + return d3.rebind(address, event, 'on'); }; diff --git a/js/id/ui/preset/check.js b/js/id/ui/preset/check.js index 4c46cda91..19f9031d0 100644 --- a/js/id/ui/preset/check.js +++ b/js/id/ui/preset/check.js @@ -11,11 +11,13 @@ iD.ui.preset.check = function(field) { selection.classed('checkselect', 'true'); - label = selection.append('label'); + label = selection.append('label') + .attr('class', 'preset-input-wrap'); box = label.append('input') .property('indeterminate', true) - .attr('type', 'checkbox'); + .attr('type', 'checkbox') + .attr('id', 'preset-input-' + field.id); text = label.append('span') .text('unknown') @@ -38,5 +40,9 @@ iD.ui.preset.check = function(field) { label.classed('set', !!value); }; + check.focus = function() { + box.node().focus(); + }; + return d3.rebind(check, event, 'on'); }; diff --git a/js/id/ui/preset/combo.js b/js/id/ui/preset/combo.js index d1d0fe70e..6ac73195d 100644 --- a/js/id/ui/preset/combo.js +++ b/js/id/ui/preset/combo.js @@ -1,20 +1,17 @@ iD.ui.preset.combo = function(field) { var event = d3.dispatch('change', 'close'), - wrap, input; function combo(selection) { - - wrap = this.append('span').attr('class', 'input-wrap-position'); - - input = wrap.append('input') - .attr('type', 'text') - .on('change', change) - .on('blur', change); - var combobox = d3.combobox(); - wrap.call(combobox); + + input = selection.append('input') + .attr('type', 'text') + .attr('id', 'preset-input-' + field.id) + .on('change', change) + .on('blur', change) + .call(combobox); if (field.options) { options(field.options); @@ -51,5 +48,9 @@ iD.ui.preset.combo = function(field) { input.property('value', tags[field.key] || ''); }; + combo.focus = function() { + input.node().focus(); + }; + return d3.rebind(combo, event, 'on'); }; diff --git a/js/id/ui/preset/defaultcheck.js b/js/id/ui/preset/defaultcheck.js index 021f0862f..cf3c5d589 100644 --- a/js/id/ui/preset/defaultcheck.js +++ b/js/id/ui/preset/defaultcheck.js @@ -7,7 +7,7 @@ iD.ui.preset.defaultcheck = function(field) { input = selection.append('input') .attr('type', 'checkbox') - .attr('id', 'input-' + field.key) + .attr('id', 'preset-input-' + field.id) .on('change', function() { var t = {}; t[field.key] = input.property('checked') ? field.value || 'yes' : undefined; @@ -19,5 +19,9 @@ iD.ui.preset.defaultcheck = function(field) { input.property('checked', !!tags[field.key] && tags[field.key] !== 'no'); }; + check.focus = function() { + input.node().focus(); + }; + return d3.rebind(check, event, 'on'); }; diff --git a/js/id/ui/preset/input.js b/js/id/ui/preset/input.js index 4ac69f450..7111f8fdc 100644 --- a/js/id/ui/preset/input.js +++ b/js/id/ui/preset/input.js @@ -10,6 +10,7 @@ iD.ui.preset.url = function(field) { function i(selection) { input = selection.append('input') .attr('type', field.type) + .attr('id', 'preset-input-' + field.id) .attr('placeholder', field.placeholder || '') .on('blur', change) .on('change', change) @@ -51,5 +52,9 @@ iD.ui.preset.url = function(field) { input.property('value', tags[field.key] || ''); }; + i.focus = function() { + input.node().focus(); + }; + return d3.rebind(i, event, 'on'); }; diff --git a/js/id/ui/preset/radio.js b/js/id/ui/preset/radio.js index 24036a375..6f8171ec6 100644 --- a/js/id/ui/preset/radio.js +++ b/js/id/ui/preset/radio.js @@ -6,18 +6,18 @@ iD.ui.preset.radio = function(field) { function radio(selection) { selection.classed('preset-radio', true); - var buttonwrap = selection.append('div').attr('class','radio-wrap'); + var buttonwrap = selection.append('div') + .attr('class', 'preset-input-wrap radio-wrap'); buttons = buttonwrap.selectAll('button') .data(field.keys || field.options) .enter() .append('button') - .text(function(d) { return field.t('options.' + d, { 'default': d }); }) - .on('click', function() { - buttons.classed('active', false); - d3.select(this).classed('active', true); - change(); - }); + .text(function(d) { return field.t('options.' + d, { 'default': d }); }) + .on('click', function(d) { + buttons.classed('active', function(e) { return d === e; }); + change(); + }); buttonwrap.append('button') .on('click', function() { @@ -52,5 +52,9 @@ iD.ui.preset.radio = function(field) { }); }; + radio.focus = function() { + buttons.node().focus(); + }; + return d3.rebind(radio, event, 'on'); }; diff --git a/js/id/ui/preset/textarea.js b/js/id/ui/preset/textarea.js index d27375343..03986358b 100644 --- a/js/id/ui/preset/textarea.js +++ b/js/id/ui/preset/textarea.js @@ -5,6 +5,7 @@ iD.ui.preset.textarea = function(field) { function i(selection) { input = selection.append('textarea') + .attr('id', 'preset-input-' + field.id) .attr('placeholder', field.placeholder || '') .attr('maxlength', 255) .on('blur', change) @@ -22,5 +23,9 @@ iD.ui.preset.textarea = function(field) { input.text(tags[field.key] || ''); }; + i.focus = function() { + input.node().focus(); + }; + return d3.rebind(i, event, 'on'); }; diff --git a/js/id/ui/preset_grid.js b/js/id/ui/preset_grid.js index 004de7510..41e8330fb 100644 --- a/js/id/ui/preset_grid.js +++ b/js/id/ui/preset_grid.js @@ -1,9 +1,8 @@ iD.ui.PresetGrid = function(context, entity) { var event = d3.dispatch('choose', 'close'), - default_limit = 9, - currently_drawn = 9, - presets, - taginfo = iD.taginfo(); + defaultLimit = 9, + currentlyDrawn = 9, + presets; function presetgrid(selection, preset) { @@ -15,7 +14,7 @@ iD.ui.PresetGrid = function(context, entity) { .attr('class', 'header fillL cf'); var message = messagewrap.append('h3') - .attr('class', 'inspector-inner fl') + .attr('class', 'inspector-inner') .text(t('inspector.choose')); if (preset) { @@ -39,14 +38,14 @@ iD.ui.PresetGrid = function(context, entity) { .attr('class', 'preset-grid fillL cf') .data([context.presets().defaults(entity, 36).collection]); - var show_more = gridwrap.append('button') + var showMore = gridwrap.append('button') .attr('class', 'fillL show-more') .text(t('inspector.show_more')) .on('click', function() { - grid.call(drawGrid, (currently_drawn += default_limit)); + grid.call(drawGrid, (currentlyDrawn += defaultLimit)); }); - grid.call(drawGrid, default_limit); + grid.call(drawGrid, defaultLimit); var searchwrap = selection.append('div') .attr('class', 'preset-grid-search-wrap inspector-inner'); @@ -76,7 +75,7 @@ iD.ui.PresetGrid = function(context, entity) { if (d3.event.keyCode === 13 && value.length) { choose(grid.selectAll('.grid-entry:first-child').datum()); } else { - currently_drawn = default_limit; + currentlyDrawn = defaultLimit; grid.classed('filtered', value.length); if (value.length) { var results = presets.search(value); @@ -85,10 +84,10 @@ iD.ui.PresetGrid = function(context, entity) { search: value })); grid.data([results.collection]) - .call(drawGrid, default_limit); + .call(drawGrid, defaultLimit); } else { grid.data([context.presets().defaults(entity, 36).collection]) - .call(drawGrid, default_limit); + .call(drawGrid, defaultLimit); } } } @@ -115,7 +114,7 @@ iD.ui.PresetGrid = function(context, entity) { .attr('class', 'arrow'); subgrid.append('div') - .attr('class', 'preset-grid fillL cf fl') + .attr('class', 'preset-grid fillL3 cf fl') .data([d.members.collection]) .call(drawGrid, 1000); @@ -164,10 +163,7 @@ iD.ui.PresetGrid = function(context, entity) { .style('max-height', '0px') .style('padding-top', '0px') .style('padding-bottom', '0px') - .each('end', function() { - shown.remove(); - }); - + .remove(); if (shown.datum() === entity && shown.classed(klass)) return; shownIndex = Array.prototype.indexOf.call(shown.node().parentNode.childNodes, shown.node()); @@ -203,50 +199,29 @@ iD.ui.PresetGrid = function(context, entity) { .style('max-height', '0px') .style('padding-top', '0px') .style('padding-bottom', '0px') + .style('opacity', '0') .transition() .duration(200) .style('padding-top', '10px') .style('padding-bottom', '20px') - .style('max-height', '200px'); + .style('max-height', '200px') + .style('opacity', '1'); - presetinspect.append('h2') + presetinspect.append('h3') .text(d.name()); - var description = presetinspect.append('p'); - var link = presetinspect.append('a'); + var tag = {key: Object.keys(d.tags)[0]}; - var params = {}, - locale = iD.detect().locale.split('-')[0] || 'en'; - - params.key = Object.keys(d.tags)[0]; - if (d.tags[params.key] !== '*') { - params.value = d.tags[params.key]; + if (d.tags[tag.key] !== '*') { + tag.value = d.tags[tag.key]; } - taginfo.docs(params, function(err, data) { - if (err) return description.text(t('inspector.no_documentation_combination')); - var doc = _.find(data, function(d) { return d.lang === locale; }) || - _.find(data, function(d) { return d.lang === 'en'; }); - if (doc) { - description - .text(doc.description); - link - .attr('href', 'http://wiki.openstreetmap.org/wiki/' + - encodeURIComponent(doc.title)) - .text(t('inspector.reference')); - } - }); - - presetinspect.selectAll('*') - .style('opacity','0') - .transition() - .delay(100) - .duration(200) - .style('opacity','1'); + presetinspect.append('div') + .call(iD.ui.TagReference(entity, tag)); } if (selection.node() === grid.node()) { - show_more + showMore .style('display', (selection.data()[0].length > limit) ? 'block' : 'none'); } @@ -256,20 +231,21 @@ iD.ui.PresetGrid = function(context, entity) { .selectAll('div.grid-entry-wrap') .data(function(d) { return d.slice(0, limit); }, name); - entries.exit().remove(); + entries.exit() + .remove(); var entered = entries.enter() .append('div') .attr('class','grid-button-wrap col4 grid-entry-wrap') .classed('category', function(d) { return !!d.members; }) .classed('current', function(d) { return d === preset; }) - .append('button') - .attr('class', 'grid-entry') - .on('click', choose); + .append('button') + .attr('class', 'grid-entry') + .on('click', choose); entered.style('opacity', 0) - .transition() - .style('opacity', 1); + .transition() + .style('opacity', 1); entered.append('div') .attr('class', presetClass); @@ -286,15 +262,13 @@ iD.ui.PresetGrid = function(context, entity) { .attr('class','label') .text(name); - entered.filter(function(d) { - return !d.members; - }) + entered.filter(function(d) { return !d.members; }) .append('button') .attr('tabindex', -1) - .attr('class', 'preset-help') + .attr('class', 'tag-reference-button') .on('click', helpClick, selection) .append('span') - .attr('class', 'icon inspect'); + .attr('class', 'icon inspect light'); entries.order(); } diff --git a/js/id/ui/restore.js b/js/id/ui/restore.js index 72ffa76d8..b1dd64e33 100644 --- a/js/id/ui/restore.js +++ b/js/id/ui/restore.js @@ -6,21 +6,29 @@ iD.ui.Restore = function(context) { var modal = iD.ui.modal(selection); modal.select('.modal') - .attr('class', 'modal-splash modal'); + .attr('class', 'modal fillL col6'); var introModal = modal.select('.content'); + introModal.attr('class','cf'); + introModal.append('div') .attr('class', 'modal-section header') .append('h3') - .text(t('restore.description')); + .text(t('restore.heading')); + + introModal.append('div') + .attr('class','modal-section') + .append('p') + .text(t('restore.description')); + var buttonWrap = introModal.append('div') - .attr('class', 'modal-section cf col12'); + .attr('class', 'modal-section col12'); var buttons = buttonWrap .append('div') - .attr('class', 'button-wrap joined col6'); + .attr('class', 'button-wrap joined col4'); var restore = buttons.append('button') .attr('class', 'save action button col6') diff --git a/js/id/ui/source_switch.js b/js/id/ui/source_switch.js index 4f1afe227..8b9e2bd20 100644 --- a/js/id/ui/source_switch.js +++ b/js/id/ui/source_switch.js @@ -2,6 +2,9 @@ iD.ui.SourceSwitch = function(context) { function click() { d3.event.preventDefault(); + if (context.history().hasChanges() && + !window.confirm(t('source_switch.lose_changes'))) return; + var live = d3.select(this).classed('live'); context.connection() diff --git a/js/id/ui/spinner.js b/js/id/ui/spinner.js index c567dd257..4085d62f7 100644 --- a/js/id/ui/spinner.js +++ b/js/id/ui/spinner.js @@ -15,5 +15,5 @@ iD.ui.Spinner = function(context) { img.transition() .style('opacity', 0); }); - } + }; }; diff --git a/js/id/ui/tag_editor.js b/js/id/ui/tag_editor.js index 69d29d057..a0e942de0 100644 --- a/js/id/ui/tag_editor.js +++ b/js/id/ui/tag_editor.js @@ -26,26 +26,18 @@ iD.ui.TagEditor = function(context, entity) { var messagewrap = selection.append('div') .attr('class', 'header fillL cf'); - var back = messagewrap.append('button') - .attr('class', 'preset-reset fl ' + geometry) + messagewrap.append('button') + .attr('class', 'preset-reset fl ') .on('click', function() { event.choose(preset); - }); - - var icon = preset.icon || (geometry === 'line' ? 'other-line' : 'marker-stroked'); - - back.append('div') - .attr('class', 'col12') - .append('span') - .attr('class', 'preset-icon icon feature-' + icon); - - back.append('div') - .attr('class', 'col12') + }) .append('span') .attr('class', 'icon back'); + var icon = preset.icon || (geometry === 'line' ? 'other-line' : 'marker-stroked'); + messagewrap.append('h3') - .attr('class', 'inspector-inner fl') + .attr('class', 'inspector-inner') .text(t('inspector.editing_feature', { feature: preset.name() })); messagewrap.append('button') @@ -57,8 +49,14 @@ iD.ui.TagEditor = function(context, entity) { var editorwrap = selection.append('div') .attr('class', 'tag-wrap inspector-body fillL2 inspector-body-' + geometry); - presetUI = iD.ui.preset(context, entity) - .preset(preset) + editorwrap.append('div') + .attr('class', 'col12 inspector-inner preset-icon-wrap fillL3') + .append('div') + .attr('class','fillL') + .append('span') + .attr('class', geometry + ' preset-icon icon feature-' + icon); + + presetUI = iD.ui.preset(context, entity, preset) .on('change', changeTags) .on('close', event.close); @@ -75,7 +73,7 @@ iD.ui.TagEditor = function(context, entity) { if (!entity.isNew()) { tageditorpreset.append('div') - .attr('class', 'view-on-osm') + .attr('class', 'col12 inspector-inner') .append('a') .attr('href', 'http://www.openstreetmap.org/browse/' + entity.type + '/' + entity.osmId()) .attr('target', '_blank') diff --git a/js/id/ui/tag_reference.js b/js/id/ui/tag_reference.js index 67ae91b84..34780c0d0 100644 --- a/js/id/ui/tag_reference.js +++ b/js/id/ui/tag_reference.js @@ -1,50 +1,64 @@ -iD.ui.tagReference = function(selection) { - selection.each(function() { - function g(x) { return function(d) { return d[x]; }; } - var selection = d3.select(this); - var header = selection.append('div') - .attr('class','modal-section fillL header') - .append('h3'); +iD.ui.TagReference = function(entity, tag) { + var taginfo = iD.taginfo(); - header.selectAll('span.icon') - .data(g('types')) - .enter() - .append('span') - .attr('title', function(d) { - return t('tag_reference.used_with', {type: d}); - }) - .attr('class', function(d) { - return 'icon big icon-pre-text big-' + d; + function findLocal(docs) { + var locale = iD.detect().locale.toLowerCase(), + localized; + + localized = _.find(docs, function(d) { + return d.lang.toLowerCase() === locale; + }); + if (localized) return localized; + + // try the non-regional version of a language, like + // 'en' if the language is 'en-US' + if (locale.indexOf('-') !== -1) { + var first = locale.split('-')[0]; + localized = _.find(docs, function(d) { + return d.lang.toLowerCase() === first; }); - header.append('span') - .text(g('title')); - - var referenceBody = selection.append('div') - .attr('class','modal-section fillL2'); - - referenceBody - .append('h4') - .text(t('tag_reference.description')); - - if (selection.datum().image) { - referenceBody - .append('img') - .attr('class', 'wiki-image') - .attr('src', selection.datum().image.image_url); + if (localized) return localized; } - referenceBody - .append('p') - .text(g('description')); + // finally fall back to english + return _.find(docs, function(d) { + return d.lang.toLowerCase() === 'en'; + }); + } - referenceBody - .append('a') - .attr('target', '_blank') - .attr('href', function(d) { - return 'http://wiki.openstreetmap.org/wiki/' + d.title; - }) - .text(function(d) { - return t('tag_reference.on_wiki', {tag: d.title}); - }); - }); + return function(selection) { + selection.html(''); + + selection.classed('cf', true); + + taginfo.docs(tag, function(err, docs) { + if (!err && docs) { + docs = findLocal(docs); + } + + if (!docs || !docs.description) { + return selection.text(t('inspector.no_documentation_key')); + } + + var referenceBody = selection.append('div') + .attr('class','tag-reference-wrap'); + + if (docs.image && docs.image.thumb_url_prefix) { + referenceBody + .append('img') + .attr('class', 'wiki-image') + .attr('src', docs.image.thumb_url_prefix + "100" + docs.image.thumb_url_suffix); + } + + referenceBody + .append('p') + .text(docs.description); + + referenceBody + .append('a') + .attr('target', '_blank') + .attr('href', 'http://wiki.openstreetmap.org/wiki/' + docs.title) + .text(t('inspector.reference')); + }); + }; }; diff --git a/js/id/ui/taglist.js b/js/id/ui/taglist.js index 81042d679..c0a3bcfab 100644 --- a/js/id/ui/taglist.js +++ b/js/id/ui/taglist.js @@ -1,7 +1,6 @@ iD.ui.Taglist = function(context, entity) { var event = d3.dispatch('change'), taginfo = iD.taginfo(), - initial = false, collapsebutton, list; @@ -26,12 +25,8 @@ iD.ui.Taglist = function(context, entity) { .attr('class', 'tag-list'); var newTag = wrap.append('button') - .attr('class', 'add-tag col6'); - - newTag.on('click', function() { - addTag(); - focusNewKey(); - }); + .attr('class', 'add-tag col6') + .on('click', addTag); newTag.append('span') .attr('class', 'icon plus'); @@ -86,101 +81,30 @@ iD.ui.Taglist = function(context, entity) { row.each(bindTypeahead); - var removeBtn = row.append('button') + row.append('button') .attr('tabindex', -1) .attr('class','remove minor') - .on('click', removeTag); - - removeBtn.append('span') + .on('click', removeTag) + .append('span') .attr('class', 'icon delete'); - function findLocal(docs) { - var locale = iD.detect().locale.toLowerCase(), - localized; - - localized = _.find(docs, function(d) { - return d.lang.toLowerCase() === locale; - }); - if (localized) return localized; - - // try the non-regional version of a language, like - // 'en' if the language is 'en-US' - if (locale.indexOf('-') !== -1) { - var first = locale.split('-')[0]; - localized = _.find(docs, function(d) { - return d.lang.toLowerCase() === first; - }); - if (localized) return localized; - } - - // finally fall back to english - return _.find(docs, function(d) { - return d.lang.toLowerCase() === 'en'; - }); - } - - function keyValueReference(err, docs) { - var local; - if (!err && docs) { - local = findLocal(docs); - } - if (local) { - var types = []; - if (local.on_area) types.push('area'); - if (local.on_node) types.push('point'); - if (local.on_way) types.push('line'); - local.types = types; - iD.ui.modal(context.container()) - .select('.content') - .datum(local) - .call(iD.ui.tagReference); - } else { - iD.ui.flash(context.container()) - .select('.content') - .append('h3') - .text(t('inspector.no_documentation_combination')); - } - } - - function keyReference(err, values, params) { - if (!err && values.length) { - iD.ui.modal(context.container()) - .select('.content') - .datum({ - data: values, - title: 'Key:' + params.key, - geometry: params.geometry - }) - .call(iD.ui.keyReference); - } else { - iD.ui.flash(context.container()) - .select('.content') - .append('h3') - .text(t('inspector.no_documentation_key')); - } - } - - var helpBtn = row.append('button') + row.append('button') .attr('tabindex', -1) .attr('class', 'tag-help minor') - .on('click', function(d) { - var params = _.extend({}, d, { - geometry: entity.geometry(context.graph()) - }); - if (d.key && d.value) { - taginfo.docs(params, keyValueReference); - } else if (d.key) { - taginfo.values(params, keyReference); - } - }); + .on('click', function(tag) { + row.selectAll('div.tag-help') + .style('display', 'none'); - helpBtn.append('span') - .attr('class', 'icon inspect'); + d3.select(d3.select(this).node().parentNode) + .select('div.tag-help') + .style('display', 'block') + .call(iD.ui.TagReference(entity, {key: tag.key})); + }) + .append('span') + .attr('class', 'icon inspect light'); - if (initial && tags.length === 1 && - tags[0].key === '' && tags[0].value === '') { - focusNewKey(); - } + row.append('div') + .attr('class', 'tag-help'); return li; } @@ -190,7 +114,6 @@ iD.ui.Taglist = function(context, entity) { list.selectAll('li:last-child input.value').node() === this && !d3.event.shiftKey) { addTag(); - focusNewKey(); d3.event.preventDefault(); } } @@ -198,8 +121,8 @@ iD.ui.Taglist = function(context, entity) { function bindTypeahead() { var geometry = entity.geometry(context.graph()), row = d3.select(this), - key = row.selectAll('.key-wrap'), - value = row.selectAll('.input-wrap-position'); + key = row.selectAll('input.key'), + value = row.selectAll('input.value'); function sort(value, data) { var sameletter = [], @@ -214,40 +137,35 @@ iD.ui.Taglist = function(context, entity) { return sameletter.concat(other); } - var keyinput = key.select('input'); key.call(d3.combobox() - .fetcher(function(_, __, callback) { + .fetcher(function(value, __, callback) { taginfo.keys({ debounce: true, geometry: geometry, - query: keyinput.property('value') + query: value }, function(err, data) { - if (!err) callback(sort(keyinput.property('value'), data)); + if (!err) callback(sort(value, data)); }); })); - var valueinput = value.select('input'); value.call(d3.combobox() - .fetcher(function(_, __, callback) { + .fetcher(function(value, __, callback) { taginfo.values({ debounce: true, - key: keyinput.property('value'), + key: key.property('value'), geometry: geometry, - query: valueinput.property('value') + query: value }, function(err, data) { - if (!err) callback(sort(valueinput.property('value'), data)); + if (!err) callback(sort(value, data)); }); })); } - function focusNewKey() { - list.selectAll('li:last-child input.key').node().focus(); - } - function addTag() { var tags = taglist.tags(); tags[''] = ''; drawTags(tags); + list.selectAll('li:last-child input.key').node().focus(); } function removeTag(d) { diff --git a/js/lib/d3.combobox.js b/js/lib/d3.combobox.js index f237c181e..2d42aa08c 100644 --- a/js/lib/d3.combobox.js +++ b/js/lib/d3.combobox.js @@ -1,7 +1,7 @@ d3.combobox = function() { var event = d3.dispatch('accept'), id = d3.combobox.id ++, - container, input, shown = false, data = []; + data = []; var fetcher = function(val, data, cb) { cb(data.filter(function(d) { @@ -12,22 +12,28 @@ d3.combobox = function() { })); }; - var typeahead = function(selection) { - var idx = -1; - input = selection.select('input').classed('combobox-input', true); + var typeahead = function(input) { + var idx = -1, container, shown = false; - selection.append('div', selection.select('input')) - .attr('class', 'combobox-carat') - .on('mousedown', stop) - .on('mousedown', function() { - d3.event.preventDefault(); - mousedown(); + input + .classed('combobox-input', true) + .each(function() { + var parent = this.parentNode, + sibling = this.nextSibling; + d3.select(parent) + .insert('div', function() { return sibling; }) + .attr('class', 'combobox-carat') + .on('mousedown', function () { + // prevent the form element from blurring. it blurs + // on mousedown + d3.event.stopPropagation(); + d3.event.preventDefault(); + mousedown(); + }); }); function updateSize() { - var rect = selection.select('input') - .node() - .getBoundingClientRect(); + var rect = input.node().getBoundingClientRect(); container.style({ 'left': rect.left + 'px', 'width': rect.width + 'px', @@ -35,13 +41,6 @@ d3.combobox = function() { }); } - function stop() { - // prevent the form element from blurring. it blurs - // on mousedown - d3.event.stopPropagation(); - d3.event.preventDefault(); - } - function blur() { // hide the combobox whenever the input element // loses focus @@ -210,7 +209,7 @@ d3.combobox = function() { .order(); } - fetcher.apply(selection, [value, data, render]); + fetcher.apply(input, [value, data, render]); } // select the choice given as d diff --git a/js/lib/d3.jsonp.js b/js/lib/d3.jsonp.js new file mode 100644 index 000000000..e0cd4a80e --- /dev/null +++ b/js/lib/d3.jsonp.js @@ -0,0 +1,25 @@ +d3.jsonp = function (url, callback) { + function rand() { + var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', + c = '', i = -1; + while (++i < 15) c += chars.charAt(Math.floor(Math.random() * 52)); + return c; + } + + function create(url) { + var e = url.match(/callback=d3.jsonp.(\w+)/), + c = e ? e[1] : rand(); + d3.jsonp[c] = function(data) { + callback(data); + delete d3.jsonp[c]; + script.remove(); + }; + return 'd3.jsonp.' + c; + } + + var cb = create(url), + script = d3.select('head') + .append('script') + .attr('type', 'text/javascript') + .attr('src', url.replace(/(\{|%7B)callback(\}|%7D)/, cb)); +}; diff --git a/package.json b/package.json index 4cd2317a8..6d8d7ba81 100644 --- a/package.json +++ b/package.json @@ -29,5 +29,8 @@ }, "engines": { "node": "~0.8.20" + }, + "dependencies": { + "marked": "~0.2.8" } } diff --git a/test/index.html b/test/index.html index c6c1f11c7..c0f7be5f2 100644 --- a/test/index.html +++ b/test/index.html @@ -26,6 +26,7 @@ + @@ -81,7 +82,6 @@ - @@ -184,6 +184,7 @@ + diff --git a/test/spec/actions/orthogonalize.js b/test/spec/actions/orthogonalize.js new file mode 100644 index 000000000..690d73140 --- /dev/null +++ b/test/spec/actions/orthogonalize.js @@ -0,0 +1,30 @@ +describe("iD.actions.Orthogonalize", function () { + var projection = d3.geo.mercator(); + + it("orthoganalizes a quad", function () { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a', loc: [0, 0]}), + 'b': iD.Node({id: 'b', loc: [4, 0]}), + 'c': iD.Node({id: 'c', loc: [3, 2]}), + 'd': iD.Node({id: 'd', loc: [0, 2]}), + '-': iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']}) + }); + + graph = iD.actions.Orthogonalize('-', projection)(graph); + + expect(graph.entity('-').nodes).to.have.length(5); + }); + + it("orthoganalizes a triangle", function () { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a', loc: [0, 0]}), + 'b': iD.Node({id: 'b', loc: [3, 0]}), + 'c': iD.Node({id: 'c', loc: [2, 2]}), + '-': iD.Way({id: '-', nodes: ['a', 'b', 'c', 'a']}) + }); + + graph = iD.actions.Orthogonalize('-', projection)(graph); + + expect(graph.entity('-').nodes).to.have.length(4); + }); +});