mirror of
https://github.com/FoggedLens/iD.git
synced 2026-06-05 22:46:38 +02:00
Merge branch 'keep-right_QA'
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));
|
||||
}
|
||||
|
||||
@@ -263,6 +263,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) {
|
||||
|
||||
+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';
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import { geoBounds as d3_geoBounds } from 'd3-geo';
|
||||
|
||||
import {
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
import {
|
||||
event as d3_event,
|
||||
select as d3_select
|
||||
} from 'd3-selection';
|
||||
|
||||
import {
|
||||
behaviorBreathe,
|
||||
behaviorHover,
|
||||
behaviorLasso,
|
||||
behaviorSelect
|
||||
} from '../behavior';
|
||||
|
||||
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.enter = function() {
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
var error = checkSelectedID();
|
||||
if (!error) return;
|
||||
|
||||
behaviors.forEach(context.install);
|
||||
keybinding.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);
|
||||
};
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -72,6 +72,7 @@ export function modeSelectNote(context, selectedNoteID) {
|
||||
} else {
|
||||
selection
|
||||
.classed('selected', true);
|
||||
|
||||
context.selectedNoteID(selectedNoteID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import serviceKeepRight from './keepRight';
|
||||
import serviceMapillary from './mapillary';
|
||||
import serviceMapRules from './maprules';
|
||||
import serviceNominatim from './nominatim';
|
||||
@@ -13,6 +14,7 @@ import serviceWikipedia from './wikipedia';
|
||||
|
||||
export var services = {
|
||||
geocoder: serviceNominatim,
|
||||
keepRight: serviceKeepRight,
|
||||
mapillary: serviceMapillary,
|
||||
openstreetcam: serviceOpenstreetcam,
|
||||
osm: serviceOsm,
|
||||
@@ -26,6 +28,7 @@ export var services = {
|
||||
};
|
||||
|
||||
export {
|
||||
serviceKeepRight,
|
||||
serviceMapillary,
|
||||
serviceMapRules,
|
||||
serviceNominatim,
|
||||
|
||||
@@ -0,0 +1,454 @@
|
||||
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 } from '../../data/keepRight.json';
|
||||
|
||||
|
||||
var tiler = utilTiler();
|
||||
var dispatch = d3_dispatch('loaded');
|
||||
|
||||
var _krCache;
|
||||
var _krZoom = 14;
|
||||
var _krUrlRoot = 'https://www.keepright.at/';
|
||||
var _krLocalize = {
|
||||
node: 'node',
|
||||
way: 'way',
|
||||
relation: 'relation',
|
||||
highway: 'highway',
|
||||
railway: 'railway',
|
||||
waterway: 'waterway',
|
||||
cycleway: 'cycleway',
|
||||
footpath: 'footpath',
|
||||
'cycleway/footpath': 'cycleway_footpath',
|
||||
riverbank: 'riverbank',
|
||||
bridge: 'bridge',
|
||||
tunnel: 'tunnel',
|
||||
place_of_worship: 'place_of_worship',
|
||||
pub: 'pub',
|
||||
restaurant: 'restaurant',
|
||||
school: 'school',
|
||||
university: 'university',
|
||||
hospital: 'hospital',
|
||||
library: 'library',
|
||||
theatre: 'theatre',
|
||||
courthouse: 'courthouse',
|
||||
bank: 'bank',
|
||||
cinema: 'cinema',
|
||||
pharmacy: 'pharmacy',
|
||||
cafe: 'cafe',
|
||||
fast_food: 'fast_food',
|
||||
fuel: 'fuel',
|
||||
from: 'from',
|
||||
to: 'to'
|
||||
};
|
||||
|
||||
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 as groups
|
||||
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 group = errorMatch[i];
|
||||
var idType;
|
||||
|
||||
idType = 'IDs' in errorTemplate ? errorTemplate.IDs[i-1] : '';
|
||||
if (idType && group) { // link IDs if present in the group
|
||||
group = parseError(group, idType);
|
||||
} else if (htmlRegex.test(group)) { // escape any html in non-IDs
|
||||
group = '\\' + group + '\\';
|
||||
} else if (_krLocalize[group]) { // some replacement strings can be localized
|
||||
group = t('QA.keepRight.error_parts.' + _krLocalize[group]);
|
||||
}
|
||||
|
||||
replacements['var' + i] = group;
|
||||
}
|
||||
|
||||
return replacements;
|
||||
}
|
||||
|
||||
|
||||
function parseError(group, idType) {
|
||||
|
||||
function linkEntity(d) {
|
||||
return '<span><a class="kr_error_description-id">' + d + '</a></span>';
|
||||
}
|
||||
|
||||
// arbitrary node list of form: #ID, #ID, #ID...
|
||||
function parseError211(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 parseError231(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 parseError294(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 parseError370(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 parseWarning20(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(', ');
|
||||
}
|
||||
|
||||
switch (idType) {
|
||||
// simple case just needs a linking span
|
||||
case 'n':
|
||||
case 'w':
|
||||
case 'r':
|
||||
group = linkEntity(idType + group);
|
||||
break;
|
||||
// some errors have more complex ID lists/variance
|
||||
case '211':
|
||||
group = parseError211(group);
|
||||
break;
|
||||
case '231':
|
||||
group = parseError231(group);
|
||||
break;
|
||||
case '294':
|
||||
group = parseError294(group);
|
||||
break;
|
||||
case '370':
|
||||
group = parseError370(group);
|
||||
break;
|
||||
case '20':
|
||||
group = parseWarning20(group);
|
||||
}
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
init: function() {
|
||||
if (!_krCache) {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
this.event = utilRebind(this, dispatch, 'on');
|
||||
},
|
||||
|
||||
reset: function() {
|
||||
if (_krCache) {
|
||||
_forEach(_krCache.inflight, abortRequest);
|
||||
}
|
||||
_krCache = { loaded: {}, inflight: {}, keepRight: {}, 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;
|
||||
|
||||
// - 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.00002, 0] : [0, 0.00002];
|
||||
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,
|
||||
object_id: props.object_id,
|
||||
object_type: props.object_type,
|
||||
schema: props.schema,
|
||||
title: props.title
|
||||
});
|
||||
|
||||
d.replacements = tokenReplacements(d);
|
||||
|
||||
_krCache.keepRight[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' || d.state === 'ignore_t') {
|
||||
that.removeError(d);
|
||||
} 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.keepRight[id];
|
||||
},
|
||||
|
||||
|
||||
// replace a single error in the cache
|
||||
replaceError: function(error) {
|
||||
if (!(error instanceof krError) || !error.id) return;
|
||||
|
||||
_krCache.keepRight[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.keepRight[error.id];
|
||||
updateRtree(encodeErrorRtree(error), false); // false = remove
|
||||
},
|
||||
|
||||
|
||||
errorURL: function(error) {
|
||||
return _krUrlRoot + 'report_map.php?schema=' + error.schema + '&error=' + error.id;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,241 @@
|
||||
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 : 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; });
|
||||
|
||||
@@ -180,6 +180,13 @@ export function uiHelp(context) {
|
||||
'using',
|
||||
'tracing',
|
||||
'upload'
|
||||
]],
|
||||
['qa', [
|
||||
'intro',
|
||||
'tools_h',
|
||||
'tools',
|
||||
'issues_h',
|
||||
'issues'
|
||||
]]
|
||||
];
|
||||
|
||||
@@ -227,6 +234,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';
|
||||
@@ -64,4 +67,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';
|
||||
|
||||
@@ -92,9 +92,9 @@ export function uiInspector(context) {
|
||||
}
|
||||
|
||||
|
||||
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,80 @@
|
||||
import { event as d3_event } from 'd3-selection';
|
||||
|
||||
import { dataEn } from '../../data';
|
||||
import { t } from '../util/locale';
|
||||
|
||||
|
||||
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];
|
||||
|
||||
if (et && et.description) {
|
||||
return t('QA.keepRight.errorTypes.' + errorType + '.description', d.replacements);
|
||||
} else if (pt && pt.description) {
|
||||
return t('QA.keepRight.errorTypes.' + parentErrorType + '.description', d.replacements);
|
||||
} else {
|
||||
return unknown;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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 description = detailsEnter
|
||||
.append('div')
|
||||
.attr('class', 'kr_error-details-description');
|
||||
|
||||
description
|
||||
.append('h4')
|
||||
.text(function() { return t('QA.keepRight.detail_description'); });
|
||||
|
||||
description
|
||||
.append('div')
|
||||
.attr('class', 'kr_error-details-description-text')
|
||||
.html(errorDetail);
|
||||
|
||||
description.selectAll('.kr_error_description-id')
|
||||
.on('click', function() { clickLink(context, this.text); });
|
||||
|
||||
|
||||
function clickLink(context, entityID) {
|
||||
d3_event.preventDefault();
|
||||
context.layers().layer('osm').enabled(true);
|
||||
context.zoomToEntity(entityID);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
keepRightDetails.error = function(val) {
|
||||
if (!arguments.length) return _error;
|
||||
_error = val;
|
||||
return keepRightDetails;
|
||||
};
|
||||
|
||||
|
||||
return keepRightDetails;
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
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, 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 _error;
|
||||
|
||||
|
||||
function keepRightEditor(selection) {
|
||||
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('.error-editor')
|
||||
.data([0]);
|
||||
|
||||
editor.enter()
|
||||
.append('div')
|
||||
.attr('class', 'modal-section keepRight-editor')
|
||||
.merge(editor)
|
||||
.call(keepRightHeader.error(_error))
|
||||
.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;
|
||||
}
|
||||
+78
-2
@@ -29,6 +29,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) {
|
||||
@@ -37,6 +38,7 @@ export function uiMapData(context) {
|
||||
|
||||
|
||||
function autoHiddenFeature(d) {
|
||||
if (d.type === 'kr_error') return context.errors().autoHidden(d);
|
||||
return context.features().autoHidden(d);
|
||||
}
|
||||
|
||||
@@ -47,6 +49,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;
|
||||
}
|
||||
@@ -206,6 +224,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) {
|
||||
@@ -429,7 +499,8 @@ export function uiMapData(context) {
|
||||
var tip = t(name + '.' + d + '.tooltip'),
|
||||
key = (d === 'wireframe' ? t('area_fill.wireframe.key') : null);
|
||||
|
||||
if (name === 'feature' && autoHiddenFeature(d)) {
|
||||
|
||||
if ((name === 'feature' || name === 'keepRight') && autoHiddenFeature(d)) {
|
||||
var msg = showsLayer('osm') ? t('map_data.autohidden') : t('map_data.osmhidden');
|
||||
tip += '<div>' + msg + '</div>';
|
||||
}
|
||||
@@ -460,7 +531,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));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -501,6 +572,7 @@ export function uiMapData(context) {
|
||||
function update() {
|
||||
_dataLayerContainer
|
||||
.call(drawOsmItems)
|
||||
.call(drawQAItems)
|
||||
.call(drawPhotoItems)
|
||||
.call(drawCustomDataItems)
|
||||
.call(drawVectorItems); // Beta - Detroit mapping challenge
|
||||
@@ -510,6 +582,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -609,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;
|
||||
};
|
||||
|
||||
|
||||
@@ -155,7 +155,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 +425,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;
|
||||
};
|
||||
|
||||
|
||||
+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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -4,6 +4,7 @@ export { utilCleanTags } from './clean_tags';
|
||||
export { utilDisplayName } from './util';
|
||||
export { utilDisplayNameForPath } from './util';
|
||||
export { utilDisplayType } from './util';
|
||||
export { utilEntityRoot } from './util';
|
||||
export { utilEditDistance } from './util';
|
||||
export { utilEntitySelector } from './util';
|
||||
export { utilEntityOrMemberSelector } from './util';
|
||||
@@ -27,7 +28,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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -121,6 +121,15 @@ export function utilDisplayType(id) {
|
||||
}
|
||||
|
||||
|
||||
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('=');
|
||||
|
||||
Reference in New Issue
Block a user