Merge branch 'master' into Psigio-3375

This commit is contained in:
Bryan Housel
2016-12-14 11:30:53 -05:00
250 changed files with 19027 additions and 4204 deletions
+3 -1
View File
@@ -152,7 +152,9 @@ export function behaviorBreathe() {
breathe.off = function() {
done = true;
timer.stop();
if (timer) {
timer.stop();
}
selected
.interrupt()
.call(reset);
+4 -4
View File
@@ -14,8 +14,8 @@ export function behaviorHash(context) {
if (args.length < 3 || args.some(isNaN)) {
return true; // replace bogus hash
} else if (s !== formatter(map).slice(1)) {
map.centerZoom([args[1],
Math.min(lat, Math.max(-lat, args[2]))], args[0]);
map.centerZoom([args[2],
Math.min(lat, Math.max(-lat, args[1]))], args[0]);
}
};
@@ -40,8 +40,8 @@ export function behaviorHash(context) {
}
newParams.map = zoom.toFixed(2) +
'/' + center[0].toFixed(precision) +
'/' + center[1].toFixed(precision);
'/' + center[1].toFixed(precision) +
'/' + center[0].toFixed(precision);
return '#' + utilQsString(_.assign(q, newParams), true);
};
+40 -39
View File
@@ -1,11 +1,11 @@
import * as d3 from 'd3';
import _ from 'lodash';
import { t, addTranslation, setLocale } from '../util/locale';
import { t, currentLocale, addTranslation, setLocale } from '../util/locale';
import { coreHistory } from './history';
import { dataLocales, dataEn } from '../../data/index';
import { geoRawMercator } from '../geo/raw_mercator';
import { modeSelect } from '../modes/select';
import { presetInit } from '../presets/init';
import { presetIndex } from '../presets/index';
import { rendererBackground } from '../renderer/background';
import { rendererFeatures } from '../renderer/features';
import { rendererMap } from '../renderer/map';
@@ -22,12 +22,7 @@ export function setAreaKeys(value) {
}
export function coreContext(root) {
if (!root.locale) {
root.locale = {
current: function(_) { this._current = _; }
};
}
export function coreContext() {
addTranslation('en', dataEn);
setLocale('en');
@@ -217,6 +212,11 @@ export function coreContext(root) {
};
/* Presets */
var presets;
context.presets = function() { return presets; };
/* Map */
var map;
context.map = function() { return map; };
@@ -254,23 +254,6 @@ export function coreContext(root) {
};
/* Presets */
var presets;
context.presets = function(_) {
if (!arguments.length) return presets;
presets.load(_);
areaKeys = presets.areaKeys();
return context;
};
/* Imagery */
context.imagery = function(_) {
background.load(_);
return context;
};
/* Container */
var container, embed;
context.container = function(_) {
@@ -310,24 +293,40 @@ export function coreContext(root) {
return context.asset('img/' + _);
};
/* locales */
// `locale` variable contains a "requested locale".
// It won't become the `currentLocale` until after loadLocale() is called.
var locale, localePath;
context.locale = function(loc, path) {
if (!arguments.length) return locale;
if (!arguments.length) return currentLocale;
locale = loc;
localePath = path;
return context;
};
context.loadLocale = function(cb) {
if (locale && locale !== 'en' && dataLocales.indexOf(locale) !== -1) {
context.loadLocale = function(callback) {
if (locale && locale !== 'en' && dataLocales.hasOwnProperty(locale)) {
localePath = localePath || context.asset('locales/' + locale + '.json');
d3.json(localePath, function(err, result) {
addTranslation(locale, result[locale]);
setLocale(locale);
cb();
if (!err) {
addTranslation(locale, result[locale]);
setLocale(locale);
utilDetect(true);
}
if (callback) {
callback(err);
}
});
} else {
cb();
if (locale) {
setLocale(locale);
utilDetect(true);
}
if (callback) {
callback();
}
}
};
@@ -336,7 +335,7 @@ export function coreContext(root) {
context.reset = context.flush = function() {
context.debouncedSave.cancel();
_.each(services, function(service) {
if (typeof service.reset === 'function') {
if (service && typeof service.reset === 'function') {
service.reset(context);
}
});
@@ -347,12 +346,12 @@ export function coreContext(root) {
/* Init */
context.version = '2.0.0-beta.1';
context.version = '2.0.1';
context.projection = geoRawMercator();
locale = utilDetect().locale;
if (locale && dataLocales.indexOf(locale) === -1) {
if (locale && !dataLocales.hasOwnProperty(locale)) {
locale = locale.split('-')[0];
}
@@ -382,9 +381,9 @@ export function coreContext(root) {
ui = uiInit(context);
connection = services.osm;
background = rendererBackground(context);
features = rendererFeatures(context);
presets = presetIndex();
map = rendererMap(context);
context.mouse = map.mouse;
@@ -396,14 +395,16 @@ export function coreContext(root) {
context.zoomOutFurther = map.zoomOutFurther;
context.redrawEnable = map.redrawEnable;
presets = presetInit();
_.each(services, function(service) {
if (typeof service.init === 'function') {
if (service && typeof service.init === 'function') {
service.init(context);
}
});
background.init();
presets.init();
areaKeys = presets.areaKeys();
return utilRebind(context, dispatch, 'on');
}
+5 -2
View File
@@ -26,6 +26,9 @@ 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);
@@ -129,7 +132,7 @@ export function coreHistory(context) {
if (stack[index].annotation) break;
}
dispatch.call('undone');
dispatch.call('undone', this, stack[index]);
return change(previous);
},
@@ -142,7 +145,7 @@ export function coreHistory(context) {
if (stack[index].annotation) break;
}
dispatch.call('redone');
dispatch.call('redone', this, stack[index]);
return change(previous);
},
+29 -23
View File
@@ -58,33 +58,37 @@ export function d3keybinding(namespace) {
return keybinding;
};
keybinding.on = function(code, callback, capture) {
var binding = {
event: {
keyCode: 0,
shiftKey: false,
ctrlKey: false,
altKey: false,
metaKey: false
},
capture: capture,
callback: callback
};
keybinding.on = function(codes, callback, capture) {
var arr = [].concat(codes);
for (var i = 0; i < arr.length; i++) {
var code = arr[i];
var binding = {
event: {
keyCode: 0,
shiftKey: false,
ctrlKey: false,
altKey: false,
metaKey: false
},
capture: capture,
callback: callback
};
code = code.toLowerCase().match(/(?:(?:[^+⇧⌃⌥⌘])+|[⇧⌃⌥⌘]|\+\+|^\+$)/g);
code = code.toLowerCase().match(/(?:(?:[^+⇧⌃⌥⌘])+|[⇧⌃⌥⌘]|\+\+|^\+$)/g);
for (var i = 0; i < code.length; i++) {
// Normalise matching errors
if (code[i] === '++') code[i] = '+';
for (var j = 0; j < code.length; j++) {
// Normalise matching errors
if (code[j] === '++') code[i] = '+';
if (code[i] in d3keybinding.modifierCodes) {
binding.event[d3keybinding.modifierProperties[d3keybinding.modifierCodes[code[i]]]] = true;
} else if (code[i] in d3keybinding.keyCodes) {
binding.event.keyCode = d3keybinding.keyCodes[code[i]];
if (code[j] in d3keybinding.modifierCodes) {
binding.event[d3keybinding.modifierProperties[d3keybinding.modifierCodes[code[j]]]] = true;
} else if (code[j] in d3keybinding.keyCodes) {
binding.event.keyCode = d3keybinding.keyCodes[code[j]];
}
}
}
bindings.push(binding);
bindings.push(binding);
}
return keybinding;
};
@@ -92,6 +96,7 @@ export function d3keybinding(namespace) {
return keybinding;
}
d3keybinding.modifierCodes = {
// Shift key, ⇧
'⇧': 16, shift: 16,
@@ -164,7 +169,8 @@ d3keybinding.keyCodes = {
'=': 187, 'equals': 187,
// Comma, or ,
',': 188, comma: 188,
'dash': 189, //???
// Dash / Underscore key
'dash': 189,
// Period, or ., or full-stop
'.': 190, period: 190, 'full-stop': 190,
// Slash, or /, or forward-slash
+228 -11
View File
@@ -29,7 +29,11 @@ import { modeBrowse } from './browse';
import { modeDragNode } from './drag_node';
import * as Operations from '../operations/index';
import { uiRadialMenu, uiSelectionList } from '../ui/index';
import { utilEntityOrMemberSelector } from '../util/index';
import { uiCmd } from '../ui/cmd';
import { utilEntityOrMemberSelector, utilEntitySelector } from '../util/index';
var relatedParent;
export function modeSelect(context, selectedIDs) {
@@ -52,7 +56,9 @@ export function modeSelect(context, selectedIDs) {
inspector,
radialMenu,
newFeature = false,
suppressMenu = false;
suppressMenu = false,
follow = false;
var wrap = context.container()
.select('.inspector-wrap');
@@ -65,6 +71,73 @@ export function modeSelect(context, selectedIDs) {
}
function checkSelectedIDs() {
var ids = [];
if (Array.isArray(selectedIDs)) {
ids = selectedIDs.filter(function(id) {
return context.hasEntity(id);
});
}
if (ids.length) {
selectedIDs = ids;
} else {
context.enter(modeBrowse(context));
}
return !!ids.length;
}
// find the common parent ways for nextVertex, previousVertex
function commonParents() {
var graph = context.graph(),
commonParents = [];
for (var i = 0; i < selectedIDs.length; i++) {
var entity = context.hasEntity(selectedIDs[i]);
if (!entity || entity.geometry(graph) !== 'vertex') {
return []; // selection includes some not vertexes
}
var currParents = _.map(graph.parentWays(entity), 'id');
if (!commonParents.length) {
commonParents = currParents;
continue;
}
commonParents = _.intersection(commonParents, currParents);
if (!commonParents.length) {
return [];
}
}
return commonParents;
}
function singularParent() {
var parents = commonParents();
if (!parents) {
relatedParent = null;
return null;
}
// relatedParent is used when we visit a vertex with multiple
// parents, and we want to remember which parent line we started on.
if (parents.length === 1) {
relatedParent = parents[0]; // remember this parent for later
return relatedParent;
}
if (parents.indexOf(relatedParent) !== -1) {
return relatedParent; // prefer the previously seen parent
}
return parents[0];
}
function closeMenu() {
if (radialMenu) {
context.surface().call(radialMenu.close);
@@ -115,6 +188,8 @@ export function modeSelect(context, selectedIDs) {
mode.reselect = function() {
if (!checkSelectedIDs()) return;
var surfaceNode = context.surface().node();
if (surfaceNode.focus) { // FF doesn't support it
surfaceNode.focus();
@@ -139,14 +214,18 @@ export function modeSelect(context, selectedIDs) {
};
mode.follow = function(_) {
if (!arguments.length) return follow;
follow = _;
return mode;
};
mode.enter = function() {
function update() {
closeMenu();
if (_.some(selectedIDs, function(id) { return !context.hasEntity(id); })) {
// Exit mode if selected entity gets undone
context.enter(modeBrowse(context));
}
checkSelectedIDs();
}
@@ -173,17 +252,33 @@ export function modeSelect(context, selectedIDs) {
function selectElements(drawn) {
var entity = singular();
if (!checkSelectedIDs()) return;
var surface = context.surface(),
entity = singular();
if (entity && context.geometry(entity.id) === 'relation') {
suppressMenu = true;
return;
}
surface.selectAll('.related')
.classed('related', false);
singularParent();
if (relatedParent) {
surface.selectAll(utilEntitySelector([relatedParent]))
.classed('related', true);
}
var selection = context.surface()
.selectAll(utilEntityOrMemberSelector(selectedIDs, context.graph()));
.selectAll(utilEntityOrMemberSelector(selectedIDs, context.graph()));
if (selection.empty()) {
if (drawn) { // Exit mode if selected DOM elements have disappeared..
// 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 {
@@ -200,6 +295,103 @@ export function modeSelect(context, selectedIDs) {
}
function firstVertex() {
d3.event.preventDefault();
var parent = singularParent();
if (parent) {
var way = context.entity(parent);
context.enter(
modeSelect(context, [way.first()]).follow(true).suppressMenu(true)
);
}
}
function lastVertex() {
d3.event.preventDefault();
var parent = singularParent();
if (parent) {
var way = context.entity(parent);
context.enter(
modeSelect(context, [way.last()]).follow(true).suppressMenu(true)
);
}
}
function previousVertex() {
d3.event.preventDefault();
var parent = singularParent();
if (!parent) return;
var way = context.entity(parent),
length = way.nodes.length,
curr = way.nodes.indexOf(selectedIDs[0]),
index = -1;
if (curr > 0) {
index = curr - 1;
} else if (way.isClosed()) {
index = length - 2;
}
if (index !== -1) {
context.enter(
modeSelect(context, [way.nodes[index]]).follow(true).suppressMenu(true)
);
}
}
function nextVertex() {
d3.event.preventDefault();
var parent = singularParent();
if (!parent) return;
var way = context.entity(parent),
length = way.nodes.length,
curr = way.nodes.indexOf(selectedIDs[0]),
index = -1;
if (curr < length - 1) {
index = curr + 1;
} else if (way.isClosed()) {
index = 0;
}
if (index !== -1) {
context.enter(
modeSelect(context, [way.nodes[index]]).follow(true).suppressMenu(true)
);
}
}
function nextParent() {
d3.event.preventDefault();
var parents = _.uniq(commonParents());
if (!parents || parents.length < 2) return;
var index = parents.indexOf(relatedParent);
if (index < 0 || index > parents.length - 2) {
relatedParent = parents[0];
} else {
relatedParent = parents[index + 1];
}
var surface = context.surface();
surface.selectAll('.related')
.classed('related', false);
if (relatedParent) {
surface.selectAll(utilEntitySelector([relatedParent]))
.classed('related', true);
}
}
if (!checkSelectedIDs()) return;
behaviors.forEach(function(behavior) {
context.install(behavior);
});
@@ -211,6 +403,11 @@ export function modeSelect(context, selectedIDs) {
operations.unshift(Operations.operationDelete(selectedIDs, context));
keybinding
.on(['[','pgup'], previousVertex)
.on([']', 'pgdown'], nextVertex)
.on([uiCmd('⌘['), 'home'], firstVertex)
.on([uiCmd('⌘]'), 'end'], lastVertex)
.on(['\\', 'pause'], nextParent)
.on('⎋', esc, true)
.on('space', toggleMenu);
@@ -248,6 +445,18 @@ export function modeSelect(context, selectedIDs) {
positionMenu();
}
if (follow) {
var extent = geoExtent(),
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);
}
timeout = window.setTimeout(function() {
if (show) {
showMenu();
@@ -281,11 +490,19 @@ export function modeSelect(context, selectedIDs) {
.on('undone.select', null)
.on('redone.select', null);
context.surface()
.on('dblclick.select', null)
var surface = context.surface();
surface
.on('dblclick.select', null);
surface
.selectAll('.selected')
.classed('selected', false);
surface
.selectAll('.related')
.classed('related', false);
context.map().on('drawn.select', null);
context.ui().sidebar.hide();
};
+6 -3
View File
@@ -26,7 +26,7 @@ export function operationDelete(selectedIDs, context) {
annotation = t('operations.delete.annotation.' + geometry);
// Select the next closest node in the way.
if (geometry === 'vertex' && parents.length === 1 && parent.nodes.length > 2) {
if (geometry === 'vertex' && parent.nodes.length > 2) {
var nodes = parent.nodes,
i = nodes.indexOf(id);
@@ -44,13 +44,16 @@ export function operationDelete(selectedIDs, context) {
}
}
context.perform(action, annotation);
if (nextSelectedID && context.hasEntity(nextSelectedID)) {
context.enter(modeSelect(context, [nextSelectedID]));
context.enter(
modeSelect(context, [nextSelectedID]).follow(true).suppressMenu(true)
);
} else {
context.enter(modeBrowse(context));
}
context.perform(action, annotation);
};
+1 -1
View File
@@ -286,7 +286,7 @@ _.extend(osmRelation.prototype, {
for (o = 0; o < outers.length; o++) {
outer = outers[o];
if (geoPolygonIntersectsPolygon(outer, inner))
if (geoPolygonIntersectsPolygon(outer, inner, false))
return o;
}
}
+177 -5
View File
@@ -1,5 +1,177 @@
export { presetCategory } from './category.js';
export { presetCollection } from './collection.js';
export { presetField } from './field.js';
export { presetInit } from './init.js';
export { presetPreset } from './preset.js';
import _ from 'lodash';
import { data } from '../../data/index';
import { presetCategory } from './category';
import { presetCollection } from './collection';
import { presetField } from './field';
import { presetPreset } from './preset';
export { presetCategory };
export { presetCollection };
export { presetField };
export { presetPreset };
export function presetIndex() {
// a presetCollection with methods for
// loading new data and returning defaults
var all = presetCollection([]),
defaults = { area: all, line: all, point: all, vertex: all, relation: all },
fields = {},
universal = [],
recent = presetCollection([]);
// Index of presets by (geometry, tag key).
var index = {
point: {},
vertex: {},
line: {},
area: {},
relation: {}
};
all.match = function(entity, resolver) {
var geometry = entity.geometry(resolver);
// Treat entities on addr:interpolation lines as points, not vertices (#3241)
if (geometry === 'vertex' && entity.isOnAddressLine(resolver)) {
geometry = 'point';
}
var geometryMatches = index[geometry],
best = -1,
match;
for (var k in entity.tags) {
var keyMatches = geometryMatches[k];
if (!keyMatches) continue;
for (var i = 0; i < keyMatches.length; i++) {
var score = keyMatches[i].matchScore(entity);
if (score > best) {
best = score;
match = keyMatches[i];
}
}
}
return match || all.item(geometry);
};
// Because of the open nature of tagging, iD will never have a complete
// list of tags used in OSM, so we want it to have logic like "assume
// that a closed way with an amenity tag is an area, unless the amenity
// is one of these specific types". This function computes a structure
// that allows testing of such conditions, based on the presets designated
// as as supporting (or not supporting) the area geometry.
//
// The returned object L is a whitelist/blacklist of tags. A closed way
// with a tag (k, v) is considered to be an area if `k in L && !(v in L[k])`
// (see `Way#isArea()`). In other words, the keys of L form the whitelist,
// and the subkeys form the blacklist.
all.areaKeys = function() {
var areaKeys = {},
ignore = ['barrier', 'highway', 'footway', 'railway', 'type'],
presets = _.reject(all.collection, 'suggestion');
// whitelist
presets.forEach(function(d) {
for (var key in d.tags) break;
if (!key) return;
if (ignore.indexOf(key) !== -1) return;
if (d.geometry.indexOf('area') !== -1) {
areaKeys[key] = areaKeys[key] || {};
}
});
// blacklist
presets.forEach(function(d) {
for (var key in d.tags) break;
if (!key) return;
if (ignore.indexOf(key) !== -1) return;
var value = d.tags[key];
if (d.geometry.indexOf('area') === -1 &&
d.geometry.indexOf('line') !== -1 &&
key in areaKeys && value !== '*') {
areaKeys[key][value] = true;
}
});
return areaKeys;
};
all.init = function() {
var d = data.presets;
if (d.fields) {
_.forEach(d.fields, function(d, id) {
fields[id] = presetField(id, d);
if (d.universal) universal.push(fields[id]);
});
}
if (d.presets) {
_.forEach(d.presets, function(d, id) {
all.collection.push(presetPreset(id, d, fields));
});
}
if (d.categories) {
_.forEach(d.categories, function(d, id) {
all.collection.push(presetCategory(id, d, all));
});
}
if (d.defaults) {
var getItem = _.bind(all.item, all);
defaults = {
area: presetCollection(d.defaults.area.map(getItem)),
line: presetCollection(d.defaults.line.map(getItem)),
point: presetCollection(d.defaults.point.map(getItem)),
vertex: presetCollection(d.defaults.vertex.map(getItem)),
relation: presetCollection(d.defaults.relation.map(getItem))
};
}
for (var i = 0; i < all.collection.length; i++) {
var preset = all.collection[i],
geometry = preset.geometry;
for (var j = 0; j < geometry.length; j++) {
var g = index[geometry[j]];
for (var k in preset.tags) {
(g[k] = g[k] || []).push(preset);
}
}
}
return all;
};
all.field = function(id) {
return fields[id];
};
all.universal = function() {
return universal;
};
all.defaults = function(geometry, n) {
var rec = recent.matchGeometry(geometry).collection.slice(0, 4),
def = _.uniq(rec.concat(defaults[geometry].collection)).slice(0, n - 1);
return presetCollection(_.uniq(rec.concat(def).concat(all.item(geometry))));
};
all.choose = function(preset) {
if (!preset.isFallback()) {
recent = presetCollection(_.uniq([preset].concat(recent.collection)));
}
return all;
};
return all;
}
-168
View File
@@ -1,168 +0,0 @@
import _ from 'lodash';
import { presetCategory } from './category';
import { presetCollection } from './collection';
import { presetField } from './field';
import { presetPreset } from './preset';
export function presetInit() {
// a presetCollection with methods for
// loading new data and returning defaults
var all = presetCollection([]),
defaults = { area: all, line: all, point: all, vertex: all, relation: all },
fields = {},
universal = [],
recent = presetCollection([]);
// Index of presets by (geometry, tag key).
var index = {
point: {},
vertex: {},
line: {},
area: {},
relation: {}
};
all.match = function(entity, resolver) {
var geometry = entity.geometry(resolver);
// Treat entities on addr:interpolation lines as points, not vertices (#3241)
if (geometry === 'vertex' && entity.isOnAddressLine(resolver)) {
geometry = 'point';
}
var geometryMatches = index[geometry],
best = -1,
match;
for (var k in entity.tags) {
var keyMatches = geometryMatches[k];
if (!keyMatches) continue;
for (var i = 0; i < keyMatches.length; i++) {
var score = keyMatches[i].matchScore(entity);
if (score > best) {
best = score;
match = keyMatches[i];
}
}
}
return match || all.item(geometry);
};
// Because of the open nature of tagging, iD will never have a complete
// list of tags used in OSM, so we want it to have logic like "assume
// that a closed way with an amenity tag is an area, unless the amenity
// is one of these specific types". This function computes a structure
// that allows testing of such conditions, based on the presets designated
// as as supporting (or not supporting) the area geometry.
//
// The returned object L is a whitelist/blacklist of tags. A closed way
// with a tag (k, v) is considered to be an area if `k in L && !(v in L[k])`
// (see `Way#isArea()`). In other words, the keys of L form the whitelist,
// and the subkeys form the blacklist.
all.areaKeys = function() {
var areaKeys = {},
ignore = ['barrier', 'highway', 'footway', 'railway', 'type'],
presets = _.reject(all.collection, 'suggestion');
// whitelist
presets.forEach(function(d) {
for (var key in d.tags) break;
if (!key) return;
if (ignore.indexOf(key) !== -1) return;
if (d.geometry.indexOf('area') !== -1) {
areaKeys[key] = areaKeys[key] || {};
}
});
// blacklist
presets.forEach(function(d) {
for (var key in d.tags) break;
if (!key) return;
if (ignore.indexOf(key) !== -1) return;
var value = d.tags[key];
if (d.geometry.indexOf('area') === -1 &&
d.geometry.indexOf('line') !== -1 &&
key in areaKeys && value !== '*') {
areaKeys[key][value] = true;
}
});
return areaKeys;
};
all.load = function(d) {
if (d.fields) {
_.forEach(d.fields, function(d, id) {
fields[id] = presetField(id, d);
if (d.universal) universal.push(fields[id]);
});
}
if (d.presets) {
_.forEach(d.presets, function(d, id) {
all.collection.push(presetPreset(id, d, fields));
});
}
if (d.categories) {
_.forEach(d.categories, function(d, id) {
all.collection.push(presetCategory(id, d, all));
});
}
if (d.defaults) {
var getItem = _.bind(all.item, all);
defaults = {
area: presetCollection(d.defaults.area.map(getItem)),
line: presetCollection(d.defaults.line.map(getItem)),
point: presetCollection(d.defaults.point.map(getItem)),
vertex: presetCollection(d.defaults.vertex.map(getItem)),
relation: presetCollection(d.defaults.relation.map(getItem))
};
}
for (var i = 0; i < all.collection.length; i++) {
var preset = all.collection[i],
geometry = preset.geometry;
for (var j = 0; j < geometry.length; j++) {
var g = index[geometry[j]];
for (var k in preset.tags) {
(g[k] = g[k] || []).push(preset);
}
}
}
return all;
};
all.field = function(id) {
return fields[id];
};
all.universal = function() {
return universal;
};
all.defaults = function(geometry, n) {
var rec = recent.matchGeometry(geometry).collection.slice(0, 4),
def = _.uniq(rec.concat(defaults[geometry].collection)).slice(0, n - 1);
return presetCollection(_.uniq(rec.concat(def).concat(all.item(geometry))));
};
all.choose = function(preset) {
if (!preset.isFallback()) {
recent = presetCollection(_.uniq([preset].concat(recent.collection)));
}
return all;
};
return all;
}
+47 -21
View File
@@ -1,10 +1,11 @@
import * as d3 from 'd3';
import _ from 'lodash';
import { utilRebind } from '../util/rebind';
import { data } from '../../data/index';
import { geoExtent, geoMetersToOffset, geoOffsetToMeters} from '../geo/index';
import { utilQsString, utilStringQs } from '../util/index';
import { rendererBackgroundSource } from './background_source';
import { rendererTileLayer } from './tile_layer';
import { utilQsString, utilStringQs } from '../util/index';
import { utilRebind } from '../util/rebind';
export function rendererBackground(context) {
@@ -26,9 +27,9 @@ export function rendererBackground(context) {
.data([0]);
base.enter()
.insert('div', '.layer-data')
.insert('div', '.layer-data')
.attr('class', 'layer layer-background')
.merge(base)
.merge(base)
.call(baseLayer);
var overlays = selection.selectAll('.layer-overlay')
@@ -38,16 +39,19 @@ export function rendererBackground(context) {
.remove();
overlays.enter()
.insert('div', '.layer-data')
.insert('div', '.layer-data')
.attr('class', 'layer layer-overlay')
.merge(overlays)
.merge(overlays)
.each(function(layer) { d3.select(this).call(layer); });
}
background.updateImagery = function() {
var b = background.baseLayerSource(),
o = overlayLayers.map(function (d) { return d.source().id; }).join(','),
o = overlayLayers
.filter(function (d) { return !d.source().isLocatorOverlay(); })
.map(function (d) { return d.source().id; })
.join(','),
meters = geoOffsetToMeters(b.offset()),
epsilon = 0.01,
x = +meters[0].toFixed(2),
@@ -81,12 +85,9 @@ export function rendererBackground(context) {
var imageryUsed = [b.imageryUsed()];
overlayLayers.forEach(function (d) {
var source = d.source();
if (!source.isLocatorOverlay()) {
imageryUsed.push(source.imageryUsed());
}
});
overlayLayers
.filter(function (d) { return !d.source().isLocatorOverlay(); })
.forEach(function (d) { imageryUsed.push(d.source().imageryUsed()); });
var gpx = context.layers().layer('gpx');
if (gpx && gpx.enabled() && gpx.hasGpx()) {
@@ -126,7 +127,32 @@ export function rendererBackground(context) {
background.baseLayerSource = function(d) {
if (!arguments.length) return baseLayer.source();
baseLayer.source(d);
// test source against OSM imagery blacklists..
var blacklists = context.connection().imageryBlacklists();
var fail = false,
tested = 0,
regex, i;
for (i = 0; i < blacklists; i++) {
try {
regex = new RegExp(blacklists[i]);
fail = regex.test(d.template);
tested++;
if (fail) break;
} catch (e) {
/* noop */
}
}
// ensure at least one test was run.
if (!tested) {
regex = new RegExp('.*\.google(apis)?\..*/(vt|kh)[\?/].*([xyz]=.*){3}.*');
fail = regex.test(d.template);
}
baseLayer.source(!fail ? d : rendererBackgroundSource.None());
dispatch.call('change');
background.updateImagery();
return background;
@@ -139,9 +165,8 @@ export function rendererBackground(context) {
background.showsLayer = function(d) {
return d === baseLayer.source() ||
(d.id === 'custom' && baseLayer.source().id === 'custom') ||
overlayLayers.some(function(l) { return l.source() === d; });
return d.id === baseLayer.source().id ||
overlayLayers.some(function(layer) { return d.id === layer.source().id; });
};
@@ -191,20 +216,21 @@ export function rendererBackground(context) {
};
background.load = function(imagery) {
background.init = function() {
function parseMap(qmap) {
if (!qmap) return false;
var args = qmap.split('/').map(Number);
if (args.length < 3 || args.some(isNaN)) return false;
return geoExtent([args[1], args[2]]);
return geoExtent([args[2], args[1]]);
}
var q = utilStringQs(window.location.hash.substring(1)),
var dataImagery = data.imagery || [],
q = utilStringQs(window.location.hash.substring(1)),
chosen = q.background || q.layer,
extent = parseMap(q.map),
best;
backgroundSources = imagery.map(function(source) {
backgroundSources = dataImagery.map(function(source) {
if (source.type === 'bing') {
return rendererBackgroundSource.Bing(source, dispatch);
} else {
+2 -2
View File
@@ -47,7 +47,7 @@ export function rendererBackgroundSource(data) {
source.imageryUsed = function() {
return source.id || name;
return name || source.id;
};
@@ -91,7 +91,7 @@ export function rendererBackgroundSource(data) {
source.isLocatorOverlay = function() {
return name === 'Locator Overlay';
return source.id === 'mapbox_locator_overlay';
};
+96 -11
View File
@@ -17,6 +17,7 @@ import {
} from '../svg/index';
import { geoExtent } from '../geo/index';
import { modeSelect } from '../modes/select';
import {
utilFastMouse,
@@ -44,16 +45,17 @@ export function rendererMap(context) {
drawAreas = svgAreas(projection, context),
drawMidpoints = svgMidpoints(projection, context),
drawLabels = svgLabels(projection, context),
supersurface,
wrapper,
surface,
supersurface = d3.select(null),
wrapper = d3.select(null),
surface = d3.select(null),
mouse,
mousemove;
var zoom = d3.zoom()
.scaleExtent([ztok(2), ztok(24)]) // TODO: uncomment interpolate when d3.zoom 1.0.4 avail:
// .interpolate(d3.interpolate) // https://github.com/d3/d3-zoom/issues/54
.on('zoom', zoomPan); // default zoom interpolator does a fly-out-in
.scaleExtent([ztok(2), ztok(24)])
.interpolate(d3.interpolate)
.filter(zoomEventFilter)
.on('zoom', zoomPan);
var _selection = d3.select(null);
@@ -64,12 +66,31 @@ export function rendererMap(context) {
context
.on('change.map', immediateRedraw);
context.history()
context.connection()
.on('change.map', immediateRedraw);
context.history()
.on('change.map', immediateRedraw)
.on('undone.context redone.context', function(stack) {
var followSelected = false;
if (Array.isArray(stack.selectedIDs)) {
followSelected = (stack.selectedIDs.length === 1 && stack.selectedIDs[0][0] === 'n');
context.enter(
modeSelect(context, stack.selectedIDs).suppressMenu(true).follow(followSelected)
);
}
if (!followSelected && stack.transform) {
map.transformEase(stack.transform);
}
});
context.background()
.on('change.map', immediateRedraw);
context.features()
.on('redraw.map', immediateRedraw);
drawLayers
.on('change.map', function() {
context.background().updateImagery();
@@ -143,7 +164,40 @@ export function rendererMap(context) {
});
map.dimensions(utilGetDimensions(selection));
}
function zoomEventFilter() {
// Fix for #2151, (see also d3/d3-zoom#60, d3/d3-brush#18)
// Intercept `mousedown` and check if there is an orphaned zoom gesture.
// This can happen if a previous `mousedown` occurred without a `mouseup`.
// If we detect this, dispatch `mouseup` to complete the orphaned gesture,
// so that d3-zoom won't stop propagation of new `mousedown` events.
if (d3.event.type === 'mousedown') {
var hasOrphan = false;
var listeners = window.__on;
for (var i = 0; i < listeners.length; i++) {
var listener = listeners[i];
if (listener.name === 'zoom' && listener.type === 'mouseup') {
hasOrphan = true;
break;
}
}
if (hasOrphan) {
var event = window.CustomEvent;
if (event) {
event = new event('mouseup');
} else {
event = window.document.createEvent('Event');
event.initEvent('mouseup', false, false);
}
// Event needs to be dispatched with an event.view property.
event.view = window;
window.dispatchEvent(event);
}
}
return d3.event.button !== 2; // ignore right clicks
}
@@ -264,7 +318,7 @@ export function rendererMap(context) {
function redraw(difference, extent) {
if (!surface || !redrawEnabled) return;
if (surface.empty() || !redrawEnabled) return;
// If we are in the middle of a zoom/pan, we can't do differenced redraws.
// It would result in artifacts where differenced entities are redrawn with
@@ -324,9 +378,13 @@ export function rendererMap(context) {
map.mouse = function() {
var e = mousemove || d3.event, s;
while ((s = e.sourceEvent)) e = s;
return mouse(e);
var event = mousemove || d3.event;
if (event) {
var s;
while ((s = event.sourceEvent)) { event = s; }
return mouse(event);
}
return null;
};
@@ -349,6 +407,26 @@ export function rendererMap(context) {
};
function setTransform(t2, duration, force) {
var t = projection.transform();
if (!force && t2.k === t.k && t2.x === t.x && t2.y === t.y) {
return false;
}
if (duration) {
_selection
.transition()
.duration(duration)
.on('start', function() { map.startEase(); })
.call(zoom.transform, d3.zoomIdentity.translate(t2.x, t2.y).scale(t2.k));
} else {
projection.transform(t2);
transformStart = t2;
_selection.call(zoom.transform, transformStart);
}
}
function setZoom(z2, force, duration) {
if (z2 === map.zoom() && !force) {
return false;
@@ -542,6 +620,13 @@ export function rendererMap(context) {
};
map.transformEase = function(t2, duration) {
duration = duration || 250;
setTransform(t2, duration, false);
return map;
};
map.startEase = function() {
utilBindOnce(surface, 'mousedown.ease', function() {
map.cancelEase();
+31 -13
View File
@@ -16,7 +16,6 @@ var apibase = 'https://a.mapillary.com/v2/',
trafficocss = 'traffico/stylesheets/traffico.css',
clientId = 'NzNRM2otQkR2SHJzaXJmNmdQWVQ0dzo1ZWYyMmYwNjdmNDdlNmVi',
maxResults = 1000,
maxPages = 10,
tileZoom = 14,
dispatch = d3.dispatch('loadedImages', 'loadedSigns'),
mapillaryCache,
@@ -43,6 +42,16 @@ function nearNullIsland(x, y, z) {
}
function maxPageAtZoom(z) {
if (z < 15) return 2;
if (z === 15) return 5;
if (z === 16) return 10;
if (z === 17) return 20;
if (z === 18) return 40;
if (z > 18) return 80;
}
function getTiles(projection) {
var s = projection.scale() * 2 * Math.PI,
z = Math.max(Math.log(s) / Math.log(2) - 8, 0),
@@ -62,6 +71,7 @@ function getTiles(projection) {
return {
id: tile.toString(),
xyz: tile,
extent: geoExtent(
projection.invert([x, y + ts]),
projection.invert([x + ts, y])
@@ -72,9 +82,11 @@ function getTiles(projection) {
function loadTiles(which, url, projection) {
var s = projection.scale() * 2 * Math.PI,
currZoom = Math.floor(Math.max(Math.log(s) / Math.log(2) - 8, 0));
var tiles = getTiles(projection).filter(function(t) {
var xyz = t.id.split(',');
return !nearNullIsland(xyz[0], xyz[1], xyz[2]);
return !nearNullIsland(t.xyz[0], t.xyz[1], t.xyz[2]);
});
_.filter(which.inflight, function(v, k) {
@@ -84,23 +96,27 @@ function loadTiles(which, url, projection) {
}).map(abortRequest);
tiles.forEach(function(tile) {
loadTilePage(which, url, tile, 0);
loadNextTilePage(which, currZoom, url, tile);
});
}
function loadTilePage(which, url, tile, page) {
function loadNextTilePage(which, currZoom, url, tile) {
var cache = mapillaryCache[which],
id = tile.id + ',' + String(page),
rect = tile.extent.rectangle();
rect = tile.extent.rectangle(),
maxPages = maxPageAtZoom(currZoom),
nextPage = cache.nextPage[tile.id] || 0;
if (nextPage > maxPages) return;
var id = tile.id + ',' + String(nextPage);
if (cache.loaded[id] || cache.inflight[id]) return;
cache.inflight[id] = d3.json(url +
utilQsString({
geojson: 'true',
limit: maxResults,
page: page,
page: nextPage,
client_id: clientId,
min_lon: rect[0],
min_lat: rect[1],
@@ -112,7 +128,6 @@ function loadTilePage(which, url, tile, page) {
if (err || !data.features || !data.features.length) return;
var features = [],
nextPage = page + 1,
feature, loc, d;
for (var i = 0; i < data.features.length; i++) {
@@ -130,8 +145,11 @@ function loadTilePage(which, url, tile, page) {
if (which === 'images') dispatch.call('loadedImages');
if (which === 'signs') dispatch.call('loadedSigns');
if (data.features.length === maxResults && nextPage < maxPages) {
loadTilePage(which, url, tile, nextPage);
if (data.features.length === maxResults) { // more pages to load
cache.nextPage[tile.id] = nextPage + 1;
loadNextTilePage(which, currZoom, url, tile);
} else {
cache.nextPage[tile.id] = Infinity; // no more pages to load
}
}
);
@@ -196,8 +214,8 @@ export default {
}
mapillaryCache = {
images: { inflight: {}, loaded: {}, rtree: rbush() },
signs: { inflight: {}, loaded: {}, rtree: rbush() }
images: { inflight: {}, loaded: {}, nextPage: {}, rtree: rbush() },
signs: { inflight: {}, loaded: {}, nextPage: {}, rtree: rbush() }
};
mapillaryImage = null;
+84 -24
View File
@@ -9,10 +9,11 @@ import { utilDetect } from '../util/detect';
import { utilRebind } from '../util/rebind';
var dispatch = d3.dispatch('authenticating', 'authenticated', 'auth', 'loading', 'loaded'),
var dispatch = d3.dispatch('authLoading', 'authDone', 'change', 'loading', 'loaded'),
useHttps = window.location.protocol === 'https:',
protocol = useHttps ? 'https:' : 'http:',
urlroot = protocol + '//www.openstreetmap.org',
blacklists = ['.*\.google(apis)?\..*/(vt|kh)[\?/].*([xyz]=.*){3}.*'],
inflight = {},
loadedTiles = {},
tileZoom = 16,
@@ -20,25 +21,28 @@ var dispatch = d3.dispatch('authenticating', 'authenticated', 'auth', 'loading',
url: urlroot,
oauth_consumer_key: '5A043yRSEugj4DJ5TljuapfnrflWDte8jTOcWLlT',
oauth_secret: 'aB3jKq1TRsCOUrfOIZ6oQMEDmv2ptV76PA54NGLL',
loading: authenticating,
done: authenticated
loading: authLoading,
done: authDone
}),
rateLimitError,
userDetails,
off;
function authenticating() {
dispatch.call('authenticating');
function authLoading() {
dispatch.call('authLoading');
}
function authenticated() {
dispatch.call('authenticated');
function authDone() {
dispatch.call('authDone');
}
function abortRequest(i) {
i.abort();
if (i) {
i.abort();
}
}
@@ -129,10 +133,10 @@ var parsers = {
};
function parse(dom) {
if (!dom || !dom.childNodes) return;
function parse(xml) {
if (!xml || !xml.childNodes) return;
var root = dom.childNodes[0],
var root = xml.childNodes[0],
children = root.childNodes,
entities = [];
@@ -151,12 +155,13 @@ function parse(dom) {
export default {
init: function() {
this.event = utilRebind(this, dispatch, 'on');
utilRebind(this, dispatch, 'on');
},
reset: function() {
userDetails = undefined;
rateLimitError = undefined;
_.forEach(inflight, abortRequest);
loadedTiles = {};
inflight = {};
@@ -189,9 +194,34 @@ export default {
loadFromAPI: function(path, callback) {
function done(err, dom) {
return callback(err, parse(dom));
var that = this;
function done(err, xml) {
var isAuthenticated = that.authenticated();
// 400 Bad Request, 401 Unauthorized, 403 Forbidden
// Logout and retry the request..
if (isAuthenticated && err &&
(err.status === 400 || err.status === 401 || err.status === 403)) {
that.logout();
that.loadFromAPI(path, callback);
// else, no retry..
} else {
// 509 Bandwidth Limit Exceeded, 429 Too Many Requests
// Set the rateLimitError flag and trigger a warning..
if (!isAuthenticated && !rateLimitError && err &&
(err.status === 509 || err.status === 429)) {
rateLimitError = err;
dispatch.call('change');
}
if (callback) {
callback(err, parse(xml));
}
}
}
if (this.authenticated()) {
return oauth.xhr({ method: 'GET', path: path }, done);
} else {
@@ -401,16 +431,42 @@ export default {
status: function(callback) {
function done(capabilities) {
var apiStatus = capabilities.getElementsByTagName('status');
callback(undefined, apiStatus[0].getAttribute('api'));
function done(xml) {
// update blacklists
var elements = xml.getElementsByTagName('blacklist'),
regexes = [];
for (var i = 0; i < elements.length; i++) {
var regex = elements[i].getAttribute('regex'); // needs unencode?
if (regex) {
regexes.push(regex);
}
}
if (regexes.length) {
blacklists = regexes;
}
if (rateLimitError) {
callback(rateLimitError, 'rateLimited');
} else {
var apiStatus = xml.getElementsByTagName('status'),
val = apiStatus[0].getAttribute('api');
callback(undefined, val);
}
}
d3.xml(urlroot + '/api/capabilities').get()
.on('load', done)
.on('error', callback);
},
imageryBlacklists: function() {
return blacklists;
},
tileZoom: function(_) {
if (!arguments.length) return tileZoom;
tileZoom = _;
@@ -467,8 +523,10 @@ export default {
inflight[id] = that.loadFromAPI(
'/api/0.6/map?bbox=' + tile.extent.toParam(),
function(err, parsed) {
loadedTiles[id] = true;
delete inflight[id];
if (!err) {
loadedTiles[id] = true;
}
if (callback) {
callback(err, _.extend({ data: parsed }, tile));
@@ -477,7 +535,8 @@ export default {
if (_.isEmpty(inflight)) {
dispatch.call('loaded');
}
});
}
);
});
},
@@ -487,10 +546,10 @@ export default {
oauth.options(_.extend({
url: urlroot,
loading: authenticating,
done: authenticated
loading: authLoading,
done: authDone
}, options));
dispatch.call('auth');
dispatch.call('change');
this.reset();
return this;
},
@@ -512,7 +571,7 @@ export default {
logout: function() {
userDetails = undefined;
oauth.logout();
dispatch.call('auth');
dispatch.call('change');
return this;
},
@@ -520,7 +579,8 @@ export default {
authenticate: function(callback) {
userDetails = undefined;
function done(err, res) {
dispatch.call('auth');
rateLimitError = undefined;
dispatch.call('change');
if (callback) callback(err, res);
}
return oauth.authenticate(done);
+3 -2
View File
@@ -1,9 +1,9 @@
import * as d3 from 'd3';
import { geoPolygonIntersectsPolygon } from '../geo/index';
import {
data,
dataImperial,
dataDriveLeft,
dataImagery
dataDriveLeft
} from '../../data/index';
@@ -83,6 +83,7 @@ export function svgDebug(projection, context) {
var extent = context.map().extent(),
dataImagery = data.imagery || [],
availableImagery = showsImagery && multipolygons(dataImagery.filter(function(source) {
if (!source.polygon) return false;
return source.polygon.some(function(polygon) {
+62 -27
View File
@@ -244,9 +244,9 @@ export function svgLabels(projection, context) {
function drawLabels(selection, graph, entities, filter, dimensions, fullRedraw) {
var hidePoints = !selection.selectAll('.node.point').node();
var lowZoom = context.surface().classed('low-zoom');
var labelable = [], i, j, k, entity;
var labelable = [], i, j, k, entity, geometry;
for (i = 0; i < labelStack.length; i++) {
labelable.push([]);
}
@@ -272,12 +272,8 @@ export function svgLabels(projection, context) {
// Split entities into groups specified by labelStack
for (i = 0; i < entities.length; i++) {
entity = entities[i];
var geometry = entity.geometry(graph);
if (geometry === 'vertex')
continue;
if (hidePoints && geometry === 'point')
continue;
geometry = entity.geometry(graph);
if (geometry === 'vertex') { geometry = 'point'; } // treat vertex like point
var preset = geometry === 'area' && context.presets().match(entity, graph),
icon = preset && !blacklisted(preset) && preset.icon;
@@ -315,29 +311,37 @@ export function svgLabels(projection, context) {
var fontSize = labelStack[k][3];
for (i = 0; i < labelable[k].length; i++) {
entity = labelable[k][i];
geometry = entity.geometry(graph);
var name = utilDisplayName(entity),
width = name && textWidth(name, fontSize),
p;
if (entity.geometry(graph) === 'point') {
p = getPointLabel(entity, width, fontSize);
} else if (entity.geometry(graph) === 'line') {
if (geometry === 'point') {
p = getPointLabel(entity, width, fontSize, geometry);
} else if (geometry === 'vertex' && !lowZoom) {
// don't label vertices at low zoom because they don't have icons
p = getPointLabel(entity, width, fontSize, geometry);
} else if (geometry === 'line') {
p = getLineLabel(entity, width, fontSize);
} else if (entity.geometry(graph) === 'area') {
} else if (geometry === 'area') {
p = getAreaLabel(entity, width, fontSize);
}
if (p) {
p.classes = entity.geometry(graph) + ' tag-' + labelStack[k][1];
positions[entity.geometry(graph)].push(p);
labelled[entity.geometry(graph)].push(entity);
if (geometry === 'vertex') { geometry = 'point'; } // treat vertex like point
p.classes = geometry + ' tag-' + labelStack[k][1];
positions[geometry].push(p);
labelled[geometry].push(entity);
}
}
}
function getPointLabel(entity, width, height) {
var pointOffsets = {
ltr: [15, -12, 'start'],
rtl: [-15, -12, 'end']
function getPointLabel(entity, width, height, geometry) {
var y = (geometry === 'point' ? -12 : 0),
pointOffsets = {
ltr: [15, y, 'start'],
rtl: [-15, y, 'end']
};
var coord = projection(entity.loc),
@@ -584,35 +588,66 @@ export function svgLabels(projection, context) {
// debug
drawCollisionBoxes(label, rskipped, 'debug-skipped');
drawCollisionBoxes(label, rdrawn, 'debug-drawn');
selection.call(filterLabels);
}
function hideOnMouseover() {
if (d3.event.buttons) return;
var layers = d3.select(this)
function filterLabels(selection) {
var layers = selection
.selectAll('.layer-label, .layer-halo');
layers.selectAll('.proximate')
.classed('proximate', false);
var mouse = context.mouse(),
pad = 20,
bbox = { minX: mouse[0] - pad, minY: mouse[1] - pad, maxX: mouse[0] + pad, maxY: mouse[1] + pad },
ids = _.map(rdrawn.search(bbox), 'id');
graph = context.graph(),
selectedIDs = context.selectedIDs(),
ids = [],
pad, bbox;
// hide labels near the mouse
if (mouse) {
pad = 20;
bbox = { minX: mouse[0] - pad, minY: mouse[1] - pad, maxX: mouse[0] + pad, maxY: mouse[1] + pad };
ids.push.apply(ids, _.map(rdrawn.search(bbox), 'id'));
}
// hide labels along selected ways, or near selected vertices
for (var i = 0; i < selectedIDs.length; i++) {
var entity = graph.hasEntity(selectedIDs[i]);
if (!entity) continue;
var geometry = entity.geometry(graph);
if (geometry === 'line') {
ids.push(selectedIDs[i]);
} else if (geometry === 'vertex') {
var point = context.projection(entity.loc);
pad = 10;
bbox = { minX: point[0] - pad, minY: point[1] - pad, maxX: point[0] + pad, maxY: point[1] + pad };
ids.push.apply(ids, _.map(rdrawn.search(bbox), 'id'));
}
}
layers.selectAll(utilEntitySelector(ids))
.classed('proximate', true);
}
var throttleFilterLabels = _.throttle(filterLabels, 100);
drawLabels.observe = function(selection) {
selection.on('mousemove.hidelabels', hideOnMouseover);
var listener = function() { throttleFilterLabels(selection); };
selection.on('mousemove.hidelabels', listener);
context.on('enter.hidelabels', listener);
};
drawLabels.off = function(selection) {
throttleFilterLabels.cancel();
selection.on('mousemove.hidelabels', null);
context.on('enter.hidelabels', null);
};
+1 -1
View File
@@ -7,7 +7,7 @@ import { services } from '../services/index';
export function svgMapillaryImages(projection, context, dispatch) {
var throttledRedraw = _.throttle(function () { dispatch.call('change'); }, 1000),
minZoom = 12,
minViewfieldZoom = 16,
minViewfieldZoom = 17,
layer = d3.select(null),
_mapillary;
+3 -1
View File
@@ -67,7 +67,9 @@ export function uiAccount(context) {
.attr('id', 'userLink')
.classed('hide', true);
connection.event.on('auth.account', function() { update(selection); });
connection
.on('change.account', function() { update(selection); });
update(selection);
};
}
+93 -50
View File
@@ -58,21 +58,23 @@ export function uiBackground(context) {
function setTooltips(selection) {
selection.each(function(d) {
var item = d3.select(this);
selection.each(function(d, i, nodes) {
var item = d3.select(this).select('label'),
placement = (i < nodes.length / 2) ? 'bottom' : 'top';
if (d === previous) {
item.call(tooltip()
.placement(placement)
.html(true)
.title(function() {
var tip = '<div>' + t('background.switch') + '</div>';
return uiTooltipHtml(tip, uiCmd('⌘B'));
})
.placement('top')
);
} else if (d.description) {
item.call(tooltip()
.placement(placement)
.title(d.description)
.placement('top')
);
} else {
item.call(tooltip().destroy);
@@ -96,8 +98,8 @@ export function uiBackground(context) {
function clickSetSource(d) {
previous = context.background().baseLayerSource();
d3.event.preventDefault();
previous = context.background().baseLayerSource();
context.background().baseLayerSource(d);
selectLayer();
document.activeElement.blur();
@@ -107,21 +109,19 @@ export function uiBackground(context) {
function editCustom() {
d3.event.preventDefault();
var template = window.prompt(t('background.custom_prompt'), customTemplate);
if (!template ||
template.indexOf('google.com') !== -1 ||
template.indexOf('googleapis.com') !== -1 ||
template.indexOf('google.ru') !== -1) {
if (template) {
setCustom(template);
} else {
selectLayer();
return;
}
setCustom(template);
}
function setCustom(template) {
context.background().baseLayerSource(rendererBackgroundSource.Custom(template));
selectLayer();
context.storage('background-custom-template', template);
var d = rendererBackgroundSource.Custom(template);
content.selectAll('.custom_layer').datum(d);
clickSetSource(d);
}
@@ -141,6 +141,9 @@ export function uiBackground(context) {
var layerLinks = layerList.selectAll('li.layer')
.data(sources, function(d) { return d.name(); });
layerLinks.exit()
.remove();
var enter = layerLinks.enter()
.insert('li', '.custom_layer')
.attr('class', 'layer')
@@ -155,20 +158,20 @@ export function uiBackground(context) {
.append('span')
.html('&#9733;');
var label = enter.append('label');
var label = enter
.append('label');
label.append('input')
label
.append('input')
.attr('type', type)
.attr('name', 'layers')
.on('change', change);
label.append('span')
label
.append('span')
.text(function(d) { return d.name(); });
layerLinks.exit()
.remove();
layerList.selectAll('li.layer')
.sort(sortSources)
.style('display', layerList.selectAll('li.layer').data().length > 0 ? 'block' : 'none');
@@ -208,6 +211,7 @@ export function uiBackground(context) {
function resetOffset() {
if (d3.event.button !== 0) return;
context.background().offset([0, 0]);
updateOffsetVal();
}
@@ -220,22 +224,30 @@ export function uiBackground(context) {
function buttonOffset(d) {
if (d3.event.button !== 0) return;
var timeout = window.setTimeout(function() {
interval = window.setInterval(nudge.bind(null, d), 100);
}, 500),
interval;
d3.select(window).on('mouseup', function() {
window.clearInterval(interval);
function doneNudge() {
window.clearTimeout(timeout);
d3.select(window).on('mouseup', null);
});
window.clearInterval(interval);
d3.select(window)
.on('mouseup.buttonoffset', null, true)
.on('mousedown.buttonoffset', null, true);
}
d3.select(window)
.on('mouseup.buttonoffset', doneNudge, true)
.on('mousedown.buttonoffset', doneNudge, true);
nudge(d);
}
function inputOffset() {
if (d3.event.button !== 0) return;
var input = d3.select(this);
var d = input.node().value;
@@ -257,6 +269,7 @@ export function uiBackground(context) {
function dragOffset() {
if (d3.event.button !== 0) return;
var origin = [d3.event.clientX, d3.event.clientY];
context.container()
@@ -275,6 +288,7 @@ export function uiBackground(context) {
nudge(d);
})
.on('mouseup.offset', function() {
if (d3.event.button !== 0) return;
d3.selectAll('.nudge-surface')
.remove();
@@ -313,15 +327,17 @@ export function uiBackground(context) {
if (show) {
selection.on('mousedown.background-inside', function() {
return d3.event.stopPropagation();
d3.event.stopPropagation();
});
content.style('display', 'block')
content
.style('display', 'block')
.style('right', '-300px')
.transition()
.duration(200)
.style('right', '0px');
} else {
content.style('display', 'block')
content
.style('display', 'block')
.style('right', '0px')
.transition()
.duration(200)
@@ -335,13 +351,15 @@ export function uiBackground(context) {
}
var content = selection.append('div')
var content = selection
.append('div')
.attr('class', 'fillL map-overlay col3 content hide'),
tooltipBehavior = tooltip()
.placement((textDirection === 'rtl') ? 'right' : 'left')
.html(true)
.title(uiTooltipHtml(t('background.description'), key)),
button = selection.append('button')
button = selection
.append('button')
.attr('tabindex', -1)
.on('click', toggle)
.call(svgIcon('#icon-layers', 'light'))
@@ -351,13 +369,16 @@ export function uiBackground(context) {
/* opacity switcher */
var opa = content.append('div')
var opawrap = content
.append('div')
.attr('class', 'opacity-options-wrapper');
opa.append('h4')
opawrap
.append('h4')
.text(t('background.title'));
var opacityList = opa.append('ul')
var opacityList = opawrap
.append('ul')
.attr('class', 'opacity-options');
opacityList.selectAll('div.opacity')
@@ -378,15 +399,18 @@ export function uiBackground(context) {
/* background switcher */
var backgroundList = content.append('ul')
var backgroundList = content
.append('ul')
.attr('class', 'layer-list')
.attr('dir', 'auto');
var custom = backgroundList.append('li')
var custom = backgroundList
.append('li')
.attr('class', 'custom_layer')
.datum(rendererBackgroundSource.Custom());
custom.append('button')
custom
.append('button')
.attr('class', 'layer-browse')
.call(tooltip()
.title(t('background.custom_button'))
@@ -394,9 +418,11 @@ export function uiBackground(context) {
.on('click', editCustom)
.call(svgIcon('#icon-search'));
var label = custom.append('label');
var label = custom
.append('label');
label.append('input')
label
.append('input')
.attr('type', 'radio')
.attr('name', 'layers')
.on('change', function () {
@@ -407,10 +433,12 @@ export function uiBackground(context) {
}
});
label.append('span')
label
.append('span')
.text(t('background.custom'));
content.append('div')
content
.append('div')
.attr('class', 'imagery-faq')
.append('a')
.attr('target', '_blank')
@@ -420,10 +448,12 @@ export function uiBackground(context) {
.append('span')
.text(t('background.imagery_source_faq'));
var overlayList = content.append('ul')
var overlayList = content
.append('ul')
.attr('class', 'layer-list');
var controls = content.append('div')
var controls = content
.append('div')
.attr('class', 'controls-list');
@@ -437,7 +467,8 @@ export function uiBackground(context) {
.placement('top')
);
minimapLabel.classed('minimap-toggle', true)
minimapLabel
.classed('minimap-toggle', true)
.append('input')
.attr('type', 'checkbox')
.on('change', function() {
@@ -445,57 +476,69 @@ export function uiBackground(context) {
d3.event.preventDefault();
});
minimapLabel.append('span')
minimapLabel
.append('span')
.text(t('background.minimap.description'));
/* imagery offset controls */
var adjustments = content.append('div')
var adjustments = content
.append('div')
.attr('class', 'adjustments');
adjustments.append('a')
adjustments
.append('a')
.text(t('background.fix_misalignment'))
.attr('href', '#')
.classed('hide-toggle', true)
.classed('expanded', false)
.on('click', function() {
if (d3.event.button !== 0) return;
var exp = d3.select(this).classed('expanded');
nudgeContainer.style('display', exp ? 'none' : 'block');
d3.select(this).classed('expanded', !exp);
d3.event.preventDefault();
});
var nudgeContainer = adjustments.append('div')
var nudgeContainer = adjustments
.append('div')
.attr('class', 'nudge-container cf')
.style('display', 'none');
nudgeContainer.append('div')
nudgeContainer
.append('div')
.attr('class', 'nudge-instructions')
.text(t('background.offset'));
var nudgeRect = nudgeContainer.append('div')
var nudgeRect = nudgeContainer
.append('div')
.attr('class', 'nudge-outer-rect')
.on('mousedown', dragOffset);
nudgeRect.append('div')
nudgeRect
.append('div')
.attr('class', 'nudge-inner-rect')
.append('input')
.on('change', inputOffset)
.on('mousedown', function() {
if (d3.event.button !== 0) return;
d3.event.stopPropagation();
});
nudgeContainer.append('div')
nudgeContainer
.append('div')
.selectAll('button')
.data(directions).enter()
.append('button')
.attr('class', function(d) { return d[0] + ' nudge'; })
.on('mousedown', function(d) {
if (d3.event.button !== 0) return;
buttonOffset(d[1]);
});
nudgeContainer.append('button')
nudgeContainer
.append('button')
.attr('title', t('background.reset'))
.attr('class', 'nudge-reset disabled')
.on('click', resetOffset)
+10 -3
View File
@@ -1,11 +1,15 @@
import * as d3 from 'd3';
import _ from 'lodash';
import { d3combobox } from '../lib/d3.combobox.js';
import { t } from '../util/locale';
import { d3combobox } from '../lib/d3.combobox.js';
import { modeSelect } from '../modes/index';
import { svgIcon } from '../svg/index';
import { tooltip } from '../util/tooltip';
import { utilDisplayName, utilEntityOrMemberSelector } from '../util/index';
import {
utilDisplayName,
utilDisplayType,
utilEntityOrMemberSelector
} from '../util/index';
import { utilRebind } from '../util/rebind';
import { utilTriggerEvent } from '../util/trigger_event';
@@ -229,7 +233,10 @@ export function uiCommit(context) {
li.append('strong')
.attr('class', 'entity-type')
.text(function(d) { return context.presets().match(d.entity, d.graph).name(); });
.text(function(d) {
var matched = context.presets().match(d.entity, d.graph);
return (matched && matched.name()) || utilDisplayType(d.entity.id);
});
li.append('span')
.attr('class', 'entity-name')
+8 -2
View File
@@ -5,7 +5,11 @@ import { geoExtent, geoChooseEdge } from '../geo/index';
import { modeSelect } from '../modes/index';
import { osmEntity } from '../osm/index';
import { svgIcon } from '../svg/index';
import { utilDisplayName, utilEntityOrMemberSelector } from '../util/index';
import {
utilDisplayName,
utilDisplayType,
utilEntityOrMemberSelector
} from '../util/index';
export function uiFeatureList(context) {
@@ -111,11 +115,13 @@ export function uiFeatureList(context) {
var name = utilDisplayName(entity) || '';
if (name.toLowerCase().indexOf(q) >= 0) {
var matched = context.presets().match(entity, graph),
type = (matched && matched.name()) || utilDisplayType(entity.id);
result.push({
id: entity.id,
entity: entity,
geometry: context.geometry(entity.id),
type: context.presets().match(entity, graph).name(),
type: type,
name: name
});
}
+39 -30
View File
@@ -30,12 +30,12 @@ export function uiFieldAddress(field, context) {
};
function getStreets() {
function getNearStreets() {
var extent = entity.extent(context.graph()),
l = extent.center(),
box = geoExtent(l).padByMeters(200);
return context.intersects(box)
var streets = context.intersects(box)
.filter(isAddressable)
.map(function(d) {
var loc = context.projection([
@@ -47,22 +47,25 @@ export function uiFieldAddress(field, context) {
value: d.tags.name,
dist: choice.distance
};
}).sort(function(a, b) {
})
.sort(function(a, b) {
return a.dist - b.dist;
});
return _.uniqBy(streets, 'value');
function isAddressable(d) {
return d.tags.highway && d.tags.name && d.type === 'way';
}
}
function getCities() {
function getNearCities() {
var extent = entity.extent(context.graph()),
l = extent.center(),
box = geoExtent(l).padByMeters(200);
return context.intersects(box)
var cities = context.intersects(box)
.filter(isAddressable)
.map(function(d) {
return {
@@ -70,10 +73,14 @@ export function uiFieldAddress(field, context) {
value: d.tags['addr:city'] || d.tags.name,
dist: geoSphericalDistance(d.extent(context.graph()).center(), l)
};
}).sort(function(a, b) {
})
.sort(function(a, b) {
return a.dist - b.dist;
});
return _.uniqBy(cities, 'value');
function isAddressable(d) {
if (d.tags.name &&
(d.tags.admin_level === '8' || d.tags.border_type === 'city'))
@@ -92,26 +99,27 @@ export function uiFieldAddress(field, context) {
}
function getPostCodes() {
function getNearValues(key) {
var extent = entity.extent(context.graph()),
l = extent.center(),
box = geoExtent(l).padByMeters(200);
return context.intersects(box)
.filter(isAddressable)
var results = context.intersects(box)
.filter(function hasTag(d) {
return d.tags[key];
})
.map(function(d) {
return {
title: d.tags['addr:postcode'],
value: d.tags['addr:postcode'],
title: d.tags[key],
value: d.tags[key],
dist: geoSphericalDistance(d.extent(context.graph()).center(), l)
};
}).sort(function(a, b) {
})
.sort(function(a, b) {
return a.dist - b.dist;
});
function isAddressable(d) {
return d.tags['addr:postcode'];
}
return _.uniqBy(results, 'value');
}
@@ -151,23 +159,24 @@ export function uiFieldAddress(field, context) {
.style('width', function (d) { return d.width * 100 + '%'; });
// Update
wrap.selectAll('.addr-street')
.call(d3combobox()
.fetcher(function(value, callback) {
callback(getStreets());
}));
// setup dropdowns for common address tags
var addrTags = [
'street', 'city', 'state', 'province', 'district',
'subdistrict', 'suburb', 'place', 'postcode'
];
wrap.selectAll('.addr-city')
.call(d3combobox()
.fetcher(function(value, callback) {
callback(getCities());
}));
addrTags.forEach(function(tag) {
var nearValues = (tag === 'street') ? getNearStreets
: (tag === 'city') ? getNearCities
: getNearValues;
wrap.selectAll('.addr-postcode')
.call(d3combobox()
.fetcher(function(value, callback) {
callback(getPostCodes());
}));
wrap.selectAll('.addr-' + tag)
.call(d3combobox()
.minItems(1)
.fetcher(function(value, callback) {
callback(nearValues('addr:' + tag));
}));
});
wrap.selectAll('input')
.on('blur', change())
+18 -15
View File
@@ -11,7 +11,7 @@ export function uiFieldCheck(field) {
options = field.strings && field.strings.options,
values = [],
texts = [],
box = d3.select(null),
input = d3.select(null),
text = d3.select(null),
label = d3.select(null),
entity, value;
@@ -52,25 +52,28 @@ export function uiFieldCheck(field) {
.append('label')
.attr('class', 'preset-input-wrap');
enter.append('input')
enter
.append('input')
.property('indeterminate', field.type === 'check')
.attr('type', 'checkbox')
.attr('id', 'preset-input-' + field.id)
.attr('id', 'preset-input-' + field.id);
enter
.append('span')
.text(texts[0])
.attr('class', 'value');
label = label.merge(enter);
input = label.selectAll('input');
text = label.selectAll('span.value');
input
.on('click', function() {
var t = {};
t[field.key] = values[(values.indexOf(value) + 1) % values.length];
dispatch.call('change', this, t);
d3.event.stopPropagation();
});
enter.append('span')
.text(texts[0])
.attr('class', 'value');
label = label.merge(enter);
box = label.selectAll('input');
text = label.selectAll('span.value');
};
@@ -83,15 +86,15 @@ export function uiFieldCheck(field) {
check.tags = function(tags) {
value = tags[field.key];
box.property('indeterminate', field.type === 'check' && !value);
box.property('checked', value === 'yes');
input.property('indeterminate', field.type === 'check' && !value);
input.property('checked', value === 'yes');
text.text(texts[values.indexOf(value)]);
label.classed('set', !!value);
};
check.focus = function() {
box.node().focus();
input.node().focus();
};
return utilRebind(check, dispatch, 'on');
+7 -2
View File
@@ -6,6 +6,11 @@ import { geoExtent } from '../geo/index';
import { utilDetect } from '../util/detect';
import { uiCmd } from './cmd';
import {
geoLength as d3GeoLength,
geoCentroid as d3GeoCentroid
} from 'd3';
export function uiInfo(context) {
var key = uiCmd('⌘I'),
@@ -149,9 +154,9 @@ export function uiInfo(context) {
if (geometry === 'line' || geometry === 'area') {
var closed = (entity.type === 'relation') || (entity.isClosed() && !entity.isDegenerate()),
feature = entity.asGeoJSON(resolver),
length = radiansToMeters(d3.geoLength(toLineString(feature))),
length = radiansToMeters(d3GeoLength(toLineString(feature))),
lengthLabel = t('infobox.' + (closed ? 'perimeter' : 'length')),
centroid = d3.geoCentroid(feature);
centroid = d3GeoCentroid(feature);
list.append('li')
.text(t('infobox.geometry') + ': ' +
+16 -14
View File
@@ -256,14 +256,10 @@ export function uiInit(context) {
.on('↑', pan([0, pa]))
.on('→', pan([-pa, 0]))
.on('↓', pan([0, -pa]))
.on('⇧←', pan([mapDimensions[0], 0]))
.on('⇧↑', pan([0, mapDimensions[1]]))
.on('⇧→', pan([-mapDimensions[0], 0]))
.on('⇧↓', pan([0, -mapDimensions[1]]))
.on(uiCmd('⌘←'), pan([mapDimensions[0], 0]))
.on(uiCmd('⌘↑'), pan([0, mapDimensions[1]]))
.on(uiCmd('⌘→'), pan([-mapDimensions[0], 0]))
.on(uiCmd('⌘↓'), pan([0, -mapDimensions[1]]));
.on(['⇧←', uiCmd('⌘←')], pan([mapDimensions[0], 0]))
.on(['⇧↑', uiCmd('⌘↑')], pan([0, mapDimensions[1]]))
.on(['⇧→', uiCmd('⌘→')], pan([-mapDimensions[0], 0]))
.on(['⇧↓', uiCmd('⌘↓')], pan([0, -mapDimensions[1]]));
d3.select(document)
.call(keybinding);
@@ -275,24 +271,30 @@ export function uiInit(context) {
.call(uiRestore(context));
var authenticating = uiLoading(context)
.message(t('loading_auth'));
.message(t('loading_auth'))
.blocking(true);
context.connection()
.on('authenticating.ui', function() {
.on('authLoading.ui', function() {
context.container()
.call(authenticating);
})
.on('authenticated.ui', function() {
.on('authDone.ui', function() {
authenticating.close();
});
}
function ui(node) {
function ui(node, callback) {
var container = d3.select(node);
context.container(container);
context.loadLocale(function() {
render(container);
context.loadLocale(function(err) {
if (!err) {
render(container);
}
if (callback) {
callback(err);
}
});
}
+10 -5
View File
@@ -95,9 +95,14 @@ export function uiPreset(context) {
function content(selection) {
if (!fieldsArr) {
var entity = context.entity(id),
geometry = context.geometry(id);
geometry = context.geometry(id),
presets = context.presets();
fieldsArr = [UIField(context.presets().field('name'), entity)];
fieldsArr = [];
if (presets.field('name')) {
fieldsArr.push(UIField(presets.field('name'), entity));
}
preset.fields.forEach(function(field) {
if (field.matchGeometry(geometry)) {
@@ -105,11 +110,11 @@ export function uiPreset(context) {
}
});
if (entity.isHighwayIntersection(context.graph())) {
fieldsArr.push(UIField(context.presets().field('restrictions'), entity, true));
if (entity.isHighwayIntersection(context.graph()) && presets.field('restrictions')) {
fieldsArr.push(UIField(presets.field('restrictions'), entity, true));
}
context.presets().universal().forEach(function(field) {
presets.universal().forEach(function(field) {
if (preset.fields.indexOf(field) < 0) {
fieldsArr.push(UIField(field, entity));
}
+5 -2
View File
@@ -7,7 +7,7 @@ import { osmEntity } from '../osm/index';
import { svgIcon } from '../svg/index';
import { services } from '../services/index';
import { uiDisclosure } from './disclosure';
import { utilDisplayName } from '../util/index';
import { utilDisplayName, utilDisplayType } from '../util/index';
export function uiRawMemberEditor(context) {
@@ -109,7 +109,10 @@ export function uiRawMemberEditor(context) {
label.append('span')
.attr('class', 'member-entity-type')
.text(function(d) { return context.presets().match(d.member, context.graph()).name(); });
.text(function(d) {
var matched = context.presets().match(d.member, context.graph());
return (matched && matched.name()) || utilDisplayType(d.member.id);
});
label.append('span')
.attr('class', 'member-entity-name')
+4 -2
View File
@@ -80,7 +80,8 @@ export function uiRawMembershipEditor(context) {
if (entity.type !== 'relation' || entity.id === id)
return;
var presetName = context.presets().match(entity, graph).name(),
var matched = context.presets().match(entity, graph),
presetName = (matched && matched.name()) || t('inspector.relation'),
entityName = utilDisplayName(entity) || '';
var value = presetName + ' ' + entityName;
@@ -175,7 +176,8 @@ export function uiRawMembershipEditor(context) {
.append('span')
.attr('class', 'member-entity-type')
.text(function(d) {
return context.presets().match(d.relation, context.graph()).name();
var matched = context.presets().match(d.relation, context.graph());
return (matched && matched.name()) || t('inspector.relation');
});
label
+2
View File
@@ -28,6 +28,8 @@ export function uiScale(context) {
if (dist >= val) {
scale.dist = Math.floor(dist / val) * val;
break;
} else {
scale.dist = +dist.toFixed(2);
}
}
+2 -2
View File
@@ -8,13 +8,13 @@ export function uiSpinner(context) {
.attr('src', context.imagePath('loader-black.gif'))
.style('opacity', 0);
connection.event
connection
.on('loading.spinner', function() {
img.transition()
.style('opacity', 1);
});
connection.event
connection
.on('loaded.spinner', function() {
img.transition()
.style('opacity', 0);
+23 -6
View File
@@ -1,18 +1,36 @@
import * as d3 from 'd3';
import { t } from '../util/locale';
import { svgIcon } from '../svg/index';
export function uiStatus(context) {
var connection = context.connection(),
errCount = 0;
var connection = context.connection();
return function(selection) {
function update() {
connection.status(function(err, apiStatus) {
selection.html('');
if (err && errCount++ < 2) return;
if (err) {
selection.text(t('status.error'));
if (apiStatus === 'rateLimited') {
selection
.text(t('status.rateLimit'))
.append('a')
.attr('class', 'api-status-login')
.attr('target', '_blank')
.call(svgIcon('#icon-out-link', 'inline'))
.append('span')
.text(t('login'))
.on('click.login', function() {
d3.event.preventDefault();
connection.authenticate();
});
} else {
// TODO: nice messages for different error types
selection.text(t('status.error'));
}
} else if (apiStatus === 'readonly') {
selection.text(t('status.readonly'));
} else if (apiStatus === 'offline') {
@@ -20,12 +38,11 @@ export function uiStatus(context) {
}
selection.attr('class', 'api-status ' + (err ? 'error' : apiStatus));
if (!err) errCount = 0;
});
}
connection
.on('auth', function() { update(selection); });
.on('change', function() { update(selection); });
window.setInterval(update, 90000);
update(selection);
+5 -9
View File
@@ -72,16 +72,12 @@ export function uiZoom(context) {
var keybinding = d3keybinding('zoom');
_.each(['=','ffequals','plus','ffplus'], function(key) {
keybinding.on(key, zoomIn);
keybinding.on('⇧' + key, zoomIn);
keybinding.on(uiCmd('⌘' + key), zoomInFurther);
keybinding.on(uiCmd('⌘⇧' + key), zoomInFurther);
keybinding.on([key, '⇧' + key], zoomIn);
keybinding.on([uiCmd('⌘' + key), uiCmd('⌘⇧' + key)], zoomInFurther);
});
_.each(['-','ffminus','_','dash'], function(key) {
keybinding.on(key, zoomOut);
keybinding.on('⇧' + key, zoomOut);
keybinding.on(uiCmd('⌘' + key), zoomOutFurther);
keybinding.on(uiCmd('⌘⇧' + key), zoomOutFurther);
_.each(['-','ffminus','dash'], function(key) {
keybinding.on([key, '⇧' + key], zoomOut);
keybinding.on([uiCmd('⌘' + key), uiCmd('⌘⇧' + key)], zoomOutFurther);
});
d3.select(document)
+3 -1
View File
@@ -1,4 +1,5 @@
import { currentLocale, setTextDirection } from './locale';
import { dataLocales } from '../../data/index';
import { utilStringQs } from './index';
var detected;
@@ -70,7 +71,8 @@ export function utilDetect(force) {
// detect text direction
var q = utilStringQs(window.location.hash.substring(1));
if (['ar', 'fa', 'iw', 'dv'].indexOf(detected.locale.split('-')[0]) > -1 || q.hasOwnProperty('rtl')) {
var lang = dataLocales[detected.locale];
if ((lang && lang.rtl) || q.hasOwnProperty('rtl')) {
detected.textDirection = 'rtl';
} else {
detected.textDirection = 'ltr';