mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-17 22:24:49 +02:00
Merge branch 'master' into summer_toboggan
This commit is contained in:
@@ -3,6 +3,7 @@ import { actionAddEntity } from '../actions';
|
||||
import { behaviorDraw } from '../behavior';
|
||||
import { modeBrowse, modeSelect } from './index';
|
||||
import { osmNode } from '../osm';
|
||||
import { actionAddMidpoint } from '../actions';
|
||||
|
||||
|
||||
export function modeAddPoint(context) {
|
||||
@@ -37,8 +38,17 @@ export function modeAddPoint(context) {
|
||||
}
|
||||
|
||||
|
||||
function addWay(loc) {
|
||||
add(loc);
|
||||
function addWay(loc, edge) {
|
||||
var node = osmNode();
|
||||
|
||||
context.perform(
|
||||
actionAddMidpoint({loc: loc, edge: edge}, node),
|
||||
t('operations.add.annotation.vertex')
|
||||
);
|
||||
|
||||
context.enter(
|
||||
modeSelect(context, [node.id]).newFeature(true)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -335,7 +335,7 @@ export default {
|
||||
loadSigns: function(context, projection) {
|
||||
// if we are looking at signs, we'll actually need to fetch images too
|
||||
loadTiles('images', apibase + 'images?', projection);
|
||||
loadTiles('objects', apibase + 'objects?', projection);
|
||||
loadTiles('objects', apibase + 'map_features?layers=trafficsigns&', projection);
|
||||
},
|
||||
|
||||
|
||||
@@ -636,7 +636,7 @@ export default {
|
||||
|
||||
|
||||
function loadDetection(detectionKey) {
|
||||
var url = apibase + 'detections/' +
|
||||
var url = apibase + 'image_detections/' +
|
||||
detectionKey + '?' + utilQsString({ client_id: clientId });
|
||||
|
||||
d3_request(url)
|
||||
|
||||
@@ -182,7 +182,9 @@ export default {
|
||||
init: function() {
|
||||
inflight = {};
|
||||
taginfoCache = {};
|
||||
popularKeys = {};
|
||||
popularKeys = {
|
||||
postal_code: true // #5377
|
||||
};
|
||||
|
||||
// Fetch popular keys. We'll exclude these from `values`
|
||||
// lookups because they stress taginfo, and they aren't likely
|
||||
|
||||
@@ -6,7 +6,7 @@ export function svgTagClasses() {
|
||||
var primaries = [
|
||||
'building', 'highway', 'railway', 'waterway', 'aeroway',
|
||||
'motorway', 'boundary', 'power', 'amenity', 'natural', 'landuse',
|
||||
'leisure', 'military', 'place', 'man_made', 'attraction'
|
||||
'leisure', 'military', 'place', 'man_made', 'route', 'attraction'
|
||||
];
|
||||
var statuses = [
|
||||
'proposed', 'construction', 'disused', 'abandoned', 'dismantled',
|
||||
@@ -15,7 +15,7 @@ export function svgTagClasses() {
|
||||
var secondaries = [
|
||||
'oneway', 'bridge', 'tunnel', 'embankment', 'cutting', 'barrier',
|
||||
'surface', 'tracktype', 'footway', 'crossing', 'service', 'sport',
|
||||
'public_transport'
|
||||
'public_transport', 'location', 'parking'
|
||||
];
|
||||
var tagClassRe = /^tag-/;
|
||||
var _tags = function(entity) { return entity.tags; };
|
||||
|
||||
+1
-1
@@ -318,7 +318,7 @@ export function uiHelp(context) {
|
||||
|
||||
function clickHelp(d, i) {
|
||||
var rtl = (textDirection === 'rtl');
|
||||
pane.property('scrollTop', 0);
|
||||
content.property('scrollTop', 0);
|
||||
doctitle.html(d.title);
|
||||
|
||||
body.html(d.html);
|
||||
|
||||
+132
-12
@@ -61,7 +61,7 @@ export function uiPresetList(context) {
|
||||
.call(svgIcon('#iD-icon-close'));
|
||||
}
|
||||
|
||||
function keydown() {
|
||||
function initialKeydown() {
|
||||
// hack to let delete shortcut work when search is autofocused
|
||||
if (search.property('value').length === 0 &&
|
||||
(d3_event.keyCode === d3_keybinding.keyCodes['⌫'] ||
|
||||
@@ -69,6 +69,8 @@ export function uiPresetList(context) {
|
||||
d3_event.preventDefault();
|
||||
d3_event.stopPropagation();
|
||||
operationDelete([id], context)();
|
||||
|
||||
// hack to let undo work when search is autofocused
|
||||
} else if (search.property('value').length === 0 &&
|
||||
(d3_event.ctrlKey || d3_event.metaKey) &&
|
||||
d3_event.keyCode === d3_keybinding.keyCodes.z) {
|
||||
@@ -76,7 +78,21 @@ export function uiPresetList(context) {
|
||||
d3_event.stopPropagation();
|
||||
context.undo();
|
||||
} else if (!d3_event.ctrlKey && !d3_event.metaKey) {
|
||||
d3_select(this).on('keydown', null);
|
||||
// don't check for delete/undo hack on future keydown events
|
||||
d3_select(this).on('keydown', keydown);
|
||||
keydown.call(this);
|
||||
}
|
||||
}
|
||||
|
||||
function keydown() {
|
||||
// down arrow
|
||||
if (d3_event.keyCode === d3_keybinding.keyCodes['↓'] &&
|
||||
// if insertion point is at the end of the string
|
||||
search.node().selectionStart === search.property('value').length) {
|
||||
d3_event.preventDefault();
|
||||
d3_event.stopPropagation();
|
||||
// move focus to the first item in the preset list
|
||||
list.select('.preset-list-button').node().focus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +130,7 @@ export function uiPresetList(context) {
|
||||
.attr('placeholder', t('inspector.search'))
|
||||
.attr('type', 'search')
|
||||
.call(utilNoAuto)
|
||||
.on('keydown', keydown)
|
||||
.on('keydown', initialKeydown)
|
||||
.on('keypress', keypress)
|
||||
.on('input', inputevent);
|
||||
|
||||
@@ -159,6 +175,84 @@ export function uiPresetList(context) {
|
||||
.style('opacity', 1);
|
||||
}
|
||||
|
||||
function itemKeydown(){
|
||||
// the actively focused item
|
||||
var item = d3_select(this.closest('.preset-list-item'));
|
||||
var parentItem = d3_select(item.node().parentElement.closest('.preset-list-item'));
|
||||
|
||||
// arrow down, move focus to the next, lower item
|
||||
if (d3_event.keyCode === d3_keybinding.keyCodes['↓']) {
|
||||
d3_event.preventDefault();
|
||||
d3_event.stopPropagation();
|
||||
// the next item in the list at the same level
|
||||
var nextItem = d3_select(item.node().nextElementSibling);
|
||||
// if there is no next item in this list
|
||||
if (nextItem.empty()) {
|
||||
// if there is a parent item
|
||||
if (!parentItem.empty()) {
|
||||
// the item is the last item of a sublist,
|
||||
// select the next item at the parent level
|
||||
nextItem = d3_select(parentItem.node().nextElementSibling);
|
||||
}
|
||||
// if the focused item is expanded
|
||||
} else if (d3_select(this).classed('expanded')) {
|
||||
// select the first subitem instead
|
||||
nextItem = item.select('.subgrid .preset-list-item:first-child');
|
||||
}
|
||||
if (!nextItem.empty()) {
|
||||
// focus on the next item
|
||||
nextItem.select('.preset-list-button').node().focus();
|
||||
}
|
||||
|
||||
// arrow up, move focus to the previous, higher item
|
||||
} else if (d3_event.keyCode === d3_keybinding.keyCodes['↑']) {
|
||||
d3_event.preventDefault();
|
||||
d3_event.stopPropagation();
|
||||
// the previous item in the list at the same level
|
||||
var previousItem = d3_select(item.node().previousElementSibling);
|
||||
|
||||
// if there is no previous item in this list
|
||||
if (previousItem.empty()) {
|
||||
// if there is a parent item
|
||||
if (!parentItem.empty()) {
|
||||
// the item is the first subitem of a sublist,
|
||||
// select the parent item
|
||||
previousItem = parentItem;
|
||||
}
|
||||
// if the previous item is expanded
|
||||
} else if (previousItem.select('.preset-list-button').classed('expanded')) {
|
||||
// select the last subitem of the sublist of the previous item
|
||||
previousItem = previousItem.select('.subgrid .preset-list-item:last-child');
|
||||
}
|
||||
if (!previousItem.empty()) {
|
||||
// focus on the previous item
|
||||
previousItem.select('.preset-list-button').node().focus();
|
||||
}
|
||||
else {
|
||||
// the focus is at the top of the list, move focus back to the search field
|
||||
var search = d3_select(this.closest('.preset-list-pane')).select('.preset-search-input');
|
||||
search.node().focus();
|
||||
}
|
||||
}
|
||||
// arrow left, move focus to the parent item if there is one
|
||||
else if (d3_event.keyCode === d3_keybinding.keyCodes[(textDirection === 'rtl') ? '→' : '←']) {
|
||||
d3_event.preventDefault();
|
||||
d3_event.stopPropagation();
|
||||
|
||||
// if there is a parent item
|
||||
if (!parentItem.empty()) {
|
||||
// focus on the parent item
|
||||
parentItem.select('.preset-list-button').node().focus();
|
||||
}
|
||||
}
|
||||
// arrow right, choose this item
|
||||
else if (d3_event.keyCode === d3_keybinding.keyCodes[(textDirection === 'rtl') ? '←' : '→']) {
|
||||
d3_event.preventDefault();
|
||||
d3_event.stopPropagation();
|
||||
item.datum().choose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function CategoryItem(preset) {
|
||||
var box, sublist, shown = false;
|
||||
@@ -167,6 +261,17 @@ export function uiPresetList(context) {
|
||||
var wrap = selection.append('div')
|
||||
.attr('class', 'preset-list-button-wrap category col12');
|
||||
|
||||
function click() {
|
||||
var isExpanded = d3_select(this).classed('expanded');
|
||||
var iconName = isExpanded ?
|
||||
(textDirection === 'rtl' ? '#iD-icon-backward' : '#iD-icon-forward') : '#iD-icon-down';
|
||||
d3_select(this)
|
||||
.classed('expanded', !isExpanded);
|
||||
d3_select(this).selectAll('div.label svg.icon use')
|
||||
.attr('href', iconName);
|
||||
item.choose();
|
||||
}
|
||||
|
||||
var button = wrap
|
||||
.append('button')
|
||||
.attr('class', 'preset-list-button')
|
||||
@@ -174,15 +279,29 @@ export function uiPresetList(context) {
|
||||
.call(uiPresetIcon()
|
||||
.geometry(context.geometry(id))
|
||||
.preset(preset))
|
||||
.on('click', function() {
|
||||
var isExpanded = d3_select(this).classed('expanded');
|
||||
var iconName = isExpanded ?
|
||||
(textDirection === 'rtl' ? '#iD-icon-backward' : '#iD-icon-forward') : '#iD-icon-down';
|
||||
d3_select(this)
|
||||
.classed('expanded', !isExpanded);
|
||||
d3_select(this).selectAll('div.label svg.icon use')
|
||||
.attr('href', iconName);
|
||||
item.choose();
|
||||
.on('click', click)
|
||||
.on('keydown', function() {
|
||||
// right arrow, expand the focused item
|
||||
if (d3_event.keyCode === d3_keybinding.keyCodes[(textDirection === 'rtl') ? '←' : '→']) {
|
||||
d3_event.preventDefault();
|
||||
d3_event.stopPropagation();
|
||||
// if the item isn't expanded
|
||||
if (!d3_select(this).classed('expanded')) {
|
||||
// toggle expansion (expand the item)
|
||||
click.call(this);
|
||||
}
|
||||
// left arrow, collapse the focused item
|
||||
} else if (d3_event.keyCode === d3_keybinding.keyCodes[(textDirection === 'rtl') ? '→' : '←']) {
|
||||
d3_event.preventDefault();
|
||||
d3_event.stopPropagation();
|
||||
// if the item is expanded
|
||||
if (d3_select(this).classed('expanded')) {
|
||||
// toggle expansion (collapse the item)
|
||||
click.call(this);
|
||||
}
|
||||
} else {
|
||||
itemKeydown.call(this);
|
||||
}
|
||||
});
|
||||
|
||||
var label = button
|
||||
@@ -245,6 +364,7 @@ export function uiPresetList(context) {
|
||||
.geometry(context.geometry(id))
|
||||
.preset(preset))
|
||||
.on('click', item.choose)
|
||||
.on('keydown', itemKeydown)
|
||||
.append('div')
|
||||
.attr('class', 'label')
|
||||
.text(preset.name());
|
||||
|
||||
@@ -15,7 +15,8 @@ import { uiDisclosure } from './disclosure';
|
||||
import {
|
||||
utilDisplayName,
|
||||
utilDisplayType,
|
||||
utilNoAuto
|
||||
utilNoAuto,
|
||||
utilHighlightEntity
|
||||
} from '../util';
|
||||
|
||||
|
||||
@@ -23,9 +24,34 @@ export function uiRawMemberEditor(context) {
|
||||
var taginfo = services.taginfo,
|
||||
_entityID;
|
||||
|
||||
function downloadMember(d) {
|
||||
d3_event.preventDefault();
|
||||
// display the loading indicator
|
||||
d3_select(this.parentNode).classed('tag-reference-loading', true);
|
||||
context.loadEntity(d.id);
|
||||
}
|
||||
|
||||
function zoomToMember(d) {
|
||||
d3_event.preventDefault();
|
||||
|
||||
var entity = context.entity(d.id);
|
||||
context.map().zoomTo(entity);
|
||||
|
||||
// highlight the feature in case it wasn't previously on-screen
|
||||
utilHighlightEntity(d.id, true, context);
|
||||
}
|
||||
|
||||
|
||||
function selectMember(d) {
|
||||
d3_event.preventDefault();
|
||||
|
||||
var entity = context.entity(d.id);
|
||||
var mapExtent = context.map().extent();
|
||||
if (!entity.intersects(mapExtent, context.graph())) {
|
||||
// zoom to the entity if its extent is not visible now
|
||||
context.map().zoomTo(entity);
|
||||
}
|
||||
|
||||
context.enter(modeSelect(context, [d.id]));
|
||||
}
|
||||
|
||||
@@ -107,27 +133,64 @@ export function uiRawMemberEditor(context) {
|
||||
enter
|
||||
.each(function(d) {
|
||||
if (d.member) {
|
||||
|
||||
// highlight the member feature in the map while hovering on the list item
|
||||
d3_select(this).on('mouseover', function() {
|
||||
utilHighlightEntity(d.id, true, context);
|
||||
});
|
||||
d3_select(this).on('mouseout', function() {
|
||||
utilHighlightEntity(d.id, false, context);
|
||||
});
|
||||
|
||||
var label = d3_select(this).append('label')
|
||||
.attr('class', 'form-label')
|
||||
.append('a')
|
||||
.attr('class', 'form-label');
|
||||
|
||||
var labelLink = label.append('a')
|
||||
.attr('href', '#')
|
||||
.on('click', selectMember);
|
||||
|
||||
label.append('span')
|
||||
labelLink.append('span')
|
||||
.attr('class', 'member-entity-type')
|
||||
.text(function(d) {
|
||||
var matched = context.presets().match(d.member, context.graph());
|
||||
return (matched && matched.name()) || utilDisplayType(d.member.id);
|
||||
});
|
||||
|
||||
label.append('span')
|
||||
labelLink.append('span')
|
||||
.attr('class', 'member-entity-name')
|
||||
.text(function(d) { return utilDisplayName(d.member); });
|
||||
|
||||
var buttonWrap = label.append('div')
|
||||
.attr('class', 'form-label-button-wrap');
|
||||
|
||||
buttonWrap.append('button')
|
||||
.attr('class', 'download-icon')
|
||||
.attr('title', t('icons.zoom_to'))
|
||||
.attr('tabindex', -1)
|
||||
.call(svgIcon('#iD-icon-geolocate'))
|
||||
.on('click', zoomToMember);
|
||||
|
||||
} else {
|
||||
d3_select(this).append('label')
|
||||
.attr('class', 'form-label')
|
||||
var incompleteLabel = d3_select(this).append('label')
|
||||
.attr('class', 'form-label');
|
||||
|
||||
incompleteLabel.append('span')
|
||||
.attr('class', 'member-entity-type')
|
||||
.text(t('inspector.'+d.type, { id: d.id }));
|
||||
|
||||
incompleteLabel.append('span')
|
||||
.attr('class', 'member-entity-name')
|
||||
.text(t('inspector.incomplete', { id: d.id }));
|
||||
|
||||
var wrap = incompleteLabel.append('div')
|
||||
.attr('class', 'form-label-button-wrap');
|
||||
|
||||
wrap.append('button')
|
||||
.attr('class', 'download-icon')
|
||||
.attr('title', t('icons.download'))
|
||||
.attr('tabindex', -1)
|
||||
.call(svgIcon('#iD-icon-load'))
|
||||
.on('click', downloadMember);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -144,6 +207,7 @@ export function uiRawMemberEditor(context) {
|
||||
enter
|
||||
.append('button')
|
||||
.attr('tabindex', -1)
|
||||
.attr('title', t('icons.remove'))
|
||||
.attr('class', 'remove button-input-action member-delete minor')
|
||||
.on('click', deleteMember)
|
||||
.call(svgIcon('#iD-operation-delete'));
|
||||
|
||||
@@ -24,7 +24,7 @@ import { osmEntity, osmRelation } from '../osm';
|
||||
import { services } from '../services';
|
||||
import { svgIcon } from '../svg';
|
||||
import { uiDisclosure } from './disclosure';
|
||||
import { utilDisplayName, utilNoAuto } from '../util';
|
||||
import { utilDisplayName, utilNoAuto, utilHighlightEntity } from '../util';
|
||||
|
||||
|
||||
export function uiRawMembershipEditor(context) {
|
||||
@@ -173,6 +173,16 @@ export function uiRawMembershipEditor(context) {
|
||||
.append('li')
|
||||
.attr('class', 'member-row member-row-normal form-field');
|
||||
|
||||
enter.each(function(d){
|
||||
// highlight the relation in the map while hovering on the list item
|
||||
d3_select(this).on('mouseover', function() {
|
||||
utilHighlightEntity(d.relation.id, true, context);
|
||||
});
|
||||
d3_select(this).on('mouseout', function() {
|
||||
utilHighlightEntity(d.relation.id, false, context);
|
||||
});
|
||||
});
|
||||
|
||||
var label = enter
|
||||
.append('label')
|
||||
.attr('class', 'form-label')
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { event as d3_event } from 'd3-selection';
|
||||
import {
|
||||
event as d3_event,
|
||||
select as d3_select
|
||||
} from 'd3-selection';
|
||||
|
||||
import { t } from '../util/locale';
|
||||
import { modeSelect } from '../modes';
|
||||
import { osmEntity } from '../osm';
|
||||
import { svgIcon } from '../svg';
|
||||
import { utilDisplayName } from '../util';
|
||||
import { utilDisplayName, utilHighlightEntity } from '../util';
|
||||
|
||||
|
||||
export function uiSelectionList(context, selectedIDs) {
|
||||
@@ -64,6 +67,17 @@ export function uiSelectionList(context, selectedIDs) {
|
||||
.attr('class', 'feature-list-item')
|
||||
.on('click', selectEntity);
|
||||
|
||||
enter
|
||||
.each(function(d) {
|
||||
// highlight the feature in the map while hovering on the list item
|
||||
d3_select(this).on('mouseover', function() {
|
||||
utilHighlightEntity(d.id, true, context);
|
||||
});
|
||||
d3_select(this).on('mouseout', function() {
|
||||
utilHighlightEntity(d.id, false, context);
|
||||
});
|
||||
});
|
||||
|
||||
var label = enter
|
||||
.append('button')
|
||||
.attr('class', 'label');
|
||||
|
||||
@@ -7,12 +7,14 @@ export { utilDisplayType } from './util';
|
||||
export { utilEditDistance } from './util';
|
||||
export { utilEntitySelector } from './util';
|
||||
export { utilEntityOrMemberSelector } from './util';
|
||||
export { utilEntityOrDeepMemberSelector } from './util';
|
||||
export { utilFastMouse } from './util';
|
||||
export { utilFunctor } from './util';
|
||||
export { utilGetAllNodes } from './util';
|
||||
export { utilGetPrototypeOf } from './util';
|
||||
export { utilGetSetValue } from './get_set_value';
|
||||
export { utilHashcode } from './util';
|
||||
export { utilHighlightEntity } from './util';
|
||||
export { utilIdleWorker } from './idle_worker';
|
||||
export { utilNoAuto } from './util';
|
||||
export { utilPrefixCSSProperty } from './util';
|
||||
|
||||
@@ -34,6 +34,32 @@ export function utilEntityOrMemberSelector(ids, graph) {
|
||||
}
|
||||
|
||||
|
||||
export function utilEntityOrDeepMemberSelector(ids, graph) {
|
||||
var seen = {};
|
||||
var allIDs = [];
|
||||
function addEntityAndMembersIfNotYetSeen(id) {
|
||||
// avoid infinite recursion for circular relations by skipping seen entities
|
||||
if (seen[id]) return;
|
||||
// mark the entity as seen
|
||||
seen[id] = true;
|
||||
// add the id;
|
||||
allIDs.push(id);
|
||||
if (graph.hasEntity(id)) {
|
||||
var entity = graph.entity(id);
|
||||
if (entity.type === 'relation' && entity.members) {
|
||||
entity.members.forEach(function(member){
|
||||
addEntityAndMembersIfNotYetSeen(member.id);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
ids.forEach(function(id) {
|
||||
addEntityAndMembersIfNotYetSeen(id);
|
||||
});
|
||||
return utilEntitySelector(allIDs);
|
||||
}
|
||||
|
||||
|
||||
export function utilGetAllNodes(ids, graph) {
|
||||
var seen = {};
|
||||
var nodes = [];
|
||||
@@ -282,3 +308,10 @@ export function utilHashcode(str) {
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
// Adds or removes highlight styling for the specified entity's SVG elements in the map.
|
||||
export function utilHighlightEntity(id, highlighted, context) {
|
||||
context.surface()
|
||||
.selectAll(utilEntityOrDeepMemberSelector([id], context.graph()))
|
||||
.classed('highlighted', highlighted);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user