mirror of
https://github.com/FoggedLens/iD.git
synced 2026-06-05 14:38:05 +02:00
Merge branch 'master' into validation
This commit is contained in:
@@ -35,7 +35,7 @@ export function setAreaKeys(value) {
|
||||
|
||||
export function coreContext() {
|
||||
var context = {};
|
||||
context.version = '2.12.2';
|
||||
context.version = '2.13.0';
|
||||
|
||||
// create a special translation that contains the keys in place of the strings
|
||||
var tkeys = _cloneDeep(dataEn);
|
||||
@@ -491,15 +491,14 @@ export function coreContext() {
|
||||
features = rendererFeatures(context);
|
||||
presets = presetIndex();
|
||||
|
||||
if (services.maprules && utilStringQs(window.location.hash).validations) {
|
||||
var validations = utilStringQs(window.location.hash).validations;
|
||||
d3_json(validations, function (err, mapcss) {
|
||||
if (services.maprules && utilStringQs(window.location.hash).maprules) {
|
||||
var maprules = utilStringQs(window.location.hash).maprules;
|
||||
d3_json(maprules, function (err, mapcss) {
|
||||
if (err) return;
|
||||
services.maprules.init(context.presets().areaKeys());
|
||||
_each(mapcss, function(mapcssSelector) {
|
||||
return services.maprules.addRule(mapcssSelector);
|
||||
});
|
||||
context.validationRules = true;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -434,6 +434,14 @@ export function coreHistory(context) {
|
||||
if (id in base.graph.entities) {
|
||||
baseEntities[id] = base.graph.entities[id];
|
||||
}
|
||||
if (entity && entity.nodes) {
|
||||
// get originals of pre-existing child nodes
|
||||
_forEach(entity.nodes, function(nodeId) {
|
||||
if (nodeId in base.graph.entities) {
|
||||
baseEntities[nodeId] = base.graph.entities[nodeId];
|
||||
}
|
||||
});
|
||||
}
|
||||
// get originals of parent entities too
|
||||
_forEach(base.graph._parentWays[id], function(parentId) {
|
||||
if (parentId in base.graph.entities) {
|
||||
|
||||
@@ -195,7 +195,7 @@ export function modeSelect(context, selectedIDs) {
|
||||
mode.zoomToSelected = function() {
|
||||
var entity = singular();
|
||||
if (entity) {
|
||||
context.map().zoomTo(entity);
|
||||
context.map().zoomToEase(entity);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ export function modeSelectData(context, selectedDatum) {
|
||||
|
||||
mode.zoomToSelected = function() {
|
||||
var extent = geoExtent(d3_geoBounds(selectedDatum));
|
||||
context.map().centerZoom(extent.center(), context.map().trimmedExtentZoom(extent));
|
||||
context.map().centerZoomEase(extent.center(), context.map().trimmedExtentZoom(extent));
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ export function modeSelectError(context, selectedErrorID) {
|
||||
if (!keepRight) return;
|
||||
var error = keepRight.getError(selectedErrorID);
|
||||
if (error) {
|
||||
context.map().centerZoom(error.loc, 20);
|
||||
context.map().centerZoomEase(error.loc, 20);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ export function modeSelectNote(context, selectedNoteID) {
|
||||
if (!osm) return;
|
||||
var note = osm.getNote(selectedNoteID);
|
||||
if (note) {
|
||||
context.map().centerZoom(note.loc, 20);
|
||||
context.map().centerZoomEase(note.loc, 20);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -131,10 +131,6 @@ export function presetIndex() {
|
||||
});
|
||||
}
|
||||
|
||||
// move the wikidata field to directly follow the wikipedia field
|
||||
_universal.splice(_universal.indexOf(_fields.wikidata), 1);
|
||||
_universal.splice(_universal.indexOf(_fields.wikipedia)+1, 0, _fields.wikidata);
|
||||
|
||||
if (d.presets) {
|
||||
var rawPresets = d.presets;
|
||||
_forEach(d.presets, function(d, id) {
|
||||
|
||||
+63
-48
@@ -1,7 +1,6 @@
|
||||
import _clone from 'lodash-es/clone';
|
||||
import _omit from 'lodash-es/omit';
|
||||
import _union from 'lodash-es/union';
|
||||
import _filter from 'lodash-es/filter';
|
||||
import _uniq from 'lodash-es/uniq';
|
||||
|
||||
import { t } from '../util/locale';
|
||||
import { areaKeys } from '../core/context';
|
||||
@@ -14,62 +13,77 @@ export function presetPreset(id, preset, fields, visible, rawPresets) {
|
||||
|
||||
preset.parentPresetID = function() {
|
||||
var endIndex = preset.id.lastIndexOf('/');
|
||||
if (endIndex < 0) {
|
||||
return null;
|
||||
}
|
||||
if (endIndex < 0) return null;
|
||||
|
||||
return preset.id.substring(0, endIndex);
|
||||
};
|
||||
|
||||
|
||||
// For a preset without fields, use the fields of the parent preset.
|
||||
// Replace {preset} placeholders with the fields of the specified presets.
|
||||
function resolveFieldInheritance() {
|
||||
|
||||
function filterTargetFields(targetFieldIDs) {
|
||||
// only inherit `fields` that don't define this preset
|
||||
return _filter(targetFieldIDs, function(targetFieldID) {
|
||||
var targetField = fields[targetFieldID];
|
||||
if (targetField.key) {
|
||||
return preset.tags[targetField.key] === undefined;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
// Skip `fields` for the keys which define the preset.
|
||||
// These are usually `typeCombo` fields like `shop=*`
|
||||
function withoutKeyFields(fieldID) {
|
||||
var f = fields[fieldID];
|
||||
if (f.key) {
|
||||
return preset.tags[f.key] === undefined;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
var betweenBracketsRegex = /([^{]*?)(?=\})/;
|
||||
// the keys for properties that contain arrays of field ids
|
||||
var fieldKeys = ['fields', 'moreFields'];
|
||||
fieldKeys.forEach(function(fieldsKey) {
|
||||
if (preset[fieldsKey]) {
|
||||
var wrappedTargetPresets = _filter(preset[fieldsKey], function(fieldID) {
|
||||
return fieldID.indexOf('{') > -1;
|
||||
});
|
||||
wrappedTargetPresets.forEach(function(wrappedTargetPresetID) {
|
||||
var targetPresetID = betweenBracketsRegex.exec(wrappedTargetPresetID)[0];
|
||||
var targetFields = rawPresets[targetPresetID][fieldsKey];
|
||||
if (fieldsKey === 'fields') {
|
||||
targetFields = filterTargetFields(targetFields);
|
||||
}
|
||||
var targetIndex = preset[fieldsKey].indexOf(wrappedTargetPresetID);
|
||||
// replace the {preset} placeholder with the target preset's fields
|
||||
preset[fieldsKey].splice.apply(preset[fieldsKey], [targetIndex, 1].concat(targetFields));
|
||||
});
|
||||
// remove duplicates
|
||||
preset[fieldsKey] = _union(preset[fieldsKey]);
|
||||
} else {
|
||||
// there are no fields defined, so use the parent's if possible
|
||||
var parentPreset = rawPresets[preset.parentPresetID()];
|
||||
if (parentPreset && parentPreset[fieldsKey]) {
|
||||
var parentFields = parentPreset[fieldsKey];
|
||||
if (fieldsKey === 'fields') {
|
||||
parentFields = filterTargetFields(parentFields);
|
||||
}
|
||||
preset[fieldsKey] = parentFields;
|
||||
}
|
||||
// returns an array of field IDs to inherit from the given presetID, if found
|
||||
function inheritedFieldIDs(presetID, prop) {
|
||||
if (!presetID) return null;
|
||||
|
||||
var inheritPreset = rawPresets[presetID];
|
||||
if (!inheritPreset) return null;
|
||||
|
||||
var inheritFieldIDs = inheritPreset[prop] || [];
|
||||
|
||||
if (prop === 'fields') {
|
||||
inheritFieldIDs = inheritFieldIDs.filter(withoutKeyFields);
|
||||
}
|
||||
|
||||
return inheritFieldIDs;
|
||||
}
|
||||
|
||||
|
||||
['fields', 'moreFields'].forEach(function(prop) {
|
||||
var fieldIDs = [];
|
||||
if (preset[prop] && preset[prop].length) { // fields were defined
|
||||
preset[prop].forEach(function(fieldID) {
|
||||
var match = fieldID.match(/\{(.*)\}/);
|
||||
if (match !== null) { // presetID wrapped in braces {}
|
||||
var inheritIDs = inheritedFieldIDs(match[1], prop);
|
||||
if (inheritIDs !== null) {
|
||||
fieldIDs = fieldIDs.concat(inheritIDs);
|
||||
} else {
|
||||
/* eslint-disable no-console */
|
||||
console.log('Cannot resolve presetID ' + match[0] +
|
||||
' found in ' + preset.id + ' ' + prop);
|
||||
/* eslint-enable no-console */
|
||||
}
|
||||
} else {
|
||||
fieldIDs.push(fieldID); // no braces - just a normal field
|
||||
}
|
||||
});
|
||||
|
||||
} else { // no fields defined, so use the parent's if possible
|
||||
fieldIDs = inheritedFieldIDs(preset.parentPresetID(), prop);
|
||||
}
|
||||
// resolve duplicate fields
|
||||
fieldIDs = _uniq(fieldIDs);
|
||||
|
||||
// update this preset with the results
|
||||
preset[prop] = fieldIDs;
|
||||
|
||||
// update the raw object to allow for multiple levels of inheritance
|
||||
rawPresets[preset.id][fieldsKey] = preset[fieldsKey];
|
||||
rawPresets[preset.id][prop] = fieldIDs;
|
||||
});
|
||||
}
|
||||
|
||||
if (rawPresets) {
|
||||
resolveFieldInheritance();
|
||||
}
|
||||
@@ -121,7 +135,8 @@ export function presetPreset(id, preset, fields, visible, rawPresets) {
|
||||
if (preset.suggestion) {
|
||||
var path = id.split('/');
|
||||
path.pop(); // remove brand name
|
||||
return origName + ' - ' + t('presets.presets.' + path.join('/') + '.name');
|
||||
// NOTE: insert an en-dash, not a hypen (to avoid conflict with fr - nl names in Brussels etc)
|
||||
return origName + ' – ' + t('presets.presets.' + path.join('/') + '.name');
|
||||
}
|
||||
return preset.t('name', { 'default': origName });
|
||||
};
|
||||
@@ -137,9 +152,9 @@ export function presetPreset(id, preset, fields, visible, rawPresets) {
|
||||
return tagCount === 0 || (tagCount === 1 && preset.tags.hasOwnProperty('area'));
|
||||
};
|
||||
|
||||
preset.visible = function(_) {
|
||||
preset.visible = function(val) {
|
||||
if (!arguments.length) return visible;
|
||||
visible = _;
|
||||
visible = val;
|
||||
return visible;
|
||||
};
|
||||
|
||||
|
||||
@@ -61,13 +61,13 @@ export function rendererBackgroundSource(data) {
|
||||
|
||||
|
||||
source.name = function() {
|
||||
var id_safe = source.id.replace('.', '<TX_DOT>');
|
||||
var id_safe = source.id.replace(/\./g, '<TX_DOT>');
|
||||
return t('imagery.' + id_safe + '.name', { default: name });
|
||||
};
|
||||
|
||||
|
||||
source.description = function() {
|
||||
var id_safe = source.id.replace('.', '<TX_DOT>');
|
||||
var id_safe = source.id.replace(/\./g, '<TX_DOT>');
|
||||
return t('imagery.' + id_safe + '.description', { default: description });
|
||||
};
|
||||
|
||||
@@ -124,13 +124,33 @@ export function rendererBackgroundSource(data) {
|
||||
}
|
||||
}).bind(this);
|
||||
|
||||
var tileSize = this.tileSize;
|
||||
var projection = this.projection;
|
||||
var minXmaxY = tileToProjectedCoords(coord[0], coord[1], coord[2]);
|
||||
var maxXminY = tileToProjectedCoords(coord[0]+1, coord[1]+1, coord[2]);
|
||||
return template
|
||||
.replace('{width}', this.tileSize)
|
||||
.replace('{height}', this.tileSize)
|
||||
.replace('{proj}', this.projection)
|
||||
.replace('{bbox}', minXmaxY.x + ',' + maxXminY.y + ',' + maxXminY.x + ',' + minXmaxY.y);
|
||||
return template.replace(/\{(\w+)\}/g, function (token, key) {
|
||||
switch (key) {
|
||||
case 'width':
|
||||
case 'height':
|
||||
return tileSize;
|
||||
case 'proj':
|
||||
return projection;
|
||||
case 'wkid':
|
||||
return projection.replace(/^EPSG:/, '');
|
||||
case 'bbox':
|
||||
return minXmaxY.x + ',' + maxXminY.y + ',' + maxXminY.x + ',' + minXmaxY.y;
|
||||
case 'w':
|
||||
return minXmaxY.x;
|
||||
case 's':
|
||||
return maxXminY.y;
|
||||
case 'n':
|
||||
return maxXminY.x;
|
||||
case 'e':
|
||||
return minXmaxY.y;
|
||||
default:
|
||||
return token;
|
||||
}
|
||||
});
|
||||
}
|
||||
return template
|
||||
.replace('{x}', coord[0])
|
||||
|
||||
+91
-73
@@ -34,6 +34,10 @@ var TILESIZE = 256;
|
||||
var kMin = geoZoomToScale(2, TILESIZE);
|
||||
var kMax = geoZoomToScale(24, TILESIZE);
|
||||
|
||||
function clamp(num, min, max) {
|
||||
return Math.max(min, Math.min(num, max));
|
||||
}
|
||||
|
||||
|
||||
export function rendererMap(context) {
|
||||
var dispatch = d3_dispatch('move', 'drawn');
|
||||
@@ -52,16 +56,16 @@ export function rendererMap(context) {
|
||||
var wrapper = d3_select(null);
|
||||
var surface = d3_select(null);
|
||||
|
||||
var dimensions = [1, 1];
|
||||
var _dimensions = [1, 1];
|
||||
var _dblClickEnabled = true;
|
||||
var _redrawEnabled = true;
|
||||
var _gestureTransformStart;
|
||||
var _transformStart = projection.transform();
|
||||
var _transformLast;
|
||||
var _transformed = false;
|
||||
var minzoom = 0;
|
||||
var mouse;
|
||||
var mousemove;
|
||||
var _isTransformed = false;
|
||||
var _minzoom = 0;
|
||||
var _getMouseCoords;
|
||||
var _mouseEvent;
|
||||
|
||||
var zoom = d3_zoom()
|
||||
.scaleExtent([kMin, kMax])
|
||||
@@ -170,17 +174,17 @@ export function rendererMap(context) {
|
||||
}
|
||||
})
|
||||
.on('mousemove.map', function() {
|
||||
mousemove = d3_event;
|
||||
_mouseEvent = d3_event;
|
||||
})
|
||||
.on('mouseover.vertices', function() {
|
||||
if (map.editable() && !_transformed) {
|
||||
if (map.editable() && !_isTransformed) {
|
||||
var hover = d3_event.target.__data__;
|
||||
surface.call(drawVertices.drawHover, context.graph(), hover, map.extent());
|
||||
dispatch.call('drawn', this, { full: false });
|
||||
}
|
||||
})
|
||||
.on('mouseout.vertices', function() {
|
||||
if (map.editable() && !_transformed) {
|
||||
if (map.editable() && !_isTransformed) {
|
||||
var hover = d3_event.relatedTarget && d3_event.relatedTarget.__data__;
|
||||
surface.call(drawVertices.drawHover, context.graph(), hover, map.extent());
|
||||
dispatch.call('drawn', this, { full: false });
|
||||
@@ -191,7 +195,7 @@ export function rendererMap(context) {
|
||||
.call(context.background());
|
||||
|
||||
context.on('enter.map', function() {
|
||||
if (map.editable() && !_transformed) {
|
||||
if (map.editable() && !_isTransformed) {
|
||||
// redraw immediately any objects affected by a change in selectedIDs.
|
||||
var graph = context.graph();
|
||||
var selectedAndParents = {};
|
||||
@@ -263,7 +267,7 @@ export function rendererMap(context) {
|
||||
|
||||
|
||||
function pxCenter() {
|
||||
return [dimensions[0] / 2, dimensions[1] / 2];
|
||||
return [_dimensions[0] / 2, _dimensions[1] / 2];
|
||||
}
|
||||
|
||||
|
||||
@@ -285,7 +289,7 @@ export function rendererMap(context) {
|
||||
} else {
|
||||
// force a full redraw if gatherStats detects that a feature
|
||||
// should be auto-hidden (e.g. points or buildings)..
|
||||
if (features.gatherStats(all, graph, dimensions)) {
|
||||
if (features.gatherStats(all, graph, _dimensions)) {
|
||||
extent = undefined;
|
||||
}
|
||||
|
||||
@@ -314,7 +318,7 @@ export function rendererMap(context) {
|
||||
.call(drawLines, graph, data, filter)
|
||||
.call(drawAreas, graph, data, filter)
|
||||
.call(drawMidpoints, graph, data, filter, map.trimmedExtent())
|
||||
.call(drawLabels, graph, data, filter, dimensions, fullRedraw)
|
||||
.call(drawLabels, graph, data, filter, _dimensions, fullRedraw)
|
||||
.call(drawPoints, graph, data, filter);
|
||||
|
||||
dispatch.call('drawn', this, {full: true});
|
||||
@@ -326,9 +330,16 @@ export function rendererMap(context) {
|
||||
surface.selectAll('.layer-osm *').remove();
|
||||
surface.selectAll('.layer-touch:not(.markers) *').remove();
|
||||
|
||||
var allowed = {
|
||||
'browse': true,
|
||||
'save': true,
|
||||
'select-note': true,
|
||||
'select-data': true,
|
||||
'select-error': true
|
||||
};
|
||||
|
||||
var mode = context.mode();
|
||||
if (mode && mode.id !== 'save' && mode.id !== 'select-note' &&
|
||||
mode.id !== 'select-data' && mode.id !== 'select-error') {
|
||||
if (mode && !allowed[mode.id]) {
|
||||
context.enter(modeBrowse(context));
|
||||
}
|
||||
|
||||
@@ -422,8 +433,8 @@ export function rendererMap(context) {
|
||||
}
|
||||
|
||||
// recalculate x2,y2,k2
|
||||
t0 = _transformed ? _transformLast : _transformStart;
|
||||
p0 = mouse(source);
|
||||
t0 = _isTransformed ? _transformLast : _transformStart;
|
||||
p0 = _getMouseCoords(source);
|
||||
p1 = t0.invert(p0);
|
||||
k2 = t0.k * Math.pow(2, -dY / 500);
|
||||
x2 = p0[0] - p1[0] * k2;
|
||||
@@ -434,7 +445,7 @@ export function rendererMap(context) {
|
||||
} else if (source._scale) {
|
||||
// recalculate x2,y2,k2
|
||||
t0 = _gestureTransformStart;
|
||||
p0 = mouse(source);
|
||||
p0 = _getMouseCoords(source);
|
||||
p1 = t0.invert(p0);
|
||||
k2 = t0.k * source._scale;
|
||||
x2 = p0[0] - p1[0] * k2;
|
||||
@@ -448,8 +459,8 @@ export function rendererMap(context) {
|
||||
dY *= 6; // slightly scale up whatever the browser gave us
|
||||
|
||||
// recalculate x2,y2,k2
|
||||
t0 = _transformed ? _transformLast : _transformStart;
|
||||
p0 = mouse(source);
|
||||
t0 = _isTransformed ? _transformLast : _transformStart;
|
||||
p0 = _getMouseCoords(source);
|
||||
p1 = t0.invert(p0);
|
||||
k2 = t0.k * Math.pow(2, -dY / 500);
|
||||
x2 = p0[0] - p1[0] * k2;
|
||||
@@ -458,8 +469,8 @@ export function rendererMap(context) {
|
||||
// Trackpad scroll zooming with shift or alt/option key down
|
||||
} else if ((source.altKey || source.shiftKey) && isInteger(dY)) {
|
||||
// recalculate x2,y2,k2
|
||||
t0 = _transformed ? _transformLast : _transformStart;
|
||||
p0 = mouse(source);
|
||||
t0 = _isTransformed ? _transformLast : _transformStart;
|
||||
p0 = _getMouseCoords(source);
|
||||
p1 = t0.invert(p0);
|
||||
k2 = t0.k * Math.pow(2, -dY / 500);
|
||||
x2 = p0[0] - p1[0] * k2;
|
||||
@@ -487,7 +498,7 @@ export function rendererMap(context) {
|
||||
|
||||
}
|
||||
|
||||
if (geoScaleToZoom(k, TILESIZE) < minzoom) {
|
||||
if (geoScaleToZoom(k, TILESIZE) < _minzoom) {
|
||||
surface.interrupt();
|
||||
uiFlash().text(t('cannot_zoom'))();
|
||||
setCenterZoom(map.center(), context.minEditableZoom(), 0, true);
|
||||
@@ -510,8 +521,10 @@ export function rendererMap(context) {
|
||||
});
|
||||
}
|
||||
|
||||
if (source) mousemove = event;
|
||||
_transformed = true;
|
||||
if (source) {
|
||||
_mouseEvent = event;
|
||||
}
|
||||
_isTransformed = true;
|
||||
_transformLast = eventTransform;
|
||||
utilSetTransform(supersurface, tX, tY, scale);
|
||||
scheduleRedraw();
|
||||
@@ -519,10 +532,6 @@ export function rendererMap(context) {
|
||||
dispatch.call('move', this, map);
|
||||
|
||||
|
||||
function clamp(num, min, max) {
|
||||
return Math.max(min, Math.min(num, max));
|
||||
}
|
||||
|
||||
function isInteger(val) {
|
||||
return typeof val === 'number' && isFinite(val) && Math.floor(val) === val;
|
||||
}
|
||||
@@ -530,12 +539,12 @@ export function rendererMap(context) {
|
||||
|
||||
|
||||
function resetTransform() {
|
||||
if (!_transformed) return false;
|
||||
if (!_isTransformed) return false;
|
||||
|
||||
// deprecation warning - Radial Menu to be removed in iD v3
|
||||
surface.selectAll('.edit-menu, .radial-menu').interrupt().remove();
|
||||
utilSetTransform(supersurface, 0, 0);
|
||||
_transformed = false;
|
||||
_isTransformed = false;
|
||||
if (context.inIntro()) {
|
||||
curtainProjection.transform(projection.transform());
|
||||
}
|
||||
@@ -600,11 +609,11 @@ export function rendererMap(context) {
|
||||
|
||||
|
||||
map.mouse = function() {
|
||||
var event = mousemove || d3_event;
|
||||
var event = _mouseEvent || d3_event;
|
||||
if (event) {
|
||||
var s;
|
||||
while ((s = event.sourceEvent)) { event = s; }
|
||||
return mouse(event);
|
||||
return _getMouseCoords(event);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -617,22 +626,22 @@ export function rendererMap(context) {
|
||||
};
|
||||
|
||||
|
||||
map.dblclickEnable = function(_) {
|
||||
map.dblclickEnable = function(val) {
|
||||
if (!arguments.length) return _dblClickEnabled;
|
||||
_dblClickEnabled = _;
|
||||
_dblClickEnabled = val;
|
||||
return map;
|
||||
};
|
||||
|
||||
|
||||
map.redrawEnable = function(_) {
|
||||
map.redrawEnable = function(val) {
|
||||
if (!arguments.length) return _redrawEnabled;
|
||||
_redrawEnabled = _;
|
||||
_redrawEnabled = val;
|
||||
return map;
|
||||
};
|
||||
|
||||
|
||||
map.isTransformed = function() {
|
||||
return _transformed;
|
||||
return _isTransformed;
|
||||
};
|
||||
|
||||
|
||||
@@ -663,7 +672,7 @@ export function rendererMap(context) {
|
||||
|
||||
var proj = geoRawMercator().transform(projection.transform()); // copy projection
|
||||
|
||||
var k2 = Math.max(kMin, Math.min(kMax, geoZoomToScale(z2, TILESIZE)));
|
||||
var k2 = clamp(geoZoomToScale(z2, TILESIZE), kMin, kMax);
|
||||
proj.scale(k2);
|
||||
|
||||
var t = proj.translate();
|
||||
@@ -702,13 +711,14 @@ export function rendererMap(context) {
|
||||
};
|
||||
|
||||
|
||||
map.dimensions = function(_) {
|
||||
if (!arguments.length) return dimensions;
|
||||
dimensions = _;
|
||||
drawLayers.dimensions(dimensions);
|
||||
context.background().dimensions(dimensions);
|
||||
projection.clipExtent([[0, 0], dimensions]);
|
||||
mouse = utilFastMouse(supersurface.node());
|
||||
map.dimensions = function(val) {
|
||||
if (!arguments.length) return _dimensions;
|
||||
|
||||
_dimensions = val;
|
||||
drawLayers.dimensions(_dimensions);
|
||||
context.background().dimensions(_dimensions);
|
||||
projection.clipExtent([[0, 0], _dimensions]);
|
||||
_getMouseCoords = utilFastMouse(supersurface.node());
|
||||
|
||||
scheduleRedraw();
|
||||
return map;
|
||||
@@ -749,7 +759,7 @@ export function rendererMap(context) {
|
||||
return Math.max(geoScaleToZoom(projection.scale(), TILESIZE), 0);
|
||||
}
|
||||
|
||||
if (z2 < minzoom) {
|
||||
if (z2 < _minzoom) {
|
||||
surface.interrupt();
|
||||
uiFlash().text(t('cannot_zoom'))();
|
||||
z2 = context.minEditableZoom();
|
||||
@@ -764,16 +774,6 @@ export function rendererMap(context) {
|
||||
};
|
||||
|
||||
|
||||
map.zoomTo = function(entity, zoomLimits) {
|
||||
var extent = entity.extent(context.graph());
|
||||
if (!isFinite(extent.area())) return;
|
||||
|
||||
var z2 = map.trimmedExtentZoom(extent);
|
||||
zoomLimits = zoomLimits || [context.minEditableZoom(), 20];
|
||||
map.centerZoom(extent.center(), Math.min(Math.max(z2, zoomLimits[0]), zoomLimits[1]));
|
||||
};
|
||||
|
||||
|
||||
map.centerZoom = function(loc2, z2) {
|
||||
if (setCenterZoom(loc2, z2)) {
|
||||
dispatch.call('move', this, map);
|
||||
@@ -784,6 +784,15 @@ export function rendererMap(context) {
|
||||
};
|
||||
|
||||
|
||||
map.zoomTo = function(entity) {
|
||||
var extent = entity.extent(context.graph());
|
||||
if (!isFinite(extent.area())) return map;
|
||||
|
||||
var z2 = clamp(map.trimmedExtentZoom(extent), context.minEditableZoom(), 20);
|
||||
return map.centerZoom(extent.center(), z2);
|
||||
};
|
||||
|
||||
|
||||
map.centerEase = function(loc2, duration) {
|
||||
duration = duration || 250;
|
||||
setCenterZoom(loc2, map.zoom(), duration);
|
||||
@@ -812,6 +821,15 @@ export function rendererMap(context) {
|
||||
};
|
||||
|
||||
|
||||
map.zoomToEase = function(entity, duration) {
|
||||
var extent = entity.extent(context.graph());
|
||||
if (!isFinite(extent.area())) return map;
|
||||
|
||||
var z2 = clamp(map.trimmedExtentZoom(extent), context.minEditableZoom(), 20);
|
||||
return map.centerZoomEase(extent.center(), z2, duration);
|
||||
};
|
||||
|
||||
|
||||
map.startEase = function() {
|
||||
utilBindOnce(surface, 'mousedown.ease', function() {
|
||||
map.cancelEase();
|
||||
@@ -826,36 +844,36 @@ export function rendererMap(context) {
|
||||
};
|
||||
|
||||
|
||||
map.extent = function(_) {
|
||||
map.extent = function(val) {
|
||||
if (!arguments.length) {
|
||||
return new geoExtent(
|
||||
projection.invert([0, dimensions[1]]),
|
||||
projection.invert([dimensions[0], 0])
|
||||
projection.invert([0, _dimensions[1]]),
|
||||
projection.invert([_dimensions[0], 0])
|
||||
);
|
||||
} else {
|
||||
var extent = geoExtent(_);
|
||||
var extent = geoExtent(val);
|
||||
map.centerZoom(extent.center(), map.extentZoom(extent));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
map.trimmedExtent = function(_) {
|
||||
map.trimmedExtent = function(val) {
|
||||
if (!arguments.length) {
|
||||
var headerY = 60;
|
||||
var footerY = 30;
|
||||
var pad = 10;
|
||||
return new geoExtent(
|
||||
projection.invert([pad, dimensions[1] - footerY - pad]),
|
||||
projection.invert([dimensions[0] - pad, headerY + pad])
|
||||
projection.invert([pad, _dimensions[1] - footerY - pad]),
|
||||
projection.invert([_dimensions[0] - pad, headerY + pad])
|
||||
);
|
||||
} else {
|
||||
var extent = geoExtent(_);
|
||||
var extent = geoExtent(val);
|
||||
map.centerZoom(extent.center(), map.trimmedExtentZoom(extent));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function calcZoom(extent, dim) {
|
||||
function calcExtentZoom(extent, dim) {
|
||||
var tl = projection([extent[0][0], extent[1][1]]);
|
||||
var br = projection([extent[1][0], extent[0][1]]);
|
||||
|
||||
@@ -870,16 +888,16 @@ export function rendererMap(context) {
|
||||
}
|
||||
|
||||
|
||||
map.extentZoom = function(_) {
|
||||
return calcZoom(geoExtent(_), dimensions);
|
||||
map.extentZoom = function(val) {
|
||||
return calcExtentZoom(geoExtent(val), _dimensions);
|
||||
};
|
||||
|
||||
|
||||
map.trimmedExtentZoom = function(_) {
|
||||
map.trimmedExtentZoom = function(val) {
|
||||
var trimY = 120;
|
||||
var trimX = 40;
|
||||
var trimmed = [dimensions[0] - trimX, dimensions[1] - trimY];
|
||||
return calcZoom(geoExtent(_), trimmed);
|
||||
var trimmed = [_dimensions[0] - trimX, _dimensions[1] - trimY];
|
||||
return calcExtentZoom(geoExtent(val), trimmed);
|
||||
};
|
||||
|
||||
|
||||
@@ -899,9 +917,9 @@ export function rendererMap(context) {
|
||||
};
|
||||
|
||||
|
||||
map.minzoom = function(_) {
|
||||
if (!arguments.length) return minzoom;
|
||||
minzoom = _;
|
||||
map.minzoom = function(val) {
|
||||
if (!arguments.length) return _minzoom;
|
||||
_minzoom = val;
|
||||
return map;
|
||||
};
|
||||
|
||||
|
||||
@@ -41,13 +41,13 @@ function abortRequest(i) {
|
||||
}
|
||||
|
||||
function abortUnwantedRequests(cache, tiles) {
|
||||
_forEach(cache.inflight, function(v, k) {
|
||||
_forEach(cache.inflightTile, function(v, k) {
|
||||
var wanted = _find(tiles, function(tile) {
|
||||
return k === tile.id;
|
||||
});
|
||||
if (!wanted) {
|
||||
abortRequest(v);
|
||||
delete cache.inflight[k];
|
||||
delete cache.inflightTile[k];
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -279,12 +279,13 @@ export default {
|
||||
|
||||
reset: function() {
|
||||
if (_krCache) {
|
||||
_forEach(_krCache.inflight, abortRequest);
|
||||
_forEach(_krCache.inflightTile, abortRequest);
|
||||
}
|
||||
_krCache = {
|
||||
data: {},
|
||||
loaded: {},
|
||||
inflight: {},
|
||||
loadedTile: {},
|
||||
inflightTile: {},
|
||||
inflightPost: {},
|
||||
closed: {},
|
||||
rtree: rbush()
|
||||
};
|
||||
@@ -306,18 +307,18 @@ export default {
|
||||
|
||||
// issue new requests..
|
||||
tiles.forEach(function(tile) {
|
||||
if (_krCache.loaded[tile.id] || _krCache.inflight[tile.id]) return;
|
||||
if (_krCache.loadedTile[tile.id] || _krCache.inflightTile[tile.id]) return;
|
||||
|
||||
var rect = tile.extent.rectangle();
|
||||
var params = _extend({}, options, { left: rect[0], bottom: rect[3], right: rect[2], top: rect[1] });
|
||||
var url = _krUrlRoot + 'export.php?' + utilQsString(params) + '&ch=' + rules;
|
||||
|
||||
_krCache.inflight[tile.id] = d3_json(url,
|
||||
_krCache.inflightTile[tile.id] = d3_json(url,
|
||||
function(err, data) {
|
||||
delete _krCache.inflight[tile.id];
|
||||
delete _krCache.inflightTile[tile.id];
|
||||
|
||||
if (err) return;
|
||||
_krCache.loaded[tile.id] = true;
|
||||
_krCache.loadedTile[tile.id] = true;
|
||||
|
||||
if (!data.features || !data.features.length) return;
|
||||
|
||||
@@ -404,7 +405,7 @@ export default {
|
||||
|
||||
|
||||
postKeepRightUpdate: function(d, callback) {
|
||||
if (_krCache.inflight[d.id]) {
|
||||
if (_krCache.inflightPost[d.id]) {
|
||||
return callback({ message: 'Error update already inflight', status: -2 }, d);
|
||||
}
|
||||
|
||||
@@ -421,9 +422,9 @@ export default {
|
||||
// NOTE: This throws a CORS err, but it seems successful.
|
||||
// We don't care too much about the response, so this is fine.
|
||||
var url = _krUrlRoot + 'comment.php?' + utilQsString(params);
|
||||
_krCache.inflight[d.id] = d3_request(url)
|
||||
_krCache.inflightPost[d.id] = d3_request(url)
|
||||
.post(function(err) {
|
||||
delete _krCache.inflight[d.id];
|
||||
delete _krCache.inflightPost[d.id];
|
||||
|
||||
if (d.state === 'ignore') { // ignore permanently (false positive)
|
||||
that.removeError(d);
|
||||
|
||||
@@ -88,6 +88,21 @@ export default {
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Convert monolingual property into a key-value object (language -> value)
|
||||
* @param entity object from wikibase
|
||||
* @param property string e.g. 'P31' for monolingual wiki page title
|
||||
*/
|
||||
monolingualClaimToValueObj: function(entity, property) {
|
||||
if (!entity || !entity.claims[property]) return undefined;
|
||||
return entity.claims[property].reduce(function(acc, obj) {
|
||||
var value = obj.mainsnak.datavalue.value;
|
||||
acc[value.language] = value.text;
|
||||
return acc;
|
||||
}, {});
|
||||
},
|
||||
|
||||
|
||||
toSitelink: function(key, value) {
|
||||
var result = value ? 'Tag:' + key + '=' + value : 'Key:' + key;
|
||||
return result.replace(/_/g, ' ').trim();
|
||||
|
||||
@@ -138,7 +138,7 @@ function sortKeys(a, b) {
|
||||
}
|
||||
|
||||
|
||||
var debouncedRequest = _debounce(request, 500, { leading: false });
|
||||
var debouncedRequest = _debounce(request, 300, { leading: false });
|
||||
|
||||
function request(url, params, exactMatch, callback, loaded) {
|
||||
if (_inflight[url]) return;
|
||||
|
||||
@@ -210,14 +210,16 @@ export function svgLines(projection, context) {
|
||||
function shouldReverse(entity) { return entity.tags.oneway === '-1'; },
|
||||
function bothDirections(entity) {
|
||||
return entity.tags.oneway === 'reversible' || entity.tags.oneway === 'alternating';
|
||||
});
|
||||
}
|
||||
);
|
||||
onewaydata[k] = _flatten(_map(onewayArr, onewaySegments));
|
||||
|
||||
var sidedArr = _filter(v, function(d) { return d.isSided(); });
|
||||
var sidedSegments = svgMarkerSegments(
|
||||
projection, graph, 30,
|
||||
function shouldReverse() { return false; },
|
||||
function bothDirections() { return false; });
|
||||
function bothDirections() { return false; }
|
||||
);
|
||||
sideddata[k] = _flatten(_map(sidedArr, sidedSegments));
|
||||
});
|
||||
|
||||
@@ -261,10 +263,11 @@ export function svgLines(projection, context) {
|
||||
|
||||
addMarkers(layergroup, 'oneway', 'onewaygroup', onewaydata, 'url(#oneway-marker)');
|
||||
addMarkers(layergroup, 'sided', 'sidedgroup', sideddata,
|
||||
function marker(d) {
|
||||
var category = graph.entity(d.id).sidednessIdentifier();
|
||||
return 'url(#sided-marker-' + category + ')';
|
||||
});
|
||||
function marker(d) {
|
||||
var category = graph.entity(d.id).sidednessIdentifier();
|
||||
return 'url(#sided-marker-' + category + ')';
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Draw touch targets..
|
||||
|
||||
+33
-19
@@ -17,22 +17,33 @@ export function svgTagClasses() {
|
||||
'surface', 'tracktype', 'footway', 'crossing', 'service', 'sport',
|
||||
'public_transport', 'location', 'parking'
|
||||
];
|
||||
var tagClassRe = /^tag-/;
|
||||
var _tags = function(entity) { return entity.tags; };
|
||||
|
||||
|
||||
var tagClasses = function(selection) {
|
||||
selection.each(function tagClassesEach(entity) {
|
||||
var value = this.className;
|
||||
var classes, primary, status;
|
||||
var primary, status;
|
||||
|
||||
if (value.baseVal !== undefined) value = value.baseVal;
|
||||
if (value.baseVal !== undefined) {
|
||||
value = value.baseVal;
|
||||
}
|
||||
|
||||
classes = value.trim().split(/\s+/).filter(function(name) {
|
||||
return name.length && !tagClassRe.test(name);
|
||||
}).join(' ');
|
||||
var t = _tags(entity);
|
||||
var isMultiPolygon = (t.type === 'multipolygon');
|
||||
var shouldRenderLineAsArea = isMultiPolygon && !entity.hasInterestingTags();
|
||||
|
||||
var i, k, v;
|
||||
|
||||
// preserve base classes (nothing with `tag-`)
|
||||
var classes = value.trim().split(/\s+/)
|
||||
.filter(function(klass) {
|
||||
return klass.length && !/^tag-/.test(klass);
|
||||
})
|
||||
.map(function(klass) { // style multipolygon inner/outers as areas not lines
|
||||
return (klass === 'line' && shouldRenderLineAsArea) ? 'area' : klass;
|
||||
});
|
||||
|
||||
var t = _tags(entity), i, k, v;
|
||||
|
||||
// pick at most one primary classification tag..
|
||||
for (i = 0; i < primaries.length; i++) {
|
||||
@@ -43,9 +54,10 @@ export function svgTagClasses() {
|
||||
primary = k;
|
||||
if (statuses.indexOf(v) !== -1) { // e.g. `railway=abandoned`
|
||||
status = v;
|
||||
classes += ' tag-' + k;
|
||||
classes.push('tag-' + k);
|
||||
} else {
|
||||
classes += ' tag-' + k + ' tag-' + k + '-' + v;
|
||||
classes.push('tag-' + k);
|
||||
classes.push('tag-' + k + '-' + v);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -66,7 +78,7 @@ export function svgTagClasses() {
|
||||
} else if (!primary && primaries.indexOf(v) !== -1) { // e.g. `abandoned=railway`
|
||||
status = k;
|
||||
primary = v;
|
||||
classes += ' tag-' + v;
|
||||
classes.push('tag-' + v);
|
||||
} // else ignore e.g. `highway=path + abandoned=railway`
|
||||
|
||||
if (status) break;
|
||||
@@ -74,15 +86,17 @@ export function svgTagClasses() {
|
||||
}
|
||||
|
||||
if (status) {
|
||||
classes += ' tag-status tag-status-' + status;
|
||||
classes.push('tag-status');
|
||||
classes.push('tag-status-' + status);
|
||||
}
|
||||
|
||||
// add any secondary (structure) tags
|
||||
// add any secondary tags
|
||||
for (i = 0; i < secondaries.length; i++) {
|
||||
k = secondaries[i];
|
||||
v = t[k];
|
||||
if (!v || v === 'no') continue;
|
||||
classes += ' tag-' + k + ' tag-' + k + '-' + v;
|
||||
classes.push('tag-' + k);
|
||||
classes.push('tag-' + k + '-' + v);
|
||||
}
|
||||
|
||||
// For highways, look for surface tagging..
|
||||
@@ -96,22 +110,22 @@ export function svgTagClasses() {
|
||||
}
|
||||
}
|
||||
if (!paved) {
|
||||
classes += ' tag-unpaved';
|
||||
classes.push('tag-unpaved');
|
||||
}
|
||||
}
|
||||
|
||||
classes = classes.trim();
|
||||
|
||||
if (classes !== value) {
|
||||
d3_select(this).attr('class', classes);
|
||||
var computed = classes.join(' ').trim();
|
||||
if (computed !== value) {
|
||||
d3_select(this).attr('class', computed);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
tagClasses.tags = function(_) {
|
||||
tagClasses.tags = function(val) {
|
||||
if (!arguments.length) return _tags;
|
||||
_tags = _;
|
||||
_tags = val;
|
||||
return tagClasses;
|
||||
};
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ export function uiAttribution(context) {
|
||||
}
|
||||
|
||||
|
||||
var id_safe = d.id.replace('.', '<TX_DOT>');
|
||||
var id_safe = d.id.replace(/\./g, '<TX_DOT>');
|
||||
var terms_text = t('imagery.' + id_safe + '.attribution.text',
|
||||
{ default: d.terms_text || d.id || d.name() }
|
||||
);
|
||||
|
||||
@@ -29,6 +29,8 @@ import { tooltip } from '../util/tooltip';
|
||||
export function uiBackground(context) {
|
||||
var key = t('background.key');
|
||||
|
||||
var pane = d3_select(null);
|
||||
|
||||
var _customSource = context.background().findSource('custom');
|
||||
var _previousBackground = context.background().findSource(context.storage('background-last-used-toggle'));
|
||||
var _shown = false;
|
||||
@@ -252,6 +254,8 @@ export function uiBackground(context) {
|
||||
.attr('href', 'https://github.com/openstreetmap/iD/blob/master/FAQ.md#how-can-i-report-an-issue-with-background-imagery')
|
||||
.append('span')
|
||||
.text(t('background.imagery_source_faq'));
|
||||
|
||||
updateBackgroundList();
|
||||
}
|
||||
|
||||
|
||||
@@ -264,15 +268,30 @@ export function uiBackground(context) {
|
||||
.attr('class', 'layer-list layer-overlay-list')
|
||||
.attr('dir', 'auto')
|
||||
.merge(container);
|
||||
|
||||
updateOverlayList();
|
||||
}
|
||||
|
||||
function updateBackgroundList() {
|
||||
_backgroundList
|
||||
.call(drawListItems, 'radio', chooseBackground, function(d) { return !d.isHidden() && !d.overlay; });
|
||||
}
|
||||
|
||||
function updateOverlayList() {
|
||||
_overlayList
|
||||
.call(drawListItems, 'checkbox', chooseOverlay, function(d) { return !d.isHidden() && d.overlay; });
|
||||
}
|
||||
|
||||
|
||||
function update() {
|
||||
_backgroundList
|
||||
.call(drawListItems, 'radio', chooseBackground, function(d) { return !d.isHidden() && !d.overlay; });
|
||||
|
||||
_overlayList
|
||||
.call(drawListItems, 'checkbox', chooseOverlay, function(d) { return !d.isHidden() && d.overlay; });
|
||||
if (!pane.select('.disclosure-wrap-background_list').classed('hide')) {
|
||||
updateBackgroundList();
|
||||
}
|
||||
|
||||
if (!pane.select('.disclosure-wrap-overlay_list').classed('hide')) {
|
||||
updateOverlayList();
|
||||
}
|
||||
|
||||
_displayOptionsContainer
|
||||
.call(backgroundDisplayOptions);
|
||||
@@ -338,7 +357,7 @@ export function uiBackground(context) {
|
||||
}
|
||||
|
||||
|
||||
var pane = selection
|
||||
pane = selection
|
||||
.append('div')
|
||||
.attr('class', 'fillL map-pane hide');
|
||||
|
||||
|
||||
+160
-81
@@ -18,17 +18,20 @@ import { utilGetSetValue, utilRebind, utilTriggerEvent } from '../util';
|
||||
// value: 'display text'
|
||||
// }, ...]
|
||||
|
||||
var _comboTimerID;
|
||||
var _comboHideTimerID;
|
||||
|
||||
export function uiCombobox(context, klass) {
|
||||
var dispatch = d3_dispatch('accept', 'cancel');
|
||||
var container = context.container();
|
||||
|
||||
var _suggestions = [];
|
||||
var _values = [];
|
||||
var _choice = null;
|
||||
var _selected = null;
|
||||
var _canAutocomplete = true;
|
||||
var _caseSensitive = false;
|
||||
var _cancelFetch = false;
|
||||
var _minItems = 2;
|
||||
var _tDown = 0;
|
||||
|
||||
var _fetcher = function(val, cb) {
|
||||
cb(_values.filter(function(d) {
|
||||
@@ -44,14 +47,15 @@ export function uiCombobox(context, klass) {
|
||||
|
||||
input
|
||||
.classed('combobox-input', true)
|
||||
.on('focus.typeahead', focus)
|
||||
.on('blur.typeahead', blur)
|
||||
.on('keydown.typeahead', keydown)
|
||||
.on('keyup.typeahead', keyup)
|
||||
.on('input.typeahead', change)
|
||||
.on('mousedown', mousedown)
|
||||
.on('focus.combobox', focus)
|
||||
.on('blur.combobox', blur)
|
||||
.on('keydown.combobox', keydown)
|
||||
.on('keyup.combobox', keyup)
|
||||
.on('input.combobox', change)
|
||||
.on('mousedown.combobox', mousedown)
|
||||
.each(addCaret);
|
||||
|
||||
|
||||
function addCaret() {
|
||||
var parent = this.parentNode;
|
||||
var sibling = this.nextSibling;
|
||||
@@ -65,14 +69,52 @@ export function uiCombobox(context, klass) {
|
||||
}
|
||||
|
||||
|
||||
function mousedown() {
|
||||
if (d3_event.button !== 0) return; // left click only
|
||||
|
||||
var start = input.property('selectionStart');
|
||||
var end = input.property('selectionEnd');
|
||||
if (start !== end) return; // exit if user is deselecting
|
||||
|
||||
_tDown = +new Date();
|
||||
input.on('mouseup.combobox', mouseup);
|
||||
}
|
||||
|
||||
|
||||
function mouseup() {
|
||||
input.on('mouseup.combobox', null);
|
||||
|
||||
if (d3_event.button !== 0) return; // left click only
|
||||
|
||||
var start = input.property('selectionStart');
|
||||
var end = input.property('selectionEnd');
|
||||
if (start !== end) return; // exit if user is selecting
|
||||
|
||||
var combo = container.selectAll('.combobox');
|
||||
if (combo.empty()) { // not showing - try to show it.
|
||||
var tOrig = _tDown;
|
||||
window.setTimeout(function() {
|
||||
if (tOrig !== _tDown) return; // exit if user double clicked
|
||||
input.node().focus();
|
||||
fetch('', function() {
|
||||
show();
|
||||
render();
|
||||
});
|
||||
}, 75);
|
||||
|
||||
} else {
|
||||
hide();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function focus() {
|
||||
_choice = null;
|
||||
fetch(''); // prefetch values (may warm taginfo cache)
|
||||
}
|
||||
|
||||
|
||||
function blur() {
|
||||
_comboTimerID = window.setTimeout(hide, 150);
|
||||
_comboHideTimerID = window.setTimeout(hide, 75);
|
||||
}
|
||||
|
||||
|
||||
@@ -86,7 +128,7 @@ export function uiCombobox(context, klass) {
|
||||
.style('position', 'absolute')
|
||||
.style('display', 'block')
|
||||
.style('left', '0px')
|
||||
.on('mousedown', function () {
|
||||
.on('mousedown.combobox', function () {
|
||||
// prevent moving focus out of the input field
|
||||
d3_event.preventDefault();
|
||||
});
|
||||
@@ -97,9 +139,9 @@ export function uiCombobox(context, klass) {
|
||||
|
||||
|
||||
function hide() {
|
||||
if (_comboTimerID) {
|
||||
window.clearTimeout(_comboTimerID);
|
||||
_comboTimerID = undefined;
|
||||
if (_comboHideTimerID) {
|
||||
window.clearTimeout(_comboHideTimerID);
|
||||
_comboHideTimerID = undefined;
|
||||
}
|
||||
|
||||
container.selectAll('.combobox')
|
||||
@@ -118,12 +160,12 @@ export function uiCombobox(context, klass) {
|
||||
case 8: // ⌫ Backspace
|
||||
case 46: // ⌦ Delete
|
||||
d3_event.stopPropagation();
|
||||
_choice = null;
|
||||
_selected = null;
|
||||
render();
|
||||
input.on('input.typeahead', function() {
|
||||
input.on('input.combobox', function() {
|
||||
var start = input.property('selectionStart');
|
||||
input.node().setSelectionRange(start, start);
|
||||
input.on('input.typeahead', change);
|
||||
input.on('input.combobox', change);
|
||||
});
|
||||
break;
|
||||
|
||||
@@ -171,51 +213,66 @@ export function uiCombobox(context, klass) {
|
||||
}
|
||||
|
||||
|
||||
// return the datum for the currently chosen value
|
||||
function datum(val) {
|
||||
for (var i = 0; i < _suggestions.length; i++) {
|
||||
var suggestion = _suggestions[i];
|
||||
if (suggestion.value === val) {
|
||||
return suggestion;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// Called whenever the input value is changed (e.g. on typing)
|
||||
function change() {
|
||||
fetch(value(), function() {
|
||||
if (input.property('selectionEnd') === input.property('value').length) {
|
||||
tryAutocomplete();
|
||||
_selected = null;
|
||||
var val = input.property('value');
|
||||
|
||||
if (_suggestions.length) {
|
||||
if (input.property('selectionEnd') === val.length) {
|
||||
_selected = tryAutocomplete();
|
||||
}
|
||||
|
||||
if (!_selected) {
|
||||
_selected = datum(val);
|
||||
}
|
||||
}
|
||||
|
||||
var combo = container.selectAll('.combobox');
|
||||
if (combo.empty()) {
|
||||
show();
|
||||
if (val.length) {
|
||||
var combo = container.selectAll('.combobox');
|
||||
if (combo.empty()) {
|
||||
show();
|
||||
}
|
||||
} else {
|
||||
hide();
|
||||
}
|
||||
|
||||
render();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function mousedown() {
|
||||
// prevent the form element from blurring. it blurs on mousedown
|
||||
d3_event.stopPropagation();
|
||||
d3_event.preventDefault();
|
||||
var combo = container.selectAll('.combobox');
|
||||
if (combo.empty()) {
|
||||
input.node().focus();
|
||||
fetch('', function() {
|
||||
show();
|
||||
render();
|
||||
});
|
||||
} else {
|
||||
hide();
|
||||
}
|
||||
}
|
||||
|
||||
// Called when the user presses up/down arrows to navigate the list
|
||||
function nav(dir) {
|
||||
if (!_suggestions.length) return;
|
||||
|
||||
var index = -1;
|
||||
for (var i = 0; i < _suggestions.length; i++) {
|
||||
if (_choice && _suggestions[i].value === _choice.value) {
|
||||
index = i;
|
||||
break;
|
||||
if (_suggestions.length) {
|
||||
// try to determine previously selected index..
|
||||
var index = -1;
|
||||
for (var i = 0; i < _suggestions.length; i++) {
|
||||
if (_selected && _suggestions[i].value === _selected.value) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// pick new _selected
|
||||
index = Math.max(Math.min(index + dir, _suggestions.length - 1), 0);
|
||||
_selected = _suggestions[index];
|
||||
input.property('value', _selected.value);
|
||||
}
|
||||
|
||||
index = Math.max(Math.min(index + dir, _suggestions.length - 1), 0);
|
||||
_choice = _suggestions[index];
|
||||
input.property('value', _choice.value);
|
||||
render();
|
||||
ensureVisible();
|
||||
}
|
||||
@@ -256,7 +313,10 @@ export function uiCombobox(context, klass) {
|
||||
|
||||
|
||||
function fetch(v, cb) {
|
||||
_cancelFetch = false;
|
||||
|
||||
_fetcher.call(input, v, function(results) {
|
||||
if (_cancelFetch) return;
|
||||
_suggestions = results;
|
||||
if (cb) {
|
||||
cb();
|
||||
@@ -268,33 +328,33 @@ export function uiCombobox(context, klass) {
|
||||
function tryAutocomplete() {
|
||||
if (!_canAutocomplete) return;
|
||||
|
||||
var v = _caseSensitive ? value() : value().toLowerCase();
|
||||
_choice = null;
|
||||
if (!v) return;
|
||||
var val = _caseSensitive ? value() : value().toLowerCase();
|
||||
if (!val) return;
|
||||
|
||||
// Don't autocomplete if user is typing a number - #4935
|
||||
if (!isNaN(parseFloat(v)) && isFinite(v)) return;
|
||||
if (!isNaN(parseFloat(val)) && isFinite(val)) return;
|
||||
|
||||
var best = -1;
|
||||
var bestIndex = -1;
|
||||
for (var i = 0; i < _suggestions.length; i++) {
|
||||
var suggestion = _suggestions[i].value;
|
||||
var compare = _caseSensitive ? suggestion : suggestion.toLowerCase();
|
||||
|
||||
// if search string matches suggestion exactly, pick it..
|
||||
if (compare === v) {
|
||||
best = i;
|
||||
if (compare === val) {
|
||||
bestIndex = i;
|
||||
break;
|
||||
|
||||
// otherwise lock in the first result that starts with the search string..
|
||||
} else if (best === -1 && compare.indexOf(v) === 0) {
|
||||
best = i;
|
||||
} else if (bestIndex === -1 && compare.indexOf(val) === 0) {
|
||||
bestIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (best !== -1) {
|
||||
_choice = _suggestions[best];
|
||||
input.property('value', _choice.value);
|
||||
input.node().setSelectionRange(v.length, _choice.value.length);
|
||||
if (bestIndex !== -1) {
|
||||
var bestVal = _suggestions[bestIndex].value;
|
||||
input.property('value', bestVal);
|
||||
input.node().setSelectionRange(val.length, bestVal.length);
|
||||
return _suggestions[bestIndex];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,8 +382,8 @@ export function uiCombobox(context, klass) {
|
||||
.text(function(d) { return d.value; })
|
||||
.merge(options)
|
||||
.attr('title', function(d) { return d.title; })
|
||||
.classed('selected', function(d) { return d === _choice; })
|
||||
.on('click', accept)
|
||||
.classed('selected', function(d) { return d === _selected; })
|
||||
.on('click.combobox', accept)
|
||||
.order();
|
||||
|
||||
var node = attachTo ? attachTo.node() : input.node();
|
||||
@@ -335,28 +395,49 @@ export function uiCombobox(context, klass) {
|
||||
.style('top', rect.height + rect.top + 'px');
|
||||
}
|
||||
|
||||
// Dispatches an 'accept' event if an option has been chosen.
|
||||
|
||||
// Dispatches an 'accept' event
|
||||
// Then hides the combobox.
|
||||
function accept(d) {
|
||||
d = d || _choice || value();
|
||||
if (d) {
|
||||
utilGetSetValue(input, d.value);
|
||||
_cancelFetch = true;
|
||||
var thiz = input.node();
|
||||
|
||||
if (d) { // user clicked on a suggestion
|
||||
utilGetSetValue(input, d.value); // replace field contents
|
||||
utilTriggerEvent(input, 'change');
|
||||
dispatch.call('accept', this, d);
|
||||
}
|
||||
|
||||
// clear (and keep) selection
|
||||
var val = utilGetSetValue(input);
|
||||
thiz.setSelectionRange(val.length, val.length);
|
||||
|
||||
d = datum(val);
|
||||
dispatch.call('accept', thiz, d, val);
|
||||
hide();
|
||||
}
|
||||
|
||||
|
||||
// Dispatches an 'cancel' event
|
||||
// Then hides the combobox.
|
||||
function cancel(d) {
|
||||
d = d || _choice;
|
||||
dispatch.call('cancel', this, d);
|
||||
function cancel() {
|
||||
_cancelFetch = true;
|
||||
var thiz = input.node();
|
||||
|
||||
// clear (and remove) selection, and replace field contents
|
||||
var val = utilGetSetValue(input);
|
||||
var start = input.property('selectionStart');
|
||||
var end = input.property('selectionEnd');
|
||||
val = val.slice(0, start) + val.slice(end);
|
||||
utilGetSetValue(input, val);
|
||||
thiz.setSelectionRange(val.length, val.length);
|
||||
|
||||
dispatch.call('cancel', thiz);
|
||||
hide();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
combobox.canAutocomplete = function(val) {
|
||||
if (!arguments.length) return _canAutocomplete;
|
||||
_canAutocomplete = val;
|
||||
@@ -394,16 +475,14 @@ export function uiCombobox(context, klass) {
|
||||
|
||||
uiCombobox.off = function(input) {
|
||||
input
|
||||
.on('focus.typeahead', null)
|
||||
.on('blur.typeahead', null)
|
||||
.on('keydown.typeahead', null)
|
||||
.on('keyup.typeahead', null)
|
||||
.on('input.typeahead', null)
|
||||
.each(function() {
|
||||
d3_select(this.parentNode).selectAll('.combobox-caret')
|
||||
.filter(function(d) { return d === input.node(); })
|
||||
.on('mousedown', null);
|
||||
});
|
||||
.on('focus.combobox', null)
|
||||
.on('blur.combobox', null)
|
||||
.on('keydown.combobox', null)
|
||||
.on('keyup.combobox', null)
|
||||
.on('input.combobox', null)
|
||||
.on('mousedown.combobox', null)
|
||||
.on('mouseup.combobox', null);
|
||||
|
||||
|
||||
d3_select('body')
|
||||
.on('scroll.combobox', null);
|
||||
|
||||
@@ -150,7 +150,7 @@ export function uiCommitChanges(context) {
|
||||
} else {
|
||||
var entity = change.entity;
|
||||
_entityID = change.entity.id;
|
||||
context.map().zoomTo(entity);
|
||||
context.map().zoomToEase(entity);
|
||||
context.surface().selectAll(utilEntityOrMemberSelector([_entityID], context.graph()))
|
||||
.classed('hover', true);
|
||||
}
|
||||
|
||||
@@ -310,7 +310,7 @@ export function uiConflicts(context) {
|
||||
if (extent) {
|
||||
context.map().trimmedExtent(extent);
|
||||
} else {
|
||||
context.map().zoomTo(entity);
|
||||
context.map().zoomToEase(entity);
|
||||
}
|
||||
context.surface().selectAll(utilEntityOrMemberSelector([entity.id], context.graph()))
|
||||
.classed('hover', true);
|
||||
|
||||
@@ -157,9 +157,10 @@ export function uiEntityEditor(context) {
|
||||
.preset(_activePreset)
|
||||
);
|
||||
|
||||
// NOTE: split on en-dash, not a hypen (to avoid conflict with hyphenated names)
|
||||
var label = body.select('.label-inner');
|
||||
var nameparts = label.selectAll('.namepart')
|
||||
.data(_activePreset.name().split(' - '), function(d) { return d; });
|
||||
.data(_activePreset.name().split(' – '), function(d) { return d; });
|
||||
|
||||
nameparts.exit()
|
||||
.remove();
|
||||
|
||||
@@ -316,7 +316,7 @@ export function uiFeatureList(context) {
|
||||
function click(d) {
|
||||
d3_event.preventDefault();
|
||||
if (d.location) {
|
||||
context.map().centerZoom([d.location[1], d.location[0]], 19);
|
||||
context.map().centerZoomEase([d.location[1], d.location[0]], 19);
|
||||
}
|
||||
else if (d.entity) {
|
||||
if (d.entity.type === 'node') {
|
||||
|
||||
@@ -42,6 +42,7 @@ export function uiFieldCombo(field, context) {
|
||||
.caseSensitive(caseSensitive)
|
||||
.minItems(isMulti || isSemi ? 1 : 2);
|
||||
var container = d3_select(null);
|
||||
var inputWrap = d3_select(null);
|
||||
var input = d3_select(null);
|
||||
var _comboData = [];
|
||||
var _multiData = [];
|
||||
@@ -190,6 +191,9 @@ export function uiFieldCombo(field, context) {
|
||||
});
|
||||
}
|
||||
|
||||
// hide the caret if there are no suggestions
|
||||
container.classed('empty-combobox', data.length === 0);
|
||||
|
||||
_comboData = _map(data, function(d) {
|
||||
var k = d.value;
|
||||
if (isMulti) k = k.replace(field.key, '');
|
||||
@@ -292,17 +296,37 @@ export function uiFieldCombo(field, context) {
|
||||
container = container.selectAll('.chiplist')
|
||||
.data([0]);
|
||||
|
||||
var listClass = 'chiplist';
|
||||
|
||||
// Use a separate line for each value in the Destinations field
|
||||
// to mimic highway exit signs
|
||||
if (field.id === 'destination_oneway') {
|
||||
listClass += ' full-line-chips';
|
||||
}
|
||||
|
||||
container = container.enter()
|
||||
.append('ul')
|
||||
.attr('class', 'chiplist')
|
||||
.attr('class', listClass)
|
||||
.on('click', function() {
|
||||
window.setTimeout(function() { input.node().focus(); }, 10);
|
||||
})
|
||||
.merge(container);
|
||||
}
|
||||
|
||||
input = container.selectAll('input')
|
||||
.data([0]);
|
||||
|
||||
inputWrap = container.selectAll('.input-wrap')
|
||||
.data([0]);
|
||||
|
||||
inputWrap = inputWrap.enter()
|
||||
.append('li')
|
||||
.attr('class', 'input-wrap')
|
||||
.merge(inputWrap);
|
||||
|
||||
input = inputWrap.selectAll('input')
|
||||
.data([0]);
|
||||
} else {
|
||||
input = container.selectAll('input')
|
||||
.data([0]);
|
||||
}
|
||||
|
||||
input = input.enter()
|
||||
.append('input')
|
||||
@@ -323,6 +347,16 @@ export function uiFieldCombo(field, context) {
|
||||
.on('change', change)
|
||||
.on('blur', change);
|
||||
|
||||
input
|
||||
.on('keydown.field', function() {
|
||||
switch (d3_event.keyCode) {
|
||||
case 13: // ↩ Return
|
||||
input.node().blur(); // blurring also enters the value
|
||||
d3_event.stopPropagation();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
if (isMulti || isSemi) {
|
||||
combobox
|
||||
.on('accept', function() {
|
||||
@@ -385,7 +419,7 @@ export function uiFieldCombo(field, context) {
|
||||
.remove();
|
||||
|
||||
var enter = chips.enter()
|
||||
.insert('li', 'input')
|
||||
.insert('li', '.input-wrap')
|
||||
.attr('class', 'chips');
|
||||
|
||||
enter.append('span');
|
||||
|
||||
@@ -8,7 +8,7 @@ import { utilGetSetValue, utilNoAuto, utilRebind } from '../../util';
|
||||
export function uiFieldCycleway(field, context) {
|
||||
var dispatch = d3_dispatch('change');
|
||||
var items = d3_select(null);
|
||||
|
||||
var wrap = d3_select(null);
|
||||
|
||||
function cycleway(selection) {
|
||||
|
||||
@@ -17,7 +17,7 @@ export function uiFieldCycleway(field, context) {
|
||||
}
|
||||
|
||||
|
||||
var wrap = selection.selectAll('.form-field-input-wrap')
|
||||
wrap = selection.selectAll('.form-field-input-wrap')
|
||||
.data([0]);
|
||||
|
||||
wrap = wrap.enter()
|
||||
@@ -122,8 +122,8 @@ export function uiFieldCycleway(field, context) {
|
||||
|
||||
|
||||
cycleway.focus = function() {
|
||||
items.selectAll('.preset-input-cycleway')
|
||||
.node().focus();
|
||||
var node = wrap.selectAll('input').node();
|
||||
if (node) node.focus();
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -147,25 +147,8 @@ export function uiFieldLocalized(field, context) {
|
||||
input
|
||||
.call(brandCombo
|
||||
.fetcher(fetchBrandNames(preset, allSuggestions))
|
||||
.on('accept', function(d) {
|
||||
var entity = context.entity(_entity.id); // get latest
|
||||
var tags = entity.tags;
|
||||
var geometry = entity.geometry(context.graph());
|
||||
var removed = preset.unsetTags(tags, geometry);
|
||||
for (var k in tags) {
|
||||
tags[k] = removed[k]; // set removed tags to `undefined`
|
||||
}
|
||||
tags = d.suggestion.setTags(tags, geometry);
|
||||
utilGetSetValue(input, tags.name);
|
||||
dispatch.call('change', this, tags);
|
||||
})
|
||||
.on('cancel', function() {
|
||||
// user hit escape, remove whatever is after the '-'
|
||||
var name = utilGetSetValue(input);
|
||||
name = name.split('-', 2)[0].trim();
|
||||
utilGetSetValue(input, name);
|
||||
dispatch.call('change', this, { name: name });
|
||||
})
|
||||
.on('accept', acceptBrand)
|
||||
.on('cancel', cancelBrand)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -216,6 +199,37 @@ export function uiFieldLocalized(field, context) {
|
||||
|
||||
|
||||
|
||||
function acceptBrand(d) {
|
||||
if (!d) {
|
||||
cancelBrand();
|
||||
return;
|
||||
}
|
||||
|
||||
var entity = context.entity(_entity.id); // get latest
|
||||
var tags = entity.tags;
|
||||
var geometry = entity.geometry(context.graph());
|
||||
var removed = preset.unsetTags(tags, geometry);
|
||||
for (var k in tags) {
|
||||
tags[k] = removed[k]; // set removed tags to `undefined`
|
||||
}
|
||||
tags = d.suggestion.setTags(tags, geometry);
|
||||
utilGetSetValue(input, tags.name);
|
||||
dispatch.call('change', this, tags);
|
||||
}
|
||||
|
||||
|
||||
function cancelBrand() {
|
||||
// user hit escape, remove whatever is after the last ' – '
|
||||
// NOTE: split/join on en-dash, not a hypen (to avoid conflict with fr - nl names in Brussels etc)
|
||||
var name = utilGetSetValue(input);
|
||||
var parts = name.split(' – ');
|
||||
parts.pop();
|
||||
name = parts.join(' – ');
|
||||
utilGetSetValue(input, name);
|
||||
dispatch.call('change', this, { name: name });
|
||||
}
|
||||
|
||||
|
||||
function fetchBrandNames(preset, suggestions) {
|
||||
var pTag = preset.id.split('/', 2);
|
||||
var pKey = pTag[0];
|
||||
|
||||
@@ -97,6 +97,7 @@ export function uiFormFields(context) {
|
||||
.call(moreCombo
|
||||
.data(notShown)
|
||||
.on('accept', function (d) {
|
||||
if (!d) return; // user entered something that was not matched
|
||||
var field = d.field;
|
||||
field.show();
|
||||
selection.call(formFields); // rerender
|
||||
|
||||
+15
-16
@@ -7,19 +7,19 @@ import { uiLoading } from './loading';
|
||||
|
||||
|
||||
export function uiGeolocate(context) {
|
||||
var geoOptions = { enableHighAccuracy: false, timeout: 6000 /* 6sec */ },
|
||||
locating = uiLoading(context).message(t('geolocate.locating')).blocking(true),
|
||||
layer = context.layers().layer('geolocate'),
|
||||
position,
|
||||
extent,
|
||||
timeoutId;
|
||||
var geoOptions = { enableHighAccuracy: false, timeout: 6000 /* 6sec */ };
|
||||
var locating = uiLoading(context).message(t('geolocate.locating')).blocking(true);
|
||||
var layer = context.layers().layer('geolocate');
|
||||
var _position;
|
||||
var _extent;
|
||||
var _timeoutID;
|
||||
|
||||
|
||||
function click() {
|
||||
if (context.inIntro()) return;
|
||||
context.enter(modeBrowse(context));
|
||||
if (!layer.enabled()) {
|
||||
if (!position) {
|
||||
if (!_position) {
|
||||
context.container().call(locating);
|
||||
navigator.geolocation.getCurrentPosition(success, error, geoOptions);
|
||||
} else {
|
||||
@@ -30,21 +30,20 @@ export function uiGeolocate(context) {
|
||||
}
|
||||
// This timeout ensures that we still call finish() even if
|
||||
// the user declines to share their location in Firefox
|
||||
timeoutId = setTimeout(finish, 10000 /* 10sec */ );
|
||||
_timeoutID = setTimeout(finish, 10000 /* 10sec */ );
|
||||
}
|
||||
|
||||
function zoomTo() {
|
||||
var map = context.map();
|
||||
layer.enabled(position, true);
|
||||
map.centerZoom(extent.center(), Math.min(20, map.extentZoom(extent)));
|
||||
layer.enabled(_position, true);
|
||||
map.centerZoomEase(_extent.center(), Math.min(20, map.extentZoom(_extent)));
|
||||
}
|
||||
|
||||
|
||||
function success(geolocation) {
|
||||
position = geolocation;
|
||||
extent = geoExtent([position.coords.longitude, position.coords.latitude])
|
||||
.padByMeters(position.coords.accuracy);
|
||||
|
||||
_position = geolocation;
|
||||
var coords = _position.coords;
|
||||
_extent = geoExtent([coords.longitude, coords.latitude]).padByMeters(coords.accuracy);
|
||||
zoomTo();
|
||||
finish();
|
||||
}
|
||||
@@ -57,8 +56,8 @@ export function uiGeolocate(context) {
|
||||
|
||||
function finish() {
|
||||
locating.close(); // unblock ui
|
||||
if (timeoutId) { clearTimeout(timeoutId); }
|
||||
timeoutId = undefined;
|
||||
if (_timeoutID) { clearTimeout(_timeoutID); }
|
||||
_timeoutID = undefined;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -331,7 +331,14 @@ export function uiIntroArea(context, reveal) {
|
||||
|
||||
d3_select('.more-fields .combobox-input')
|
||||
.on('click.intro', function() {
|
||||
continueTo(chooseDescriptionField);
|
||||
// Watch for the combobox to appear...
|
||||
var watcher;
|
||||
watcher = window.setInterval(function() {
|
||||
if (!d3_select('div.combobox').empty()) {
|
||||
window.clearInterval(watcher);
|
||||
continueTo(chooseDescriptionField);
|
||||
}
|
||||
}, 300);
|
||||
});
|
||||
}, 300); // after "Add Field" visible
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ export function uiKeepRightDetails(context) {
|
||||
osmlayer.enabled(true);
|
||||
}
|
||||
|
||||
context.map().centerZoom(_error.loc, 20);
|
||||
context.map().centerZoomEase(_error.loc, 20);
|
||||
|
||||
if (entity) {
|
||||
context.enter(modeSelect(context, [entityID]));
|
||||
|
||||
+27
-3
@@ -25,6 +25,8 @@ export function uiMapData(context) {
|
||||
var settingsCustomData = uiSettingsCustomData(context)
|
||||
.on('change', customChanged);
|
||||
|
||||
var pane = d3_select(null);
|
||||
|
||||
var _fillSelected = context.storage('area-fill') || 'partial';
|
||||
var _shown = false;
|
||||
var _dataLayerContainer = d3_select(null);
|
||||
@@ -543,6 +545,8 @@ export function uiMapData(context) {
|
||||
.append('div')
|
||||
.attr('class', 'data-layer-container')
|
||||
.merge(container);
|
||||
|
||||
updateDataLayers();
|
||||
}
|
||||
|
||||
|
||||
@@ -554,6 +558,8 @@ export function uiMapData(context) {
|
||||
.append('ul')
|
||||
.attr('class', 'layer-list layer-fill-list')
|
||||
.merge(container);
|
||||
|
||||
updateFillList();
|
||||
}
|
||||
|
||||
|
||||
@@ -565,22 +571,40 @@ export function uiMapData(context) {
|
||||
.append('ul')
|
||||
.attr('class', 'layer-list layer-feature-list')
|
||||
.merge(container);
|
||||
|
||||
updateFeatureList();
|
||||
}
|
||||
|
||||
|
||||
function update() {
|
||||
function updateDataLayers() {
|
||||
_dataLayerContainer
|
||||
.call(drawOsmItems)
|
||||
.call(drawQAItems)
|
||||
.call(drawPhotoItems)
|
||||
.call(drawCustomDataItems)
|
||||
.call(drawVectorItems); // Beta - Detroit mapping challenge
|
||||
}
|
||||
|
||||
function updateFillList() {
|
||||
_fillList
|
||||
.call(drawListItems, fills, 'radio', 'area_fill', setFill, showsFill);
|
||||
}
|
||||
|
||||
function updateFeatureList() {
|
||||
_featureList
|
||||
.call(drawListItems, features, 'checkbox', 'feature', clickFeature, showsFeature);
|
||||
}
|
||||
|
||||
function update() {
|
||||
|
||||
if (!pane.select('.disclosure-wrap-data_layers').classed('hide')) {
|
||||
updateDataLayers();
|
||||
}
|
||||
if (!pane.select('.disclosure-wrap-fill_area').classed('hide')) {
|
||||
updateFillList();
|
||||
}
|
||||
if (!pane.select('.disclosure-wrap-map_features').classed('hide')) {
|
||||
updateFeatureList();
|
||||
}
|
||||
|
||||
_QAList
|
||||
.call(drawListItems, ['keep-right'], 'checkbox', 'QA', function(d) { toggleLayer(d); }, showsQA);
|
||||
@@ -649,7 +673,7 @@ export function uiMapData(context) {
|
||||
}
|
||||
|
||||
|
||||
var pane = selection
|
||||
pane = selection
|
||||
.append('div')
|
||||
.attr('class', 'fillL map-pane hide');
|
||||
|
||||
|
||||
+20
-13
@@ -22,7 +22,16 @@ export function uiModes(context) {
|
||||
modeAddNote(context)
|
||||
];
|
||||
|
||||
function editable() {
|
||||
|
||||
function enabled(d) {
|
||||
if (d.id === 'add-note') {
|
||||
return notesEnabled() && notesEditable();
|
||||
} else {
|
||||
return osmEditable();
|
||||
}
|
||||
}
|
||||
|
||||
function osmEditable() {
|
||||
var mode = context.mode();
|
||||
return context.editable() && mode && mode.id !== 'save';
|
||||
}
|
||||
@@ -37,6 +46,7 @@ export function uiModes(context) {
|
||||
return context.map().notesEditable() && mode && mode.id !== 'save';
|
||||
}
|
||||
|
||||
|
||||
return function(selection) {
|
||||
context
|
||||
.on('enter.editor', function(entered) {
|
||||
@@ -54,8 +64,7 @@ export function uiModes(context) {
|
||||
|
||||
modes.forEach(function(mode) {
|
||||
context.keybinding().on(mode.key, function() {
|
||||
if (mode.id === 'add-note' && !(notesEnabled() && notesEditable())) return;
|
||||
if (mode.id !== 'add-note' && !editable()) return;
|
||||
if (!enabled(mode)) return;
|
||||
|
||||
if (mode.id === context.mode().id) {
|
||||
context.enter(modeBrowse(context));
|
||||
@@ -94,23 +103,23 @@ export function uiModes(context) {
|
||||
.append('button')
|
||||
.attr('tabindex', -1)
|
||||
.attr('class', function(d) { return d.id + ' add-button'; })
|
||||
.on('click.mode-buttons', function(mode) {
|
||||
.on('click.mode-buttons', function(d) {
|
||||
if (!enabled(d)) return;
|
||||
|
||||
// When drawing, ignore accidental clicks on mode buttons - #4042
|
||||
var currMode = context.mode().id;
|
||||
if (currMode.match(/^draw/) !== null) return;
|
||||
if (/^draw/.test(currMode)) return;
|
||||
|
||||
if (mode.id === currMode) {
|
||||
if (d.id === currMode) {
|
||||
context.enter(modeBrowse(context));
|
||||
} else {
|
||||
context.enter(mode);
|
||||
context.enter(d);
|
||||
}
|
||||
})
|
||||
.call(tooltip()
|
||||
.placement('bottom')
|
||||
.html(true)
|
||||
.title(function(mode) {
|
||||
return uiTooltipHtml(mode.description, mode.key);
|
||||
})
|
||||
.title(function(d) { return uiTooltipHtml(d.description, d.key); })
|
||||
);
|
||||
|
||||
buttonsEnter
|
||||
@@ -132,9 +141,7 @@ export function uiModes(context) {
|
||||
// update
|
||||
buttons = buttons
|
||||
.merge(buttonsEnter)
|
||||
.property('disabled', function(d) {
|
||||
return d.id === 'add-note' ? !notesEditable() : !editable();
|
||||
});
|
||||
.classed('disabled', function(d) { return !enabled(d); });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { dispatch as d3_dispatch } from 'd3-dispatch';
|
||||
|
||||
import _union from 'lodash-es/union';
|
||||
import {
|
||||
event as d3_event,
|
||||
select as d3_select
|
||||
@@ -53,16 +53,13 @@ export function uiPresetEditor(context) {
|
||||
);
|
||||
}
|
||||
|
||||
_preset.moreFields.forEach(function(field) {
|
||||
if (_preset.fields.indexOf(field) === -1) {
|
||||
_fieldsArr.push(
|
||||
uiField(context, field, entity, { show: false })
|
||||
);
|
||||
}
|
||||
var additionalFields = _union(_preset.moreFields, presets.universal());
|
||||
additionalFields.sort(function(field1, field2) {
|
||||
return field1.label() > field2.label();
|
||||
});
|
||||
|
||||
presets.universal().forEach(function(field) {
|
||||
if (_preset.fields.indexOf(field) === -1 && _preset.moreFields.indexOf(field) === -1) {
|
||||
additionalFields.forEach(function(field) {
|
||||
if (_preset.fields.indexOf(field) === -1) {
|
||||
_fieldsArr.push(
|
||||
uiField(context, field, entity, { show: false })
|
||||
);
|
||||
|
||||
+17
-15
@@ -36,16 +36,18 @@ export function uiPresetIcon() {
|
||||
var isPOI = isMaki || isTemaki || isFa;
|
||||
var isFramed = (geom === 'area' || geom === 'vertex');
|
||||
|
||||
|
||||
function tag_classes(p) {
|
||||
var s = '';
|
||||
for (var i in p.tags) {
|
||||
s += ' tag-' + i;
|
||||
if (p.tags[i] !== '*') {
|
||||
s += ' tag-' + i + '-' + p.tags[i];
|
||||
}
|
||||
var tagClasses = '';
|
||||
for (var k in p.tags) {
|
||||
var v = p.tags[k];
|
||||
tagClasses += ' tag-' + k;
|
||||
if (v !== '*') {
|
||||
tagClasses += ' tag-' + k + '-' + v;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// if the preset includes a `building_area` field, class it as a building
|
||||
if (p.fields && p.fields.filter(function(d) { return d.id === 'building_area'; }).length) {
|
||||
tagClasses += ' tag-building';
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +60,7 @@ export function uiPresetIcon() {
|
||||
|
||||
fill
|
||||
.attr('class', function() {
|
||||
return 'preset-icon-fill preset-icon-fill-' + geom + tag_classes(p);
|
||||
return 'preset-icon-fill preset-icon-fill-' + geom + tagClasses;
|
||||
});
|
||||
|
||||
|
||||
@@ -90,7 +92,7 @@ export function uiPresetIcon() {
|
||||
|
||||
icon.selectAll('svg')
|
||||
.attr('class', function() {
|
||||
return 'icon ' + picon + (isPOI ? '' : tag_classes(p));
|
||||
return 'icon ' + picon + (isPOI ? '' : tagClasses);
|
||||
});
|
||||
|
||||
icon.selectAll('use')
|
||||
@@ -98,16 +100,16 @@ export function uiPresetIcon() {
|
||||
}
|
||||
|
||||
|
||||
presetIcon.preset = function(_) {
|
||||
presetIcon.preset = function(val) {
|
||||
if (!arguments.length) return preset;
|
||||
preset = utilFunctor(_);
|
||||
preset = utilFunctor(val);
|
||||
return presetIcon;
|
||||
};
|
||||
|
||||
|
||||
presetIcon.geometry = function(_) {
|
||||
presetIcon.geometry = function(val) {
|
||||
if (!arguments.length) return geometry;
|
||||
geometry = utilFunctor(_);
|
||||
geometry = utilFunctor(val);
|
||||
return presetIcon;
|
||||
};
|
||||
|
||||
|
||||
@@ -364,8 +364,9 @@ export function uiPresetList(context) {
|
||||
.append('div')
|
||||
.attr('class', 'label-inner');
|
||||
|
||||
// NOTE: split/join on en-dash, not a hypen (to avoid conflict with fr - nl names in Brussels etc)
|
||||
label.selectAll('.namepart')
|
||||
.data(preset.name().split(' - '))
|
||||
.data(preset.name().split(' – '))
|
||||
.enter()
|
||||
.append('div')
|
||||
.attr('class', 'namepart')
|
||||
|
||||
@@ -51,7 +51,7 @@ export function uiRawMemberEditor(context) {
|
||||
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.map().zoomToEase(entity);
|
||||
}
|
||||
|
||||
context.enter(modeSelect(context, [d.id]));
|
||||
@@ -107,7 +107,9 @@ export function uiRawMemberEditor(context) {
|
||||
.expanded(true)
|
||||
.updatePreference(false)
|
||||
.on('toggled', function(expanded) {
|
||||
if (expanded) { selection.node().parentNode.scrollTop += 200; }
|
||||
if (expanded) {
|
||||
selection.node().parentNode.scrollTop += 200;
|
||||
}
|
||||
})
|
||||
.content(content)
|
||||
);
|
||||
@@ -133,26 +135,28 @@ export function uiRawMemberEditor(context) {
|
||||
.each(unbind)
|
||||
.remove();
|
||||
|
||||
var enter = items.enter()
|
||||
var itemsEnter = items.enter()
|
||||
.append('li')
|
||||
.attr('class', 'member-row form-field')
|
||||
.classed('member-incomplete', function(d) { return !d.member; });
|
||||
|
||||
enter
|
||||
itemsEnter
|
||||
.each(function(d) {
|
||||
var item = d3_select(this);
|
||||
|
||||
var label = item
|
||||
.append('label')
|
||||
.attr('class', 'form-field-label');
|
||||
|
||||
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-field-label');
|
||||
item
|
||||
.on('mouseover', function() {
|
||||
utilHighlightEntity(d.id, true, context);
|
||||
})
|
||||
.on('mouseout', function() {
|
||||
utilHighlightEntity(d.id, false, context);
|
||||
});
|
||||
|
||||
var labelLink = label
|
||||
.append('span')
|
||||
@@ -176,18 +180,14 @@ export function uiRawMemberEditor(context) {
|
||||
|
||||
label
|
||||
.append('button')
|
||||
.attr('class', 'download-icon')
|
||||
.attr('class', 'member-zoom')
|
||||
.attr('title', t('icons.zoom_to'))
|
||||
.attr('tabindex', -1)
|
||||
.call(svgIcon('#iD-icon-geolocate'))
|
||||
.on('click', zoomToMember);
|
||||
|
||||
} else {
|
||||
var incompleteLabel = d3_select(this)
|
||||
.append('label')
|
||||
.attr('class', 'form-field-label');
|
||||
|
||||
var labelText = incompleteLabel
|
||||
var labelText = label
|
||||
.append('span')
|
||||
.attr('class', 'label-text');
|
||||
|
||||
@@ -201,9 +201,9 @@ export function uiRawMemberEditor(context) {
|
||||
.attr('class', 'member-entity-name')
|
||||
.text(t('inspector.incomplete', { id: d.id }));
|
||||
|
||||
incompleteLabel
|
||||
label
|
||||
.append('button')
|
||||
.attr('class', 'download-icon')
|
||||
.attr('class', 'member-download')
|
||||
.attr('title', t('icons.download'))
|
||||
.attr('tabindex', -1)
|
||||
.call(svgIcon('#iD-icon-load'))
|
||||
@@ -211,7 +211,7 @@ export function uiRawMemberEditor(context) {
|
||||
}
|
||||
});
|
||||
|
||||
var wrapEnter = enter
|
||||
var wrapEnter = itemsEnter
|
||||
.append('div')
|
||||
.attr('class', 'form-field-input-wrap form-field-input-member');
|
||||
|
||||
@@ -221,24 +221,34 @@ export function uiRawMemberEditor(context) {
|
||||
.property('type', 'text')
|
||||
.attr('maxlength', 255)
|
||||
.attr('placeholder', t('inspector.role'))
|
||||
.call(utilNoAuto)
|
||||
.property('value', function(d) { return d.role; })
|
||||
.on('blur', changeRole)
|
||||
.on('change', changeRole);
|
||||
.call(utilNoAuto);
|
||||
|
||||
wrapEnter
|
||||
.append('button')
|
||||
.attr('tabindex', -1)
|
||||
.attr('title', t('icons.remove'))
|
||||
.attr('class', 'remove form-field-button member-delete')
|
||||
.call(svgIcon('#iD-operation-delete'))
|
||||
.on('click', deleteMember);
|
||||
.call(svgIcon('#iD-operation-delete'));
|
||||
|
||||
if (taginfo) {
|
||||
wrapEnter.each(bindTypeahead);
|
||||
}
|
||||
|
||||
|
||||
// update
|
||||
items = items
|
||||
.merge(itemsEnter);
|
||||
|
||||
items.select('input.member-role')
|
||||
.property('value', function(d) { return d.role; })
|
||||
.on('blur', changeRole)
|
||||
.on('change', changeRole);
|
||||
|
||||
items.select('button.member-delete')
|
||||
.on('click', deleteMember);
|
||||
|
||||
|
||||
|
||||
function bindTypeahead(d) {
|
||||
var row = d3_select(this);
|
||||
var role = row.selectAll('input.member-role');
|
||||
@@ -301,9 +311,9 @@ export function uiRawMemberEditor(context) {
|
||||
}
|
||||
|
||||
|
||||
rawMemberEditor.entityID = function(_) {
|
||||
rawMemberEditor.entityID = function(val) {
|
||||
if (!arguments.length) return _entityID;
|
||||
_entityID = _;
|
||||
_entityID = val;
|
||||
return rawMemberEditor;
|
||||
};
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ export function uiRawMembershipEditor(context) {
|
||||
var nearbyCombo = uiCombobox(context, 'parent-relation')
|
||||
.minItems(1)
|
||||
.fetcher(fetchNearbyRelations);
|
||||
var _inChange = false;
|
||||
var _entityID;
|
||||
var _showBlank;
|
||||
|
||||
@@ -44,16 +45,20 @@ export function uiRawMembershipEditor(context) {
|
||||
|
||||
|
||||
function changeRole(d) {
|
||||
if (d === 0) return; // called on newrow (shoudn't happen)
|
||||
if (d === 0) return; // called on newrow (shoudn't happen)
|
||||
if (_inChange) return; // avoid accidental recursive call #5731
|
||||
|
||||
var oldRole = d.member.role;
|
||||
var newRole = d3_select(this).property('value');
|
||||
|
||||
if (oldRole !== newRole) {
|
||||
_inChange = true;
|
||||
context.perform(
|
||||
actionChangeMember(d.relation.id, _extend({}, d.member, { role: newRole }), d.index),
|
||||
t('operations.change_role.annotation')
|
||||
);
|
||||
}
|
||||
_inChange = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -187,12 +192,13 @@ export function uiRawMembershipEditor(context) {
|
||||
|
||||
itemsEnter.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);
|
||||
});
|
||||
d3_select(this)
|
||||
.on('mouseover', function() {
|
||||
utilHighlightEntity(d.relation.id, true, context);
|
||||
})
|
||||
.on('mouseout', function() {
|
||||
utilHighlightEntity(d.relation.id, false, context);
|
||||
});
|
||||
});
|
||||
|
||||
var labelEnter = itemsEnter
|
||||
@@ -281,11 +287,11 @@ export function uiRawMembershipEditor(context) {
|
||||
.append('button')
|
||||
.attr('tabindex', -1)
|
||||
.attr('class', 'remove form-field-button member-delete')
|
||||
.call(svgIcon('#iD-operation-delete'))
|
||||
.on('click', function() {
|
||||
list.selectAll('.member-row-new')
|
||||
.remove();
|
||||
})
|
||||
.call(svgIcon('#iD-operation-delete'));
|
||||
});
|
||||
|
||||
// Update
|
||||
newMembership = newMembership
|
||||
@@ -293,26 +299,38 @@ export function uiRawMembershipEditor(context) {
|
||||
|
||||
newMembership.selectAll('.member-entity-input')
|
||||
.call(nearbyCombo
|
||||
.on('accept', function (d) {
|
||||
var role = list.selectAll('.member-row-new .member-role').property('value');
|
||||
addMembership(d, role);
|
||||
})
|
||||
.on('cancel', function() { delete this.value; })
|
||||
.on('accept', acceptEntity)
|
||||
.on('cancel', cancelEntity)
|
||||
);
|
||||
|
||||
|
||||
var addrel = selection.selectAll('.add-relation')
|
||||
// Container for the Add button
|
||||
var addRow = selection.selectAll('.add-row')
|
||||
.data([0]);
|
||||
|
||||
// Enter
|
||||
var addrelEnter = addrel.enter()
|
||||
.append('button')
|
||||
.attr('class', 'add-relation');
|
||||
// enter
|
||||
var addRowEnter = addRow.enter()
|
||||
.append('div')
|
||||
.attr('class', 'add-row');
|
||||
|
||||
// Update
|
||||
addrel
|
||||
.merge(addrelEnter)
|
||||
.call(svgIcon('#iD-icon-plus', 'light'))
|
||||
addRowEnter
|
||||
.append('button')
|
||||
.attr('class', 'add-relation')
|
||||
.call(svgIcon('#iD-icon-plus', 'light'));
|
||||
|
||||
addRowEnter
|
||||
.append('div')
|
||||
.attr('class', 'space-value'); // preserve space
|
||||
|
||||
addRowEnter
|
||||
.append('div')
|
||||
.attr('class', 'space-buttons'); // preserve space
|
||||
|
||||
// update
|
||||
addRow = addRow
|
||||
.merge(addRowEnter);
|
||||
|
||||
addRow.select('.add-relation')
|
||||
.on('click', function() {
|
||||
_showBlank = true;
|
||||
content(selection);
|
||||
@@ -320,6 +338,23 @@ export function uiRawMembershipEditor(context) {
|
||||
});
|
||||
|
||||
|
||||
|
||||
function acceptEntity(d) {
|
||||
if (!d) {
|
||||
cancelEntity();
|
||||
return;
|
||||
}
|
||||
var role = list.selectAll('.member-row-new .member-role').property('value');
|
||||
addMembership(d, role);
|
||||
}
|
||||
|
||||
|
||||
function cancelEntity() {
|
||||
var input = newMembership.selectAll('.member-entity-input');
|
||||
input.property('value', '');
|
||||
}
|
||||
|
||||
|
||||
function bindTypeahead(d) {
|
||||
var row = d3_select(this);
|
||||
var role = row.selectAll('input.member-role');
|
||||
@@ -367,9 +402,9 @@ export function uiRawMembershipEditor(context) {
|
||||
}
|
||||
|
||||
|
||||
rawMembershipEditor.entityID = function(_) {
|
||||
rawMembershipEditor.entityID = function(val) {
|
||||
if (!arguments.length) return _entityID;
|
||||
_entityID = _;
|
||||
_entityID = val;
|
||||
_showBlank = false;
|
||||
return rawMembershipEditor;
|
||||
};
|
||||
|
||||
@@ -67,6 +67,7 @@ export function uiRawTagEditor(context) {
|
||||
_newRow = '';
|
||||
}
|
||||
|
||||
// List of tags
|
||||
var list = wrap.selectAll('.tag-list')
|
||||
.data([0]);
|
||||
|
||||
@@ -75,16 +76,30 @@ export function uiRawTagEditor(context) {
|
||||
.attr('class', 'tag-list')
|
||||
.merge(list);
|
||||
|
||||
var newTag = wrap.selectAll('.add-tag')
|
||||
.data([0]);
|
||||
|
||||
newTag.enter()
|
||||
// Container for the Add button
|
||||
var addRowEnter = wrap.selectAll('.add-row')
|
||||
.data([0])
|
||||
.enter()
|
||||
.append('div')
|
||||
.attr('class', 'add-row');
|
||||
|
||||
addRowEnter
|
||||
.append('button')
|
||||
.attr('class', 'add-tag')
|
||||
.on('click', addTag)
|
||||
.call(svgIcon('#iD-icon-plus', 'light'));
|
||||
.call(svgIcon('#iD-icon-plus', 'light'))
|
||||
.on('click', addTag);
|
||||
|
||||
addRowEnter
|
||||
.append('div')
|
||||
.attr('class', 'space-value'); // preserve space
|
||||
|
||||
addRowEnter
|
||||
.append('div')
|
||||
.attr('class', 'space-buttons'); // preserve space
|
||||
|
||||
|
||||
// Tag list items
|
||||
var items = list.selectAll('.tag-row')
|
||||
.data(entries, function(d) { return d.key; });
|
||||
|
||||
@@ -92,8 +107,8 @@ export function uiRawTagEditor(context) {
|
||||
.each(unbind)
|
||||
.remove();
|
||||
|
||||
// Enter
|
||||
|
||||
// Enter
|
||||
var enter = items.enter()
|
||||
.append('li')
|
||||
.attr('class', 'tag-row')
|
||||
@@ -133,7 +148,6 @@ export function uiRawTagEditor(context) {
|
||||
|
||||
|
||||
// Update
|
||||
|
||||
items = items
|
||||
.merge(enter)
|
||||
.sort(function(a, b) {
|
||||
|
||||
+48
-40
@@ -12,57 +12,61 @@ import { tooltip } from '../util/tooltip';
|
||||
export function uiSave(context) {
|
||||
var history = context.history();
|
||||
var key = uiCmd('⌘S');
|
||||
|
||||
|
||||
function saving() {
|
||||
var mode = context.mode();
|
||||
return mode && mode.id === 'save';
|
||||
}
|
||||
|
||||
|
||||
function save() {
|
||||
d3_event.preventDefault();
|
||||
if (!context.inIntro() && !saving() && history.hasChanges()) {
|
||||
context.enter(modeSave(context));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getBackground(numChanges) {
|
||||
var step;
|
||||
if (numChanges === 0) {
|
||||
return null;
|
||||
} else if (numChanges <= 50) {
|
||||
step = numChanges / 50;
|
||||
return d3_interpolateRgb('#fff', '#ff8')(step); // white -> yellow
|
||||
} else {
|
||||
step = Math.min((numChanges - 50) / 50, 1.0);
|
||||
return d3_interpolateRgb('#ff8', '#f88')(step); // yellow -> red
|
||||
}
|
||||
}
|
||||
var _numChanges = 0;
|
||||
|
||||
|
||||
return function(selection) {
|
||||
var numChanges = 0;
|
||||
|
||||
|
||||
function isSaving() {
|
||||
var mode = context.mode();
|
||||
return mode && mode.id === 'save';
|
||||
}
|
||||
|
||||
|
||||
function isDisabled() {
|
||||
return _numChanges === 0 || isSaving();
|
||||
}
|
||||
|
||||
|
||||
function save() {
|
||||
d3_event.preventDefault();
|
||||
if (!context.inIntro() && !isSaving() && history.hasChanges()) {
|
||||
context.enter(modeSave(context));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function bgColor() {
|
||||
var step;
|
||||
if (_numChanges === 0) {
|
||||
return null;
|
||||
} else if (_numChanges <= 50) {
|
||||
step = _numChanges / 50;
|
||||
return d3_interpolateRgb('#fff', '#ff8')(step); // white -> yellow
|
||||
} else {
|
||||
step = Math.min((_numChanges - 50) / 50, 1.0);
|
||||
return d3_interpolateRgb('#ff8', '#f88')(step); // yellow -> red
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function updateCount() {
|
||||
var _ = history.difference().summary().length;
|
||||
if (_ === numChanges) return;
|
||||
numChanges = _;
|
||||
var val = history.difference().summary().length;
|
||||
if (val === _numChanges) return;
|
||||
_numChanges = val;
|
||||
|
||||
tooltipBehavior
|
||||
.title(uiTooltipHtml(
|
||||
t(numChanges > 0 ? 'save.help' : 'save.no_changes'), key)
|
||||
t(_numChanges > 0 ? 'save.help' : 'save.no_changes'), key)
|
||||
);
|
||||
|
||||
var background = getBackground(numChanges);
|
||||
|
||||
button
|
||||
.classed('disabled', numChanges === 0)
|
||||
.style('background', background);
|
||||
.classed('disabled', isDisabled())
|
||||
.style('background', bgColor(_numChanges));
|
||||
|
||||
button.select('span.count')
|
||||
.text(numChanges);
|
||||
.text(_numChanges);
|
||||
}
|
||||
|
||||
|
||||
@@ -102,8 +106,12 @@ export function uiSave(context) {
|
||||
|
||||
context
|
||||
.on('enter.save', function() {
|
||||
button.property('disabled', saving());
|
||||
if (saving()) button.call(tooltipBehavior.hide);
|
||||
button
|
||||
.classed('disabled', isDisabled());
|
||||
|
||||
if (isSaving()) {
|
||||
button.call(tooltipBehavior.hide);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -51,6 +51,31 @@ export function uiTagReference(tag) {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to get wiki info if a given language exists
|
||||
function getWikiInfo(wiki, langCode, msg) {
|
||||
if (wiki && wiki[langCode]) {
|
||||
return {title: wiki[langCode], text: t(msg)};
|
||||
}
|
||||
}
|
||||
|
||||
// Try to get a wiki page from tag data item first, followed by the corresponding key data item.
|
||||
// If neither tag nor key data item contain a wiki page in the needed language nor English,
|
||||
// get the first found wiki page from either the tag or the key item.
|
||||
var tagWiki = wikibase.monolingualClaimToValueObj(data.tag, 'P31');
|
||||
var keyWiki = wikibase.monolingualClaimToValueObj(data.key, 'P31');
|
||||
|
||||
// If exact language code does not exist, try to find the first part before the '-'
|
||||
// BUG: in some cases, a more elaborate fallback logic might be needed
|
||||
var langPrefix = langCode.split('-', 2)[0];
|
||||
|
||||
result.wiki =
|
||||
getWikiInfo(tagWiki, langCode, 'inspector.wiki_reference') ||
|
||||
getWikiInfo(tagWiki, langPrefix, 'inspector.wiki_reference') ||
|
||||
getWikiInfo(tagWiki, 'en', 'inspector.wiki_en_reference') ||
|
||||
getWikiInfo(keyWiki, langCode, 'inspector.wiki_reference') ||
|
||||
getWikiInfo(keyWiki, langPrefix, 'inspector.wiki_reference') ||
|
||||
getWikiInfo(keyWiki, 'en', 'inspector.wiki_en_reference');
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -110,6 +135,18 @@ export function uiTagReference(tag) {
|
||||
.append('span')
|
||||
.text(t('inspector.edit_reference'));
|
||||
|
||||
if (docs.wiki) {
|
||||
_body
|
||||
.append('a')
|
||||
.attr('class', 'tag-reference-link')
|
||||
.attr('target', '_blank')
|
||||
.attr('tabindex', -1)
|
||||
.attr('href', 'https://wiki.openstreetmap.org/wiki/' + docs.wiki.title)
|
||||
.call(svgIcon('#iD-icon-out-link', 'inline'))
|
||||
.append('span')
|
||||
.text(docs.wiki.text);
|
||||
}
|
||||
|
||||
// Add link to info about "good changeset comments" - #2923
|
||||
if (param.key === 'comment') {
|
||||
_body
|
||||
|
||||
@@ -10,8 +10,6 @@ export { utilEditDistance } from './util';
|
||||
export { utilEntitySelector } from './util';
|
||||
export { utilEntityOrMemberSelector } from './util';
|
||||
export { utilEntityOrDeepMemberSelector } from './util';
|
||||
export { utilExternalPresets } from './util';
|
||||
export { utilExternalValidationRules } from './util';
|
||||
export { utilFastMouse } from './util';
|
||||
export { utilFunctor } from './util';
|
||||
export { utilGetAllNodes } from './util';
|
||||
|
||||
@@ -3,11 +3,11 @@ var translations = Object.create(null);
|
||||
export var currentLocale = 'en';
|
||||
export var textDirection = 'ltr';
|
||||
|
||||
export function setLocale(_) {
|
||||
if (translations[_] !== undefined) {
|
||||
currentLocale = _;
|
||||
} else if (translations[_.split('-')[0]]) {
|
||||
currentLocale = _.split('-')[0];
|
||||
export function setLocale(val) {
|
||||
if (translations[val] !== undefined) {
|
||||
currentLocale = val;
|
||||
} else if (translations[val.split('-')[0]]) {
|
||||
currentLocale = val.split('-')[0];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ export function t(s, o, loc) {
|
||||
|
||||
var path = s
|
||||
.split('.')
|
||||
.map(function (s) { return s.replace('<TX_DOT>', '.'); })
|
||||
.map(function (s) { return s.replace(/<TX_DOT>/g, '.'); })
|
||||
.reverse();
|
||||
|
||||
var rep = translations[loc];
|
||||
|
||||
@@ -327,13 +327,6 @@ export function utilNoAuto(selection) {
|
||||
.attr('spellcheck', isText ? 'true' : 'false');
|
||||
}
|
||||
|
||||
export function utilExternalPresets() {
|
||||
return utilStringQs(window.location.hash).hasOwnProperty('presets');
|
||||
}
|
||||
|
||||
export function utilExternalValidationRules() {
|
||||
return utilStringQs(window.location.hash).hasOwnProperty('validations');
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/194846/is-there-any-kind-of-hash-code-function-in-javascript
|
||||
// https://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
|
||||
|
||||
Reference in New Issue
Block a user