mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-26 18:07:50 +02:00
Merge branch 'master' into validation
# Conflicts: # data/core.yaml # dist/locales/en.json # modules/ui/commit_warnings.js # modules/ui/entity_editor.js # modules/util/index.js # modules/util/util.js # modules/validations/index.js # modules/validations/many_deletions.js # modules/validations/missing_tag.js
This commit is contained in:
@@ -160,8 +160,8 @@ export function behaviorDrag() {
|
||||
for (; target && target !== root; target = target.parentNode) {
|
||||
var datum = target.__data__;
|
||||
|
||||
var entity = datum instanceof osmNote ?
|
||||
datum : datum && datum.properties && datum.properties.entity;
|
||||
var entity = datum instanceof osmNote ? datum
|
||||
: datum && datum.properties && datum.properties.entity;
|
||||
|
||||
if (entity && target[matchesSelector](_selector)) {
|
||||
return dragstart.call(target, entity);
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
select as d3_select
|
||||
} from 'd3-selection';
|
||||
|
||||
import { osmEntity, osmNote } from '../osm';
|
||||
import { osmEntity, osmNote, krError } from '../osm';
|
||||
import { utilKeybinding, utilRebind } from '../util';
|
||||
|
||||
|
||||
@@ -112,6 +112,10 @@ export function behaviorHover(context) {
|
||||
entity = datum;
|
||||
selector = '.data' + datum.__featurehash__;
|
||||
|
||||
} else if (datum instanceof krError) {
|
||||
entity = datum;
|
||||
selector = '.kr_error-' + datum.id;
|
||||
|
||||
} else if (datum instanceof osmNote) {
|
||||
entity = datum;
|
||||
selector = '.note-' + datum.id;
|
||||
|
||||
@@ -12,12 +12,14 @@ import {
|
||||
modeBrowse,
|
||||
modeSelect,
|
||||
modeSelectData,
|
||||
modeSelectNote
|
||||
modeSelectNote,
|
||||
modeSelectError
|
||||
} from '../modes';
|
||||
|
||||
import {
|
||||
osmEntity,
|
||||
osmNote
|
||||
osmNote,
|
||||
krError
|
||||
} from '../osm';
|
||||
|
||||
|
||||
@@ -130,6 +132,7 @@ export function behaviorSelect(context) {
|
||||
if (datum instanceof osmEntity) { // clicked an entity..
|
||||
var selectedIDs = context.selectedIDs();
|
||||
context.selectedNoteID(null);
|
||||
context.selectedErrorID(null);
|
||||
|
||||
if (!isMultiselect) {
|
||||
if (selectedIDs.length > 1 && (!suppressMenu && !isShowAlways)) {
|
||||
@@ -167,9 +170,13 @@ export function behaviorSelect(context) {
|
||||
context
|
||||
.selectedNoteID(datum.id)
|
||||
.enter(modeSelectNote(context, datum.id));
|
||||
|
||||
} else if (datum instanceof krError & !isMultiselect) { // clicked a krError error
|
||||
context
|
||||
.selectedErrorID(datum.id)
|
||||
.enter(modeSelectError(context, datum.id));
|
||||
} else { // clicked nothing..
|
||||
context.selectedNoteID(null);
|
||||
context.selectedErrorID(null);
|
||||
if (!isMultiselect && mode.id !== 'browse') {
|
||||
context.enter(modeBrowse(context));
|
||||
}
|
||||
|
||||
+10
-1
@@ -146,7 +146,9 @@ export function coreContext() {
|
||||
this.loadEntity(entityID, function(err, result) {
|
||||
if (err) return;
|
||||
var entity = _find(result.data, function(e) { return e.id === entityID; });
|
||||
if (entity) { map.zoomTo(entity); }
|
||||
if (entity) {
|
||||
map.zoomTo(entity);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -264,6 +266,13 @@ export function coreContext() {
|
||||
return context;
|
||||
};
|
||||
|
||||
var _selectedErrorID;
|
||||
context.selectedErrorID = function(errorID) {
|
||||
if (!arguments.length) return _selectedErrorID;
|
||||
_selectedErrorID = errorID;
|
||||
return context;
|
||||
};
|
||||
|
||||
|
||||
/* Behaviors */
|
||||
context.install = function(behavior) {
|
||||
|
||||
+15
-6
@@ -52,9 +52,6 @@ export function coreHistory(context) {
|
||||
annotation = actions.pop();
|
||||
}
|
||||
|
||||
_stack[_index].transform = context.projection.transform();
|
||||
_stack[_index].selectedIDs = context.selectedIDs();
|
||||
|
||||
var graph = _stack[_index].graph;
|
||||
for (var i = 0; i < actions.length; i++) {
|
||||
graph = actions[i](graph, t);
|
||||
@@ -63,7 +60,9 @@ export function coreHistory(context) {
|
||||
return {
|
||||
graph: graph,
|
||||
annotation: annotation,
|
||||
imageryUsed: _imageryUsed
|
||||
imageryUsed: _imageryUsed,
|
||||
transform: context.projection.transform(),
|
||||
selectedIDs: context.selectedIDs()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -410,7 +409,8 @@ export function coreHistory(context) {
|
||||
var base = _stack[0];
|
||||
|
||||
var s = _stack.map(function(i) {
|
||||
var modified = [], deleted = [];
|
||||
var modified = [];
|
||||
var deleted = [];
|
||||
|
||||
_forEach(i.graph.entities, function(entity, id) {
|
||||
if (entity) {
|
||||
@@ -440,6 +440,8 @@ export function coreHistory(context) {
|
||||
if (deleted.length) x.deleted = deleted;
|
||||
if (i.imageryUsed) x.imageryUsed = i.imageryUsed;
|
||||
if (i.annotation) x.annotation = i.annotation;
|
||||
if (i.transform) x.transform = i.transform;
|
||||
if (i.selectedIDs) x.selectedIDs = i.selectedIDs;
|
||||
|
||||
return x;
|
||||
});
|
||||
@@ -537,7 +539,9 @@ export function coreHistory(context) {
|
||||
return {
|
||||
graph: coreGraph(_stack[0].graph).load(entities),
|
||||
annotation: d.annotation,
|
||||
imageryUsed: d.imageryUsed
|
||||
imageryUsed: d.imageryUsed,
|
||||
transform: d.transform,
|
||||
selectedIDs: d.selectedIDs
|
||||
};
|
||||
});
|
||||
|
||||
@@ -555,6 +559,11 @@ export function coreHistory(context) {
|
||||
});
|
||||
}
|
||||
|
||||
var transform = _stack[_index].transform;
|
||||
if (transform) {
|
||||
context.map().transformEase(transform, 0); // 0 = immediate, no easing
|
||||
}
|
||||
|
||||
if (loadComplete) {
|
||||
dispatch.call('change');
|
||||
}
|
||||
|
||||
+23
-14
@@ -20,13 +20,14 @@ export function modeDragNote(context) {
|
||||
|
||||
var _nudgeInterval;
|
||||
var _lastLoc;
|
||||
var _note; // most current note.. dragged note may have stale datum.
|
||||
|
||||
|
||||
function startNudge(note, nudge) {
|
||||
function startNudge(nudge) {
|
||||
if (_nudgeInterval) window.clearInterval(_nudgeInterval);
|
||||
_nudgeInterval = window.setInterval(function() {
|
||||
context.pan(nudge);
|
||||
doMove(note, nudge);
|
||||
doMove(nudge);
|
||||
}, 50);
|
||||
}
|
||||
|
||||
@@ -45,58 +46,66 @@ export function modeDragNote(context) {
|
||||
|
||||
|
||||
function start(note) {
|
||||
context.surface().selectAll('.note-' + note.id)
|
||||
_note = note;
|
||||
var osm = services.osm;
|
||||
if (osm) {
|
||||
// Get latest note from cache.. The marker may have a stale datum bound to it
|
||||
// and dragging it around can sometimes delete the users note comment.
|
||||
_note = osm.getNote(_note.id);
|
||||
}
|
||||
|
||||
context.surface().selectAll('.note-' + _note.id)
|
||||
.classed('active', true);
|
||||
|
||||
context.perform(actionNoop());
|
||||
context.enter(mode);
|
||||
context.selectedNoteID(note.id);
|
||||
context.selectedNoteID(_note.id);
|
||||
}
|
||||
|
||||
|
||||
function move(note) {
|
||||
function move() {
|
||||
d3_event.sourceEvent.stopPropagation();
|
||||
_lastLoc = context.projection.invert(d3_event.point);
|
||||
|
||||
doMove(note);
|
||||
doMove();
|
||||
var nudge = geoViewportEdge(d3_event.point, context.map().dimensions());
|
||||
if (nudge) {
|
||||
startNudge(note, nudge);
|
||||
startNudge(nudge);
|
||||
} else {
|
||||
stopNudge();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function doMove(note, nudge) {
|
||||
function doMove(nudge) {
|
||||
nudge = nudge || [0, 0];
|
||||
|
||||
var currPoint = (d3_event && d3_event.point) || context.projection(_lastLoc);
|
||||
var currMouse = geoVecSubtract(currPoint, nudge);
|
||||
var loc = context.projection.invert(currMouse);
|
||||
|
||||
note = note.move(loc);
|
||||
_note = _note.move(loc);
|
||||
|
||||
var osm = services.osm;
|
||||
if (osm) {
|
||||
osm.replaceNote(note); // update note cache
|
||||
osm.replaceNote(_note); // update note cache
|
||||
}
|
||||
|
||||
context.replace(actionNoop()); // trigger redraw
|
||||
}
|
||||
|
||||
|
||||
function end(note) {
|
||||
function end() {
|
||||
context.replace(actionNoop()); // trigger redraw
|
||||
|
||||
context
|
||||
.selectedNoteID(note.id)
|
||||
.enter(modeSelectNote(context, note.id));
|
||||
.selectedNoteID(_note.id)
|
||||
.enter(modeSelectNote(context, _note.id));
|
||||
}
|
||||
|
||||
|
||||
var drag = behaviorDrag()
|
||||
.selector('.layer-notes .new')
|
||||
.selector('.layer-touch.markers .target.note.new')
|
||||
.surface(d3_select('#map').node())
|
||||
.origin(origin)
|
||||
.on('start', start)
|
||||
|
||||
@@ -12,4 +12,5 @@ export { modeRotate } from './rotate';
|
||||
export { modeSave } from './save';
|
||||
export { modeSelect } from './select';
|
||||
export { modeSelectData } from './select_data';
|
||||
export { modeSelectError} from './select_error';
|
||||
export { modeSelectNote } from './select_note';
|
||||
|
||||
+93
-85
@@ -192,6 +192,14 @@ export function modeSelect(context, selectedIDs) {
|
||||
};
|
||||
|
||||
|
||||
mode.zoomToSelected = function() {
|
||||
var entity = singular();
|
||||
if (entity) {
|
||||
context.map().zoomTo(entity);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
mode.reselect = function() {
|
||||
if (!checkSelectedIDs()) return;
|
||||
|
||||
@@ -229,6 +237,91 @@ export function modeSelect(context, selectedIDs) {
|
||||
|
||||
|
||||
mode.enter = function() {
|
||||
if (!checkSelectedIDs()) return;
|
||||
|
||||
var operations = _without(_values(Operations), Operations.operationDelete)
|
||||
.map(function(o) { return o(selectedIDs, context); })
|
||||
.filter(function(o) { return o.available(); });
|
||||
|
||||
// deprecation warning - Radial Menu to be removed in iD v3
|
||||
var isRadialMenu = context.storage('edit-menu-style') === 'radial';
|
||||
if (isRadialMenu) {
|
||||
operations = operations.slice(0,7);
|
||||
operations.unshift(Operations.operationDelete(selectedIDs, context));
|
||||
} else {
|
||||
operations.push(Operations.operationDelete(selectedIDs, context));
|
||||
}
|
||||
|
||||
operations.forEach(function(operation) {
|
||||
if (operation.behavior) {
|
||||
behaviors.push(operation.behavior);
|
||||
}
|
||||
});
|
||||
|
||||
behaviors.forEach(context.install);
|
||||
|
||||
keybinding
|
||||
.on(t('inspector.zoom_to.key'), mode.zoomToSelected)
|
||||
.on(['[', 'pgup'], previousVertex)
|
||||
.on([']', 'pgdown'], nextVertex)
|
||||
.on(['{', uiCmd('⌘['), 'home'], firstVertex)
|
||||
.on(['}', uiCmd('⌘]'), 'end'], lastVertex)
|
||||
.on(['\\', 'pause'], nextParent)
|
||||
.on('⎋', esc, true)
|
||||
.on('space', toggleMenu);
|
||||
|
||||
d3_select(document)
|
||||
.call(keybinding);
|
||||
|
||||
|
||||
// deprecation warning - Radial Menu to be removed in iD v3
|
||||
editMenu = isRadialMenu
|
||||
? uiRadialMenu(context, operations)
|
||||
: uiEditMenu(context, operations);
|
||||
|
||||
context.ui().sidebar
|
||||
.select(singular() ? singular().id : null, newFeature);
|
||||
|
||||
context.history()
|
||||
.on('undone.select', update)
|
||||
.on('redone.select', update);
|
||||
|
||||
context.map()
|
||||
.on('move.select', closeMenu)
|
||||
.on('drawn.select', selectElements);
|
||||
|
||||
context.surface()
|
||||
.on('dblclick.select', dblclick);
|
||||
|
||||
|
||||
selectElements();
|
||||
|
||||
if (selectedIDs.length > 1) {
|
||||
var entities = uiSelectionList(context, selectedIDs);
|
||||
context.ui().sidebar.show(entities);
|
||||
}
|
||||
|
||||
if (follow) {
|
||||
var extent = geoExtent();
|
||||
var graph = context.graph();
|
||||
selectedIDs.forEach(function(id) {
|
||||
var entity = context.entity(id);
|
||||
extent._extend(entity.extent(graph));
|
||||
});
|
||||
|
||||
var loc = extent.center();
|
||||
context.map().centerEase(loc);
|
||||
} else if (singular() && singular().type === 'way') {
|
||||
context.map().pan([0,0]); // full redraw, to adjust z-sorting #2914
|
||||
}
|
||||
|
||||
timeout = window.setTimeout(function() {
|
||||
positionMenu();
|
||||
if (!suppressMenu) {
|
||||
showMenu();
|
||||
}
|
||||
}, 270); /* after any centerEase completes */
|
||||
|
||||
|
||||
function update() {
|
||||
closeMenu();
|
||||
@@ -419,91 +512,6 @@ export function modeSelect(context, selectedIDs) {
|
||||
.classed('related', true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!checkSelectedIDs()) return;
|
||||
|
||||
var operations = _without(_values(Operations), Operations.operationDelete)
|
||||
.map(function(o) { return o(selectedIDs, context); })
|
||||
.filter(function(o) { return o.available(); });
|
||||
|
||||
// deprecation warning - Radial Menu to be removed in iD v3
|
||||
var isRadialMenu = context.storage('edit-menu-style') === 'radial';
|
||||
if (isRadialMenu) {
|
||||
operations = operations.slice(0,7);
|
||||
operations.unshift(Operations.operationDelete(selectedIDs, context));
|
||||
} else {
|
||||
operations.push(Operations.operationDelete(selectedIDs, context));
|
||||
}
|
||||
|
||||
operations.forEach(function(operation) {
|
||||
if (operation.behavior) {
|
||||
behaviors.push(operation.behavior);
|
||||
}
|
||||
});
|
||||
|
||||
behaviors.forEach(context.install);
|
||||
|
||||
keybinding
|
||||
.on(['[', 'pgup'], previousVertex)
|
||||
.on([']', 'pgdown'], nextVertex)
|
||||
.on(['{', uiCmd('⌘['), 'home'], firstVertex)
|
||||
.on(['}', uiCmd('⌘]'), 'end'], lastVertex)
|
||||
.on(['\\', 'pause'], nextParent)
|
||||
.on('⎋', esc, true)
|
||||
.on('space', toggleMenu);
|
||||
|
||||
d3_select(document)
|
||||
.call(keybinding);
|
||||
|
||||
|
||||
// deprecation warning - Radial Menu to be removed in iD v3
|
||||
editMenu = isRadialMenu
|
||||
? uiRadialMenu(context, operations)
|
||||
: uiEditMenu(context, operations);
|
||||
|
||||
context.ui().sidebar
|
||||
.select(singular() ? singular().id : null, newFeature);
|
||||
|
||||
context.history()
|
||||
.on('undone.select', update)
|
||||
.on('redone.select', update);
|
||||
|
||||
context.map()
|
||||
.on('move.select', closeMenu)
|
||||
.on('drawn.select', selectElements);
|
||||
|
||||
context.surface()
|
||||
.on('dblclick.select', dblclick);
|
||||
|
||||
|
||||
selectElements();
|
||||
|
||||
if (selectedIDs.length > 1) {
|
||||
var entities = uiSelectionList(context, selectedIDs);
|
||||
context.ui().sidebar.show(entities);
|
||||
}
|
||||
|
||||
if (follow) {
|
||||
var extent = geoExtent();
|
||||
var graph = context.graph();
|
||||
selectedIDs.forEach(function(id) {
|
||||
var entity = context.entity(id);
|
||||
extent._extend(entity.extent(graph));
|
||||
});
|
||||
|
||||
var loc = extent.center();
|
||||
context.map().centerEase(loc);
|
||||
} else if (singular() && singular().type === 'way') {
|
||||
context.map().pan([0,0]); // full redraw, to adjust z-sorting #2914
|
||||
}
|
||||
|
||||
timeout = window.setTimeout(function() {
|
||||
positionMenu();
|
||||
if (!suppressMenu) {
|
||||
showMenu();
|
||||
}
|
||||
}, 270); /* after any centerEase completes */
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import { geoBounds as d3_geoBounds } from 'd3-geo';
|
||||
|
||||
import {
|
||||
@@ -13,6 +12,8 @@ import {
|
||||
behaviorSelect
|
||||
} from '../behavior';
|
||||
|
||||
import { t } from '../util/locale';
|
||||
|
||||
import { geoExtent } from '../geo';
|
||||
import { modeBrowse, modeDragNode, modeDragNote } from '../modes';
|
||||
import { uiDataEditor } from '../ui';
|
||||
@@ -61,9 +62,18 @@ export function modeSelectData(context, selectedDatum) {
|
||||
}
|
||||
|
||||
|
||||
mode.zoomToSelected = function() {
|
||||
var extent = geoExtent(d3_geoBounds(selectedDatum));
|
||||
context.map().centerZoom(extent.center(), context.map().trimmedExtentZoom(extent));
|
||||
};
|
||||
|
||||
|
||||
mode.enter = function() {
|
||||
behaviors.forEach(context.install);
|
||||
keybinding.on('⎋', esc, true);
|
||||
|
||||
keybinding
|
||||
.on(t('inspector.zoom_to.key'), mode.zoomToSelected)
|
||||
.on('⎋', esc, true);
|
||||
|
||||
d3_select(document)
|
||||
.call(keybinding);
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
import {
|
||||
event as d3_event,
|
||||
select as d3_select
|
||||
} from 'd3-selection';
|
||||
|
||||
import {
|
||||
behaviorBreathe,
|
||||
behaviorHover,
|
||||
behaviorLasso,
|
||||
behaviorSelect
|
||||
} from '../behavior';
|
||||
|
||||
import { t } from '../util/locale';
|
||||
import { services } from '../services';
|
||||
import { modeBrowse, modeDragNode, modeDragNote } from '../modes';
|
||||
import { uiKeepRightEditor } from '../ui';
|
||||
import { utilKeybinding } from '../util';
|
||||
|
||||
|
||||
export function modeSelectError(context, selectedErrorID) {
|
||||
var mode = {
|
||||
id: 'select-error',
|
||||
button: 'browse'
|
||||
};
|
||||
|
||||
var keepRight = services.keepRight;
|
||||
var keybinding = utilKeybinding('select-error');
|
||||
var keepRightEditor = uiKeepRightEditor(context)
|
||||
.on('change', function() {
|
||||
context.map().pan([0,0]); // trigger a redraw
|
||||
var error = checkSelectedID();
|
||||
if (!error) return;
|
||||
context.ui().sidebar
|
||||
.show(keepRightEditor.error(error));
|
||||
});
|
||||
|
||||
var behaviors = [
|
||||
behaviorBreathe(context),
|
||||
behaviorHover(context),
|
||||
behaviorSelect(context),
|
||||
behaviorLasso(context),
|
||||
modeDragNode(context).behavior,
|
||||
modeDragNote(context).behavior
|
||||
];
|
||||
|
||||
|
||||
function checkSelectedID() {
|
||||
if (!keepRight) return;
|
||||
var error = keepRight.getError(selectedErrorID);
|
||||
if (!error) {
|
||||
context.enter(modeBrowse(context));
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
mode.zoomToSelected = function() {
|
||||
if (!keepRight) return;
|
||||
var error = keepRight.getError(selectedErrorID);
|
||||
if (error) {
|
||||
context.map().centerZoom(error.loc, 20);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
mode.enter = function() {
|
||||
var error = checkSelectedID();
|
||||
if (!error) return;
|
||||
|
||||
behaviors.forEach(context.install);
|
||||
keybinding
|
||||
.on(t('inspector.zoom_to.key'), mode.zoomToSelected)
|
||||
.on('⎋', esc, true);
|
||||
|
||||
d3_select(document)
|
||||
.call(keybinding);
|
||||
|
||||
selectError();
|
||||
|
||||
var sidebar = context.ui().sidebar;
|
||||
sidebar.show(keepRightEditor.error(error));
|
||||
|
||||
context.map()
|
||||
.on('drawn.select-error', selectError);
|
||||
|
||||
|
||||
// class the error as selected, or return to browse mode if the error is gone
|
||||
function selectError(drawn) {
|
||||
if (!checkSelectedID()) return;
|
||||
|
||||
var selection = context.surface()
|
||||
.selectAll('.kr_error-' + selectedErrorID);
|
||||
|
||||
if (selection.empty()) {
|
||||
// Return to browse mode if selected DOM elements have
|
||||
// disappeared because the user moved them out of view..
|
||||
var source = d3_event && d3_event.type === 'zoom' && d3_event.sourceEvent;
|
||||
if (drawn && source && (source.type === 'mousemove' || source.type === 'touchmove')) {
|
||||
context.enter(modeBrowse(context));
|
||||
}
|
||||
|
||||
} else {
|
||||
selection
|
||||
.classed('selected', true);
|
||||
|
||||
context.selectedErrorID(selectedErrorID);
|
||||
}
|
||||
}
|
||||
|
||||
function esc() {
|
||||
if (d3_select('.combobox').size()) return;
|
||||
context.enter(modeBrowse(context));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
mode.exit = function() {
|
||||
behaviors.forEach(context.uninstall);
|
||||
|
||||
d3_select(document)
|
||||
.call(keybinding.unbind);
|
||||
|
||||
context.surface()
|
||||
.selectAll('.kr_error.selected')
|
||||
.classed('selected hover', false);
|
||||
|
||||
context.map()
|
||||
.on('drawn.select-error', null);
|
||||
|
||||
context.ui().sidebar
|
||||
.hide();
|
||||
|
||||
context.selectedErrorID(null);
|
||||
};
|
||||
|
||||
|
||||
return mode;
|
||||
}
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
behaviorSelect
|
||||
} from '../behavior';
|
||||
|
||||
import { t } from '../util/locale';
|
||||
|
||||
import { modeBrowse, modeDragNode, modeDragNote } from '../modes';
|
||||
import { services } from '../services';
|
||||
import { uiNoteEditor } from '../ui';
|
||||
@@ -72,6 +74,7 @@ export function modeSelectNote(context, selectedNoteID) {
|
||||
} else {
|
||||
selection
|
||||
.classed('selected', true);
|
||||
|
||||
context.selectedNoteID(selectedNoteID);
|
||||
}
|
||||
}
|
||||
@@ -83,9 +86,18 @@ export function modeSelectNote(context, selectedNoteID) {
|
||||
}
|
||||
|
||||
|
||||
mode.newFeature = function(_) {
|
||||
mode.zoomToSelected = function() {
|
||||
if (!osm) return;
|
||||
var note = osm.getNote(selectedNoteID);
|
||||
if (note) {
|
||||
context.map().centerZoom(note.loc, 20);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
mode.newFeature = function(val) {
|
||||
if (!arguments.length) return newFeature;
|
||||
newFeature = _;
|
||||
newFeature = val;
|
||||
return mode;
|
||||
};
|
||||
|
||||
@@ -95,7 +107,10 @@ export function modeSelectNote(context, selectedNoteID) {
|
||||
if (!note) return;
|
||||
|
||||
behaviors.forEach(context.install);
|
||||
keybinding.on('⎋', esc, true);
|
||||
|
||||
keybinding
|
||||
.on(t('inspector.zoom_to.key'), mode.zoomToSelected)
|
||||
.on('⎋', esc, true);
|
||||
|
||||
d3_select(document)
|
||||
.call(keybinding);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export { osmChangeset } from './changeset';
|
||||
export { osmEntity } from './entity';
|
||||
export { krError } from './keepRight';
|
||||
export { osmNode } from './node';
|
||||
export { osmNote } from './note';
|
||||
export { osmRelation } from './relation';
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import _extend from 'lodash-es/extend';
|
||||
|
||||
|
||||
export function krError() {
|
||||
if (!(this instanceof krError)) {
|
||||
return (new krError()).initialize(arguments);
|
||||
} else if (arguments.length) {
|
||||
this.initialize(arguments);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
krError.id = function() {
|
||||
return krError.id.next--;
|
||||
};
|
||||
|
||||
|
||||
krError.id.next = -1;
|
||||
|
||||
|
||||
_extend(krError.prototype, {
|
||||
|
||||
type: 'krError',
|
||||
|
||||
initialize: function(sources) {
|
||||
for (var i = 0; i < sources.length; ++i) {
|
||||
var source = sources[i];
|
||||
for (var prop in source) {
|
||||
if (Object.prototype.hasOwnProperty.call(source, prop)) {
|
||||
if (source[prop] === undefined) {
|
||||
delete this[prop];
|
||||
} else {
|
||||
this[prop] = source[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.id) {
|
||||
this.id = krError.id() + ''; // as string
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
update: function(attrs) {
|
||||
return krError(this, attrs); // {v: 1 + (this.v || 0)}
|
||||
}
|
||||
});
|
||||
@@ -351,10 +351,11 @@ export function rendererMap(context) {
|
||||
function editOff() {
|
||||
context.features().resetStats();
|
||||
surface.selectAll('.layer-osm *').remove();
|
||||
surface.selectAll('.layer-touch *').remove();
|
||||
surface.selectAll('.layer-touch:not(.markers) *').remove();
|
||||
|
||||
var mode = context.mode();
|
||||
if (mode && mode.id !== 'save' && mode.id !== 'select-note' && mode.id !== 'select-data') {
|
||||
if (mode && mode.id !== 'save' && mode.id !== 'select-note' &&
|
||||
mode.id !== 'select-data' && mode.id !== 'select-error') {
|
||||
context.enter(modeBrowse(context));
|
||||
}
|
||||
|
||||
@@ -857,7 +858,7 @@ export function rendererMap(context) {
|
||||
if (!isFinite(extent.area())) return;
|
||||
|
||||
var z2 = map.trimmedExtentZoom(extent);
|
||||
zoomLimits = zoomLimits || [context.minEditableZoom(), 19];
|
||||
zoomLimits = zoomLimits || [context.minEditableZoom(), 20];
|
||||
map.centerZoom(extent.center(), Math.min(Math.max(z2, zoomLimits[0]), zoomLimits[1]));
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import serviceKeepRight from './keepRight';
|
||||
import serviceMapillary from './mapillary';
|
||||
import serviceMapRules from './maprules';
|
||||
import serviceNominatim from './nominatim';
|
||||
import serviceOpenstreetcam from './openstreetcam';
|
||||
import serviceOsm from './osm';
|
||||
import serviceOsmWikibase from './osm_wikibase';
|
||||
import serviceStreetside from './streetside';
|
||||
import serviceTaginfo from './taginfo';
|
||||
import serviceVectorTile from './vector_tile';
|
||||
@@ -12,9 +14,11 @@ import serviceWikipedia from './wikipedia';
|
||||
|
||||
export var services = {
|
||||
geocoder: serviceNominatim,
|
||||
keepRight: serviceKeepRight,
|
||||
mapillary: serviceMapillary,
|
||||
openstreetcam: serviceOpenstreetcam,
|
||||
osm: serviceOsm,
|
||||
osmWikibase: serviceOsmWikibase,
|
||||
maprules: serviceMapRules,
|
||||
streetside: serviceStreetside,
|
||||
taginfo: serviceTaginfo,
|
||||
@@ -24,11 +28,13 @@ export var services = {
|
||||
};
|
||||
|
||||
export {
|
||||
serviceKeepRight,
|
||||
serviceMapillary,
|
||||
serviceMapRules,
|
||||
serviceNominatim,
|
||||
serviceOpenstreetcam,
|
||||
serviceOsm,
|
||||
serviceOsmWikibase,
|
||||
serviceStreetside,
|
||||
serviceTaginfo,
|
||||
serviceVectorTile,
|
||||
|
||||
@@ -0,0 +1,498 @@
|
||||
import _extend from 'lodash-es/extend';
|
||||
import _find from 'lodash-es/find';
|
||||
import _forEach from 'lodash-es/forEach';
|
||||
|
||||
import rbush from 'rbush';
|
||||
|
||||
import { dispatch as d3_dispatch } from 'd3-dispatch';
|
||||
import { json as d3_json } from 'd3-request';
|
||||
import { request as d3_request } from 'd3-request';
|
||||
|
||||
import { geoExtent, geoVecAdd } from '../geo';
|
||||
import { krError } from '../osm';
|
||||
import { t } from '../util/locale';
|
||||
import { utilRebind, utilTiler, utilQsString } from '../util';
|
||||
|
||||
import { errorTypes, localizeStrings } from '../../data/keepRight.json';
|
||||
|
||||
|
||||
var tiler = utilTiler();
|
||||
var dispatch = d3_dispatch('loaded');
|
||||
|
||||
var _krCache;
|
||||
var _krZoom = 14;
|
||||
var _krUrlRoot = 'https://www.keepright.at/';
|
||||
|
||||
var _krRuleset = [
|
||||
// no 20 - multiple node on same spot - these are mostly boundaries overlapping roads
|
||||
30, 40, 50, 60, 70, 90, 100, 110, 120, 130, 150, 160, 170, 180,
|
||||
190, 191, 192, 193, 194, 195, 196, 197, 198,
|
||||
200, 201, 202, 203, 204, 205, 206, 207, 208, 210, 220,
|
||||
230, 231, 232, 270, 280, 281, 282, 283, 284, 285,
|
||||
290, 291, 292, 293, 294, 295, 296, 297, 298, 300, 310, 311, 312, 313,
|
||||
320, 350, 360, 370, 380, 390, 400, 401, 402, 410, 411, 412, 413
|
||||
];
|
||||
|
||||
|
||||
function abortRequest(i) {
|
||||
if (i) {
|
||||
i.abort();
|
||||
}
|
||||
}
|
||||
|
||||
function abortUnwantedRequests(cache, tiles) {
|
||||
_forEach(cache.inflight, function(v, k) {
|
||||
var wanted = _find(tiles, function(tile) {
|
||||
return k === tile.id;
|
||||
});
|
||||
if (!wanted) {
|
||||
abortRequest(v);
|
||||
delete cache.inflight[k];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function encodeErrorRtree(d) {
|
||||
return { minX: d.loc[0], minY: d.loc[1], maxX: d.loc[0], maxY: d.loc[1], data: d };
|
||||
}
|
||||
|
||||
|
||||
// replace or remove error from rtree
|
||||
function updateRtree(item, replace) {
|
||||
_krCache.rtree.remove(item, function isEql(a, b) {
|
||||
return a.data.id === b.data.id;
|
||||
});
|
||||
|
||||
if (replace) {
|
||||
_krCache.rtree.insert(item);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function tokenReplacements(d) {
|
||||
if (!(d instanceof krError)) return;
|
||||
|
||||
var htmlRegex = new RegExp(/<\/[a-z][\s\S]*>/);
|
||||
var replacements = {};
|
||||
|
||||
var errorTemplate = errorTypes[d.which_type];
|
||||
if (!errorTemplate) {
|
||||
/* eslint-disable no-console */
|
||||
console.log('No Template: ', d.which_type);
|
||||
console.log(' ', d.description);
|
||||
/* eslint-enable no-console */
|
||||
return;
|
||||
}
|
||||
|
||||
// some descriptions are just fixed text
|
||||
if (!errorTemplate.regex) return;
|
||||
|
||||
// regex pattern should match description with variable details captured
|
||||
var errorRegex = new RegExp(errorTemplate.regex, 'i');
|
||||
var errorMatch = errorRegex.exec(d.description);
|
||||
if (!errorMatch) {
|
||||
/* eslint-disable no-console */
|
||||
console.log('Unmatched: ', d.which_type);
|
||||
console.log(' ', d.description);
|
||||
console.log(' ', errorRegex);
|
||||
/* eslint-enable no-console */
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 1; i < errorMatch.length; i++) { // skip first
|
||||
var capture = errorMatch[i];
|
||||
var idType;
|
||||
|
||||
idType = 'IDs' in errorTemplate ? errorTemplate.IDs[i-1] : '';
|
||||
if (idType && capture) { // link IDs if present in the capture
|
||||
capture = parseError(capture, idType);
|
||||
} else if (htmlRegex.test(capture)) { // escape any html in non-IDs
|
||||
capture = '\\' + capture + '\\';
|
||||
} else {
|
||||
var compare = capture.toLowerCase();
|
||||
if (localizeStrings[compare]) { // some replacement strings can be localized
|
||||
capture = t('QA.keepRight.error_parts.' + localizeStrings[compare]);
|
||||
}
|
||||
}
|
||||
|
||||
replacements['var' + i] = capture;
|
||||
}
|
||||
|
||||
return replacements;
|
||||
}
|
||||
|
||||
|
||||
function parseError(capture, idType) {
|
||||
var compare = capture.toLowerCase();
|
||||
if (localizeStrings[compare]) { // some replacement strings can be localized
|
||||
capture = t('QA.keepRight.error_parts.' + localizeStrings[compare]);
|
||||
}
|
||||
|
||||
switch (idType) {
|
||||
// link a string like "this node"
|
||||
case 'this':
|
||||
capture = linkErrorObject(capture);
|
||||
break;
|
||||
|
||||
case 'url':
|
||||
capture = linkURL(capture);
|
||||
break;
|
||||
|
||||
// link an entity ID
|
||||
case 'n':
|
||||
case 'w':
|
||||
case 'r':
|
||||
capture = linkEntity(idType + capture);
|
||||
break;
|
||||
|
||||
// some errors have more complex ID lists/variance
|
||||
case '20':
|
||||
capture = parse20(capture);
|
||||
break;
|
||||
case '211':
|
||||
capture = parse211(capture);
|
||||
break;
|
||||
case '231':
|
||||
capture = parse231(capture);
|
||||
break;
|
||||
case '294':
|
||||
capture = parse294(capture);
|
||||
break;
|
||||
case '370':
|
||||
capture = parse370(capture);
|
||||
break;
|
||||
}
|
||||
|
||||
return capture;
|
||||
|
||||
|
||||
function linkErrorObject(d) {
|
||||
return '<a class="kr_error_object_link">' + d + '</a>';
|
||||
}
|
||||
|
||||
function linkEntity(d) {
|
||||
return '<a class="kr_error_entity_link">' + d + '</a>';
|
||||
}
|
||||
|
||||
function linkURL(d) {
|
||||
return '<a class="kr_external_link" target="_blank" href="' + d + '">' + d + '</a>';
|
||||
}
|
||||
|
||||
// arbitrary node list of form: #ID, #ID, #ID...
|
||||
function parse211(capture) {
|
||||
var newList = [];
|
||||
var items = capture.split(', ');
|
||||
|
||||
items.forEach(function(item) {
|
||||
// ID has # at the front
|
||||
var id = linkEntity('n' + item.slice(1));
|
||||
newList.push(id);
|
||||
});
|
||||
|
||||
return newList.join(', ');
|
||||
}
|
||||
|
||||
// arbitrary way list of form: #ID(layer),#ID(layer),#ID(layer)...
|
||||
function parse231(capture) {
|
||||
var newList = [];
|
||||
// unfortunately 'layer' can itself contain commas, so we split on '),'
|
||||
var items = capture.split('),');
|
||||
|
||||
items.forEach(function(item) {
|
||||
var match = item.match(/\#(\d+)\((.+)\)?/);
|
||||
if (match !== null && match.length > 2) {
|
||||
newList.push(linkEntity('w' + match[1]) + ' ' +
|
||||
t('QA.keepRight.errorTypes.231.layer', { layer: match[2] })
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return newList.join(', ');
|
||||
}
|
||||
|
||||
// arbitrary node/relation list of form: from node #ID,to relation #ID,to node #ID...
|
||||
function parse294(capture) {
|
||||
var newList = [];
|
||||
var items = capture.split(',');
|
||||
|
||||
items.forEach(function(item) {
|
||||
var role;
|
||||
var idType;
|
||||
var id;
|
||||
|
||||
// item of form "from/to node/relation #ID"
|
||||
item = item.split(' ');
|
||||
|
||||
// to/from role is more clear in quotes
|
||||
role = '"' + item[0] + '"';
|
||||
|
||||
// first letter of node/relation provides the type
|
||||
idType = item[1].slice(0,1);
|
||||
|
||||
// ID has # at the front
|
||||
id = item[2].slice(1);
|
||||
id = linkEntity(idType + id);
|
||||
|
||||
item = [role, item[1], id].join(' ');
|
||||
newList.push(item);
|
||||
});
|
||||
|
||||
return newList.join(', ');
|
||||
}
|
||||
|
||||
// may or may not include the string "(including the name 'name')"
|
||||
function parse370(capture) {
|
||||
if (!capture) return '';
|
||||
|
||||
var match = capture.match(/\(including the name (\'.+\')\)/);
|
||||
if (match !== null && match.length) {
|
||||
return t('QA.keepRight.errorTypes.370.including_the_name', { name: match[1] });
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// arbitrary node list of form: #ID,#ID,#ID...
|
||||
function parse20(capture) {
|
||||
var newList = [];
|
||||
var items = capture.split(',');
|
||||
|
||||
items.forEach(function(item) {
|
||||
// ID has # at the front
|
||||
var id = linkEntity('n' + item.slice(1));
|
||||
newList.push(id);
|
||||
});
|
||||
|
||||
return newList.join(', ');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
init: function() {
|
||||
if (!_krCache) {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
this.event = utilRebind(this, dispatch, 'on');
|
||||
},
|
||||
|
||||
reset: function() {
|
||||
if (_krCache) {
|
||||
_forEach(_krCache.inflight, abortRequest);
|
||||
}
|
||||
_krCache = {
|
||||
data: {},
|
||||
loaded: {},
|
||||
inflight: {},
|
||||
closed: {},
|
||||
rtree: rbush()
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
// KeepRight API: http://osm.mueschelsoft.de/keepright/interfacing.php
|
||||
loadErrors: function(projection) {
|
||||
var options = { format: 'geojson' };
|
||||
var rules = _krRuleset.join();
|
||||
|
||||
// determine the needed tiles to cover the view
|
||||
var tiles = tiler
|
||||
.zoomExtent([_krZoom, _krZoom])
|
||||
.getTiles(projection);
|
||||
|
||||
// abort inflight requests that are no longer needed
|
||||
abortUnwantedRequests(_krCache, tiles);
|
||||
|
||||
// issue new requests..
|
||||
tiles.forEach(function(tile) {
|
||||
if (_krCache.loaded[tile.id] || _krCache.inflight[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,
|
||||
function(err, data) {
|
||||
delete _krCache.inflight[tile.id];
|
||||
|
||||
if (err) return;
|
||||
_krCache.loaded[tile.id] = true;
|
||||
|
||||
if (!data.features || !data.features.length) return;
|
||||
|
||||
data.features.forEach(function(feature) {
|
||||
var loc = feature.geometry.coordinates;
|
||||
var props = feature.properties;
|
||||
|
||||
// if there is a parent, save its error type e.g.:
|
||||
// Error 191 = "highway-highway"
|
||||
// Error 190 = "intersections without junctions" (parent)
|
||||
var errorType = props.error_type;
|
||||
var errorTemplate = errorTypes[errorType];
|
||||
var parentErrorType = (Math.floor(errorType / 10) * 10).toString();
|
||||
|
||||
// try to handle error type directly, fallback to parent error type.
|
||||
var whichType = errorTemplate ? errorType : parentErrorType;
|
||||
var whichTemplate = errorTypes[whichType];
|
||||
|
||||
// Rewrite a few of the errors at this point..
|
||||
// This is done to make them easier to linkify and translate.
|
||||
switch (whichType) {
|
||||
case '170':
|
||||
props.description = 'This feature has a FIXME tag: ' + props.description;
|
||||
break;
|
||||
case '292':
|
||||
case '293':
|
||||
props.description = props.description.replace('A turn-', 'This turn-');
|
||||
break;
|
||||
case '294':
|
||||
case '295':
|
||||
case '296':
|
||||
case '297':
|
||||
case '298':
|
||||
props.description = 'This turn-restriction~' + props.description;
|
||||
break;
|
||||
case '300':
|
||||
props.description = 'This highway is missing a maxspeed tag';
|
||||
break;
|
||||
case '411':
|
||||
case '412':
|
||||
case '413':
|
||||
props.description = 'This feature~' + props.description;
|
||||
break;
|
||||
}
|
||||
|
||||
// - move markers slightly so it doesn't obscure the geometry,
|
||||
// - then move markers away from other coincident markers
|
||||
var coincident = false;
|
||||
do {
|
||||
// first time, move marker up. after that, move marker right.
|
||||
var delta = coincident ? [0.00001, 0] : [0, 0.00001];
|
||||
loc = geoVecAdd(loc, delta);
|
||||
var bbox = geoExtent(loc).bbox();
|
||||
coincident = _krCache.rtree.search(bbox).length;
|
||||
} while (coincident);
|
||||
|
||||
var d = new krError({
|
||||
loc: loc,
|
||||
id: props.error_id,
|
||||
comment: props.comment || null,
|
||||
description: props.description || '',
|
||||
error_id: props.error_id,
|
||||
which_type: whichType,
|
||||
error_type: errorType,
|
||||
parent_error_type: parentErrorType,
|
||||
severity: whichTemplate.severity || 'error',
|
||||
object_id: props.object_id,
|
||||
object_type: props.object_type,
|
||||
schema: props.schema,
|
||||
title: props.title
|
||||
});
|
||||
|
||||
d.replacements = tokenReplacements(d);
|
||||
|
||||
_krCache.data[d.id] = d;
|
||||
_krCache.rtree.insert(encodeErrorRtree(d));
|
||||
});
|
||||
|
||||
dispatch.call('loaded');
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
postKeepRightUpdate: function(d, callback) {
|
||||
if (_krCache.inflight[d.id]) {
|
||||
return callback({ message: 'Error update already inflight', status: -2 }, d);
|
||||
}
|
||||
|
||||
var that = this;
|
||||
var params = { schema: d.schema, id: d.error_id };
|
||||
|
||||
if (d.state) {
|
||||
params.st = d.state;
|
||||
}
|
||||
if (d.newComment !== undefined) {
|
||||
params.co = d.newComment;
|
||||
}
|
||||
|
||||
// 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)
|
||||
.post(function(err) {
|
||||
delete _krCache.inflight[d.id];
|
||||
|
||||
if (d.state === 'ignore') { // ignore permanently (false positive)
|
||||
that.removeError(d);
|
||||
|
||||
} else if (d.state === 'ignore_t') { // ignore temporarily (error fixed)
|
||||
that.removeError(d);
|
||||
_krCache.closed[d.schema + ':' + d.error_id] = true;
|
||||
|
||||
} else {
|
||||
d = that.replaceError(d.update({
|
||||
comment: d.newComment,
|
||||
newComment: undefined,
|
||||
state: undefined
|
||||
}));
|
||||
}
|
||||
|
||||
return callback(err, d);
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
|
||||
// get all cached errors covering the viewport
|
||||
getErrors: function(projection) {
|
||||
var viewport = projection.clipExtent();
|
||||
var min = [viewport[0][0], viewport[1][1]];
|
||||
var max = [viewport[1][0], viewport[0][1]];
|
||||
var bbox = geoExtent(projection.invert(min), projection.invert(max)).bbox();
|
||||
|
||||
return _krCache.rtree.search(bbox).map(function(d) {
|
||||
return d.data;
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
// get a single error from the cache
|
||||
getError: function(id) {
|
||||
return _krCache.data[id];
|
||||
},
|
||||
|
||||
|
||||
// replace a single error in the cache
|
||||
replaceError: function(error) {
|
||||
if (!(error instanceof krError) || !error.id) return;
|
||||
|
||||
_krCache.data[error.id] = error;
|
||||
updateRtree(encodeErrorRtree(error), true); // true = replace
|
||||
return error;
|
||||
},
|
||||
|
||||
|
||||
// remove a single error from the cache
|
||||
removeError: function(error) {
|
||||
if (!(error instanceof krError) || !error.id) return;
|
||||
|
||||
delete _krCache.data[error.id];
|
||||
updateRtree(encodeErrorRtree(error), false); // false = remove
|
||||
},
|
||||
|
||||
|
||||
errorURL: function(error) {
|
||||
return _krUrlRoot + 'report_map.php?schema=' + error.schema + '&error=' + error.id;
|
||||
},
|
||||
|
||||
|
||||
// Get an array of errors closed during this session.
|
||||
// Used to populate `closed:keepright` changeset tag
|
||||
getClosedIDs: function() {
|
||||
return Object.keys(_krCache.closed).sort();
|
||||
}
|
||||
|
||||
};
|
||||
+21
-2
@@ -47,7 +47,7 @@ var oauth = osmAuth({
|
||||
|
||||
var _blacklists = ['.*\.google(apis)?\..*/(vt|kh)[\?/].*([xyz]=.*){3}.*'];
|
||||
var _tileCache = { loaded: {}, inflight: {}, seen: {} };
|
||||
var _noteCache = { loaded: {}, inflight: {}, inflightPost: {}, note: {}, rtree: rbush() };
|
||||
var _noteCache = { loaded: {}, inflight: {}, inflightPost: {}, note: {}, closed: {}, rtree: rbush() };
|
||||
var _userCache = { toLoad: {}, user: {} };
|
||||
var _changeset = {};
|
||||
|
||||
@@ -385,7 +385,7 @@ export default {
|
||||
if (_changeset.inflight) abortRequest(_changeset.inflight);
|
||||
|
||||
_tileCache = { loaded: {}, inflight: {}, seen: {} };
|
||||
_noteCache = { loaded: {}, inflight: {}, inflightPost: {}, note: {}, rtree: rbush() };
|
||||
_noteCache = { loaded: {}, inflight: {}, inflightPost: {}, note: {}, closed: {}, rtree: rbush() };
|
||||
_userCache = { toLoad: {}, user: {} };
|
||||
_changeset = {};
|
||||
|
||||
@@ -432,6 +432,11 @@ export default {
|
||||
},
|
||||
|
||||
|
||||
noteReportURL: function(note) {
|
||||
return urlroot + '/reports/new?reportable_type=Note&reportable_id=' + note.id;
|
||||
},
|
||||
|
||||
|
||||
// Generic method to load data from the OSM API
|
||||
// Can handle either auth or unauth calls.
|
||||
loadFromAPI: function(path, callback, options) {
|
||||
@@ -951,6 +956,13 @@ export default {
|
||||
// we get the updated note back, remove from caches and reparse..
|
||||
this.removeNote(note);
|
||||
|
||||
// update closed note cache - used to populate `closed:note` changeset tag
|
||||
if (action === 'close') {
|
||||
_noteCache.closed[note.id] = true;
|
||||
} else if (action === 'reopen') {
|
||||
delete _noteCache.closed[note.id];
|
||||
}
|
||||
|
||||
var options = { skipSeen: false };
|
||||
return parseXML(xml, function(err, results) {
|
||||
if (err) {
|
||||
@@ -1113,6 +1125,13 @@ export default {
|
||||
_noteCache.note[note.id] = note;
|
||||
updateRtree(encodeNoteRtree(note), true); // true = replace
|
||||
return note;
|
||||
},
|
||||
|
||||
|
||||
// Get an array of note IDs closed during this session.
|
||||
// Used to populate `closed:note` changeset tag
|
||||
getClosedIDs: function() {
|
||||
return Object.keys(_noteCache.closed).sort();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
import _debounce from 'lodash-es/debounce';
|
||||
import _forEach from 'lodash-es/forEach';
|
||||
|
||||
import { json as d3_json } from 'd3-request';
|
||||
|
||||
import { utilQsString } from '../util';
|
||||
|
||||
|
||||
var apibase = 'https://wiki.openstreetmap.org/w/api.php';
|
||||
var _inflight = {};
|
||||
var _wikibaseCache = {};
|
||||
var _localeIDs = { en: false };
|
||||
|
||||
|
||||
var debouncedRequest = _debounce(request, 500, { leading: false });
|
||||
|
||||
function request(url, callback) {
|
||||
if (_inflight[url]) return;
|
||||
|
||||
_inflight[url] = d3_json(url, function (err, data) {
|
||||
delete _inflight[url];
|
||||
callback(err, data);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the best string value from the descriptions/labels result
|
||||
* Note that if mediawiki doesn't recognize language code, it will return all values.
|
||||
* In that case, fallback to use English.
|
||||
* @param values object - either descriptions or labels
|
||||
* @param langCode String
|
||||
* @returns localized string
|
||||
*/
|
||||
function localizedToString(values, langCode) {
|
||||
if (values) {
|
||||
values = values[langCode] || values.en;
|
||||
}
|
||||
return values ? values.value : '';
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
|
||||
init: function() {
|
||||
_inflight = {};
|
||||
_wikibaseCache = {};
|
||||
_localeIDs = {};
|
||||
},
|
||||
|
||||
|
||||
reset: function() {
|
||||
_forEach(_inflight, function(req) { req.abort(); });
|
||||
_inflight = {};
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Get the best value for the property, or undefined if not found
|
||||
* @param entity object from wikibase
|
||||
* @param property string e.g. 'P4' for image
|
||||
* @param langCode string e.g. 'fr' for French
|
||||
*/
|
||||
claimToValue: function(entity, property, langCode) {
|
||||
if (!entity.claims[property]) return undefined;
|
||||
var locale = _localeIDs[langCode];
|
||||
var preferredPick, localePick;
|
||||
_forEach(entity.claims[property], function(stmt) {
|
||||
// If exists, use value limited to the needed language (has a qualifier P26 = locale)
|
||||
// Or if not found, use the first value with the "preferred" rank
|
||||
if (!preferredPick && stmt.rank === 'preferred') {
|
||||
preferredPick = stmt;
|
||||
}
|
||||
if (locale && stmt.qualifiers && stmt.qualifiers.P26 &&
|
||||
stmt.qualifiers.P26[0].datavalue.value.id === locale
|
||||
) {
|
||||
localePick = stmt;
|
||||
}
|
||||
});
|
||||
var result = localePick || preferredPick;
|
||||
|
||||
if (result) {
|
||||
var datavalue = result.mainsnak.datavalue;
|
||||
return datavalue.type === 'wikibase-entityid' ? datavalue.value.id : datavalue.value;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
toSitelink: function(key, value) {
|
||||
var result = value ? 'Tag:' + key + '=' + value : 'Key:' + key;
|
||||
return result.replace(/_/g, ' ').trim();
|
||||
},
|
||||
|
||||
|
||||
getEntity: function(params, callback) {
|
||||
var doRequest = params.debounce ? debouncedRequest : request;
|
||||
var self = this;
|
||||
var titles = [];
|
||||
var result = {};
|
||||
var keySitelink = this.toSitelink(params.key);
|
||||
var tagSitelink = params.value ? this.toSitelink(params.key, params.value) : false;
|
||||
var localeSitelink;
|
||||
|
||||
if (params.langCode && _localeIDs[params.langCode] === undefined) {
|
||||
// If this is the first time we are asking about this locale,
|
||||
// fetch corresponding entity (if it exists), and cache it.
|
||||
// If there is no such entry, cache `false` value to avoid re-requesting it.
|
||||
localeSitelink = ('Locale:' + params.langCode).replace(/_/g, ' ').trim();
|
||||
titles.push(localeSitelink);
|
||||
}
|
||||
|
||||
if (_wikibaseCache[keySitelink]) {
|
||||
result.key = _wikibaseCache[keySitelink];
|
||||
} else {
|
||||
titles.push(keySitelink);
|
||||
}
|
||||
|
||||
if (tagSitelink) {
|
||||
if (_wikibaseCache[tagSitelink]) {
|
||||
result.tag = _wikibaseCache[tagSitelink];
|
||||
} else {
|
||||
titles.push(tagSitelink);
|
||||
}
|
||||
}
|
||||
|
||||
if (!titles.length) {
|
||||
// Nothing to do, we already had everything in the cache
|
||||
return callback(null, result);
|
||||
}
|
||||
|
||||
// Requesting just the user language code
|
||||
// If backend recognizes the code, it will perform proper fallbacks,
|
||||
// and the result will contain the requested code. If not, all values are returned:
|
||||
// {"zh-tw":{"value":"...","language":"zh-tw","source-language":"zh-hant"}
|
||||
// {"pt-br":{"value":"...","language":"pt","for-language":"pt-br"}}
|
||||
var obj = {
|
||||
action: 'wbgetentities',
|
||||
sites: 'wiki',
|
||||
titles: titles.join('|'),
|
||||
languages: params.langCode,
|
||||
languagefallback: 1,
|
||||
origin: '*',
|
||||
format: 'json',
|
||||
// There is an MW Wikibase API bug https://phabricator.wikimedia.org/T212069
|
||||
// We shouldn't use v1 until it gets fixed, but should switch to it afterwards
|
||||
// formatversion: 2,
|
||||
};
|
||||
|
||||
var url = apibase + '?' + utilQsString(obj);
|
||||
doRequest(url, function(err, d) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else if (!d.success || d.error) {
|
||||
callback(d.error.messages.map(function(v) { return v.html['*']; }).join('<br>'));
|
||||
} else {
|
||||
var localeID = false;
|
||||
_forEach(d.entities, function(res) {
|
||||
if (res.missing !== '') {
|
||||
var title = res.sitelinks.wiki.title;
|
||||
// Simplify access to the localized values
|
||||
res.description = localizedToString(res.descriptions, params.langCode);
|
||||
res.label = localizedToString(res.labels, params.langCode);
|
||||
if (title === keySitelink) {
|
||||
_wikibaseCache[keySitelink] = res;
|
||||
result.key = res;
|
||||
} else if (title === tagSitelink) {
|
||||
_wikibaseCache[tagSitelink] = res;
|
||||
result.tag = res;
|
||||
} else if (title === localeSitelink) {
|
||||
localeID = res.id;
|
||||
} else {
|
||||
console.log('Unexpected title ' + title); // eslint-disable-line no-console
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (localeSitelink) {
|
||||
// If locale ID is not found, store false to prevent repeated queries
|
||||
self.addLocale(params.langCode, localeID);
|
||||
}
|
||||
|
||||
callback(null, result);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
addLocale: function(langCode, qid) {
|
||||
// Makes it easier to unit test
|
||||
_localeIDs[langCode] = qid;
|
||||
},
|
||||
|
||||
apibase: function(val) {
|
||||
if (!arguments.length) return apibase;
|
||||
apibase = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -2,9 +2,9 @@ import { select as d3_select } from 'd3-selection';
|
||||
|
||||
import { svgPointTransform } from './helpers';
|
||||
import { geoMetersToLat } from '../geo';
|
||||
import _throttle from 'lodash-es/throttle';
|
||||
|
||||
export function svgGeolocate(projection, context, dispatch) {
|
||||
|
||||
export function svgGeolocate(projection) {
|
||||
var layer = d3_select(null);
|
||||
var _position;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ export { svgAreas } from './areas.js';
|
||||
export { svgData } from './data.js';
|
||||
export { svgDebug } from './debug.js';
|
||||
export { svgDefs } from './defs.js';
|
||||
export { svgKeepRight } from './keepRight';
|
||||
export { svgIcon } from './icon.js';
|
||||
export { svgGeolocate } from './geolocate';
|
||||
export { svgLabels } from './labels.js';
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
import _throttle from 'lodash-es/throttle';
|
||||
import { select as d3_select } from 'd3-selection';
|
||||
|
||||
import { modeBrowse } from '../modes';
|
||||
import { svgPointTransform } from './index';
|
||||
import { services } from '../services';
|
||||
|
||||
var _keepRightEnabled = false;
|
||||
var _keepRightService;
|
||||
|
||||
|
||||
export function svgKeepRight(projection, context, dispatch) {
|
||||
var throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000);
|
||||
var minZoom = 12;
|
||||
var touchLayer = d3_select(null);
|
||||
var drawLayer = d3_select(null);
|
||||
var _keepRightVisible = false;
|
||||
|
||||
|
||||
function markerPath(selection, klass) {
|
||||
selection
|
||||
.attr('class', klass)
|
||||
.attr('transform', 'translate(-4, -24)')
|
||||
.attr('d', 'M11.6,6.2H7.1l1.4-5.1C8.6,0.6,8.1,0,7.5,0H2.2C1.7,0,1.3,0.3,1.3,0.8L0,10.2c-0.1,0.6,0.4,1.1,0.9,1.1h4.6l-1.8,7.6C3.6,19.4,4.1,20,4.7,20c0.3,0,0.6-0.2,0.8-0.5l6.9-11.9C12.7,7,12.3,6.2,11.6,6.2z');
|
||||
}
|
||||
|
||||
|
||||
// Loosely-coupled keepRight service for fetching errors.
|
||||
function getService() {
|
||||
if (services.keepRight && !_keepRightService) {
|
||||
_keepRightService = services.keepRight;
|
||||
_keepRightService.on('loaded', throttledRedraw);
|
||||
} else if (!services.keepRight && _keepRightService) {
|
||||
_keepRightService = null;
|
||||
}
|
||||
|
||||
return _keepRightService;
|
||||
}
|
||||
|
||||
|
||||
// Show the errors
|
||||
function editOn() {
|
||||
if (!_keepRightVisible) {
|
||||
_keepRightVisible = true;
|
||||
drawLayer
|
||||
.style('display', 'block');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Immediately remove the errors and their touch targets
|
||||
function editOff() {
|
||||
if (_keepRightVisible) {
|
||||
_keepRightVisible = false;
|
||||
drawLayer
|
||||
.style('display', 'none');
|
||||
drawLayer.selectAll('.kr_error')
|
||||
.remove();
|
||||
touchLayer.selectAll('.kr_error')
|
||||
.remove();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Enable the layer. This shows the errors and transitions them to visible.
|
||||
function layerOn() {
|
||||
editOn();
|
||||
|
||||
drawLayer
|
||||
.style('opacity', 0)
|
||||
.transition()
|
||||
.duration(250)
|
||||
.style('opacity', 1)
|
||||
.on('end interrupt', function () {
|
||||
dispatch.call('change');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Disable the layer. This transitions the layer invisible and then hides the errors.
|
||||
function layerOff() {
|
||||
throttledRedraw.cancel();
|
||||
drawLayer.interrupt();
|
||||
touchLayer.selectAll('.kr_error')
|
||||
.remove();
|
||||
|
||||
drawLayer
|
||||
.transition()
|
||||
.duration(250)
|
||||
.style('opacity', 0)
|
||||
.on('end interrupt', function () {
|
||||
editOff();
|
||||
dispatch.call('change');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Update the error markers
|
||||
function updateMarkers() {
|
||||
if (!_keepRightVisible || !_keepRightEnabled) return;
|
||||
|
||||
var service = getService();
|
||||
var selectedID = context.selectedErrorID();
|
||||
var data = (service ? service.getErrors(projection) : []);
|
||||
var getTransform = svgPointTransform(projection);
|
||||
|
||||
// Draw markers..
|
||||
var markers = drawLayer.selectAll('.kr_error')
|
||||
.data(data, function(d) { return d.id; });
|
||||
|
||||
// exit
|
||||
markers.exit()
|
||||
.remove();
|
||||
|
||||
// enter
|
||||
var markersEnter = markers.enter()
|
||||
.append('g')
|
||||
.attr('class', function(d) {
|
||||
return 'kr_error kr_error-' + d.id + ' kr_error_type_' + d.parent_error_type; }
|
||||
);
|
||||
|
||||
markersEnter
|
||||
.append('ellipse')
|
||||
.attr('cx', 0.5)
|
||||
.attr('cy', 1)
|
||||
.attr('rx', 6.5)
|
||||
.attr('ry', 3)
|
||||
.attr('class', 'stroke');
|
||||
|
||||
markersEnter
|
||||
.append('path')
|
||||
.call(markerPath, 'shadow');
|
||||
|
||||
markersEnter
|
||||
.append('use')
|
||||
.attr('class', 'kr_error-fill')
|
||||
.attr('width', '20px')
|
||||
.attr('height', '20px')
|
||||
.attr('x', '-8px')
|
||||
.attr('y', '-22px')
|
||||
.attr('xlink:href', '#iD-icon-bolt');
|
||||
|
||||
// update
|
||||
markers
|
||||
.merge(markersEnter)
|
||||
.sort(sortY)
|
||||
.classed('selected', function(d) { return d.id === selectedID; })
|
||||
.attr('transform', getTransform);
|
||||
|
||||
|
||||
// Draw targets..
|
||||
if (touchLayer.empty()) return;
|
||||
var fillClass = context.getDebug('target') ? 'pink ' : 'nocolor ';
|
||||
|
||||
var targets = touchLayer.selectAll('.kr_error')
|
||||
.data(data, function(d) { return d.id; });
|
||||
|
||||
// exit
|
||||
targets.exit()
|
||||
.remove();
|
||||
|
||||
// enter/update
|
||||
targets.enter()
|
||||
.append('rect')
|
||||
.attr('width', '20px')
|
||||
.attr('height', '20px')
|
||||
.attr('x', '-8px')
|
||||
.attr('y', '-22px')
|
||||
.merge(targets)
|
||||
.sort(sortY)
|
||||
.attr('class', function(d) {
|
||||
return 'kr_error target kr_error-' + d.id + ' ' + fillClass;
|
||||
})
|
||||
.attr('transform', getTransform);
|
||||
|
||||
|
||||
function sortY(a, b) {
|
||||
return (a.id === selectedID) ? 1
|
||||
: (b.id === selectedID) ? -1
|
||||
: (a.severity === 'error' && b.severity !== 'error') ? 1
|
||||
: (b.severity === 'error' && a.severity !== 'error') ? -1
|
||||
: b.loc[1] - a.loc[1];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Draw the keepRight layer and schedule loading errors and updating markers.
|
||||
function drawKeepRight(selection) {
|
||||
var service = getService();
|
||||
|
||||
var surface = context.surface();
|
||||
if (surface && !surface.empty()) {
|
||||
touchLayer = surface.selectAll('.data-layer.touch .layer-touch.markers');
|
||||
}
|
||||
|
||||
drawLayer = selection.selectAll('.layer-keepRight')
|
||||
.data(service ? [0] : []);
|
||||
|
||||
drawLayer.exit()
|
||||
.remove();
|
||||
|
||||
drawLayer = drawLayer.enter()
|
||||
.append('g')
|
||||
.attr('class', 'layer-keepRight')
|
||||
.style('display', _keepRightEnabled ? 'block' : 'none')
|
||||
.merge(drawLayer);
|
||||
|
||||
if (_keepRightEnabled) {
|
||||
if (service && ~~context.map().zoom() >= minZoom) {
|
||||
editOn();
|
||||
service.loadErrors(projection);
|
||||
updateMarkers();
|
||||
} else {
|
||||
editOff();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Toggles the layer on and off
|
||||
drawKeepRight.enabled = function(val) {
|
||||
if (!arguments.length) return _keepRightEnabled;
|
||||
|
||||
_keepRightEnabled = val;
|
||||
if (_keepRightEnabled) {
|
||||
layerOn();
|
||||
} else {
|
||||
layerOff();
|
||||
if (context.selectedErrorID()) {
|
||||
context.enter(modeBrowse(context));
|
||||
}
|
||||
}
|
||||
|
||||
dispatch.call('change');
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
drawKeepRight.supported = function() {
|
||||
return !!getService();
|
||||
};
|
||||
|
||||
|
||||
return drawKeepRight;
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import { select as d3_select } from 'd3-selection';
|
||||
import { svgData } from './data';
|
||||
import { svgDebug } from './debug';
|
||||
import { svgGeolocate } from './geolocate';
|
||||
import { svgKeepRight } from './keepRight';
|
||||
import { svgStreetside } from './streetside';
|
||||
import { svgMapillaryImages } from './mapillary_images';
|
||||
import { svgMapillarySigns } from './mapillary_signs';
|
||||
@@ -28,6 +29,7 @@ export function svgLayers(projection, context) {
|
||||
{ id: 'osm', layer: svgOsm(projection, context, dispatch) },
|
||||
{ id: 'notes', layer: svgNotes(projection, context, dispatch) },
|
||||
{ id: 'data', layer: svgData(projection, context, dispatch) },
|
||||
{ id: 'keepRight', layer: svgKeepRight(projection, context, dispatch) },
|
||||
{ id: 'streetside', layer: svgStreetside(projection, context, dispatch)},
|
||||
{ id: 'mapillary-images', layer: svgMapillaryImages(projection, context, dispatch) },
|
||||
{ id: 'mapillary-signs', layer: svgMapillarySigns(projection, context, dispatch) },
|
||||
|
||||
+123
-68
@@ -8,12 +8,18 @@ import { svgPointTransform } from './index';
|
||||
import { services } from '../services';
|
||||
|
||||
|
||||
var _notesEnabled = false;
|
||||
var _osmService;
|
||||
|
||||
|
||||
export function svgNotes(projection, context, dispatch) {
|
||||
if (!dispatch) { dispatch = d3_dispatch('change'); }
|
||||
var throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000);
|
||||
var minZoom = 12;
|
||||
var layer = d3_select(null);
|
||||
var _notes;
|
||||
var touchLayer = d3_select(null);
|
||||
var drawLayer = d3_select(null);
|
||||
var _notesVisible = false;
|
||||
|
||||
|
||||
function markerPath(selection, klass) {
|
||||
selection
|
||||
@@ -22,40 +28,49 @@ export function svgNotes(projection, context, dispatch) {
|
||||
.attr('d', 'm17.5,0l-15,0c-1.37,0 -2.5,1.12 -2.5,2.5l0,11.25c0,1.37 1.12,2.5 2.5,2.5l3.75,0l0,3.28c0,0.38 0.43,0.6 0.75,0.37l4.87,-3.65l5.62,0c1.37,0 2.5,-1.12 2.5,-2.5l0,-11.25c0,-1.37 -1.12,-2.5 -2.5,-2.5z');
|
||||
}
|
||||
|
||||
function init() {
|
||||
if (svgNotes.initialized) return; // run once
|
||||
svgNotes.enabled = false;
|
||||
svgNotes.initialized = true;
|
||||
}
|
||||
|
||||
function editOn() {
|
||||
layer.style('display', 'block');
|
||||
}
|
||||
|
||||
|
||||
function editOff() {
|
||||
layer.selectAll('.note').remove();
|
||||
layer.style('display', 'none');
|
||||
}
|
||||
|
||||
|
||||
// Loosely-coupled osm service for fetching notes.
|
||||
function getService() {
|
||||
if (services.osm && !_notes) {
|
||||
_notes = services.osm;
|
||||
_notes.on('loadedNotes', throttledRedraw);
|
||||
} else if (!services.osm && _notes) {
|
||||
_notes = null;
|
||||
if (services.osm && !_osmService) {
|
||||
_osmService = services.osm;
|
||||
_osmService.on('loadedNotes', throttledRedraw);
|
||||
} else if (!services.osm && _osmService) {
|
||||
_osmService = null;
|
||||
}
|
||||
|
||||
return _notes;
|
||||
return _osmService;
|
||||
}
|
||||
|
||||
|
||||
function showLayer() {
|
||||
// Show the notes
|
||||
function editOn() {
|
||||
if (!_notesVisible) {
|
||||
_notesVisible = true;
|
||||
drawLayer
|
||||
.style('display', 'block');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Immediately remove the notes and their touch targets
|
||||
function editOff() {
|
||||
if (_notesVisible) {
|
||||
_notesVisible = false;
|
||||
drawLayer
|
||||
.style('display', 'none');
|
||||
drawLayer.selectAll('.note')
|
||||
.remove();
|
||||
touchLayer.selectAll('.note')
|
||||
.remove();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Enable the layer. This shows the notes and transitions them to visible.
|
||||
function layerOn() {
|
||||
editOn();
|
||||
|
||||
layer
|
||||
.classed('disabled', false)
|
||||
drawLayer
|
||||
.style('opacity', 0)
|
||||
.transition()
|
||||
.duration(250)
|
||||
@@ -66,30 +81,35 @@ export function svgNotes(projection, context, dispatch) {
|
||||
}
|
||||
|
||||
|
||||
function hideLayer() {
|
||||
editOff();
|
||||
|
||||
// Disable the layer. This transitions the layer invisible and then hides the notes.
|
||||
function layerOff() {
|
||||
throttledRedraw.cancel();
|
||||
layer.interrupt();
|
||||
drawLayer.interrupt();
|
||||
touchLayer.selectAll('.note')
|
||||
.remove();
|
||||
|
||||
layer
|
||||
drawLayer
|
||||
.transition()
|
||||
.duration(250)
|
||||
.style('opacity', 0)
|
||||
.on('end interrupt', function () {
|
||||
layer.classed('disabled', true);
|
||||
editOff();
|
||||
dispatch.call('change');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
function update() {
|
||||
// Update the note markers
|
||||
function updateMarkers() {
|
||||
if (!_notesVisible || !_notesEnabled) return;
|
||||
|
||||
var service = getService();
|
||||
var selectedID = context.selectedNoteID();
|
||||
var data = (service ? service.notes(projection) : []);
|
||||
var transform = svgPointTransform(projection);
|
||||
var notes = layer.selectAll('.note')
|
||||
var getTransform = svgPointTransform(projection);
|
||||
|
||||
// Draw markers..
|
||||
var notes = drawLayer.selectAll('.note')
|
||||
.data(data, function(d) { return d.status + d.id; });
|
||||
|
||||
// exit
|
||||
@@ -139,55 +159,90 @@ export function svgNotes(projection, context, dispatch) {
|
||||
// update
|
||||
notes
|
||||
.merge(notesEnter)
|
||||
.sort(function(a, b) {
|
||||
return (a.id === selectedID) ? 1
|
||||
: (b.id === selectedID) ? -1
|
||||
: b.loc[1] - a.loc[1]; // sort Y
|
||||
.sort(sortY)
|
||||
.classed('selected', function(d) {
|
||||
var mode = context.mode();
|
||||
var isMoving = mode && mode.id === 'drag-note'; // no shadows when dragging
|
||||
return !isMoving && d.id === selectedID;
|
||||
})
|
||||
.classed('selected', function(d) { return d.id === selectedID; })
|
||||
.attr('transform', transform);
|
||||
.attr('transform', getTransform);
|
||||
|
||||
|
||||
// Draw targets..
|
||||
if (touchLayer.empty()) return;
|
||||
var fillClass = context.getDebug('target') ? 'pink ' : 'nocolor ';
|
||||
|
||||
var targets = touchLayer.selectAll('.note')
|
||||
.data(data, function(d) { return d.id; });
|
||||
|
||||
// exit
|
||||
targets.exit()
|
||||
.remove();
|
||||
|
||||
// enter/update
|
||||
targets.enter()
|
||||
.append('rect')
|
||||
.attr('width', '20px')
|
||||
.attr('height', '20px')
|
||||
.attr('x', '-8px')
|
||||
.attr('y', '-22px')
|
||||
.merge(targets)
|
||||
.sort(sortY)
|
||||
.attr('class', function(d) {
|
||||
var newClass = (d.id < 0 ? 'new' : '');
|
||||
return 'note target note-' + d.id + ' ' + fillClass + newClass;
|
||||
})
|
||||
.attr('transform', getTransform);
|
||||
|
||||
|
||||
function sortY(a, b) {
|
||||
return (a.id === selectedID) ? 1 : (b.id === selectedID) ? -1 : b.loc[1] - a.loc[1];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Draw the notes layer and schedule loading notes and updating markers.
|
||||
function drawNotes(selection) {
|
||||
var enabled = svgNotes.enabled;
|
||||
var service = getService();
|
||||
|
||||
layer = selection.selectAll('.layer-notes')
|
||||
.data(service ? [0] : []);
|
||||
|
||||
layer.exit()
|
||||
.remove();
|
||||
|
||||
layer.enter()
|
||||
.append('g')
|
||||
.attr('class', 'layer-notes')
|
||||
.style('display', enabled ? 'block' : 'none')
|
||||
.merge(layer);
|
||||
|
||||
function dimensions() {
|
||||
return [window.innerWidth, window.innerHeight];
|
||||
var surface = context.surface();
|
||||
if (surface && !surface.empty()) {
|
||||
touchLayer = surface.selectAll('.data-layer.touch .layer-touch.markers');
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
drawLayer = selection.selectAll('.layer-notes')
|
||||
.data(service ? [0] : []);
|
||||
|
||||
drawLayer.exit()
|
||||
.remove();
|
||||
|
||||
drawLayer = drawLayer.enter()
|
||||
.append('g')
|
||||
.attr('class', 'layer-notes')
|
||||
.style('display', _notesEnabled ? 'block' : 'none')
|
||||
.merge(drawLayer);
|
||||
|
||||
if (_notesEnabled) {
|
||||
if (service && ~~context.map().zoom() >= minZoom) {
|
||||
editOn();
|
||||
service.loadNotes(projection, dimensions());
|
||||
update();
|
||||
service.loadNotes(projection);
|
||||
updateMarkers();
|
||||
} else {
|
||||
editOff();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawNotes.enabled = function(val) {
|
||||
if (!arguments.length) return svgNotes.enabled;
|
||||
|
||||
svgNotes.enabled = val;
|
||||
if (svgNotes.enabled) {
|
||||
showLayer();
|
||||
// Toggles the layer on and off
|
||||
drawNotes.enabled = function(val) {
|
||||
if (!arguments.length) return _notesEnabled;
|
||||
|
||||
_notesEnabled = val;
|
||||
if (_notesEnabled) {
|
||||
layerOn();
|
||||
} else {
|
||||
hideLayer();
|
||||
layerOff();
|
||||
if (context.selectedNoteID()) {
|
||||
context.enter(modeBrowse(context));
|
||||
}
|
||||
@@ -197,6 +252,6 @@ export function svgNotes(projection, context, dispatch) {
|
||||
return this;
|
||||
};
|
||||
|
||||
init();
|
||||
|
||||
return drawNotes;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ export function svgTouch() {
|
||||
|
||||
function drawTouch(selection) {
|
||||
selection.selectAll('.layer-touch')
|
||||
.data(['areas', 'lines', 'points', 'turns', 'notes'])
|
||||
.data(['areas', 'lines', 'points', 'turns', 'markers'])
|
||||
.enter()
|
||||
.append('g')
|
||||
.attr('class', function(d) { return 'layer-touch ' + d; });
|
||||
|
||||
@@ -56,15 +56,12 @@ export function uiCombobox(context, klass) {
|
||||
var parent = this.parentNode;
|
||||
var sibling = this.nextSibling;
|
||||
|
||||
var caret = d3_select(parent).selectAll('.combobox-caret')
|
||||
d3_select(parent).selectAll('.combobox-caret')
|
||||
.filter(function(d) { return d === input.node(); })
|
||||
.data([input.node()]);
|
||||
|
||||
caret = caret.enter()
|
||||
.data([input.node()])
|
||||
.enter()
|
||||
.insert('div', function() { return sibling; })
|
||||
.attr('class', 'combobox-caret')
|
||||
.merge(caret);
|
||||
|
||||
.attr('class', 'combobox-caret');
|
||||
}
|
||||
|
||||
|
||||
@@ -341,7 +338,7 @@ export function uiCombobox(context, klass) {
|
||||
// Dispatches an 'accept' event if an option has been chosen.
|
||||
// Then hides the combobox.
|
||||
function accept(d) {
|
||||
d = d || _choice;
|
||||
d = d || _choice || value();
|
||||
if (d) {
|
||||
utilGetSetValue(input, d.value);
|
||||
utilTriggerEvent(input, 'change');
|
||||
|
||||
+21
-8
@@ -8,6 +8,7 @@ import { select as d3_select } from 'd3-selection';
|
||||
|
||||
import { t } from '../util/locale';
|
||||
import { osmChangeset } from '../osm';
|
||||
import { services } from '../services';
|
||||
import { uiChangesetEditor } from './changeset_editor';
|
||||
import { uiCommitChanges } from './commit_changes';
|
||||
import { uiCommitWarnings } from './commit_warnings';
|
||||
@@ -63,6 +64,8 @@ export function uiCommit(context) {
|
||||
}
|
||||
|
||||
var tags;
|
||||
// Initialize changeset if one does not exist yet.
|
||||
// Also pull values from local storage.
|
||||
if (!_changeset) {
|
||||
var detected = utilDetect();
|
||||
tags = {
|
||||
@@ -81,13 +84,9 @@ export function uiCommit(context) {
|
||||
tags.hashtags = hashtags;
|
||||
}
|
||||
|
||||
// iD 2.8.1 could write a literal 'undefined' here.. see #5021
|
||||
// (old source values expire after 2 days, so 'undefined' checks can go away in v2.9)
|
||||
var source = context.storage('source');
|
||||
if (source && source !== 'undefined') {
|
||||
if (source) {
|
||||
tags.source = source;
|
||||
} else if (source === 'undefined') {
|
||||
context.storage('source', null);
|
||||
}
|
||||
|
||||
_changeset = new osmChangeset({ tags: tags });
|
||||
@@ -95,8 +94,22 @@ export function uiCommit(context) {
|
||||
|
||||
tags = _clone(_changeset.tags);
|
||||
|
||||
// assign tags for imagery used
|
||||
var imageryUsed = context.history().imageryUsed().join(';').substr(0, 255);
|
||||
tags.imagery_used = imageryUsed || 'None';
|
||||
|
||||
// assign tags for closed issues and notes
|
||||
var osmClosed = osm.getClosedIDs();
|
||||
if (osmClosed.length) {
|
||||
tags['closed:note'] = osmClosed.join(';').substr(0, 255);
|
||||
}
|
||||
if (services.keepRight) {
|
||||
var krClosed = services.keepRight.getClosedIDs();
|
||||
if (krClosed.length) {
|
||||
tags['closed:keepright'] = krClosed.join(';').substr(0, 255);
|
||||
}
|
||||
}
|
||||
|
||||
_changeset = _changeset.update({ tags: tags });
|
||||
|
||||
var header = selection.selectAll('.header')
|
||||
@@ -109,17 +122,17 @@ export function uiCommit(context) {
|
||||
headerTitle
|
||||
.append('div')
|
||||
.attr('class', 'header-block header-block-outer');
|
||||
|
||||
|
||||
headerTitle
|
||||
.append('div')
|
||||
.attr('class', 'header-block')
|
||||
.append('h3')
|
||||
.text(t('commit.title'));
|
||||
|
||||
|
||||
headerTitle
|
||||
.append('div')
|
||||
.attr('class', 'header-block header-block-outer header-block-close')
|
||||
.append('button')
|
||||
.append('button')
|
||||
.attr('class', 'close')
|
||||
.on('click', function() { context.enter(modeBrowse(context)); })
|
||||
.call(svgIcon('#iD-icon-close'));
|
||||
|
||||
@@ -26,7 +26,9 @@ export function uiCommitWarnings(context) {
|
||||
}, {});
|
||||
|
||||
_forEach(issues, function(instances, severity) {
|
||||
instances = _uniqBy(instances, function(val) { return val.id + '_' + val.message.replace(/\s+/g,''); });
|
||||
instances = _uniqBy(instances, function(val) {
|
||||
return val.entity || (val.id + '_' + val.message.replace(/\s+/g,''));
|
||||
});
|
||||
var section = severity + '-section';
|
||||
var instanceItem = severity + '-item';
|
||||
|
||||
|
||||
@@ -4,17 +4,33 @@ import { svgIcon } from '../svg';
|
||||
|
||||
import {
|
||||
uiDataHeader,
|
||||
uiRawTagEditor
|
||||
uiQuickLinks,
|
||||
uiRawTagEditor,
|
||||
uiTooltipHtml
|
||||
} from './index';
|
||||
|
||||
|
||||
export function uiDataEditor(context) {
|
||||
var dataHeader = uiDataHeader();
|
||||
var quickLinks = uiQuickLinks();
|
||||
var rawTagEditor = uiRawTagEditor(context);
|
||||
var _datum;
|
||||
|
||||
|
||||
function dataEditor(selection) {
|
||||
// quick links
|
||||
var choices = [{
|
||||
id: 'zoom_to',
|
||||
label: 'inspector.zoom_to.title',
|
||||
tooltip: function() {
|
||||
return uiTooltipHtml(t('inspector.zoom_to.tooltip_data'), t('inspector.zoom_to.key'));
|
||||
},
|
||||
click: function zoomTo() {
|
||||
context.mode().zoomToSelected();
|
||||
}
|
||||
}];
|
||||
|
||||
|
||||
var header = selection.selectAll('.header')
|
||||
.data([0]);
|
||||
|
||||
@@ -50,14 +66,15 @@ export function uiDataEditor(context) {
|
||||
.append('div')
|
||||
.attr('class', 'modal-section data-editor')
|
||||
.merge(editor)
|
||||
.call(dataHeader.datum(_datum));
|
||||
.call(dataHeader.datum(_datum))
|
||||
.call(quickLinks.choices(choices));
|
||||
|
||||
var rte = body.selectAll('.raw-tag-editor')
|
||||
.data([0]);
|
||||
|
||||
rte.enter()
|
||||
.append('div')
|
||||
.attr('class', 'inspector-border raw-tag-editor inspector-inner data-editor')
|
||||
.attr('class', 'raw-tag-editor inspector-inner data-editor')
|
||||
.merge(rte)
|
||||
.call(rawTagEditor
|
||||
.expanded(true)
|
||||
|
||||
@@ -104,7 +104,7 @@ export function uiEditMenu(context, operations) {
|
||||
.attr('transform', function () { return 'translate(' + [2 * p, 5] + ')'; })
|
||||
.attr('xlink:href', function (d) { return '#iD-operation-' + d.id; });
|
||||
|
||||
tooltip = d3_select(document.body)
|
||||
tooltip = d3_select('#id-container')
|
||||
.append('div')
|
||||
.attr('class', 'tooltip-inner edit-menu-tooltip');
|
||||
|
||||
|
||||
+52
-28
@@ -15,12 +15,14 @@ import { actionChangeTags } from '../actions';
|
||||
import { modeBrowse } from '../modes';
|
||||
import { svgIcon } from '../svg';
|
||||
import { uiPresetIcon } from './preset_icon';
|
||||
import { uiQuickLinks } from './quick_links';
|
||||
import { uiRawMemberEditor } from './raw_member_editor';
|
||||
import { uiRawMembershipEditor } from './raw_membership_editor';
|
||||
import { uiRawTagEditor } from './raw_tag_editor';
|
||||
import { uiTagReference } from './tag_reference';
|
||||
import { uiPresetEditor } from './preset_editor';
|
||||
import { uiEntityIssues } from './entity_issues';
|
||||
import { uiTooltipHtml } from './tooltipHtml';
|
||||
import { utilCleanTags, utilRebind } from '../util';
|
||||
|
||||
|
||||
@@ -35,6 +37,7 @@ export function uiEntityEditor(context) {
|
||||
var _tagReference;
|
||||
|
||||
var entityIssues = uiEntityIssues(context);
|
||||
var quickLinks = uiQuickLinks();
|
||||
var presetEditor = uiPresetEditor(context).on('change', changeTags);
|
||||
var rawTagEditor = uiRawTagEditor(context).on('change', changeTags);
|
||||
var rawMemberEditor = uiRawMemberEditor(context);
|
||||
@@ -49,28 +52,28 @@ export function uiEntityEditor(context) {
|
||||
.data([0]);
|
||||
|
||||
// Enter
|
||||
var enter = header.enter()
|
||||
var headerEnter = header.enter()
|
||||
.append('div')
|
||||
.attr('class', 'header fillL cf');
|
||||
|
||||
enter
|
||||
headerEnter
|
||||
.append('button')
|
||||
.attr('class', 'fl preset-reset preset-choose')
|
||||
.call(svgIcon((textDirection === 'rtl') ? '#iD-icon-forward' : '#iD-icon-backward'));
|
||||
|
||||
enter
|
||||
headerEnter
|
||||
.append('button')
|
||||
.attr('class', 'fr preset-close')
|
||||
.on('click', function() { context.enter(modeBrowse(context)); })
|
||||
.call(svgIcon(_modified ? '#iD-icon-apply' : '#iD-icon-close'));
|
||||
|
||||
enter
|
||||
headerEnter
|
||||
.append('h3')
|
||||
.text(t('inspector.edit'));
|
||||
|
||||
// Update
|
||||
header = header
|
||||
.merge(enter);
|
||||
.merge(headerEnter);
|
||||
|
||||
header.selectAll('.preset-reset')
|
||||
.on('click', function() {
|
||||
@@ -83,11 +86,11 @@ export function uiEntityEditor(context) {
|
||||
.data([0]);
|
||||
|
||||
// Enter
|
||||
enter = body.enter()
|
||||
var bodyEnter = body.enter()
|
||||
.append('div')
|
||||
.attr('class', 'inspector-body');
|
||||
|
||||
enter
|
||||
bodyEnter
|
||||
.append('div')
|
||||
.attr('class', 'preset-list-item inspector-inner')
|
||||
.append('div')
|
||||
@@ -100,27 +103,31 @@ export function uiEntityEditor(context) {
|
||||
.append('div')
|
||||
.attr('class', 'label-inner');
|
||||
|
||||
enter
|
||||
bodyEnter
|
||||
.append('div')
|
||||
.attr('class', 'inspector-border entity-issues');
|
||||
.attr('class', 'preset-quick-links');
|
||||
|
||||
enter
|
||||
bodyEnter
|
||||
.append('div')
|
||||
.attr('class', 'inspector-border preset-editor');
|
||||
.attr('class', 'entity-issues');
|
||||
|
||||
enter
|
||||
bodyEnter
|
||||
.append('div')
|
||||
.attr('class', 'inspector-border raw-tag-editor inspector-inner');
|
||||
.attr('class', 'preset-editor');
|
||||
|
||||
enter
|
||||
bodyEnter
|
||||
.append('div')
|
||||
.attr('class', 'inspector-border raw-member-editor inspector-inner');
|
||||
.attr('class', 'raw-tag-editor inspector-inner');
|
||||
|
||||
enter
|
||||
bodyEnter
|
||||
.append('div')
|
||||
.attr('class', 'raw-member-editor inspector-inner');
|
||||
|
||||
bodyEnter
|
||||
.append('div')
|
||||
.attr('class', 'raw-membership-editor inspector-inner');
|
||||
|
||||
enter
|
||||
bodyEnter
|
||||
.append('input')
|
||||
.attr('type', 'text')
|
||||
.attr('class', 'key-trap');
|
||||
@@ -128,8 +135,9 @@ export function uiEntityEditor(context) {
|
||||
|
||||
// Update
|
||||
body = body
|
||||
.merge(enter);
|
||||
.merge(bodyEnter);
|
||||
|
||||
// update header
|
||||
if (_tagReference) {
|
||||
body.selectAll('.preset-list-button-wrap')
|
||||
.call(_tagReference.button);
|
||||
@@ -149,7 +157,6 @@ export function uiEntityEditor(context) {
|
||||
.preset(_activePreset)
|
||||
);
|
||||
|
||||
|
||||
var label = body.select('.label-inner');
|
||||
var nameparts = label.selectAll('.namepart')
|
||||
.data(_activePreset.name().split(' - '), function(d) { return d; });
|
||||
@@ -168,6 +175,23 @@ export function uiEntityEditor(context) {
|
||||
.entityID(_entityID)
|
||||
);
|
||||
|
||||
// update quick links
|
||||
var choices = [{
|
||||
id: 'zoom_to',
|
||||
label: 'inspector.zoom_to.title',
|
||||
tooltip: function() {
|
||||
return uiTooltipHtml(t('inspector.zoom_to.tooltip_feature'), t('inspector.zoom_to.key'));
|
||||
},
|
||||
click: function zoomTo() {
|
||||
context.mode().zoomToSelected();
|
||||
}
|
||||
}];
|
||||
|
||||
body.select('.preset-quick-links')
|
||||
.call(quickLinks.choices(choices));
|
||||
|
||||
|
||||
// update editor sections
|
||||
body.select('.preset-editor')
|
||||
.call(presetEditor
|
||||
.preset(_activePreset)
|
||||
@@ -274,25 +298,25 @@ export function uiEntityEditor(context) {
|
||||
}
|
||||
|
||||
|
||||
entityEditor.modified = function(_) {
|
||||
entityEditor.modified = function(val) {
|
||||
if (!arguments.length) return _modified;
|
||||
_modified = _;
|
||||
_modified = val;
|
||||
d3_selectAll('button.preset-close use')
|
||||
.attr('xlink:href', (_modified ? '#iD-icon-apply' : '#iD-icon-close'));
|
||||
return entityEditor;
|
||||
};
|
||||
|
||||
|
||||
entityEditor.state = function(_) {
|
||||
entityEditor.state = function(val) {
|
||||
if (!arguments.length) return _state;
|
||||
_state = _;
|
||||
_state = val;
|
||||
return entityEditor;
|
||||
};
|
||||
|
||||
|
||||
entityEditor.entityID = function(_) {
|
||||
entityEditor.entityID = function(val) {
|
||||
if (!arguments.length) return _entityID;
|
||||
_entityID = _;
|
||||
_entityID = val;
|
||||
_base = context.graph();
|
||||
_coalesceChanges = false;
|
||||
|
||||
@@ -310,10 +334,10 @@ export function uiEntityEditor(context) {
|
||||
};
|
||||
|
||||
|
||||
entityEditor.preset = function(_) {
|
||||
entityEditor.preset = function(val) {
|
||||
if (!arguments.length) return _activePreset;
|
||||
if (_ !== _activePreset) {
|
||||
_activePreset = _;
|
||||
if (val !== _activePreset) {
|
||||
_activePreset = val;
|
||||
_tagReference = uiTagReference(_activePreset.reference(context.geometry(_entityID)), context)
|
||||
.showing(false);
|
||||
}
|
||||
|
||||
+17
-17
@@ -231,38 +231,38 @@ export function uiField(context, presetField, entity, options) {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// A shown field has a visible UI, a non-shown field is in the 'Add field' dropdown
|
||||
field.isShown = function() {
|
||||
return _show || isPresent();
|
||||
};
|
||||
|
||||
|
||||
// An allowed field can appear in the UI or in the 'Add field' dropdown.
|
||||
// A non-allowed field is hidden from the user altogether
|
||||
field.isAllowed = function() {
|
||||
if (!entity || isPresent()) return true; // a field with a value should always display
|
||||
|
||||
if (isPresent()) {
|
||||
// always allow a field with a value to display
|
||||
return true;
|
||||
}
|
||||
var latest = context.hasEntity(entity.id); // check the most current copy of the entity
|
||||
if (!latest) return true;
|
||||
|
||||
var prerequisiteTag = field.prerequisiteTag;
|
||||
if (prerequisiteTag && prerequisiteTag.key && field.entityID && context.hasEntity(field.entityID)) {
|
||||
var value = context.entity(field.entityID).tags[prerequisiteTag.key];
|
||||
if (value) {
|
||||
if (prerequisiteTag.valueNot) {
|
||||
return prerequisiteTag.valueNot !== value;
|
||||
}
|
||||
if (prerequisiteTag.value) {
|
||||
return prerequisiteTag.value === value;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
var require = field.prerequisiteTag;
|
||||
if (require && require.key) {
|
||||
var value = latest.tags[require.key];
|
||||
if (!value) return false;
|
||||
|
||||
if (require.valueNot) {
|
||||
return require.valueNot !== value;
|
||||
}
|
||||
if (require.value) {
|
||||
return require.value === value;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
field.focus = function() {
|
||||
if (field.impl) {
|
||||
field.impl.focus();
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
} from 'd3-selection';
|
||||
|
||||
import marked from 'marked';
|
||||
import { t } from '../util/locale';
|
||||
import { t, textDirection } from '../util/locale';
|
||||
import { svgIcon } from '../svg';
|
||||
import { icon } from './intro/helper';
|
||||
|
||||
@@ -197,7 +197,7 @@ export function uiFieldHelp(context, fieldName) {
|
||||
|
||||
titleEnter
|
||||
.append('h2')
|
||||
.attr('class', 'fl')
|
||||
.attr('class', ((textDirection === 'rtl') ? 'fr' : 'fl'))
|
||||
.text(t('help.field.' + fieldName + '.title'));
|
||||
|
||||
titleEnter
|
||||
|
||||
+12
-18
@@ -7,25 +7,13 @@ import { utilGetSetValue, utilNoAuto } from '../util';
|
||||
|
||||
export function uiFormFields(context) {
|
||||
var moreCombo = uiCombobox(context, 'more-fields').minItems(1);
|
||||
var _fieldsArr = [];
|
||||
var _state = '';
|
||||
var _fieldsArr;
|
||||
var _klass = '';
|
||||
|
||||
|
||||
function formFields(selection, klass) {
|
||||
render(selection, klass);
|
||||
}
|
||||
|
||||
|
||||
formFields.tagsChanged = function() {};
|
||||
|
||||
function render(selection, klass) {
|
||||
|
||||
formFields.tagsChanged = function() {
|
||||
render(selection, klass);
|
||||
};
|
||||
|
||||
function formFields(selection) {
|
||||
var allowedFields = _fieldsArr.filter(function(field) { return field.isAllowed(); });
|
||||
|
||||
var shown = allowedFields.filter(function(field) { return field.isShown(); });
|
||||
var notShown = allowedFields.filter(function(field) { return !field.isShown(); });
|
||||
|
||||
@@ -34,7 +22,7 @@ export function uiFormFields(context) {
|
||||
|
||||
container = container.enter()
|
||||
.append('div')
|
||||
.attr('class', 'form-fields-container ' + (klass || ''))
|
||||
.attr('class', 'form-fields-container ' + (_klass || ''))
|
||||
.merge(container);
|
||||
|
||||
|
||||
@@ -111,7 +99,7 @@ export function uiFormFields(context) {
|
||||
.on('accept', function (d) {
|
||||
var field = d.field;
|
||||
field.show();
|
||||
render(selection);
|
||||
selection.call(formFields); // rerender
|
||||
if (field.type !== 'semiCombo' && field.type !== 'multiCombo') {
|
||||
field.focus();
|
||||
}
|
||||
@@ -122,7 +110,7 @@ export function uiFormFields(context) {
|
||||
|
||||
formFields.fieldsArr = function(val) {
|
||||
if (!arguments.length) return _fieldsArr;
|
||||
_fieldsArr = val;
|
||||
_fieldsArr = val || [];
|
||||
return formFields;
|
||||
};
|
||||
|
||||
@@ -132,6 +120,12 @@ export function uiFormFields(context) {
|
||||
return formFields;
|
||||
};
|
||||
|
||||
formFields.klass = function(val) {
|
||||
if (!arguments.length) return _klass;
|
||||
_klass = val;
|
||||
return formFields;
|
||||
};
|
||||
|
||||
|
||||
return formFields;
|
||||
}
|
||||
|
||||
@@ -181,6 +181,13 @@ export function uiHelp(context) {
|
||||
'using',
|
||||
'tracing',
|
||||
'upload'
|
||||
]],
|
||||
['qa', [
|
||||
'intro',
|
||||
'tools_h',
|
||||
'tools',
|
||||
'issues_h',
|
||||
'issues'
|
||||
]]
|
||||
];
|
||||
|
||||
@@ -228,6 +235,8 @@ export function uiHelp(context) {
|
||||
'help.imagery.offsets_h': 3,
|
||||
'help.streetlevel.using_h': 3,
|
||||
'help.gps.using_h': 3,
|
||||
'help.qa.tools_h': 3,
|
||||
'help.qa.issues_h': 3
|
||||
};
|
||||
|
||||
var replacements = {
|
||||
|
||||
@@ -30,6 +30,9 @@ export { uiGeolocate } from './geolocate';
|
||||
export { uiHelp } from './help';
|
||||
export { uiInfo } from './info';
|
||||
export { uiInspector } from './inspector';
|
||||
export { uiKeepRightDetails } from './keepRight_details';
|
||||
export { uiKeepRightEditor } from './keepRight_editor';
|
||||
export { uiKeepRightHeader } from './keepRight_header';
|
||||
export { uiLasso } from './lasso';
|
||||
export { uiLoading } from './loading';
|
||||
export { uiMapData } from './map_data';
|
||||
@@ -44,6 +47,7 @@ export { uiNoteReport } from './note_report';
|
||||
export { uiPresetEditor } from './preset_editor';
|
||||
export { uiPresetIcon } from './preset_icon';
|
||||
export { uiPresetList } from './preset_list';
|
||||
export { uiQuickLinks } from './quick_links';
|
||||
export { uiRadialMenu } from './radial_menu';
|
||||
export { uiRawMemberEditor } from './raw_member_editor';
|
||||
export { uiRawMembershipEditor } from './raw_membership_editor';
|
||||
@@ -64,4 +68,5 @@ export { uiTooltipHtml } from './tooltipHtml';
|
||||
export { uiUndoRedo } from './undo_redo';
|
||||
export { uiVersion } from './version';
|
||||
export { uiViewOnOSM } from './view_on_osm';
|
||||
export { uiViewOnKeepRight } from './view_on_keepRight';
|
||||
export { uiZoom } from './zoom';
|
||||
|
||||
+1
-1
@@ -306,7 +306,7 @@ export function uiInit(context) {
|
||||
var panPixels = 80;
|
||||
context.keybinding()
|
||||
.on('⌫', function() { d3_event.preventDefault(); })
|
||||
.on(t('sidebar.key'), ui.sidebar.toggle)
|
||||
.on([t('sidebar.key'), '`', '²'], ui.sidebar.toggle) // #5663 - common QWERTY, AZERTY
|
||||
.on('←', pan([panPixels, 0]))
|
||||
.on('↑', pan([0, panPixels]))
|
||||
.on('→', pan([-panPixels, 0]))
|
||||
|
||||
@@ -92,9 +92,9 @@ export function uiInspector(context) {
|
||||
inspector.showList = function() {};
|
||||
inspector.setPreset = function() {};
|
||||
|
||||
inspector.state = function(_) {
|
||||
inspector.state = function(val) {
|
||||
if (!arguments.length) return _state;
|
||||
_state = _;
|
||||
_state = val;
|
||||
entityEditor.state(_state);
|
||||
|
||||
// remove any old field help overlay that might have gotten attached to the inspector
|
||||
@@ -104,16 +104,16 @@ export function uiInspector(context) {
|
||||
};
|
||||
|
||||
|
||||
inspector.entityID = function(_) {
|
||||
inspector.entityID = function(val) {
|
||||
if (!arguments.length) return _entityID;
|
||||
_entityID = _;
|
||||
_entityID = val;
|
||||
return inspector;
|
||||
};
|
||||
|
||||
|
||||
inspector.newFeature = function(_) {
|
||||
inspector.newFeature = function(val) {
|
||||
if (!arguments.length) return _newFeature;
|
||||
_newFeature = _;
|
||||
_newFeature = val;
|
||||
return inspector;
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import {
|
||||
event as d3_event,
|
||||
select as d3_select
|
||||
} from 'd3-selection';
|
||||
|
||||
import { dataEn } from '../../data';
|
||||
import { modeSelect } from '../modes';
|
||||
import { t } from '../util/locale';
|
||||
import { utilDisplayName, utilEntityOrMemberSelector, utilEntityRoot } from '../util';
|
||||
|
||||
|
||||
export function uiKeepRightDetails(context) {
|
||||
var _error;
|
||||
|
||||
|
||||
function errorDetail(d) {
|
||||
var unknown = t('inspector.unknown');
|
||||
|
||||
if (!d) return unknown;
|
||||
var errorType = d.error_type;
|
||||
var parentErrorType = d.parent_error_type;
|
||||
|
||||
var et = dataEn.QA.keepRight.errorTypes[errorType];
|
||||
var pt = dataEn.QA.keepRight.errorTypes[parentErrorType];
|
||||
|
||||
var detail;
|
||||
if (et && et.description) {
|
||||
detail = t('QA.keepRight.errorTypes.' + errorType + '.description', d.replacements);
|
||||
} else if (pt && pt.description) {
|
||||
detail = t('QA.keepRight.errorTypes.' + parentErrorType + '.description', d.replacements);
|
||||
} else {
|
||||
detail = unknown;
|
||||
}
|
||||
|
||||
return detail;
|
||||
}
|
||||
|
||||
|
||||
function keepRightDetails(selection) {
|
||||
var details = selection.selectAll('.kr_error-details')
|
||||
.data(
|
||||
(_error ? [_error] : []),
|
||||
function(d) { return d.id + '-' + (d.status || 0); }
|
||||
);
|
||||
|
||||
details.exit()
|
||||
.remove();
|
||||
|
||||
var detailsEnter = details.enter()
|
||||
.append('div')
|
||||
.attr('class', 'kr_error-details kr_error-details-container');
|
||||
|
||||
|
||||
// description
|
||||
var descriptionEnter = detailsEnter
|
||||
.append('div')
|
||||
.attr('class', 'kr_error-details-description');
|
||||
|
||||
descriptionEnter
|
||||
.append('h4')
|
||||
.text(function() { return t('QA.keepRight.detail_description'); });
|
||||
|
||||
descriptionEnter
|
||||
.append('div')
|
||||
.attr('class', 'kr_error-details-description-text')
|
||||
.html(errorDetail);
|
||||
|
||||
// If there are entity links in the error message..
|
||||
descriptionEnter.selectAll('.kr_error_entity_link, .kr_error_object_link')
|
||||
.each(function() {
|
||||
var link = d3_select(this);
|
||||
var isObjectLink = link.classed('kr_error_object_link');
|
||||
var entityID = isObjectLink ?
|
||||
(utilEntityRoot(_error.object_type) + _error.object_id)
|
||||
: this.textContent;
|
||||
var entity = context.hasEntity(entityID);
|
||||
|
||||
// Add click handler
|
||||
link
|
||||
.on('mouseover', function() {
|
||||
context.surface().selectAll(utilEntityOrMemberSelector([entityID], context.graph()))
|
||||
.classed('hover', true);
|
||||
})
|
||||
.on('mouseout', function() {
|
||||
context.surface().selectAll('.hover')
|
||||
.classed('hover', false);
|
||||
})
|
||||
.on('click', function() {
|
||||
d3_event.preventDefault();
|
||||
var osmlayer = context.layers().layer('osm');
|
||||
if (!osmlayer.enabled()) {
|
||||
osmlayer.enabled(true);
|
||||
}
|
||||
|
||||
context.map().centerZoom(_error.loc, 20);
|
||||
|
||||
if (entity) {
|
||||
context.enter(modeSelect(context, [entityID]));
|
||||
} else {
|
||||
context.loadEntity(entityID, function() {
|
||||
context.enter(modeSelect(context, [entityID]));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Replace with friendly name if possible
|
||||
// (The entity may not yet be loaded into the graph)
|
||||
if (entity) {
|
||||
var name = utilDisplayName(entity); // try to use common name
|
||||
|
||||
if (!name && !isObjectLink) {
|
||||
var preset = context.presets().match(entity, context.graph());
|
||||
name = preset && !preset.isFallback() && preset.name(); // fallback to preset name
|
||||
}
|
||||
|
||||
if (name) {
|
||||
this.innerText = name;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
keepRightDetails.error = function(val) {
|
||||
if (!arguments.length) return _error;
|
||||
_error = val;
|
||||
return keepRightDetails;
|
||||
};
|
||||
|
||||
|
||||
return keepRightDetails;
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
import { dispatch as d3_dispatch } from 'd3-dispatch';
|
||||
import { select as d3_select } from 'd3-selection';
|
||||
|
||||
import { t } from '../util/locale';
|
||||
import { services } from '../services';
|
||||
import { modeBrowse } from '../modes';
|
||||
import { svgIcon } from '../svg';
|
||||
|
||||
import {
|
||||
uiKeepRightDetails,
|
||||
uiKeepRightHeader,
|
||||
uiQuickLinks,
|
||||
uiTooltipHtml,
|
||||
uiViewOnKeepRight
|
||||
} from './index';
|
||||
|
||||
import { utilNoAuto, utilRebind } from '../util';
|
||||
|
||||
|
||||
export function uiKeepRightEditor(context) {
|
||||
var dispatch = d3_dispatch('change');
|
||||
var keepRightDetails = uiKeepRightDetails(context);
|
||||
var keepRightHeader = uiKeepRightHeader(context);
|
||||
var quickLinks = uiQuickLinks();
|
||||
|
||||
var _error;
|
||||
|
||||
|
||||
function keepRightEditor(selection) {
|
||||
// quick links
|
||||
var choices = [{
|
||||
id: 'zoom_to',
|
||||
label: 'inspector.zoom_to.title',
|
||||
tooltip: function() {
|
||||
return uiTooltipHtml(t('inspector.zoom_to.tooltip_issue'), t('inspector.zoom_to.key'));
|
||||
},
|
||||
click: function zoomTo() {
|
||||
context.mode().zoomToSelected();
|
||||
}
|
||||
}];
|
||||
|
||||
|
||||
var header = selection.selectAll('.header')
|
||||
.data([0]);
|
||||
|
||||
var headerEnter = header.enter()
|
||||
.append('div')
|
||||
.attr('class', 'header fillL');
|
||||
|
||||
headerEnter
|
||||
.append('button')
|
||||
.attr('class', 'fr keepRight-editor-close')
|
||||
.on('click', function() {
|
||||
context.enter(modeBrowse(context));
|
||||
})
|
||||
.call(svgIcon('#iD-icon-close'));
|
||||
|
||||
headerEnter
|
||||
.append('h3')
|
||||
.text(t('QA.keepRight.title'));
|
||||
|
||||
|
||||
var body = selection.selectAll('.body')
|
||||
.data([0]);
|
||||
|
||||
body = body.enter()
|
||||
.append('div')
|
||||
.attr('class', 'body')
|
||||
.merge(body);
|
||||
|
||||
var editor = body.selectAll('.keepRight-editor')
|
||||
.data([0]);
|
||||
|
||||
editor.enter()
|
||||
.append('div')
|
||||
.attr('class', 'modal-section keepRight-editor')
|
||||
.merge(editor)
|
||||
.call(keepRightHeader.error(_error))
|
||||
.call(quickLinks.choices(choices))
|
||||
.call(keepRightDetails.error(_error))
|
||||
.call(keepRightSaveSection);
|
||||
|
||||
|
||||
var footer = selection.selectAll('.footer')
|
||||
.data([0]);
|
||||
|
||||
footer.enter()
|
||||
.append('div')
|
||||
.attr('class', 'footer')
|
||||
.merge(footer)
|
||||
.call(uiViewOnKeepRight(context).what(_error));
|
||||
}
|
||||
|
||||
|
||||
function keepRightSaveSection(selection) {
|
||||
var isSelected = (_error && _error.id === context.selectedErrorID());
|
||||
var isShown = (_error && (isSelected || _error.newComment || _error.comment));
|
||||
var saveSection = selection.selectAll('.error-save')
|
||||
.data(
|
||||
(isShown ? [_error] : []),
|
||||
function(d) { return d.id + '-' + (d.status || 0); }
|
||||
);
|
||||
|
||||
// exit
|
||||
saveSection.exit()
|
||||
.remove();
|
||||
|
||||
// enter
|
||||
var saveSectionEnter = saveSection.enter()
|
||||
.append('div')
|
||||
.attr('class', 'keepRight-save save-section cf');
|
||||
|
||||
saveSectionEnter
|
||||
.append('h4')
|
||||
.attr('class', '.error-save-header')
|
||||
.text(t('QA.keepRight.comment'));
|
||||
|
||||
saveSectionEnter
|
||||
.append('textarea')
|
||||
.attr('class', 'new-comment-input')
|
||||
.attr('placeholder', t('QA.keepRight.comment_placeholder'))
|
||||
.attr('maxlength', 1000)
|
||||
.property('value', function(d) { return d.newComment || d.comment; })
|
||||
.call(utilNoAuto)
|
||||
.on('input', changeInput)
|
||||
.on('blur', changeInput);
|
||||
|
||||
// update
|
||||
saveSection = saveSectionEnter
|
||||
.merge(saveSection)
|
||||
.call(keepRightSaveButtons);
|
||||
|
||||
|
||||
function changeInput() {
|
||||
var input = d3_select(this);
|
||||
var val = input.property('value').trim();
|
||||
|
||||
if (val === _error.comment) {
|
||||
val = undefined;
|
||||
}
|
||||
|
||||
// store the unsaved comment with the error itself
|
||||
_error = _error.update({ newComment: val });
|
||||
|
||||
var keepRight = services.keepRight;
|
||||
if (keepRight) {
|
||||
keepRight.replaceError(_error); // update keepright cache
|
||||
}
|
||||
|
||||
saveSection
|
||||
.call(keepRightSaveButtons);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function keepRightSaveButtons(selection) {
|
||||
var isSelected = (_error && _error.id === context.selectedErrorID());
|
||||
var buttonSection = selection.selectAll('.buttons')
|
||||
.data((isSelected ? [_error] : []), function(d) { return d.status + d.id; });
|
||||
|
||||
// exit
|
||||
buttonSection.exit()
|
||||
.remove();
|
||||
|
||||
// enter
|
||||
var buttonEnter = buttonSection.enter()
|
||||
.append('div')
|
||||
.attr('class', 'buttons');
|
||||
|
||||
buttonEnter
|
||||
.append('button')
|
||||
.attr('class', 'button comment-button action')
|
||||
.text(t('QA.keepRight.save_comment'));
|
||||
|
||||
buttonEnter
|
||||
.append('button')
|
||||
.attr('class', 'button close-button action');
|
||||
|
||||
buttonEnter
|
||||
.append('button')
|
||||
.attr('class', 'button ignore-button action');
|
||||
|
||||
|
||||
// update
|
||||
buttonSection = buttonSection
|
||||
.merge(buttonEnter);
|
||||
|
||||
buttonSection.select('.comment-button') // select and propagate data
|
||||
.attr('disabled', function(d) {
|
||||
return d.newComment === undefined ? true : null;
|
||||
})
|
||||
.on('click.comment', function(d) {
|
||||
this.blur(); // avoid keeping focus on the button - #4641
|
||||
var keepRight = services.keepRight;
|
||||
if (keepRight) {
|
||||
keepRight.postKeepRightUpdate(d, function(err, error) {
|
||||
dispatch.call('change', error);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
buttonSection.select('.close-button') // select and propagate data
|
||||
.text(function(d) {
|
||||
var andComment = (d.newComment !== undefined ? '_comment' : '');
|
||||
return t('QA.keepRight.close' + andComment);
|
||||
})
|
||||
.on('click.close', function(d) {
|
||||
this.blur(); // avoid keeping focus on the button - #4641
|
||||
var keepRight = services.keepRight;
|
||||
if (keepRight) {
|
||||
d.state = 'ignore_t'; // ignore temporarily (error fixed)
|
||||
keepRight.postKeepRightUpdate(d, function(err, error) {
|
||||
dispatch.call('change', error);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
buttonSection.select('.ignore-button') // select and propagate data
|
||||
.text(function(d) {
|
||||
var andComment = (d.newComment !== undefined ? '_comment' : '');
|
||||
return t('QA.keepRight.ignore' + andComment);
|
||||
})
|
||||
.on('click.ignore', function(d) {
|
||||
this.blur(); // avoid keeping focus on the button - #4641
|
||||
var keepRight = services.keepRight;
|
||||
if (keepRight) {
|
||||
d.state = 'ignore'; // ignore permanently (false positive)
|
||||
keepRight.postKeepRightUpdate(d, function(err, error) {
|
||||
dispatch.call('change', error);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
keepRightEditor.error = function(val) {
|
||||
if (!arguments.length) return _error;
|
||||
_error = val;
|
||||
return keepRightEditor;
|
||||
};
|
||||
|
||||
|
||||
return utilRebind(keepRightEditor, dispatch, 'on');
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { dataEn } from '../../data';
|
||||
import { svgIcon } from '../svg';
|
||||
import { t } from '../util/locale';
|
||||
|
||||
|
||||
export function uiKeepRightHeader() {
|
||||
var _error;
|
||||
|
||||
|
||||
function errorTitle(d) {
|
||||
var unknown = t('inspector.unknown');
|
||||
|
||||
if (!d) return unknown;
|
||||
var errorType = d.error_type;
|
||||
var parentErrorType = d.parent_error_type;
|
||||
|
||||
var et = dataEn.QA.keepRight.errorTypes[errorType];
|
||||
var pt = dataEn.QA.keepRight.errorTypes[parentErrorType];
|
||||
|
||||
if (et && et.title) {
|
||||
return t('QA.keepRight.errorTypes.' + errorType + '.title');
|
||||
} else if (pt && pt.title) {
|
||||
return t('QA.keepRight.errorTypes.' + parentErrorType + '.title');
|
||||
} else {
|
||||
return unknown;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function keepRightHeader(selection) {
|
||||
var header = selection.selectAll('.kr_error-header')
|
||||
.data(
|
||||
(_error ? [_error] : []),
|
||||
function(d) { return d.id + '-' + (d.status || 0); }
|
||||
);
|
||||
|
||||
header.exit()
|
||||
.remove();
|
||||
|
||||
var headerEnter = header.enter()
|
||||
.append('div')
|
||||
.attr('class', 'kr_error-header');
|
||||
|
||||
var iconEnter = headerEnter
|
||||
.append('div')
|
||||
.attr('class', 'kr_error-header-icon')
|
||||
.classed('new', function(d) { return d.id < 0; });
|
||||
|
||||
iconEnter
|
||||
.append('div')
|
||||
.attr('class', function(d) {
|
||||
return 'preset-icon-28 kr_error kr_error-' + d.id + ' kr_error_type_' + d.parent_error_type;
|
||||
})
|
||||
.call(svgIcon('#iD-icon-bolt', 'kr_error-fill'));
|
||||
|
||||
headerEnter
|
||||
.append('div')
|
||||
.attr('class', 'kr_error-header-label')
|
||||
.text(errorTitle);
|
||||
}
|
||||
|
||||
|
||||
keepRightHeader.error = function(val) {
|
||||
if (!arguments.length) return _error;
|
||||
_error = val;
|
||||
return keepRightHeader;
|
||||
};
|
||||
|
||||
|
||||
return keepRightHeader;
|
||||
}
|
||||
+79
-5
@@ -30,6 +30,7 @@ export function uiMapData(context) {
|
||||
var _dataLayerContainer = d3_select(null);
|
||||
var _fillList = d3_select(null);
|
||||
var _featureList = d3_select(null);
|
||||
var _QAList = d3_select(null);
|
||||
|
||||
|
||||
function showsFeature(d) {
|
||||
@@ -38,6 +39,7 @@ export function uiMapData(context) {
|
||||
|
||||
|
||||
function autoHiddenFeature(d) {
|
||||
if (d.type === 'kr_error') return context.errors().autoHidden(d);
|
||||
return context.features().autoHidden(d);
|
||||
}
|
||||
|
||||
@@ -48,6 +50,22 @@ export function uiMapData(context) {
|
||||
}
|
||||
|
||||
|
||||
function showsQA(d) {
|
||||
var QAKeys = [d];
|
||||
var QALayers = layers.all().filter(function(obj) { return QAKeys.indexOf(obj.id) !== -1; });
|
||||
var data = QALayers.filter(function(obj) { return obj.layer.supported(); });
|
||||
|
||||
function layerSupported(d) {
|
||||
return d.layer && d.layer.supported();
|
||||
}
|
||||
function layerEnabled(d) {
|
||||
return layerSupported(d) && d.layer.enabled();
|
||||
}
|
||||
|
||||
return layerEnabled(data[0]);
|
||||
}
|
||||
|
||||
|
||||
function showsFill(d) {
|
||||
return _fillSelected === d;
|
||||
}
|
||||
@@ -207,6 +225,58 @@ export function uiMapData(context) {
|
||||
}
|
||||
|
||||
|
||||
function drawQAItems(selection) {
|
||||
var qaKeys = ['keepRight'];
|
||||
var qaLayers = layers.all().filter(function(obj) { return qaKeys.indexOf(obj.id) !== -1; });
|
||||
|
||||
var ul = selection
|
||||
.selectAll('.layer-list-qa')
|
||||
.data([0]);
|
||||
|
||||
ul = ul.enter()
|
||||
.append('ul')
|
||||
.attr('class', 'layer-list layer-list-qa')
|
||||
.merge(ul);
|
||||
|
||||
var li = ul.selectAll('.list-item')
|
||||
.data(qaLayers);
|
||||
|
||||
li.exit()
|
||||
.remove();
|
||||
|
||||
var liEnter = li.enter()
|
||||
.append('li')
|
||||
.attr('class', function(d) { return 'list-item list-item-' + d.id; });
|
||||
|
||||
var labelEnter = liEnter
|
||||
.append('label')
|
||||
.each(function(d) {
|
||||
d3_select(this)
|
||||
.call(tooltip()
|
||||
.title(t('map_data.layers.' + d.id + '.tooltip'))
|
||||
.placement('bottom')
|
||||
);
|
||||
});
|
||||
|
||||
labelEnter
|
||||
.append('input')
|
||||
.attr('type', 'checkbox')
|
||||
.on('change', function(d) { toggleLayer(d.id); });
|
||||
|
||||
labelEnter
|
||||
.append('span')
|
||||
.text(function(d) { return t('map_data.layers.' + d.id + '.title'); });
|
||||
|
||||
|
||||
// Update
|
||||
li
|
||||
.merge(liEnter)
|
||||
.classed('active', function (d) { return d.layer.enabled(); })
|
||||
.selectAll('input')
|
||||
.property('checked', function (d) { return d.layer.enabled(); });
|
||||
}
|
||||
|
||||
|
||||
// Beta feature - sample vector layers to support Detroit Mapping Challenge
|
||||
// https://github.com/osmus/detroit-mapping-challenge
|
||||
function drawVectorItems(selection) {
|
||||
@@ -427,10 +497,9 @@ export function uiMapData(context) {
|
||||
.call(tooltip()
|
||||
.html(true)
|
||||
.title(function(d) {
|
||||
var tip = t(name + '.' + d + '.tooltip'),
|
||||
key = (d === 'wireframe' ? t('area_fill.wireframe.key') : null);
|
||||
|
||||
if (name === 'feature' && autoHiddenFeature(d)) {
|
||||
var tip = t(name + '.' + d + '.tooltip');
|
||||
var key = (d === 'wireframe' ? t('area_fill.wireframe.key') : null);
|
||||
if ((name === 'feature' || name === 'keepRight') && autoHiddenFeature(d)) {
|
||||
var msg = showsLayer('osm') ? t('map_data.autohidden') : t('map_data.osmhidden');
|
||||
tip += '<div>' + msg + '</div>';
|
||||
}
|
||||
@@ -461,7 +530,7 @@ export function uiMapData(context) {
|
||||
.selectAll('input')
|
||||
.property('checked', active)
|
||||
.property('indeterminate', function(d) {
|
||||
return (name === 'feature' && autoHiddenFeature(d));
|
||||
return ((name === 'feature' || name === 'keepRight') && autoHiddenFeature(d));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -502,6 +571,7 @@ export function uiMapData(context) {
|
||||
function update() {
|
||||
_dataLayerContainer
|
||||
.call(drawOsmItems)
|
||||
.call(drawQAItems)
|
||||
.call(drawPhotoItems)
|
||||
.call(drawCustomDataItems)
|
||||
.call(drawVectorItems); // Beta - Detroit mapping challenge
|
||||
@@ -511,6 +581,9 @@ export function uiMapData(context) {
|
||||
|
||||
_featureList
|
||||
.call(drawListItems, features, 'checkbox', 'feature', clickFeature, showsFeature);
|
||||
|
||||
_QAList
|
||||
.call(drawListItems, ['keep-right'], 'checkbox', 'QA', function(d) { toggleLayer(d); }, showsQA);
|
||||
}
|
||||
|
||||
|
||||
@@ -611,6 +684,7 @@ export function uiMapData(context) {
|
||||
.append('div')
|
||||
.attr('class', 'pane-content');
|
||||
|
||||
|
||||
// data layers
|
||||
content
|
||||
.append('div')
|
||||
|
||||
@@ -110,9 +110,9 @@ export function uiNoteComments() {
|
||||
}
|
||||
|
||||
|
||||
noteComments.note = function(_) {
|
||||
noteComments.note = function(val) {
|
||||
if (!arguments.length) return _note;
|
||||
_note = _;
|
||||
_note = val;
|
||||
return noteComments;
|
||||
};
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ import {
|
||||
uiNoteComments,
|
||||
uiNoteHeader,
|
||||
uiNoteReport,
|
||||
uiQuickLinks,
|
||||
uiTooltipHtml,
|
||||
uiViewOnOSM,
|
||||
} from './index';
|
||||
|
||||
@@ -27,6 +29,7 @@ import {
|
||||
|
||||
export function uiNoteEditor(context) {
|
||||
var dispatch = d3_dispatch('change');
|
||||
var quickLinks = uiQuickLinks();
|
||||
var noteComments = uiNoteComments();
|
||||
var noteHeader = uiNoteHeader();
|
||||
|
||||
@@ -37,6 +40,19 @@ export function uiNoteEditor(context) {
|
||||
|
||||
|
||||
function noteEditor(selection) {
|
||||
// quick links
|
||||
var choices = [{
|
||||
id: 'zoom_to',
|
||||
label: 'inspector.zoom_to.title',
|
||||
tooltip: function() {
|
||||
return uiTooltipHtml(t('inspector.zoom_to.tooltip_note'), t('inspector.zoom_to.key'));
|
||||
},
|
||||
click: function zoomTo() {
|
||||
context.mode().zoomToSelected();
|
||||
}
|
||||
}];
|
||||
|
||||
|
||||
var header = selection.selectAll('.header')
|
||||
.data([0]);
|
||||
|
||||
@@ -73,6 +89,7 @@ export function uiNoteEditor(context) {
|
||||
.attr('class', 'modal-section note-editor')
|
||||
.merge(editor)
|
||||
.call(noteHeader.note(_note))
|
||||
.call(quickLinks.choices(choices))
|
||||
.call(noteComments.note(_note))
|
||||
.call(noteSaveSection);
|
||||
|
||||
@@ -155,7 +172,7 @@ export function uiNoteEditor(context) {
|
||||
|
||||
noteSaveEnter
|
||||
.append('textarea')
|
||||
.attr('id', 'new-comment-input')
|
||||
.attr('class', 'new-comment-input')
|
||||
.attr('placeholder', t('note.inputPlaceholder'))
|
||||
.attr('maxlength', 1000)
|
||||
.property('value', function(d) { return d.newComment; })
|
||||
@@ -425,9 +442,9 @@ export function uiNoteEditor(context) {
|
||||
}
|
||||
|
||||
|
||||
noteEditor.note = function(_) {
|
||||
noteEditor.note = function(val) {
|
||||
if (!arguments.length) return _note;
|
||||
_note = _;
|
||||
_note = val;
|
||||
return noteEditor;
|
||||
};
|
||||
|
||||
|
||||
@@ -49,9 +49,9 @@ export function uiNoteHeader() {
|
||||
}
|
||||
|
||||
|
||||
noteHeader.note = function(_) {
|
||||
noteHeader.note = function(val) {
|
||||
if (!arguments.length) return _note;
|
||||
_note = _;
|
||||
_note = val;
|
||||
return noteHeader;
|
||||
};
|
||||
|
||||
|
||||
+10
-13
@@ -1,23 +1,20 @@
|
||||
import { t } from '../util/locale';
|
||||
import { osmNote } from '../osm';
|
||||
import { services } from '../services';
|
||||
import { svgIcon } from '../svg';
|
||||
import {
|
||||
osmNote
|
||||
} from '../osm';
|
||||
|
||||
|
||||
export function uiNoteReport() {
|
||||
var _note;
|
||||
var url = 'https://www.openstreetmap.org/reports/new?reportable_id=';
|
||||
|
||||
function noteReport(selection) {
|
||||
var url;
|
||||
if (services.osm && (_note instanceof osmNote) && (!_note.isNew())) {
|
||||
url = services.osm.noteReportURL(_note);
|
||||
}
|
||||
|
||||
if (!(_note instanceof osmNote)) return;
|
||||
|
||||
url += _note.id + '&reportable_type=Note';
|
||||
|
||||
var data = ((!_note || _note.isNew()) ? [] : [_note]);
|
||||
var link = selection.selectAll('.note-report')
|
||||
.data(data, function(d) { return d.id; });
|
||||
.data(url ? [url] : []);
|
||||
|
||||
// exit
|
||||
link.exit()
|
||||
@@ -28,7 +25,7 @@ export function uiNoteReport() {
|
||||
.append('a')
|
||||
.attr('class', 'note-report')
|
||||
.attr('target', '_blank')
|
||||
.attr('href', url)
|
||||
.attr('href', function(d) { return d; })
|
||||
.call(svgIcon('#iD-icon-out-link', 'inline'));
|
||||
|
||||
linkEnter
|
||||
@@ -37,9 +34,9 @@ export function uiNoteReport() {
|
||||
}
|
||||
|
||||
|
||||
noteReport.note = function(_) {
|
||||
noteReport.note = function(val) {
|
||||
if (!arguments.length) return _note;
|
||||
_note = _;
|
||||
_note = val;
|
||||
return noteReport;
|
||||
};
|
||||
|
||||
|
||||
@@ -87,8 +87,9 @@ export function uiPresetEditor(context) {
|
||||
selection
|
||||
.call(formFields
|
||||
.fieldsArr(_fieldsArr)
|
||||
.state(_state),
|
||||
'inspector-inner fillL3');
|
||||
.state(_state)
|
||||
.klass('inspector-inner fillL3')
|
||||
);
|
||||
|
||||
|
||||
selection.selectAll('.wrap-form-field input')
|
||||
@@ -120,7 +121,6 @@ export function uiPresetEditor(context) {
|
||||
presetEditor.tags = function(val) {
|
||||
if (!arguments.length) return _tags;
|
||||
_tags = val;
|
||||
formFields.tagsChanged();
|
||||
// Don't reset _fieldsArr here.
|
||||
return presetEditor;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import {
|
||||
event as d3_event,
|
||||
select as d3_select
|
||||
} from 'd3-selection';
|
||||
|
||||
import { t } from '../util/locale';
|
||||
import { tooltip } from '../util/tooltip';
|
||||
|
||||
|
||||
export function uiQuickLinks() {
|
||||
var _choices = [];
|
||||
|
||||
|
||||
function quickLinks(selection) {
|
||||
var container = selection.selectAll('.quick-links')
|
||||
.data([0]);
|
||||
|
||||
container = container.enter()
|
||||
.append('div')
|
||||
.attr('class', 'quick-links')
|
||||
.merge(container);
|
||||
|
||||
var items = container.selectAll('.quick-link')
|
||||
.data(_choices, function(d) { return d.id; });
|
||||
|
||||
items.exit()
|
||||
.remove();
|
||||
|
||||
items.enter()
|
||||
.append('a')
|
||||
.attr('class', function(d) { return 'quick-link quick-link-' + d.id; })
|
||||
.attr('href', '#')
|
||||
.text(function(d) { return t(d.label); })
|
||||
.each(function(d) {
|
||||
if (typeof d.tooltip !== 'function') return;
|
||||
d3_select(this)
|
||||
.call(tooltip().html(true).title(d.tooltip).placement('bottom'));
|
||||
})
|
||||
.on('click', function(d) {
|
||||
if (typeof d.click !== 'function') return;
|
||||
d3_event.preventDefault();
|
||||
d.click(d);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// val should be an array of choices like:
|
||||
// [{
|
||||
// id: 'link-id',
|
||||
// label: 'translation.key',
|
||||
// tooltip: function(d),
|
||||
// click: function(d)
|
||||
// }, ..]
|
||||
quickLinks.choices = function(val) {
|
||||
if (!arguments.length) return _choices;
|
||||
_choices = val;
|
||||
return quickLinks;
|
||||
};
|
||||
|
||||
|
||||
return quickLinks;
|
||||
}
|
||||
+11
-7
@@ -1,3 +1,5 @@
|
||||
import _uniq from 'lodash-es/uniq';
|
||||
|
||||
import {
|
||||
select as d3_select,
|
||||
selectAll as d3_selectAll
|
||||
@@ -19,7 +21,7 @@ export function uiShortcuts(context) {
|
||||
|
||||
|
||||
context.keybinding()
|
||||
.on(t('shortcuts.toggle.key'), function () {
|
||||
.on([t('shortcuts.toggle.key'), '?'], function () {
|
||||
if (d3_selectAll('.modal-shortcuts').size()) { // already showing
|
||||
if (_modalSelection) {
|
||||
_modalSelection.close();
|
||||
@@ -179,7 +181,12 @@ export function uiShortcuts(context) {
|
||||
arr = ['F11'];
|
||||
}
|
||||
|
||||
return arr.map(function(s) {
|
||||
// replace translations
|
||||
arr = arr.map(function(s) {
|
||||
return uiCmd.display(s.indexOf('.') !== -1 ? t(s) : s);
|
||||
});
|
||||
|
||||
return _uniq(arr).map(function(s) {
|
||||
return {
|
||||
shortcut: s,
|
||||
separator: d.separator
|
||||
@@ -191,17 +198,14 @@ export function uiShortcuts(context) {
|
||||
var selection = d3_select(this);
|
||||
var click = d.shortcut.toLowerCase().match(/(.*).click/);
|
||||
|
||||
if (click && click[1]) {
|
||||
if (click && click[1]) { // replace "left_click", "right_click" with mouse icon
|
||||
selection
|
||||
.call(svgIcon('#iD-walkthrough-mouse', 'mouseclick', click[1]));
|
||||
} else {
|
||||
selection
|
||||
.append('kbd')
|
||||
.attr('class', 'shortcut')
|
||||
.text(function (d) {
|
||||
var key = d.shortcut;
|
||||
return key.indexOf('.') !== -1 ? uiCmd.display(t(key)) : uiCmd.display(key);
|
||||
});
|
||||
.text(function (d) { return d.shortcut; });
|
||||
}
|
||||
|
||||
if (i < nodes.length - 1) {
|
||||
|
||||
+30
-13
@@ -9,18 +9,9 @@ import {
|
||||
selectAll as d3_selectAll
|
||||
} from 'd3-selection';
|
||||
|
||||
import {
|
||||
osmEntity,
|
||||
osmNote
|
||||
} from '../osm';
|
||||
|
||||
import {
|
||||
uiDataEditor,
|
||||
uiFeatureList,
|
||||
uiInspector,
|
||||
uiNoteEditor
|
||||
} from './index';
|
||||
|
||||
import { osmEntity, osmNote, krError } from '../osm';
|
||||
import { services } from '../services';
|
||||
import { uiDataEditor, uiFeatureList, uiInspector, uiNoteEditor, uiKeepRightEditor } from './index';
|
||||
import { textDirection } from '../util/locale';
|
||||
|
||||
|
||||
@@ -28,9 +19,11 @@ export function uiSidebar(context) {
|
||||
var inspector = uiInspector(context);
|
||||
var dataEditor = uiDataEditor(context);
|
||||
var noteEditor = uiNoteEditor(context);
|
||||
var keepRightEditor = uiKeepRightEditor(context);
|
||||
var _current;
|
||||
var _wasData = false;
|
||||
var _wasNote = false;
|
||||
var _wasKRError = false;
|
||||
|
||||
|
||||
function sidebar(selection) {
|
||||
@@ -127,12 +120,34 @@ export function uiSidebar(context) {
|
||||
if (context.mode().id === 'drag-note') return;
|
||||
_wasNote = true;
|
||||
|
||||
var osm = services.osm;
|
||||
if (osm) {
|
||||
datum = osm.getNote(datum.id); // marker may contain stale data - get latest
|
||||
}
|
||||
|
||||
sidebar
|
||||
.show(noteEditor.note(datum));
|
||||
|
||||
selection.selectAll('.sidebar-component')
|
||||
.classed('inspector-hover', true);
|
||||
|
||||
} else if (datum instanceof krError) {
|
||||
_wasKRError = true;
|
||||
|
||||
var keepRight = services.keepRight;
|
||||
if (keepRight) {
|
||||
datum = keepRight.getError(datum.id); // marker may contain stale data - get latest
|
||||
}
|
||||
|
||||
d3_selectAll('.kr_error')
|
||||
.classed('hover', function(d) { return d.id === datum.id; });
|
||||
|
||||
sidebar
|
||||
.show(keepRightEditor.error(datum));
|
||||
|
||||
selection.selectAll('.sidebar-component')
|
||||
.classed('inspector-hover', true);
|
||||
|
||||
} else if (!_current && (datum instanceof osmEntity)) {
|
||||
featureListWrap
|
||||
.classed('inspector-hidden', true);
|
||||
@@ -158,10 +173,12 @@ export function uiSidebar(context) {
|
||||
inspector
|
||||
.state('hide');
|
||||
|
||||
} else if (_wasData || _wasNote) {
|
||||
} else if (_wasData || _wasNote || _wasKRError) {
|
||||
_wasNote = false;
|
||||
_wasData = false;
|
||||
_wasKRError = false;
|
||||
d3_selectAll('.note').classed('hover', false);
|
||||
d3_selectAll('.kr_error').classed('hover', false);
|
||||
sidebar.hide();
|
||||
}
|
||||
}
|
||||
|
||||
+49
-41
@@ -1,6 +1,3 @@
|
||||
import _find from 'lodash-es/find';
|
||||
import _omit from 'lodash-es/omit';
|
||||
|
||||
import {
|
||||
event as d3_event,
|
||||
select as d3_select
|
||||
@@ -10,10 +7,11 @@ import { t } from '../util/locale';
|
||||
import { utilDetect } from '../util/detect';
|
||||
import { services } from '../services';
|
||||
import { svgIcon } from '../svg';
|
||||
import { utilQsString } from '../util';
|
||||
|
||||
|
||||
export function uiTagReference(tag) {
|
||||
var taginfo = services.taginfo;
|
||||
var wikibase = services.osmWikibase;
|
||||
var tagReference = {};
|
||||
|
||||
var _button = d3_select(null);
|
||||
@@ -21,42 +19,49 @@ export function uiTagReference(tag) {
|
||||
var _loaded;
|
||||
var _showing;
|
||||
|
||||
|
||||
/**
|
||||
* @returns {{itemTitle: String, description: String, image: String|null}|null}
|
||||
**/
|
||||
function findLocal(data) {
|
||||
var locale = utilDetect().locale.toLowerCase();
|
||||
var localized;
|
||||
var entity = data.tag || data.key;
|
||||
if (!entity) return null;
|
||||
|
||||
if (locale !== 'pt-br') { // see #3776, prefer 'pt' over 'pt-br'
|
||||
localized = _find(data, function(d) {
|
||||
return d.lang.toLowerCase() === locale;
|
||||
});
|
||||
if (localized) return localized;
|
||||
var result = {
|
||||
title: entity.title,
|
||||
description: entity.description,
|
||||
};
|
||||
|
||||
if (entity.claims) {
|
||||
var langCode = utilDetect().locale.toLowerCase();
|
||||
var url;
|
||||
var image = wikibase.claimToValue(entity, 'P4', langCode);
|
||||
if (image) {
|
||||
url = 'https://commons.wikimedia.org/w/index.php';
|
||||
} else {
|
||||
image = wikibase.claimToValue(entity, 'P28', langCode);
|
||||
if (image) {
|
||||
url = 'https://wiki.openstreetmap.org/w/index.php';
|
||||
}
|
||||
}
|
||||
if (image) {
|
||||
result.image = {
|
||||
url: url,
|
||||
title: 'Special:Redirect/file/' + image
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// try the non-regional version of a language, like
|
||||
// 'en' if the language is 'en-US'
|
||||
if (locale.indexOf('-') !== -1) {
|
||||
var first = locale.split('-')[0];
|
||||
localized = _find(data, function(d) {
|
||||
return d.lang.toLowerCase() === first;
|
||||
});
|
||||
if (localized) return localized;
|
||||
}
|
||||
|
||||
// finally fall back to english
|
||||
return _find(data, function(d) {
|
||||
return d.lang.toLowerCase() === 'en';
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
function load(param) {
|
||||
if (!taginfo) return;
|
||||
if (!wikibase) return;
|
||||
|
||||
_button
|
||||
.classed('tag-reference-loading', true);
|
||||
|
||||
taginfo.docs(param, function show(err, data) {
|
||||
wikibase.getEntity(param, function show(err, data) {
|
||||
var docs;
|
||||
if (!err && data) {
|
||||
docs = findLocal(data);
|
||||
@@ -65,23 +70,25 @@ export function uiTagReference(tag) {
|
||||
_body.html('');
|
||||
|
||||
if (!docs || !docs.title) {
|
||||
if (param.hasOwnProperty('value')) {
|
||||
load(_omit(param, 'value')); // retry with key only
|
||||
} else {
|
||||
_body
|
||||
.append('p')
|
||||
.attr('class', 'tag-reference-description')
|
||||
.text(t('inspector.no_documentation_key'));
|
||||
done();
|
||||
}
|
||||
_body
|
||||
.append('p')
|
||||
.attr('class', 'tag-reference-description')
|
||||
.text(t('inspector.no_documentation_key'));
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
if (docs.image && docs.image.thumb_url_prefix) {
|
||||
if (docs.image) {
|
||||
var imageUrl = docs.image.url + '?' + utilQsString({
|
||||
title: docs.image.title,
|
||||
width: 100,
|
||||
height: 100,
|
||||
});
|
||||
|
||||
_body
|
||||
.append('img')
|
||||
.attr('class', 'tag-reference-wiki-image')
|
||||
.attr('src', docs.image.thumb_url_prefix + '100' + docs.image.thumb_url_suffix)
|
||||
.attr('src', imageUrl)
|
||||
.on('load', function() { done(); })
|
||||
.on('error', function() { d3_select(this).remove(); done(); });
|
||||
} else {
|
||||
@@ -91,7 +98,7 @@ export function uiTagReference(tag) {
|
||||
_body
|
||||
.append('p')
|
||||
.attr('class', 'tag-reference-description')
|
||||
.text(docs.description || t('inspector.documentation_redirect'));
|
||||
.text(docs.description || t('inspector.no_documentation_key'));
|
||||
|
||||
_body
|
||||
.append('a')
|
||||
@@ -101,7 +108,7 @@ export function uiTagReference(tag) {
|
||||
.attr('href', 'https://wiki.openstreetmap.org/wiki/' + docs.title)
|
||||
.call(svgIcon('#iD-icon-out-link', 'inline'))
|
||||
.append('span')
|
||||
.text(t('inspector.reference'));
|
||||
.text(t('inspector.edit_reference'));
|
||||
|
||||
// Add link to info about "good changeset comments" - #2923
|
||||
if (param.key === 'comment') {
|
||||
@@ -171,6 +178,7 @@ export function uiTagReference(tag) {
|
||||
} else if (_loaded) {
|
||||
done();
|
||||
} else {
|
||||
tag.langCode = utilDetect().locale.toLowerCase();
|
||||
load(tag);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { t } from '../util/locale';
|
||||
import { services } from '../services';
|
||||
import { svgIcon } from '../svg';
|
||||
import { krError } from '../osm';
|
||||
|
||||
|
||||
export function uiViewOnKeepRight() {
|
||||
var _error; // a keepright error
|
||||
|
||||
|
||||
function viewOnKeepRight(selection) {
|
||||
var url;
|
||||
if (services.keepRight && (_error instanceof krError)) {
|
||||
url = services.keepRight.errorURL(_error);
|
||||
}
|
||||
|
||||
var link = selection.selectAll('.view-on-keepRight')
|
||||
.data(url ? [url] : []);
|
||||
|
||||
// exit
|
||||
link.exit()
|
||||
.remove();
|
||||
|
||||
// enter
|
||||
var linkEnter = link.enter()
|
||||
.append('a')
|
||||
.attr('class', 'view-on-keepRight')
|
||||
.attr('target', '_blank')
|
||||
.attr('href', function(d) { return d; })
|
||||
.call(svgIcon('#iD-icon-out-link', 'inline'));
|
||||
|
||||
linkEnter
|
||||
.append('span')
|
||||
.text(t('inspector.view_on_keepRight'));
|
||||
}
|
||||
|
||||
|
||||
viewOnKeepRight.what = function(val) {
|
||||
if (!arguments.length) return _error;
|
||||
_error = val;
|
||||
return viewOnKeepRight;
|
||||
};
|
||||
|
||||
return viewOnKeepRight;
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
import { t } from '../util/locale';
|
||||
import { osmEntity, osmNote } from '../osm';
|
||||
import { svgIcon } from '../svg';
|
||||
import {
|
||||
osmEntity,
|
||||
osmNote
|
||||
} from '../osm';
|
||||
|
||||
|
||||
export function uiViewOnOSM(context) {
|
||||
|
||||
+22
-16
@@ -9,7 +9,8 @@ export function utilDetect(force) {
|
||||
detected = {};
|
||||
|
||||
var ua = navigator.userAgent,
|
||||
m = null;
|
||||
m = null,
|
||||
q = utilStringQs(window.location.hash.substring(1));
|
||||
|
||||
m = ua.match(/(edge)\/?\s*(\.?\d+(\.\d+)*)/i); // Edge
|
||||
if (m !== null) {
|
||||
@@ -59,24 +60,30 @@ export function utilDetect(force) {
|
||||
// Added due to incomplete svg style support. See #715
|
||||
detected.opera = (detected.browser.toLowerCase() === 'opera' && parseFloat(detected.version) < 15 );
|
||||
|
||||
detected.locale = (navigator.language || navigator.userLanguage || 'en-US');
|
||||
detected.language = detected.locale.split('-')[0];
|
||||
// Set locale based on url param (format 'en-US') or browser lang (default)
|
||||
if (q.hasOwnProperty('locale')) {
|
||||
detected.locale = q.locale;
|
||||
detected.language = q.locale.split('-')[0];
|
||||
} else {
|
||||
detected.locale = (navigator.language || navigator.userLanguage || 'en-US');
|
||||
detected.language = detected.locale.split('-')[0];
|
||||
|
||||
// Search `navigator.languages` for a better locale.. Prefer the first language,
|
||||
// unless the second language is a culture-specific version of the first one, see #3842
|
||||
if (navigator.languages && navigator.languages.length > 0) {
|
||||
var code0 = navigator.languages[0],
|
||||
parts0 = code0.split('-');
|
||||
// Search `navigator.languages` for a better locale. Prefer the first language,
|
||||
// unless the second language is a culture-specific version of the first one, see #3842
|
||||
if (navigator.languages && navigator.languages.length > 0) {
|
||||
var code0 = navigator.languages[0],
|
||||
parts0 = code0.split('-');
|
||||
|
||||
detected.locale = code0;
|
||||
detected.language = parts0[0];
|
||||
detected.locale = code0;
|
||||
detected.language = parts0[0];
|
||||
|
||||
if (navigator.languages.length > 1 && parts0.length === 1) {
|
||||
var code1 = navigator.languages[1],
|
||||
parts1 = code1.split('-');
|
||||
if (navigator.languages.length > 1 && parts0.length === 1) {
|
||||
var code1 = navigator.languages[1],
|
||||
parts1 = code1.split('-');
|
||||
|
||||
if (parts1[0] === parts0[0]) {
|
||||
detected.locale = code1;
|
||||
if (parts1[0] === parts0[0]) {
|
||||
detected.locale = code1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,7 +97,6 @@ export function utilDetect(force) {
|
||||
}
|
||||
|
||||
// detect text direction
|
||||
var q = utilStringQs(window.location.hash.substring(1));
|
||||
var lang = dataLocales[detected.locale];
|
||||
if ((lang && lang.rtl) || (q.rtl === 'true')) {
|
||||
detected.textDirection = 'rtl';
|
||||
|
||||
@@ -5,6 +5,7 @@ export { utilDisplayName } from './util';
|
||||
export { utilDisplayNameForPath } from './util';
|
||||
export { utilDisplayType } from './util';
|
||||
export { utilDisplayLabel } from './util';
|
||||
export { utilEntityRoot } from './util';
|
||||
export { utilEditDistance } from './util';
|
||||
export { utilEntitySelector } from './util';
|
||||
export { utilEntityOrMemberSelector } from './util';
|
||||
@@ -28,7 +29,6 @@ export { utilRebind } from './rebind';
|
||||
export { utilSetTransform } from './util';
|
||||
export { utilSessionMutex } from './session_mutex';
|
||||
export { utilStringQs } from './util';
|
||||
// export { utilSuggestNames } from './suggest_names';
|
||||
export { utilTagText } from './util';
|
||||
export { utilTiler } from './tiler';
|
||||
export { utilTriggerEvent } from './trigger_event';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import _isFunction from 'lodash-es/isFunction';
|
||||
import _uniq from 'lodash-es/uniq';
|
||||
|
||||
import {
|
||||
event as d3_event,
|
||||
@@ -125,7 +126,7 @@ export function utilKeybinding(namespace) {
|
||||
|
||||
// Remove one or more keycode bindings.
|
||||
keybinding.off = function(codes, capture) {
|
||||
var arr = [].concat(codes);
|
||||
var arr = _uniq([].concat(codes));
|
||||
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
var id = arr[i] + (capture ? '-capture' : '-bubble');
|
||||
@@ -141,7 +142,7 @@ export function utilKeybinding(namespace) {
|
||||
return keybinding.off(codes, capture);
|
||||
}
|
||||
|
||||
var arr = [].concat(codes);
|
||||
var arr = _uniq([].concat(codes));
|
||||
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
var id = arr[i] + (capture ? '-capture' : '-bubble');
|
||||
|
||||
@@ -42,7 +42,9 @@ export function t(s, o, loc) {
|
||||
if (rep !== undefined) {
|
||||
if (o) {
|
||||
for (var k in o) {
|
||||
rep = rep.replace('{' + k + '}', o[k]);
|
||||
var variable = '{' + k + '}';
|
||||
var re = new RegExp(variable, 'g'); // check globally for variables
|
||||
rep = rep.replace(re, o[k]);
|
||||
}
|
||||
}
|
||||
return rep;
|
||||
|
||||
+50
-32
@@ -37,6 +37,7 @@ 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;
|
||||
@@ -53,6 +54,7 @@ export function utilEntityOrDeepMemberSelector(ids, graph) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ids.forEach(function(id) {
|
||||
addEntityAndMembersIfNotYetSeen(id);
|
||||
});
|
||||
@@ -85,9 +87,9 @@ export function utilGetAllNodes(ids, graph) {
|
||||
|
||||
|
||||
export function utilDisplayName(entity) {
|
||||
var localizedNameKey = 'name:' + utilDetect().locale.toLowerCase().split('-')[0],
|
||||
name = entity.tags[localizedNameKey] || entity.tags.name || '',
|
||||
network = entity.tags.cycle_network || entity.tags.network;
|
||||
var localizedNameKey = 'name:' + utilDetect().locale.toLowerCase().split('-')[0];
|
||||
var name = entity.tags[localizedNameKey] || entity.tags.name || '';
|
||||
var network = entity.tags.cycle_network || entity.tags.network;
|
||||
|
||||
if (!name && entity.tags.ref) {
|
||||
name = entity.tags.ref;
|
||||
@@ -137,6 +139,15 @@ export function utilDisplayLabel(entity, context) {
|
||||
}
|
||||
|
||||
|
||||
export function utilEntityRoot(entityType) {
|
||||
return {
|
||||
node: 'n',
|
||||
way: 'w',
|
||||
relation: 'r'
|
||||
}[entityType];
|
||||
}
|
||||
|
||||
|
||||
export function utilStringQs(str) {
|
||||
return str.split('&').reduce(function(obj, pair){
|
||||
var parts = pair.split('=');
|
||||
@@ -152,11 +163,12 @@ export function utilStringQs(str) {
|
||||
|
||||
|
||||
export function utilQsString(obj, noencode) {
|
||||
// encode everything except special characters used in certain hash parameters:
|
||||
// "/" in map states, ":", ",", {" and "}" in background
|
||||
function softEncode(s) {
|
||||
// encode everything except special characters used in certain hash parameters:
|
||||
// "/" in map states, ":", ",", {" and "}" in background
|
||||
return encodeURIComponent(s).replace(/(%2F|%3A|%2C|%7B|%7D)/g, decodeURIComponent);
|
||||
return encodeURIComponent(s).replace(/(%2F|%3A|%2C|%7B|%7D)/g, decodeURIComponent);
|
||||
}
|
||||
|
||||
return Object.keys(obj).sort().map(function(key) {
|
||||
return encodeURIComponent(key) + '=' + (
|
||||
noencode ? softEncode(obj[key]) : encodeURIComponent(obj[key]));
|
||||
@@ -165,36 +177,41 @@ export function utilQsString(obj, noencode) {
|
||||
|
||||
|
||||
export function utilPrefixDOMProperty(property) {
|
||||
var prefixes = ['webkit', 'ms', 'moz', 'o'],
|
||||
i = -1,
|
||||
n = prefixes.length,
|
||||
s = document.body;
|
||||
var prefixes = ['webkit', 'ms', 'moz', 'o'];
|
||||
var i = -1;
|
||||
var n = prefixes.length;
|
||||
var s = document.body;
|
||||
|
||||
if (property in s)
|
||||
return property;
|
||||
|
||||
property = property.substr(0, 1).toUpperCase() + property.substr(1);
|
||||
|
||||
while (++i < n)
|
||||
if (prefixes[i] + property in s)
|
||||
while (++i < n) {
|
||||
if (prefixes[i] + property in s) {
|
||||
return prefixes[i] + property;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
export function utilPrefixCSSProperty(property) {
|
||||
var prefixes = ['webkit', 'ms', 'Moz', 'O'],
|
||||
i = -1,
|
||||
n = prefixes.length,
|
||||
s = document.body.style;
|
||||
var prefixes = ['webkit', 'ms', 'Moz', 'O'];
|
||||
var i = -1;
|
||||
var n = prefixes.length;
|
||||
var s = document.body.style;
|
||||
|
||||
if (property.toLowerCase() in s)
|
||||
if (property.toLowerCase() in s) {
|
||||
return property.toLowerCase();
|
||||
}
|
||||
|
||||
while (++i < n)
|
||||
if (prefixes[i] + property in s)
|
||||
while (++i < n) {
|
||||
if (prefixes[i] + property in s) {
|
||||
return '-' + prefixes[i].toLowerCase() + property.replace(/([A-Z])/g, '-$1').toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -202,10 +219,9 @@ export function utilPrefixCSSProperty(property) {
|
||||
|
||||
var transformProperty;
|
||||
export function utilSetTransform(el, x, y, scale) {
|
||||
var prop = transformProperty = transformProperty || utilPrefixCSSProperty('Transform'),
|
||||
translate = utilDetect().opera ?
|
||||
'translate(' + x + 'px,' + y + 'px)' :
|
||||
'translate3d(' + x + 'px,' + y + 'px,0)';
|
||||
var prop = transformProperty = transformProperty || utilPrefixCSSProperty('Transform');
|
||||
var translate = utilDetect().opera ? 'translate(' + x + 'px,' + y + 'px)'
|
||||
: 'translate3d(' + x + 'px,' + y + 'px,0)';
|
||||
return el.style(prop, translate + (scale ? ' scale(' + scale + ')' : ''));
|
||||
}
|
||||
|
||||
@@ -240,11 +256,12 @@ export function utilEditDistance(a, b) {
|
||||
// 1. Only works on HTML elements, not SVG
|
||||
// 2. Does not cause style recalculation
|
||||
export function utilFastMouse(container) {
|
||||
var rect = container.getBoundingClientRect(),
|
||||
rectLeft = rect.left,
|
||||
rectTop = rect.top,
|
||||
clientLeft = +container.clientLeft,
|
||||
clientTop = +container.clientTop;
|
||||
var rect = container.getBoundingClientRect();
|
||||
var rectLeft = rect.left;
|
||||
var rectTop = rect.top;
|
||||
var clientLeft = +container.clientLeft;
|
||||
var clientTop = +container.clientTop;
|
||||
|
||||
if (textDirection === 'rtl') {
|
||||
rectLeft = 0;
|
||||
}
|
||||
@@ -262,9 +279,9 @@ export var utilGetPrototypeOf = Object.getPrototypeOf || function(obj) { return
|
||||
|
||||
|
||||
export function utilAsyncMap(inputs, func, callback) {
|
||||
var remaining = inputs.length,
|
||||
results = [],
|
||||
errors = [];
|
||||
var remaining = inputs.length;
|
||||
var results = [];
|
||||
var errors = [];
|
||||
|
||||
inputs.forEach(function(d, i) {
|
||||
func(d, function done(err, data) {
|
||||
@@ -279,8 +296,9 @@ export function utilAsyncMap(inputs, func, callback) {
|
||||
|
||||
// wraps an index to an interval [0..length-1]
|
||||
export function utilWrap(index, length) {
|
||||
if (index < 0)
|
||||
if (index < 0) {
|
||||
index += Math.ceil(-index/length)*length;
|
||||
}
|
||||
return index % length;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@ export function validationDeprecatedTag() {
|
||||
var validation = function(changes) {
|
||||
var issues = [];
|
||||
for (var i = 0; i < changes.created.length; i++) {
|
||||
var change = changes.created[i],
|
||||
deprecatedTags = change.deprecatedTags();
|
||||
var change = changes.created[i];
|
||||
var deprecatedTags = change.deprecatedTags();
|
||||
|
||||
if (!_isEmpty(deprecatedTags)) {
|
||||
var tags = utilTagText({ tags: deprecatedTags });
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { t } from '../util/locale';
|
||||
import { discardNames } from '../../node_modules/name-suggestion-index/config/filters.json';
|
||||
|
||||
export function validationGenericName() {
|
||||
|
||||
function isGenericName(entity) {
|
||||
var name = entity.tags.name;
|
||||
if (!name) return false;
|
||||
|
||||
var i, re;
|
||||
|
||||
// test if the name is just the tag value (e.g. "park")
|
||||
var keys = ['amenity', 'leisure', 'shop', 'man_made', 'tourism'];
|
||||
for (i = 0; i < keys.length; i++) {
|
||||
var val = entity.tags[keys[i]];
|
||||
if (val && val.replace(/\_/g, ' ').toLowerCase() === name.toLowerCase()) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
// test if the name is a generic name (e.g. "pizzaria")
|
||||
for (i = 0; i < discardNames.length; i++) {
|
||||
re = new RegExp(discardNames[i], 'i');
|
||||
if (re.test(name)) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return function validation(changes) {
|
||||
var warnings = [];
|
||||
|
||||
for (var i = 0; i < changes.created.length; i++) {
|
||||
var change = changes.created[i];
|
||||
var generic = isGenericName(change);
|
||||
if (generic) {
|
||||
warnings.push({
|
||||
id: 'generic_name',
|
||||
message: t('validations.generic_name'),
|
||||
tooltip: t('validations.generic_name_tooltip', { name: generic }),
|
||||
entity: change
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return warnings;
|
||||
};
|
||||
}
|
||||
@@ -3,6 +3,7 @@ export { validationDisconnectedHighway } from './disconnected_highway';
|
||||
export { validationHighwayCrossingOtherWays } from './crossing_ways';
|
||||
export { validationHighwayAlmostJunction } from './highway_almost_junction';
|
||||
export { ValidationIssueType, ValidationIssueSeverity } from './validation_issue';
|
||||
export { validationGenericName } from './generic_name.js';
|
||||
export { validationManyDeletions } from './many_deletions';
|
||||
export { validationMapCSSChecks } from './mapcss_checks';
|
||||
export { validationMissingTag } from './missing_tag';
|
||||
|
||||
@@ -10,13 +10,13 @@ export function validationManyDeletions() {
|
||||
|
||||
var validation = function(changes, graph) {
|
||||
var issues = [];
|
||||
var nodes=0, ways=0, areas=0, relations=0;
|
||||
var nodes = 0, ways = 0, areas = 0, relations = 0;
|
||||
|
||||
changes.deleted.forEach(function(c) {
|
||||
if (c.type === 'node') {nodes++;}
|
||||
else if (c.type === 'way' && c.geometry(graph) === 'line') {ways++;}
|
||||
else if (c.type === 'way' && c.geometry(graph) === 'area') {areas++;}
|
||||
else if (c.type === 'relation') {relations++;}
|
||||
if (c.type === 'node') { nodes++; }
|
||||
else if (c.type === 'way' && c.geometry(graph) === 'line') { ways++; }
|
||||
else if (c.type === 'way' && c.geometry(graph) === 'area') { areas++; }
|
||||
else if (c.type === 'relation') { relations++; }
|
||||
});
|
||||
if (changes.deleted.length > threshold) {
|
||||
issues.push(new validationIssue({
|
||||
|
||||
@@ -21,5 +21,7 @@ export function validationMapCSSChecks() {
|
||||
|
||||
return issues;
|
||||
};
|
||||
|
||||
|
||||
return validation;
|
||||
}
|
||||
|
||||
@@ -20,12 +20,12 @@ export function validationMissingTag(context) {
|
||||
}
|
||||
|
||||
var validation = function(changes, graph) {
|
||||
var types = ['point', 'line', 'area', 'relation'],
|
||||
issues = [];
|
||||
var types = ['point', 'line', 'area', 'relation'];
|
||||
var issues = [];
|
||||
|
||||
for (var i = 0; i < changes.created.length; i++) {
|
||||
var change = changes.created[i],
|
||||
geometry = change.geometry(graph);
|
||||
var change = changes.created[i];
|
||||
var geometry = change.geometry(graph);
|
||||
|
||||
if (types.indexOf(geometry) !== -1 && !hasTags(change, graph)) {
|
||||
var entityLabel = utilDisplayLabel(change, context);
|
||||
|
||||
@@ -32,9 +32,9 @@ export function validationTagSuggestsArea() {
|
||||
var validation = function(changes, graph) {
|
||||
var issues = [];
|
||||
for (var i = 0; i < changes.created.length; i++) {
|
||||
var change = changes.created[i],
|
||||
geometry = change.geometry(graph),
|
||||
suggestion = (geometry === 'line' ? tagSuggestsArea(change.tags) : undefined);
|
||||
var change = changes.created[i];
|
||||
var geometry = change.geometry(graph);
|
||||
var suggestion = (geometry === 'line' ? tagSuggestsArea(change.tags) : undefined);
|
||||
|
||||
if (suggestion) {
|
||||
issues.push(new validationIssue({
|
||||
|
||||
Reference in New Issue
Block a user