diff --git a/css/80_app.css b/css/80_app.css index bcd8ee8b2a..60ccb029d1 100644 --- a/css/80_app.css +++ b/css/80_app.css @@ -1082,7 +1082,9 @@ a.hide-toggle { } .preset-list-button:hover .label, -.preset-list-button:focus .label { +.preset-list-button:focus .label, +.preset-list-button.disabled, +.preset-list-button.disabled .label { background-color: #ececec; } diff --git a/data/core.yaml b/data/core.yaml index 180b4e8cc8..172cdb1bae 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -427,6 +427,9 @@ en: edit_reference: "edit/translate" wiki_reference: View documentation wiki_en_reference: View documentation in English + hidden_preset: + manual: "{features} are hidden. Enable them in the Map Data pane." + zoom: "{features} are hidden. Zoom in to enable them." back_tooltip: Change feature remove: Remove search: Search @@ -534,10 +537,10 @@ en: description: Power Features tooltip: "Power Lines, Power Plants, Substations, etc." past_future: - description: Past/Future + description: Past/Future Features tooltip: "Proposed, Construction, Abandoned, Demolished, etc." others: - description: Others + description: Other Features tooltip: "Everything Else" area_fill: wireframe: diff --git a/dist/locales/en.json b/dist/locales/en.json index 7dbf67c0e7..edbe6208c6 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -522,6 +522,10 @@ "edit_reference": "edit/translate", "wiki_reference": "View documentation", "wiki_en_reference": "View documentation in English", + "hidden_preset": { + "manual": "{features} are hidden. Enable them in the Map Data pane.", + "zoom": "{features} are hidden. Zoom in to enable them." + }, "back_tooltip": "Change feature", "remove": "Remove", "search": "Search", @@ -653,11 +657,11 @@ "tooltip": "Power Lines, Power Plants, Substations, etc." }, "past_future": { - "description": "Past/Future", + "description": "Past/Future Features", "tooltip": "Proposed, Construction, Abandoned, Demolished, etc." }, "others": { - "description": "Others", + "description": "Other Features", "tooltip": "Everything Else" } }, diff --git a/modules/renderer/features.js b/modules/renderer/features.js index 4929df6105..ca1e4b2327 100644 --- a/modules/renderer/features.js +++ b/modules/renderer/features.js @@ -95,96 +95,100 @@ export function rendererFeatures(context) { defaultMax: (max || Infinity), enable: function() { this.enabled = true; this.currentMax = this.defaultMax; }, disable: function() { this.enabled = false; this.currentMax = 0; }, - hidden: function() { return !context.editable() || this.count > this.currentMax * _cullFactor; }, + hidden: function() { + return !context.editable() || + (this.count === 0 && !this.enabled) || + this.count > this.currentMax * _cullFactor; + }, autoHidden: function() { return this.hidden() && this.currentMax > 0; } }; } - defineFeature('points', function isPoint(entity, resolver, geometry) { + defineFeature('points', function isPoint(tags, geometry) { return geometry === 'point'; }, 200); - defineFeature('traffic_roads', function isTrafficRoad(entity) { - return traffic_roads[entity.tags.highway]; + defineFeature('traffic_roads', function isTrafficRoad(tags) { + return traffic_roads[tags.highway]; }); - defineFeature('service_roads', function isServiceRoad(entity) { - return service_roads[entity.tags.highway]; + defineFeature('service_roads', function isServiceRoad(tags) { + return service_roads[tags.highway]; }); - defineFeature('paths', function isPath(entity) { - return paths[entity.tags.highway]; + defineFeature('paths', function isPath(tags) { + return paths[tags.highway]; }); - defineFeature('buildings', function isBuilding(entity) { + defineFeature('buildings', function isBuilding(tags) { return ( - !!entity.tags['building:part'] || - (!!entity.tags.building && entity.tags.building !== 'no') || - entity.tags.parking === 'multi-storey' || - entity.tags.parking === 'sheds' || - entity.tags.parking === 'carports' || - entity.tags.parking === 'garage_boxes' + !!tags['building:part'] || + (!!tags.building && tags.building !== 'no') || + tags.parking === 'multi-storey' || + tags.parking === 'sheds' || + tags.parking === 'carports' || + tags.parking === 'garage_boxes' ); }, 250); - defineFeature('landuse', function isLanduse(entity, resolver, geometry) { + defineFeature('landuse', function isLanduse(tags, geometry) { return geometry === 'area' && - !_features.buildings.filter(entity) && - !_features.water.filter(entity); + !_features.buildings.filter(tags) && + !_features.water.filter(tags); }); - defineFeature('boundaries', function isBoundary(entity) { + defineFeature('boundaries', function isBoundary(tags) { return ( - !!entity.tags.boundary + !!tags.boundary ) && !( - traffic_roads[entity.tags.highway] || - service_roads[entity.tags.highway] || - paths[entity.tags.highway] + traffic_roads[tags.highway] || + service_roads[tags.highway] || + paths[tags.highway] ); }); - defineFeature('water', function isWater(entity) { + defineFeature('water', function isWater(tags) { return ( - !!entity.tags.waterway || - entity.tags.natural === 'water' || - entity.tags.natural === 'coastline' || - entity.tags.natural === 'bay' || - entity.tags.landuse === 'pond' || - entity.tags.landuse === 'basin' || - entity.tags.landuse === 'reservoir' || - entity.tags.landuse === 'salt_pond' + !!tags.waterway || + tags.natural === 'water' || + tags.natural === 'coastline' || + tags.natural === 'bay' || + tags.landuse === 'pond' || + tags.landuse === 'basin' || + tags.landuse === 'reservoir' || + tags.landuse === 'salt_pond' ); }); - defineFeature('rail', function isRail(entity) { + defineFeature('rail', function isRail(tags) { return ( - !!entity.tags.railway || - entity.tags.landuse === 'railway' + !!tags.railway || + tags.landuse === 'railway' ) && !( - traffic_roads[entity.tags.highway] || - service_roads[entity.tags.highway] || - paths[entity.tags.highway] + traffic_roads[tags.highway] || + service_roads[tags.highway] || + paths[tags.highway] ); }); - defineFeature('power', function isPower(entity) { - return !!entity.tags.power; + defineFeature('power', function isPower(tags) { + return !!tags.power; }); // contains a past/future tag, but not in active use as a road/path/cycleway/etc.. - defineFeature('past_future', function isPastFuture(entity) { + defineFeature('past_future', function isPastFuture(tags) { if ( - traffic_roads[entity.tags.highway] || - service_roads[entity.tags.highway] || - paths[entity.tags.highway] + traffic_roads[tags.highway] || + service_roads[tags.highway] || + paths[tags.highway] ) { return false; } - var strings = Object.keys(entity.tags); + var strings = Object.keys(tags); for (var i = 0; i < strings.length; i++) { var s = strings[i]; - if (past_futures[s] || past_futures[entity.tags[s]]) { return true; } + if (past_futures[s] || past_futures[tags[s]]) { return true; } } return false; }); @@ -192,7 +196,7 @@ export function rendererFeatures(context) { // Lines or areas that don't match another feature filter. // IMPORTANT: The 'others' feature must be the last one defined, // so that code in getMatches can skip this test if `hasMatch = true` - defineFeature('others', function isOther(entity, resolver, geometry) { + defineFeature('others', function isOther(tags, geometry) { return (geometry === 'line' || geometry === 'area'); }); @@ -380,7 +384,7 @@ export function rendererFeatures(context) { } } - if (_features[_keys[i]].filter(entity, resolver, geometry)) { + if (_features[_keys[i]].filter(entity.tags, geometry)) { matches[_keys[i]] = hasMatch = true; } } @@ -412,6 +416,18 @@ export function rendererFeatures(context) { }; + features.isHiddenPreset = function(preset, geometry) { + if (!_hidden.length) return false; + if (!preset.tags) return false; + for (var i = 0; i < _hidden.length; i++) { + if (_features[_hidden[i]].filter(preset.setTags({}, geometry), geometry)) { + return _hidden[i]; + } + } + return false; + }; + + features.isHiddenFeature = function(entity, resolver, geometry) { if (!_hidden.length) return false; if (!entity.version) return false; diff --git a/modules/ui/preset_list.js b/modules/ui/preset_list.js index 7998ede500..76260d2fe0 100644 --- a/modules/ui/preset_list.js +++ b/modules/ui/preset_list.js @@ -2,13 +2,15 @@ import { dispatch as d3_dispatch } from 'd3-dispatch'; import { event as d3_event, - select as d3_select + select as d3_select, + selectAll as d3_selectAll } from 'd3-selection'; import { t, textDirection } from '../util/locale'; import { actionChangePreset } from '../actions/index'; import { operationDelete } from '../operations/index'; import { svgIcon } from '../svg/index'; +import { tooltip } from '../util/tooltip'; import { uiPresetIcon } from './preset_icon'; import { uiTagReference } from './tag_reference'; import { utilKeybinding, utilNoAuto, utilRebind } from '../util'; @@ -136,6 +138,8 @@ export function uiPresetList(context) { .append('div') .attr('class', 'preset-list fillL cf') .call(drawList, context.presets().defaults(geometry, 36)); + + context.features().on('change.preset-list', updateForFeatureHiddenState); } @@ -165,6 +169,8 @@ export function uiPresetList(context) { .style('opacity', 0) .transition() .style('opacity', 1); + + updateForFeatureHiddenState(); } function itemKeydown(){ @@ -377,6 +383,8 @@ export function uiPresetList(context) { } item.choose = function() { + if (d3_select(this).classed('disabled')) return; + context.presets().choose(preset); context.perform( actionChangePreset(_entityID, _currentPreset, preset), @@ -397,6 +405,34 @@ export function uiPresetList(context) { return item; } + function updateForFeatureHiddenState() { + + var geometry = context.geometry(_entityID); + + var button = d3_selectAll('.preset-list .preset-list-button'); + + // remove existing tooltips + button.call(tooltip().destroyAny); + + button.each(function(item, index) { + + var hiddenPresetFeaturesId = context.features().isHiddenPreset(item.preset, geometry); + var isHiddenPreset = !!hiddenPresetFeaturesId && item.preset !== _currentPreset; + + d3_select(this) + .classed('disabled', isHiddenPreset); + + if (isHiddenPreset) { + var isAutoHidden = context.features().autoHidden(hiddenPresetFeaturesId); + var tooltipIdSuffix = isAutoHidden ? 'zoom' : 'manual'; + var tooltipObj = { features: t('feature.' + hiddenPresetFeaturesId + '.description') }; + d3_select(this).call(tooltip() + .title(t('inspector.hidden_preset.' + tooltipIdSuffix, tooltipObj)) + .placement(index < 2 ? 'bottom' : 'top') + ); + } + }); + } presetList.autofocus = function(val) { if (!arguments.length) return _autofocus;