diff --git a/css/app.css b/css/app.css index 2ddd3d96a..243b1c43c 100644 --- a/css/app.css +++ b/css/app.css @@ -1797,6 +1797,10 @@ div.full-screen > button:hover { color: #7092FF; } +.layer-list:empty { + display: none; +} + .layer-list > li:first-child { border-radius: 3px 3px 0 0; } diff --git a/js/id/renderer/background.js b/js/id/renderer/background.js index b5fc14135..60f3e3449 100644 --- a/js/id/renderer/background.js +++ b/js/id/renderer/background.js @@ -4,8 +4,8 @@ iD.Background = function(context) { .projection(context.projection), gpxLayer = iD.GpxLayer(context, dispatch) .projection(context.projection), - mapillaryImageLayer = iD.MapillaryImageLayer(context), - mapillarySignLayer = iD.MapillarySignLayer(context), + mapillaryImageLayer, + mapillarySignLayer, overlayLayers = []; var backgroundSources; @@ -86,21 +86,43 @@ iD.Background = function(context) { gpx.call(gpxLayer); + + var mapillary = iD.services.mapillary, + supportsMapillaryImages = !!mapillary, + supportsMapillarySigns = !!mapillary && mapillary().signsSupported(); + var mapillaryImages = selection.selectAll('.layer-mapillary-images') - .data([0]); + .data(supportsMapillaryImages ? [0] : []); mapillaryImages.enter().insert('div') .attr('class', 'layer-layer layer-mapillary-images'); - mapillaryImages.call(mapillaryImageLayer); + if (supportsMapillaryImages) { + if (!mapillaryImageLayer) { mapillaryImageLayer = iD.MapillaryImageLayer(context); } + mapillaryImages.call(mapillaryImageLayer); + } else { + mapillaryImageLayer = null; + } + + mapillaryImages.exit() + .remove(); + var mapillarySigns = selection.selectAll('.layer-mapillary-signs') - .data([0]); + .data(supportsMapillarySigns ? [0] : []); mapillarySigns.enter().insert('div') .attr('class', 'layer-layer layer-mapillary-signs'); - mapillarySigns.call(mapillarySignLayer); + if (supportsMapillarySigns) { + if (!mapillarySignLayer) { mapillarySignLayer = iD.MapillarySignLayer(context); } + mapillarySigns.call(mapillarySignLayer); + } else { + mapillarySignLayer = null; + } + + mapillarySigns.exit() + .remove(); } background.sources = function(extent) { @@ -112,8 +134,8 @@ iD.Background = function(context) { background.dimensions = function(_) { baseLayer.dimensions(_); gpxLayer.dimensions(_); - mapillaryImageLayer.dimensions(_); - mapillarySignLayer.dimensions(_); + if (mapillaryImageLayer) mapillaryImageLayer.dimensions(_); + if (mapillarySignLayer) mapillarySignLayer.dimensions(_); overlayLayers.forEach(function(layer) { layer.dimensions(_); @@ -183,22 +205,25 @@ iD.Background = function(context) { }; background.showsMapillaryImageLayer = function() { - return mapillaryImageLayer.enable(); + return mapillaryImageLayer && mapillaryImageLayer.enable(); }; background.showsMapillarySignLayer = function() { - return mapillarySignLayer.enable(); + return mapillarySignLayer && mapillarySignLayer.enable(); }; background.toggleMapillaryImageLayer = function() { + if (!mapillaryImageLayer) return; mapillaryImageLayer.enable(!mapillaryImageLayer.enable()); dispatch.change(); }; background.toggleMapillarySignLayer = function() { + if (!mapillarySignLayer) return; mapillarySignLayer.enable(!mapillarySignLayer.enable()); dispatch.change(); }; + background.showsLayer = function(d) { return d === baseLayer.source() || (d.id === 'custom' && baseLayer.source().id === 'custom') || diff --git a/js/id/renderer/mapillary_image_layer.js b/js/id/renderer/mapillary_image_layer.js index 8e5fd288a..8fceb5fb8 100644 --- a/js/id/renderer/mapillary_image_layer.js +++ b/js/id/renderer/mapillary_image_layer.js @@ -1,12 +1,23 @@ iD.MapillaryImageLayer = function(context) { - var mapillary = iD.services.mapillary(), - debouncedRedraw = _.debounce(function () { context.pan([0,0]); }, 1000), + var debouncedRedraw = _.debounce(function () { context.pan([0,0]); }, 1000), enabled = false, minZoom = 12, - layer; + layer = d3.select(null), + _mapillary; + function getMapillary() { + if (iD.services.mapillary && !_mapillary) { + _mapillary = iD.services.mapillary().on('loadedImages', debouncedRedraw); + } else if (!iD.services.mapillary && _mapillary) { + _mapillary = null; + } + return _mapillary; + } function showThumbnail(image) { + var mapillary = getMapillary(); + if (!mapillary) return; + var thumb = mapillary.selectedThumbnail(), posX = context.projection(image.loc)[0], width = layer.dimensions()[0], @@ -24,7 +35,10 @@ iD.MapillaryImageLayer = function(context) { d3.selectAll('.layer-mapillary-images .viewfield-group, .layer-mapillary-signs .icon-sign') .classed('selected', false); - mapillary.hideThumbnail(); + var mapillary = getMapillary(); + if (mapillary) { + mapillary.hideThumbnail(); + } } function showLayer() { @@ -63,7 +77,8 @@ iD.MapillaryImageLayer = function(context) { } function drawMarkers() { - var data = mapillary.images(context); + var mapillary = getMapillary(), + data = (mapillary ? mapillary.images(context) : []); var markers = layer.selectAll('.viewfield-group') .data(data, function(d) { return d.key; }); @@ -83,24 +98,28 @@ iD.MapillaryImageLayer = function(context) { .attr('dy', '0') .attr('r', '6'); - // Update - markers - .attr('transform', transform); - // Exit markers.exit() .remove(); + + // Update + markers + .attr('transform', transform); } function render(selection) { - layer = selection.selectAll('svg') - .data([0]); + var mapillary = getMapillary(); + + layer = selection.selectAll('svg') + .data(mapillary ? [0] : []); - // Enter layer.enter() .append('svg') .style('display', enabled ? 'block' : 'none') + .dimensions(context.map().dimensions()) .on('click', function() { // deselect/select + var mapillary = getMapillary(); + if (!mapillary) return; var d = d3.event.target.__data__, thumb = mapillary.selectedThumbnail(); if (thumb && thumb.key === d.key) { @@ -112,9 +131,13 @@ iD.MapillaryImageLayer = function(context) { } }) .on('mouseover', function() { + var mapillary = getMapillary(); + if (!mapillary) return; showThumbnail(d3.event.target.__data__); }) .on('mouseout', function() { + var mapillary = getMapillary(); + if (!mapillary) return; var thumb = mapillary.selectedThumbnail(); if (thumb) { showThumbnail(thumb); @@ -123,13 +146,16 @@ iD.MapillaryImageLayer = function(context) { } }); + layer.exit() + .remove(); + if (enabled) { - if (~~context.map().zoom() < minZoom) { - editOff(); - } else { + if (mapillary && ~~context.map().zoom() >= minZoom) { editOn(); drawMarkers(); mapillary.loadImages(context.projection, layer.dimensions()); + } else { + editOff(); } } } @@ -146,14 +172,11 @@ iD.MapillaryImageLayer = function(context) { }; render.dimensions = function(_) { + if (layer.empty()) return null; if (!arguments.length) return layer.dimensions(); layer.dimensions(_); return render; }; - - mapillary - .on('loadedImages', debouncedRedraw); - return render; }; diff --git a/js/id/renderer/mapillary_sign_layer.js b/js/id/renderer/mapillary_sign_layer.js index 818d66718..caed2e410 100644 --- a/js/id/renderer/mapillary_sign_layer.js +++ b/js/id/renderer/mapillary_sign_layer.js @@ -1,12 +1,23 @@ iD.MapillarySignLayer = function(context) { - var mapillary = iD.services.mapillary(), - debouncedRedraw = _.debounce(function () { context.pan([0,0]); }, 1000), + var debouncedRedraw = _.debounce(function () { context.pan([0,0]); }, 1000), enabled = false, minZoom = 12, - layer; + layer = d3.select(null), + _mapillary; + function getMapillary() { + if (iD.services.mapillary && !_mapillary) { + _mapillary = iD.services.mapillary().on('loadedSigns', debouncedRedraw); + } else if (!iD.services.mapillary && _mapillary) { + _mapillary = null; + } + return _mapillary; + } function showThumbnail(image) { + var mapillary = getMapillary(); + if (!mapillary) return; + var thumb = mapillary.selectedThumbnail(), posX = context.projection(image.loc)[0], width = layer.dimensions()[0], @@ -24,7 +35,10 @@ iD.MapillarySignLayer = function(context) { d3.selectAll('.layer-mapillary-images .viewfield-group, .layer-mapillary-signs .icon-sign') .classed('selected', false); - mapillary.hideThumbnail(); + var mapillary = getMapillary(); + if (mapillary) { + mapillary.hideThumbnail(); + } } function showLayer() { @@ -48,7 +62,8 @@ iD.MapillarySignLayer = function(context) { } function drawSigns() { - var data = mapillary.signs(context); + var mapillary = getMapillary(), + data = (mapillary ? mapillary.signs(context) : []); var signs = layer.select('.mapillary-sign-offset') .selectAll('.icon-sign') @@ -67,6 +82,8 @@ iD.MapillarySignLayer = function(context) { enter .on('click', function(d) { // deselect/select + var mapillary = getMapillary(); + if (!mapillary) return; var thumb = mapillary.selectedThumbnail(); if (thumb && thumb.key === d.key) { hideThumbnail(); @@ -78,6 +95,8 @@ iD.MapillarySignLayer = function(context) { }) .on('mouseover', showThumbnail) .on('mouseout', function() { + var mapillary = getMapillary(); + if (!mapillary) return; var thumb = mapillary.selectedThumbnail(); if (thumb) { showThumbnail(thumb); @@ -86,33 +105,39 @@ iD.MapillarySignLayer = function(context) { } }); - // Update - signs - .attr('transform', iD.svg.PointTransform(context.projection)); - // Exit signs.exit() .remove(); + + // Update + signs + .attr('transform', iD.svg.PointTransform(context.projection)); } function render(selection) { + var mapillary = getMapillary(); + layer = selection.selectAll('svg') - .data([0]); + .data(mapillary ? [0] : []); layer.enter() .append('svg') .style('display', enabled ? 'block' : 'none') + .dimensions(context.map().dimensions()) .append('g') .attr('class', 'mapillary-sign-offset') .attr('transform', 'translate(-16, -16)'); // center signs on loc + layer.exit() + .remove(); + if (enabled) { - if (~~context.map().zoom() < minZoom) { - hideLayer(); - } else { - layer.style('display', 'block'); + if (mapillary && ~~context.map().zoom() >= minZoom) { + editOn(); drawSigns(); mapillary.loadSigns(context, context.projection, layer.dimensions()); + } else { + editOff(); } } } @@ -129,14 +154,11 @@ iD.MapillarySignLayer = function(context) { }; render.dimensions = function(_) { + if (layer.empty()) return null; if (!arguments.length) return layer.dimensions(); layer.dimensions(_); return render; }; - - mapillary - .on('loadedSigns', debouncedRedraw); - return render; }; diff --git a/js/id/services/mapillary.js b/js/id/services/mapillary.js index c22b924b1..c5f45730b 100644 --- a/js/id/services/mapillary.js +++ b/js/id/services/mapillary.js @@ -169,6 +169,11 @@ iD.services.mapillary = function() { return searchLimited(psize, limit, context, iD.services.mapillary.cache.signs.rtree); }; + mapillary.signsSupported = function() { + var detected = iD.detect(); + return (!(detected.ie || detected.browser.toLowerCase() === 'safari')); + }; + mapillary.signHTML = function(d) { if (!iD.services.mapillary.sign_defs) return; @@ -179,7 +184,7 @@ iD.services.mapillary = function() { }; mapillary.showThumbnail = function(imageKey, position) { - if (!imageKey) return + if (!imageKey) return; var positionClass = { 'ar': (position !== 'left'), @@ -228,7 +233,9 @@ iD.services.mapillary = function() { }; mapillary.hideThumbnail = function() { - iD.services.mapillary.thumb = null; + if (iD.services.mapillary) { + iD.services.mapillary.thumb = null; + } d3.select('#content').selectAll('.mapillary-image') .transition() .duration(200) @@ -237,6 +244,7 @@ iD.services.mapillary = function() { }; mapillary.selectedThumbnail = function(d) { + if (!iD.services.mapillary) return null; if (!arguments.length) return iD.services.mapillary.thumb; iD.services.mapillary.thumb = d; }; diff --git a/js/id/ui/map_data.js b/js/id/ui/map_data.js index b232ffb79..0298910e4 100644 --- a/js/id/ui/map_data.js +++ b/js/id/ui/map_data.js @@ -5,6 +5,7 @@ iD.ui.MapData = function(context) { fillDefault = context.storage('area-fill') || 'partial', fillSelected = fillDefault; + function map_data(selection) { function showsFeature(d) { @@ -46,16 +47,164 @@ iD.ui.MapData = function(context) { context.background().toggleMapillaryImageLayer(); update(); } + function clickMapillarySigns() { context.background().toggleMapillarySignLayer(); update(); } + + function drawMapillaryItems(selection) { + var mapillary = iD.services.mapillary, + supportsMapillaryImages = !!mapillary, + supportsMapillarySigns = !!mapillary && mapillary().signsSupported(); + + var mapillaryList = selection + .selectAll('.mapillary-list') + .data([0]); + + // Enter + mapillaryList + .enter() + .append('ul') + .attr('class', 'layer-list mapillary-list'); + + var mapillaryImageLayerItem = mapillaryList + .selectAll('.item-mapillary-images') + .data(supportsMapillaryImages ? [0] : []); + + var enterImages = mapillaryImageLayerItem.enter() + .append('li') + .attr('class', 'item-mapillary-images'); + + var labelImages = enterImages.append('label') + .call(bootstrap.tooltip() + .title(t('mapillary_images.tooltip')) + .placement('top')); + + labelImages.append('input') + .attr('type', 'checkbox') + .on('change', clickMapillaryImages); + + labelImages.append('span') + .text(t('mapillary_images.title')); + + + var mapillarySignLayerItem = mapillaryList + .selectAll('.item-mapillary-signs') + .data(supportsMapillarySigns ? [0] : []); + + var enterSigns = mapillarySignLayerItem.enter() + .append('li') + .attr('class', 'item-mapillary-signs'); + + var labelSigns = enterSigns.append('label') + .call(bootstrap.tooltip() + .title(t('mapillary_signs.tooltip')) + .placement('top')); + + labelSigns.append('input') + .attr('type', 'checkbox') + .on('change', clickMapillarySigns); + + labelSigns.append('span') + .text(t('mapillary_signs.title')); + + // Update + var showsMapillaryImages = supportsMapillaryImages && context.background().showsMapillaryImageLayer(), + showsMapillarySigns = supportsMapillarySigns && context.background().showsMapillarySignLayer(); + + mapillaryImageLayerItem + .classed('active', showsMapillaryImages) + .selectAll('input') + .property('checked', showsMapillaryImages); + + mapillarySignLayerItem + .classed('active', showsMapillarySigns) + .selectAll('input') + .property('checked', showsMapillarySigns); + + // Exit + mapillaryImageLayerItem.exit() + .remove(); + mapillarySignLayerItem.exit() + .remove(); + } + + + function drawGpxItem(selection) { + var supportsGpx = iD.detect().filedrop, + gpxLayerItem = selection + .selectAll('.layer-gpx') + .data(supportsGpx ? [0] : []); + + // Enter + var enter = gpxLayerItem.enter() + .append('ul') + .attr('class', 'layer-list layer-gpx') + .append('li') + .classed('layer-toggle-gpx', true); + + enter.append('button') + .attr('class', 'layer-extent') + .call(bootstrap.tooltip() + .title(t('gpx.zoom')) + .placement('left')) + .on('click', function() { + d3.event.preventDefault(); + d3.event.stopPropagation(); + context.background().zoomToGpxLayer(); + }) + .call(iD.svg.Icon('#icon-search')); + + enter.append('button') + .attr('class', 'layer-browse') + .call(bootstrap.tooltip() + .title(t('gpx.browse')) + .placement('left')) + .on('click', function() { + d3.select(document.createElement('input')) + .attr('type', 'file') + .on('change', function() { + context.background().gpxLayerFiles(d3.event.target.files); + }) + .node().click(); + }) + .call(iD.svg.Icon('#icon-geolocate')); + + var labelGpx = enter.append('label') + .call(bootstrap.tooltip() + .title(t('gpx.drag_drop')) + .placement('top')); + + labelGpx.append('input') + .attr('type', 'checkbox') + .on('change', clickGpx); + + labelGpx.append('span') + .text(t('gpx.local_layer')); + + // Update + var hasGpx = supportsGpx && context.background().hasGpxLayer(), + showsGpx = supportsGpx && context.background().showsGpxLayer(); + + gpxLayerItem + .classed('active', showsGpx) + .selectAll('input') + .property('disabled', !hasGpx) + .property('checked', showsGpx); + + // Exit + gpxLayerItem.exit() + .remove(); + } + + function drawList(selection, data, type, name, change, active) { var items = selection.selectAll('li') .data(data); - //enter + // Enter var enter = items.enter() .append('li') .attr('class', 'layer') @@ -83,7 +232,7 @@ iD.ui.MapData = function(context) { label.append('span') .text(function(d) { return t(name + '.' + d + '.description'); }); - //update + // Update items .classed('active', active) .selectAll('input') @@ -92,38 +241,24 @@ iD.ui.MapData = function(context) { return (name === 'feature' && autoHiddenFeature(d)); }); - //exit + // Exit items.exit() .remove(); } + function update() { - featureList.call(drawList, features, 'checkbox', 'feature', clickFeature, showsFeature); + dataLayerContainer.call(drawMapillaryItems); + dataLayerContainer.call(drawGpxItem); + fillList.call(drawList, fills, 'radio', 'area_fill', setFill, showsFill); - var hasGpx = context.background().hasGpxLayer(), - showsGpx = context.background().showsGpxLayer(), - showsMapillaryImages = context.background().showsMapillaryImageLayer(), - showsMapillarySigns = context.background().showsMapillarySignLayer(); - - gpxLayerItem - .classed('active', showsGpx) - .selectAll('input') - .property('disabled', !hasGpx) - .property('checked', showsGpx); - - mapillaryImageLayerItem - .classed('active', showsMapillaryImages) - .selectAll('input') - .property('checked', showsMapillaryImages); - - mapillarySignLayerItem - .classed('active', showsMapillarySigns) - .selectAll('input') - .property('checked', showsMapillarySigns); + featureList.call(drawList, features, 'checkbox', 'feature', clickFeature, showsFeature); } - function hidePanel() { setVisible(false); } + function hidePanel() { + setVisible(false); + } function togglePanel() { if (d3.event) d3.event.preventDefault(); @@ -146,6 +281,7 @@ iD.ui.MapData = function(context) { shown = show; if (show) { + update(); selection.on('mousedown.map_data-inside', function() { return d3.event.stopPropagation(); }); @@ -194,100 +330,16 @@ iD.ui.MapData = function(context) { .classed('expanded', true) .on('click', function() { var exp = d3.select(this).classed('expanded'); - layerContainer.style('display', exp ? 'none' : 'block'); + dataLayerContainer.style('display', exp ? 'none' : 'block'); d3.select(this).classed('expanded', !exp); d3.event.preventDefault(); }); - var layerContainer = content.append('div') - .attr('class', 'filters') + var dataLayerContainer = content.append('div') + .attr('class', 'data-data-layers') .style('display', 'block'); - // Mapillary Image Layer - var mapillaryImageLayerItem = layerContainer.append('ul') - .attr('class', 'layer-list') - .append('li'); - - var labelImage = mapillaryImageLayerItem.append('label') - .call(bootstrap.tooltip() - .title(t('mapillary_images.tooltip')) - .placement('top')); - - labelImage.append('input') - .attr('type', 'checkbox') - .on('change', clickMapillaryImages); - - labelImage.append('span') - .text(t('mapillary_images.title')); - - - // Mapillary Sign Layer - var mapillarySignLayerItem = layerContainer.append('ul') - .attr('class', 'layer-list') - .append('li'); - - var labelSigns = mapillarySignLayerItem.append('label') - .call(bootstrap.tooltip() - .title(t('mapillary_signs.tooltip')) - .placement('top')); - - labelSigns.append('input') - .attr('type', 'checkbox') - .on('change', clickMapillarySigns); - - labelSigns.append('span') - .text(t('mapillary_signs.title')); - - - // GPX Layer - var gpxLayerItem = layerContainer.append('ul') - .style('display', iD.detect().filedrop ? 'block' : 'none') - .attr('class', 'layer-list') - .append('li') - .classed('layer-toggle-gpx', true); - - gpxLayerItem.append('button') - .attr('class', 'layer-extent') - .call(bootstrap.tooltip() - .title(t('gpx.zoom')) - .placement('left')) - .on('click', function() { - d3.event.preventDefault(); - d3.event.stopPropagation(); - context.background().zoomToGpxLayer(); - }) - .call(iD.svg.Icon('#icon-search')); - - gpxLayerItem.append('button') - .attr('class', 'layer-browse') - .call(bootstrap.tooltip() - .title(t('gpx.browse')) - .placement('left')) - .on('click', function() { - d3.select(document.createElement('input')) - .attr('type', 'file') - .on('change', function() { - context.background().gpxLayerFiles(d3.event.target.files); - }) - .node().click(); - }) - .call(iD.svg.Icon('#icon-geolocate')); - - var labelGpx = gpxLayerItem.append('label') - .call(bootstrap.tooltip() - .title(t('gpx.drag_drop')) - .placement('top')); - - labelGpx.append('input') - .attr('type', 'checkbox') - .property('disabled', true) - .on('change', clickGpx); - - labelGpx.append('span') - .text(t('gpx.local_layer')); - - // area fills content.append('a') .text(t('map_data.fill_area')) @@ -302,11 +354,11 @@ iD.ui.MapData = function(context) { }); var fillContainer = content.append('div') - .attr('class', 'filters') + .attr('class', 'data-area-fills') .style('display', 'none'); var fillList = fillContainer.append('ul') - .attr('class', 'layer-list'); + .attr('class', 'layer-list layer-fill-list'); // feature filters @@ -323,11 +375,11 @@ iD.ui.MapData = function(context) { }); var featureContainer = content.append('div') - .attr('class', 'filters') + .attr('class', 'data-feature-filters') .style('display', 'none'); var featureList = featureContainer.append('ul') - .attr('class', 'layer-list'); + .attr('class', 'layer-list layer-feature-list'); context.features()