From 857a1c4821bf0ae0aa8b78dd118828754d03973e Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Mon, 27 Nov 2017 12:03:14 -0500 Subject: [PATCH 01/18] Refactor offset control into separate `uiBackgroundOffset` module --- modules/ui/background.js | 270 +++++++------------------------- modules/ui/background_offset.js | 203 ++++++++++++++++++++++++ modules/ui/index.js | 1 + 3 files changed, 257 insertions(+), 217 deletions(-) create mode 100644 modules/ui/background_offset.js diff --git a/modules/ui/background.js b/modules/ui/background.js index 9b58c79d5..5ad890c3e 100644 --- a/modules/ui/background.js +++ b/modules/ui/background.js @@ -7,20 +7,19 @@ import { import { event as d3_event, - select as d3_select, - selectAll as d3_selectAll + select as d3_select } from 'd3-selection'; import { d3keybinding as d3_keybinding } from '../lib/d3.keybinding.js'; import { t, textDirection } from '../util/locale'; -import { geoMetersToOffset, geoOffsetToMeters } from '../geo'; +import { svgIcon } from '../svg'; +import { uiBackgroundOffset } from './background_offset'; +import { uiCmd } from './cmd'; +import { uiMapInMap } from './map_in_map'; +import { uiTooltipHtml } from './tooltipHtml'; import { utilDetect } from '../util/detect'; import { utilSetTransform, utilCallWhenIdle } from '../util'; -import { svgIcon } from '../svg'; -import { uiMapInMap } from './map_in_map'; -import { uiCmd } from './cmd'; -import { uiTooltipHtml } from './tooltipHtml'; import { tooltip } from '../util/tooltip'; @@ -28,16 +27,14 @@ export function uiBackground(context) { var key = t('background.key'), detected = utilDetect(), opacities = [1, 0.75, 0.5, 0.25], - directions = [ - ['right', [0.5, 0]], - ['top', [0, -0.5]], - ['left', [-0.5, 0]], - ['bottom', [0, 0.5]]], opacityDefault = (context.storage('background-opacity') !== null) ? (+context.storage('background-opacity')) : 1.0, customSource = context.background().findSource('custom'), previous; + var backgroundOffset = uiBackgroundOffset(context); + + // Can be 0 from <1.3.0 use or due to issue #1923. if (opacityDefault === 0) opacityDefault = 1.0; @@ -206,119 +203,16 @@ export function uiBackground(context) { function update() { - backgroundList.call(drawList, 'radio', clickSetSource, function(d) { return !d.isHidden() && !d.overlay; }); - overlayList.call(drawList, 'checkbox', clickSetOverlay, function(d) { return !d.isHidden() && d.overlay; }); + backgroundList + .call(drawList, 'radio', clickSetSource, function(d) { return !d.isHidden() && !d.overlay; }); + + overlayList + .call(drawList, 'checkbox', clickSetOverlay, function(d) { return !d.isHidden() && d.overlay; }); selectLayer(); - updateOffsetVal(); - } - - function updateOffsetVal() { - var meters = geoOffsetToMeters(context.background().offset()), - x = +meters[0].toFixed(2), - y = +meters[1].toFixed(2); - - d3_selectAll('.nudge-inner-rect') - .select('input') - .classed('error', false) - .property('value', x + ', ' + y); - - d3_selectAll('.nudge-reset') - .classed('disabled', function() { - return (x === 0 && y === 0); - }); - } - - - function resetOffset() { - if (d3_event.button !== 0) return; - context.background().offset([0, 0]); - updateOffsetVal(); - } - - - function nudge(d) { - context.background().nudge(d, context.map().zoom()); - updateOffsetVal(); - } - - - function buttonOffset(d) { - if (d3_event.button !== 0) return; - var timeout = window.setTimeout(function() { - interval = window.setInterval(nudge.bind(null, d), 100); - }, 500), - interval; - - function doneNudge() { - window.clearTimeout(timeout); - 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; - - if (d === '') return resetOffset(); - - d = d.replace(/;/g, ',').split(',').map(function(n) { - // if n is NaN, it will always get mapped to false. - return !isNaN(n) && n; - }); - - if (d.length !== 2 || !d[0] || !d[1]) { - input.classed('error', true); - return; - } - - context.background().offset(geoMetersToOffset(d)); - updateOffsetVal(); - } - - - function dragOffset() { - if (d3_event.button !== 0) return; - var origin = [d3_event.clientX, d3_event.clientY]; - - context.container() - .append('div') - .attr('class', 'nudge-surface'); - - d3_select(window) - .on('mousemove.offset', function() { - var latest = [d3_event.clientX, d3_event.clientY]; - var d = [ - -(origin[0] - latest[0]) / 4, - -(origin[1] - latest[1]) / 4 - ]; - - origin = latest; - nudge(d); - }) - .on('mouseup.offset', function() { - if (d3_event.button !== 0) return; - d3_selectAll('.nudge-surface') - .remove(); - - d3_select(window) - .on('mousemove.offset', null) - .on('mouseup.offset', null); - }); - - d3_event.preventDefault(); + offsetContainer + .call(backgroundOffset); } @@ -387,26 +281,28 @@ export function uiBackground(context) { 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') - .attr('tabindex', -1) - .on('click', toggle) - .call(svgIcon('#icon-layers', 'light')) - .call(tooltipBehavior), - shown = false; + .append('div') + .attr('class', 'fillL map-overlay col3 content hide'); + + var tooltipBehavior = tooltip() + .placement((textDirection === 'rtl') ? 'right' : 'left') + .html(true) + .title(uiTooltipHtml(t('background.description'), key)); + + var button = selection + .append('button') + .attr('tabindex', -1) + .on('click', toggle) + .call(svgIcon('#icon-layers', 'light')) + .call(tooltipBehavior); + + var shown = false; - /* opacity switcher */ - + /* add opacity switcher */ var opawrap = content - .append('div') - .attr('class', 'opacity-options-wrapper'); + .append('div') + .attr('class', 'opacity-options-wrapper'); opawrap .append('h4') @@ -432,27 +328,26 @@ export function uiBackground(context) { .style('opacity', function(d) { return 1.25 - d; }); - /* background list */ - + /* add background list */ var backgroundList = content .append('ul') .attr('class', 'layer-list') .attr('dir', 'auto'); - content - .append('div') - .attr('class', 'imagery-faq') - .append('a') - .attr('target', '_blank') - .attr('tabindex', -1) - .call(svgIcon('#icon-out-link', 'inline')) - .attr('href', 'https://github.com/openstreetmap/iD/blob/master/FAQ.md#how-can-i-report-an-issue-with-background-imagery') - .append('span') - .text(t('background.imagery_source_faq')); + // "Where does this imagery come from?" + // content + // .append('div') + // .attr('class', 'imagery-faq') + // .append('a') + // .attr('target', '_blank') + // .attr('tabindex', -1) + // .call(svgIcon('#icon-out-link', 'inline')) + // .attr('href', 'https://github.com/openstreetmap/iD/blob/master/FAQ.md#how-can-i-report-an-issue-with-background-imagery') + // .append('span') + // .text(t('background.imagery_source_faq')); - /* overlay list */ - + /* add overlay list */ var overlayList = content .append('ul') .attr('class', 'layer-list'); @@ -462,8 +357,7 @@ export function uiBackground(context) { .attr('class', 'controls-list'); - /* minimap toggle */ - + /* add minimap toggle */ var minimapLabel = controls .append('label') .call(tooltip() @@ -486,71 +380,13 @@ export function uiBackground(context) { .text(t('background.minimap.description')); - /* imagery offset controls */ - - var adjustments = content + /* add offset controls */ + var offsetContainer = content .append('div') - .attr('class', 'adjustments'); + .attr('class', 'background-offset'); - 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') - .attr('class', 'nudge-container cf') - .style('display', 'none'); - - nudgeContainer - .append('div') - .attr('class', 'nudge-instructions') - .text(t('background.offset')); - - var nudgeRect = nudgeContainer - .append('div') - .attr('class', 'nudge-outer-rect') - .on('mousedown', dragOffset); - - 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') - .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') - .attr('title', t('background.reset')) - .attr('class', 'nudge-reset disabled') - .on('click', resetOffset) - .call( - (textDirection === 'rtl') ? svgIcon('#icon-redo') : svgIcon('#icon-undo') - ); + /* add listeners */ context.map() .on('move.background-update', _debounce(utilCallWhenIdle(update), 1000)); diff --git a/modules/ui/background_offset.js b/modules/ui/background_offset.js new file mode 100644 index 000000000..332cb7f9c --- /dev/null +++ b/modules/ui/background_offset.js @@ -0,0 +1,203 @@ +import { + event as d3_event, + select as d3_select, + selectAll as d3_selectAll +} from 'd3-selection'; + +import { t, textDirection } from '../util/locale'; +import { geoMetersToOffset, geoOffsetToMeters } from '../geo'; +import { svgIcon } from '../svg'; + + +export function uiBackgroundOffset(context) { + var directions = [ + ['right', [0.5, 0]], + ['top', [0, -0.5]], + ['left', [-0.5, 0]], + ['bottom', [0, 0.5]] + ]; + + + function updateValue() { + var meters = geoOffsetToMeters(context.background().offset()); + var x = +meters[0].toFixed(2); + var y = +meters[1].toFixed(2); + + d3_selectAll('.nudge-inner-rect') + .select('input') + .classed('error', false) + .property('value', x + ', ' + y); + + d3_selectAll('.nudge-reset') + .classed('disabled', function() { + return (x === 0 && y === 0); + }); + } + + + function resetOffset() { + if (d3_event.button !== 0) return; + context.background().offset([0, 0]); + updateValue(); + } + + + function nudge(d) { + context.background().nudge(d, context.map().zoom()); + updateValue(); + } + + + function buttonOffset(d) { + if (d3_event.button !== 0) return; + var timeout = window.setTimeout(function() { + interval = window.setInterval(nudge.bind(null, d), 100); + }, 500); + var interval; + + function doneNudge() { + window.clearTimeout(timeout); + 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; + + if (d === '') return resetOffset(); + + d = d.replace(/;/g, ',').split(',').map(function(n) { + // if n is NaN, it will always get mapped to false. + return !isNaN(n) && n; + }); + + if (d.length !== 2 || !d[0] || !d[1]) { + input.classed('error', true); + return; + } + + context.background().offset(geoMetersToOffset(d)); + updateValue(); + } + + + function dragOffset() { + if (d3_event.button !== 0) return; + var origin = [d3_event.clientX, d3_event.clientY]; + + context.container() + .append('div') + .attr('class', 'nudge-surface'); + + d3_select(window) + .on('mousemove.offset', function() { + var latest = [d3_event.clientX, d3_event.clientY]; + var d = [ + -(origin[0] - latest[0]) / 4, + -(origin[1] - latest[1]) / 4 + ]; + + origin = latest; + nudge(d); + }) + .on('mouseup.offset', function() { + if (d3_event.button !== 0) return; + d3_selectAll('.nudge-surface') + .remove(); + + d3_select(window) + .on('mousemove.offset', null) + .on('mouseup.offset', null); + }); + + d3_event.preventDefault(); + } + + + function backgroundOffset(selection) { + + var adjustments = selection.selectAll('.adjustments') + .data([0]); + + var adjustmentsEnter = adjustments.enter() + .append('div') + .attr('class', 'adjustments'); + + adjustmentsEnter + .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 = adjustmentsEnter + .append('div') + .attr('class', 'nudge-container cf') + .style('display', 'none'); + + nudgeContainer + .append('div') + .attr('class', 'nudge-instructions') + .text(t('background.offset')); + + var nudgeRect = nudgeContainer + .append('div') + .attr('class', 'nudge-outer-rect') + .on('mousedown', dragOffset); + + 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') + .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') + .attr('title', t('background.reset')) + .attr('class', 'nudge-reset disabled') + .on('click', resetOffset) + .call(svgIcon('#icon-' + (textDirection === 'rtl' ? 'redo' : 'undo'))); + + updateValue(); + } + + + context.background() + .on('change.backgroundOffset-update', updateValue); + + return backgroundOffset; +} diff --git a/modules/ui/index.js b/modules/ui/index.js index 742aa7b1c..f1affc4fd 100644 --- a/modules/ui/index.js +++ b/modules/ui/index.js @@ -2,6 +2,7 @@ export { uiInit } from './init'; export { uiAccount } from './account'; export { uiAttribution } from './attribution'; export { uiBackground } from './background'; +export { uiBackgroundOffset } from './background_offset'; export { uiChangesetEditor } from './changeset_editor'; export { uiCmd } from './cmd'; export { uiCommit } from './commit'; From d2c70938f6c987a6de840c4ac072615e00090368 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Mon, 27 Nov 2017 13:54:32 -0500 Subject: [PATCH 02/18] Wrap offset control in uiDisclosure, fix event handling on input field (closes #4553) --- modules/ui/background_offset.js | 82 ++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 38 deletions(-) diff --git a/modules/ui/background_offset.js b/modules/ui/background_offset.js index 332cb7f9c..0d9c1f683 100644 --- a/modules/ui/background_offset.js +++ b/modules/ui/background_offset.js @@ -7,9 +7,11 @@ import { import { t, textDirection } from '../util/locale'; import { geoMetersToOffset, geoOffsetToMeters } from '../geo'; import { svgIcon } from '../svg'; +import { uiDisclosure } from './disclosure'; export function uiBackgroundOffset(context) { + var expandedPreference = (context.storage('background_offset.expanded') !== 'false'); var directions = [ ['right', [0.5, 0]], ['top', [0, -0.5]], @@ -18,6 +20,12 @@ export function uiBackgroundOffset(context) { ]; + function d3_eventCancel() { + d3_event.stopPropagation(); + d3_event.preventDefault(); + } + + function updateValue() { var meters = geoOffsetToMeters(context.background().offset()); var x = +meters[0].toFixed(2); @@ -36,7 +44,6 @@ export function uiBackgroundOffset(context) { function resetOffset() { - if (d3_event.button !== 0) return; context.background().offset([0, 0]); updateValue(); } @@ -48,12 +55,11 @@ export function uiBackgroundOffset(context) { } - function buttonOffset(d) { - if (d3_event.button !== 0) return; + function clickNudgeButton(d) { + var interval; var timeout = window.setTimeout(function() { interval = window.setInterval(nudge.bind(null, d), 100); }, 500); - var interval; function doneNudge() { window.clearTimeout(timeout); @@ -72,7 +78,6 @@ export function uiBackgroundOffset(context) { function inputOffset() { - if (d3_event.button !== 0) return; var input = d3_select(this); var d = input.node().value; @@ -94,7 +99,9 @@ export function uiBackgroundOffset(context) { function dragOffset() { + d3_event.preventDefault(); if (d3_event.button !== 0) return; + var origin = [d3_event.clientX, d3_event.clientY]; context.container() @@ -121,50 +128,28 @@ export function uiBackgroundOffset(context) { .on('mousemove.offset', null) .on('mouseup.offset', null); }); - - d3_event.preventDefault(); } - function backgroundOffset(selection) { - - var adjustments = selection.selectAll('.adjustments') + function render(selection) { + var container = selection.selectAll('.nudge-container') .data([0]); - var adjustmentsEnter = adjustments.enter() + var containerEnter = container.enter() .append('div') - .attr('class', 'adjustments'); + .attr('class', 'nudge-container cf'); - adjustmentsEnter - .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 = adjustmentsEnter - .append('div') - .attr('class', 'nudge-container cf') - .style('display', 'none'); - - nudgeContainer + containerEnter .append('div') .attr('class', 'nudge-instructions') .text(t('background.offset')); - var nudgeRect = nudgeContainer + var nudgeEnter = containerEnter .append('div') .attr('class', 'nudge-outer-rect') .on('mousedown', dragOffset); - nudgeRect + nudgeEnter .append('div') .attr('class', 'nudge-inner-rect') .append('input') @@ -174,28 +159,49 @@ export function uiBackgroundOffset(context) { d3_event.stopPropagation(); }); - nudgeContainer + containerEnter .append('div') .selectAll('button') .data(directions).enter() .append('button') .attr('class', function(d) { return d[0] + ' nudge'; }) + .on('contextmenu', d3_eventCancel) .on('mousedown', function(d) { if (d3_event.button !== 0) return; - buttonOffset(d[1]); + clickNudgeButton(d[1]); }); - nudgeContainer + containerEnter .append('button') .attr('title', t('background.reset')) .attr('class', 'nudge-reset disabled') - .on('click', resetOffset) + .on('contextmenu', d3_eventCancel) + .on('click', function() { + if (d3_event.button !== 0) return; + resetOffset(); + }) .call(svgIcon('#icon-' + (textDirection === 'rtl' ? 'redo' : 'undo'))); updateValue(); } + function backgroundOffset(selection) { + selection + .call(uiDisclosure() + .title(t('background.fix_misalignment')) + .expanded(expandedPreference) + .on('toggled', toggled) + .content(render) + ); + + function toggled(expanded) { + expandedPreference = expanded; + context.storage('background_offset.expanded', expanded); + } + } + + context.background() .on('change.backgroundOffset-update', updateValue); From 34cdab91979d4df96bb9be6dfe9ac0057d4c36a3 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Mon, 27 Nov 2017 17:13:57 -0500 Subject: [PATCH 03/18] uiDisclosure now handles expanded state in localStorage Now all the places that use a uiDisclosure won't have to do that --- modules/ui/background_offset.js | 16 +--- modules/ui/commit.js | 1 - modules/ui/disclosure.js | 48 +++++++----- modules/ui/preset_editor.js | 10 +-- modules/ui/raw_member_editor.js | 24 +++--- modules/ui/raw_membership_editor.js | 53 ++++++------- modules/ui/raw_tag_editor.js | 114 ++++++++++++++-------------- 7 files changed, 124 insertions(+), 142 deletions(-) diff --git a/modules/ui/background_offset.js b/modules/ui/background_offset.js index 0d9c1f683..295557de2 100644 --- a/modules/ui/background_offset.js +++ b/modules/ui/background_offset.js @@ -11,7 +11,6 @@ import { uiDisclosure } from './disclosure'; export function uiBackgroundOffset(context) { - var expandedPreference = (context.storage('background_offset.expanded') !== 'false'); var directions = [ ['right', [0.5, 0]], ['top', [0, -0.5]], @@ -153,11 +152,7 @@ export function uiBackgroundOffset(context) { .append('div') .attr('class', 'nudge-inner-rect') .append('input') - .on('change', inputOffset) - .on('mousedown', function() { - if (d3_event.button !== 0) return; - d3_event.stopPropagation(); - }); + .on('change', inputOffset); containerEnter .append('div') @@ -188,17 +183,10 @@ export function uiBackgroundOffset(context) { function backgroundOffset(selection) { selection - .call(uiDisclosure() + .call(uiDisclosure(context, 'background_offset', false) .title(t('background.fix_misalignment')) - .expanded(expandedPreference) - .on('toggled', toggled) .content(render) ); - - function toggled(expanded) { - expandedPreference = expanded; - context.storage('background_offset.expanded', expanded); - } } diff --git a/modules/ui/commit.js b/modules/ui/commit.js index 52696ca30..e7d3837f5 100644 --- a/modules/ui/commit.js +++ b/modules/ui/commit.js @@ -269,7 +269,6 @@ export function uiCommit(context) { updateChangeset({ review_requested: (rr ? 'yes' : undefined) }); var expanded = !tagSection.selectAll('a.hide-toggle.expanded').empty(); - tagSection .call(rawTagEditor .expanded(expanded) diff --git a/modules/ui/disclosure.js b/modules/ui/disclosure.js index 82ee9f0b4..9d84c02f8 100644 --- a/modules/ui/disclosure.js +++ b/modules/ui/disclosure.js @@ -4,11 +4,13 @@ import { utilRebind } from '../util/rebind'; import { uiToggle } from './toggle'; -export function uiDisclosure() { +export function uiDisclosure(context, key, expandedDefault) { var dispatch = d3_dispatch('toggled'), - title, - expanded = false, - content = function () {}; + _preference = (context.storage('disclosure.' + key + '.expanded')), + _expanded = (_preference === null ? !!expandedDefault : (_preference === 'true')), + _title, + _updatePreference = true, + _content = function () {}; var disclosure = function(selection) { @@ -22,9 +24,9 @@ export function uiDisclosure() { .merge(hideToggle); hideToggle - .text(title) + .text(_title) .on('click', toggle) - .classed('expanded', expanded); + .classed('expanded', _expanded); var wrap = selection.selectAll('div') @@ -35,36 +37,46 @@ export function uiDisclosure() { .merge(wrap); wrap - .classed('hide', !expanded) - .call(content); + .classed('hide', !_expanded) + .call(_content); function toggle() { - expanded = !expanded; - hideToggle.classed('expanded', expanded); - wrap.call(uiToggle(expanded)); - dispatch.call('toggled', this, expanded); + _expanded = !_expanded; + if (_updatePreference) { + context.storage('disclosure.' + key + '.expanded', _expanded); + } + hideToggle.classed('expanded', _expanded); + wrap.call(uiToggle(_expanded)); + dispatch.call('toggled', this, _expanded); } }; disclosure.title = function(_) { - if (!arguments.length) return title; - title = _; + if (!arguments.length) return _title; + _title = _; return disclosure; }; disclosure.expanded = function(_) { - if (!arguments.length) return expanded; - expanded = _; + if (!arguments.length) return _expanded; + _expanded = _; + return disclosure; + }; + + + disclosure.updatePreference = function(_) { + if (!arguments.length) return _updatePreference; + _updatePreference = _; return disclosure; }; disclosure.content = function(_) { - if (!arguments.length) return content; - content = _; + if (!arguments.length) return _content; + _content = _; return disclosure; }; diff --git a/modules/ui/preset_editor.js b/modules/ui/preset_editor.js index 9944c8c97..602278507 100644 --- a/modules/ui/preset_editor.js +++ b/modules/ui/preset_editor.js @@ -16,7 +16,6 @@ import { utilRebind } from '../util'; export function uiPresetEditor(context) { var dispatch = d3_dispatch('change'), formFields = uiFormFields(context), - expandedPreference = (context.storage('preset_fields.expanded') !== 'false'), state, fieldsArr, preset, @@ -25,17 +24,10 @@ export function uiPresetEditor(context) { function presetEditor(selection) { - selection.call(uiDisclosure() + selection.call(uiDisclosure(context, 'preset_fields', true) .title(t('inspector.all_fields')) - .expanded(expandedPreference) - .on('toggled', toggled) .content(render) ); - - function toggled(expanded) { - expandedPreference = expanded; - context.storage('preset_fields.expanded', expanded); - } } diff --git a/modules/ui/raw_member_editor.js b/modules/ui/raw_member_editor.js index 73c7de159..e8e47b058 100644 --- a/modules/ui/raw_member_editor.js +++ b/modules/ui/raw_member_editor.js @@ -20,8 +20,8 @@ import { export function uiRawMemberEditor(context) { - var id, - taginfo = services.taginfo; + var taginfo = services.taginfo, + _entityID; function selectMember(d) { @@ -53,7 +53,7 @@ export function uiRawMemberEditor(context) { function rawMemberEditor(selection) { - var entity = context.entity(id), + var entity = context.entity(_entityID), memberships = []; entity.members.slice(0, 1000).forEach(function(member, index) { @@ -68,21 +68,17 @@ export function uiRawMemberEditor(context) { }); var gt = entity.members.length > 1000 ? '>' : ''; - selection.call(uiDisclosure() + selection.call(uiDisclosure(context, 'raw_member_editor', true) .title(t('inspector.all_members') + ' (' + gt + memberships.length + ')') .expanded(true) - .on('toggled', toggled) + .updatePreference(false) + .on('toggled', function(expanded) { + if (expanded) { selection.node().parentNode.scrollTop += 200; } + }) .content(content) ); - function toggled(expanded) { - if (expanded) { - selection.node().parentNode.scrollTop += 200; - } - } - - function content(wrap) { var list = wrap.selectAll('.member-list') .data([0]); @@ -201,8 +197,8 @@ export function uiRawMemberEditor(context) { rawMemberEditor.entityID = function(_) { - if (!arguments.length) return id; - id = _; + if (!arguments.length) return _entityID; + _entityID = _; return rawMemberEditor; }; diff --git a/modules/ui/raw_membership_editor.js b/modules/ui/raw_membership_editor.js index 21952ab08..9b67c79d6 100644 --- a/modules/ui/raw_membership_editor.js +++ b/modules/ui/raw_membership_editor.js @@ -28,7 +28,8 @@ import { utilDisplayName, utilNoAuto } from '../util'; export function uiRawMembershipEditor(context) { var taginfo = services.taginfo, - id, showBlank; + _entityID, + _showBlank; function selectRelation(d) { @@ -47,11 +48,13 @@ export function uiRawMembershipEditor(context) { function addMembership(d, role) { - showBlank = false; + _showBlank = false; + + var member = { id: _entityID, type: context.entity(_entityID).type, role: role }; if (d.relation) { context.perform( - actionAddMember(d.relation.id, { id: id, type: context.entity(id).type, role: role }), + actionAddMember(d.relation.id, member), t('operations.add_member.annotation') ); @@ -59,7 +62,7 @@ export function uiRawMembershipEditor(context) { var relation = osmRelation(); context.perform( actionAddEntity(relation), - actionAddMember(relation.id, { id: id, type: context.entity(id).type, role: role }), + actionAddMember(relation.id, member), t('operations.add.annotation.relation') ); @@ -77,15 +80,12 @@ export function uiRawMembershipEditor(context) { function relations(q) { - var newRelation = { - relation: null, - value: t('inspector.new_relation') - }, - result = [], - graph = context.graph(); + var newRelation = { relation: null, value: t('inspector.new_relation') }; + var result = []; + var graph = context.graph(); context.intersects(context.extent()).forEach(function(entity) { - if (entity.type !== 'relation' || entity.id === id) + if (entity.type !== 'relation' || entity.id === _entityID) return; var matched = context.presets().match(entity, graph), @@ -96,10 +96,7 @@ export function uiRawMembershipEditor(context) { if (q && value.toLowerCase().indexOf(q.toLowerCase()) === -1) return; - result.push({ - relation: entity, - value: value - }); + result.push({ relation: entity, value: value }); }); result.sort(function(a, b) { @@ -124,7 +121,7 @@ export function uiRawMembershipEditor(context) { function rawMembershipEditor(selection) { - var entity = context.entity(id), + var entity = context.entity(_entityID), parents = context.graph().parentRelations(entity), memberships = []; @@ -137,21 +134,17 @@ export function uiRawMembershipEditor(context) { }); var gt = parents.length > 1000 ? '>' : ''; - selection.call(uiDisclosure() + selection.call(uiDisclosure(context, 'raw_membership_editor', true) .title(t('inspector.all_relations') + ' (' + gt + memberships.length + ')') .expanded(true) - .on('toggled', toggled) + .updatePreference(false) + .on('toggled', function(expanded) { + if (expanded) { selection.node().parentNode.scrollTop += 200; } + }) .content(content) ); - function toggled(expanded) { - if (expanded) { - selection.node().parentNode.scrollTop += 200; - } - } - - function content(wrap) { var list = wrap.selectAll('.member-list') .data([0]); @@ -218,7 +211,7 @@ export function uiRawMembershipEditor(context) { var newrow = list.selectAll('.member-row-new') - .data(showBlank ? [0] : []); + .data(_showBlank ? [0] : []); newrow.exit() .remove(); @@ -272,7 +265,7 @@ export function uiRawMembershipEditor(context) { addrel .call(svgIcon('#icon-plus', 'light')) .on('click', function() { - showBlank = true; + _showBlank = true; content(wrap); list.selectAll('.member-entity-input').node().focus(); }); @@ -308,7 +301,7 @@ export function uiRawMembershipEditor(context) { taginfo.roles({ debounce: true, rtype: rtype || '', - geometry: context.geometry(id), + geometry: context.geometry(_entityID), query: role }, function(err, data) { if (!err) callback(sort(role, data)); @@ -328,8 +321,8 @@ export function uiRawMembershipEditor(context) { rawMembershipEditor.entityID = function(_) { - if (!arguments.length) return id; - id = _; + if (!arguments.length) return _entityID; + _entityID = _; return rawMembershipEditor; }; diff --git a/modules/ui/raw_tag_editor.js b/modules/ui/raw_tag_editor.js index e3cf7b6e4..9e5bccc70 100644 --- a/modules/ui/raw_tag_editor.js +++ b/modules/ui/raw_tag_editor.js @@ -25,34 +25,36 @@ import { export function uiRawTagEditor(context) { var taginfo = services.taginfo, dispatch = d3_dispatch('change'), - expandedPreference = (context.storage('raw_tag_editor.expanded') === 'true'), - expandedCurrent = expandedPreference, - updatePreference = true, - readOnlyTags = [], - showBlank = false, - newRow, - state, - preset, - tags, - id; + _readOnlyTags = [], + _showBlank = false, + _updatePreference = true, + _expanded = false, + _newRow, + _state, + _preset, + _tags, + _entityID; function rawTagEditor(selection) { - var count = Object.keys(tags).filter(function(d) { return d; }).length; + var count = Object.keys(_tags).filter(function(d) { return d; }).length; - selection.call(uiDisclosure() + var disclosure = uiDisclosure(context, 'raw_tag_editor', false) .title(t('inspector.all_tags') + ' (' + count + ')') - .expanded(expandedCurrent) .on('toggled', toggled) - .content(content) - ); + .updatePreference(_updatePreference) + .content(content); + + // Sometimes we want to force the raw_tag_editor to be opened/closed.. + // When undefined, uiDisclosure will use the user's stored preference. + if (_expanded !== undefined) { + disclosure.expanded(_expanded); + } + + selection.call(disclosure); function toggled(expanded) { - expandedCurrent = expanded; - if (updatePreference) { - expandedPreference = expanded; - context.storage('raw_tag_editor.expanded', expanded); - } + _expanded = expanded; if (expanded) { selection.node().parentNode.scrollTop += 200; } @@ -61,14 +63,14 @@ export function uiRawTagEditor(context) { function content(wrap) { - var entries = _map(tags, function(v, k) { + var entries = _map(_tags, function(v, k) { return { key: k, value: v }; }); - if (!entries.length || showBlank) { - showBlank = false; + if (!entries.length || _showBlank) { + _showBlank = false; entries.push({key: '', value: ''}); - newRow = ''; + _newRow = ''; } var list = wrap.selectAll('.tag-list') @@ -138,8 +140,8 @@ export function uiRawTagEditor(context) { items = items .merge(enter) .sort(function(a, b) { - return (a.key === newRow && b.key !== newRow) ? 1 - : (a.key !== newRow && b.key === newRow) ? -1 + return (a.key === _newRow && b.key !== _newRow) ? 1 + : (a.key !== _newRow && b.key === _newRow) ? -1 : d3_ascending(a.key, b.key); }); @@ -149,11 +151,11 @@ export function uiRawTagEditor(context) { key = row.select('input.key'), // propagate bound data to child value = row.select('input.value'); // propagate bound data to child - if (id && taginfo) { + if (_entityID && taginfo) { bindTypeahead(key, value); } - var isRelation = (id && context.entity(id).type === 'relation'), + var isRelation = (_entityID && context.entity(_entityID).type === 'relation'), reference; if (isRelation && tag.key === 'type') { @@ -162,7 +164,7 @@ export function uiRawTagEditor(context) { reference = uiTagReference({ key: tag.key, value: tag.value }, context); } - if (state === 'hover') { + if (_state === 'hover') { reference.showing(false); } @@ -187,8 +189,8 @@ export function uiRawTagEditor(context) { function isReadOnly(d) { - for (var i = 0; i < readOnlyTags.length; i++) { - if (d.key.match(readOnlyTags[i]) !== null) { + for (var i = 0; i < _readOnlyTags.length; i++) { + if (d.key.match(_readOnlyTags[i]) !== null) { return true; } } @@ -206,7 +208,7 @@ export function uiRawTagEditor(context) { function bindTypeahead(key, value) { if (isReadOnly({ key: key })) return; - var geometry = context.geometry(id); + var geometry = context.geometry(_entityID); key.call(d3_combobox() .container(context.container()) @@ -275,7 +277,7 @@ export function uiRawTagEditor(context) { var match = kNew.match(/^(.*?)(?:_(\d+))?$/), base = match[1], suffix = +(match[2] || 1); - while (tags[kNew]) { // rename key if already in use + while (_tags[kNew]) { // rename key if already in use kNew = base + '_' + suffix++; } } @@ -284,8 +286,8 @@ export function uiRawTagEditor(context) { d.key = kNew; // Maintain DOM identity through the subsequent update. - if (newRow === kOld) { // see if this row is still a new row - newRow = ((d.value === '' || kNew === '') ? kNew : undefined); + if (_newRow === kOld) { // see if this row is still a new row + _newRow = ((d.value === '' || kNew === '') ? kNew : undefined); } this.value = kNew; @@ -298,8 +300,8 @@ export function uiRawTagEditor(context) { var tag = {}; tag[d.key] = this.value; - if (newRow === d.key && d.key !== '' && d.value !== '') { // not a new row anymore - newRow = undefined; + if (_newRow === d.key && d.key !== '' && d.value !== '') { // not a new row anymore + _newRow = undefined; } dispatch.call('change', this, tag); @@ -320,7 +322,7 @@ export function uiRawTagEditor(context) { // handler. Without the setTimeout, the call to `content` would // wipe out the pending value change. setTimeout(function() { - showBlank = true; + _showBlank = true; content(wrap); list.selectAll('li:last-child input.key').node().focus(); }, 0); @@ -329,51 +331,51 @@ export function uiRawTagEditor(context) { rawTagEditor.state = function(_) { - if (!arguments.length) return state; - state = _; + if (!arguments.length) return _state; + _state = _; return rawTagEditor; }; rawTagEditor.preset = function(_) { - if (!arguments.length) return preset; - preset = _; - if (preset.isFallback()) { - expandedCurrent = true; - updatePreference = false; + if (!arguments.length) return _preset; + _preset = _; + if (_preset.isFallback()) { + _expanded = true; + _updatePreference = false; } else { - expandedCurrent = expandedPreference; - updatePreference = true; + _expanded = undefined; + _updatePreference = true; } return rawTagEditor; }; rawTagEditor.tags = function(_) { - if (!arguments.length) return tags; - tags = _; + if (!arguments.length) return _tags; + _tags = _; return rawTagEditor; }; rawTagEditor.entityID = function(_) { - if (!arguments.length) return id; - id = _; + if (!arguments.length) return _entityID; + _entityID = _; return rawTagEditor; }; rawTagEditor.expanded = function(_) { - if (!arguments.length) return expandedCurrent; - expandedCurrent = _; - updatePreference = false; + if (!arguments.length) return _expanded; + _expanded = _; + _updatePreference = false; return rawTagEditor; }; rawTagEditor.readOnlyTags = function(_) { - if (!arguments.length) return readOnlyTags; - readOnlyTags = _; + if (!arguments.length) return _readOnlyTags; + _readOnlyTags = _; return rawTagEditor; }; From 385297d99317607248d9a8b8ba8f143220e2ceff Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Mon, 27 Nov 2017 22:03:11 -0500 Subject: [PATCH 04/18] Use uiDisclosure for data pane subsections --- modules/ui/background.js | 6 +- modules/ui/disclosure.js | 5 +- modules/ui/map_data.js | 783 ++++++++++++++++++++------------------- 3 files changed, 402 insertions(+), 392 deletions(-) diff --git a/modules/ui/background.js b/modules/ui/background.js index 5ad890c3e..732188deb 100644 --- a/modules/ui/background.js +++ b/modules/ui/background.js @@ -147,7 +147,7 @@ export function uiBackground(context) { } - function drawList(layerList, type, change, filter) { + function drawListItems(layerList, type, change, filter) { var sources = context.background() .sources(context.map().extent()) .filter(filter); @@ -204,10 +204,10 @@ export function uiBackground(context) { function update() { backgroundList - .call(drawList, 'radio', clickSetSource, function(d) { return !d.isHidden() && !d.overlay; }); + .call(drawListItems, 'radio', clickSetSource, function(d) { return !d.isHidden() && !d.overlay; }); overlayList - .call(drawList, 'checkbox', clickSetOverlay, function(d) { return !d.isHidden() && d.overlay; }); + .call(drawListItems, 'checkbox', clickSetOverlay, function(d) { return !d.isHidden() && d.overlay; }); selectLayer(); diff --git a/modules/ui/disclosure.js b/modules/ui/disclosure.js index 9d84c02f8..45c6315a5 100644 --- a/modules/ui/disclosure.js +++ b/modules/ui/disclosure.js @@ -20,7 +20,7 @@ export function uiDisclosure(context, key, expandedDefault) { hideToggle = hideToggle.enter() .append('a') .attr('href', '#') - .attr('class', 'hide-toggle') + .attr('class', 'hide-toggle hide-toggle-' + key) .merge(hideToggle); hideToggle @@ -29,11 +29,12 @@ export function uiDisclosure(context, key, expandedDefault) { .classed('expanded', _expanded); - var wrap = selection.selectAll('div') + var wrap = selection.selectAll('.disclosure-wrap') .data([0]); wrap = wrap.enter() .append('div') + .attr('class', 'disclosure-wrap disclosure-wrap-' + key) .merge(wrap); wrap diff --git a/modules/ui/map_data.js b/modules/ui/map_data.js index fb522c098..a928bd649 100644 --- a/modules/ui/map_data.js +++ b/modules/ui/map_data.js @@ -5,380 +5,424 @@ import { import { d3keybinding as d3_keybinding } from '../lib/d3.keybinding.js'; -import { t, textDirection } from '../util/locale'; import { svgIcon } from '../svg'; -import { uiTooltipHtml } from './tooltipHtml'; +import { t, textDirection } from '../util/locale'; import { tooltip } from '../util/tooltip'; +import { uiDisclosure } from './disclosure'; +import { uiTooltipHtml } from './tooltipHtml'; export function uiMapData(context) { - var key = t('map_data.key'), - features = context.features().keys(), - layers = context.layers(), - fills = ['wireframe', 'partial', 'full'], - fillDefault = context.storage('area-fill') || 'partial', - fillSelected = fillDefault; + var key = t('map_data.key'); + var features = context.features().keys(); + var layers = context.layers(); + var fills = ['wireframe', 'partial', 'full']; + + var _fillDefault = context.storage('area-fill') || 'partial'; + var _fillSelected = _fillDefault; + var _shown = false; + var _dataLayerContainer = d3_select(null); + var _fillList = d3_select(null); + var _featureList = d3_select(null); - function map_data(selection) { - function showsFeature(d) { - return context.features().enabled(d); + function showsFeature(d) { + return context.features().enabled(d); + } + + + function autoHiddenFeature(d) { + return context.features().autoHidden(d); + } + + + function clickFeature(d) { + context.features().toggle(d); + update(); + } + + + function showsFill(d) { + return _fillSelected === d; + } + + + function setFill(d) { + fills.forEach(function(opt) { + context.surface().classed('fill-' + opt, Boolean(opt === d)); + }); + + _fillSelected = d; + if (d !== 'wireframe') { + _fillDefault = d; + context.storage('area-fill', d); } + update(); + } - function autoHiddenFeature(d) { - return context.features().autoHidden(d); + function showsLayer(which) { + var layer = layers.layer(which); + if (layer) { + return layer.enabled(); } + return false; + } - function clickFeature(d) { - context.features().toggle(d); + function setLayer(which, enabled) { + var layer = layers.layer(which); + if (layer) { + layer.enabled(enabled); update(); } + } - function showsFill(d) { - return fillSelected === d; + function toggleLayer(which) { + setLayer(which, !showsLayer(which)); + } + + + function drawPhotoItems(selection) { + var photoKeys = ['mapillary-images', 'mapillary-signs', 'openstreetcam-images']; + var photoLayers = layers.all().filter(function(obj) { return photoKeys.indexOf(obj.id) !== -1; }); + var data = photoLayers.filter(function(obj) { return obj.layer.supported(); }); + + function layerSupported(d) { + return d.layer && d.layer.supported(); + } + function layerEnabled(d) { + return layerSupported(d) && d.layer.enabled(); } + var ul = selection + .selectAll('.layer-list-photos') + .data([0]); - function setFill(d) { - fills.forEach(function(opt) { - context.surface().classed('fill-' + opt, Boolean(opt === d)); + ul = ul.enter() + .append('ul') + .attr('class', 'layer-list layer-list-photos') + .merge(ul); + + var li = ul.selectAll('.list-item-photos') + .data(data); + + li.exit() + .remove(); + + var liEnter = li.enter() + .append('li') + .attr('class', function(d) { return 'list-item-photos list-item-' + d.id; }); + + var labelEnter = liEnter + .append('label') + .each(function(d) { + d3_select(this) + .call(tooltip() + .title(t(d.id.replace('-', '_') + '.tooltip')) + .placement('top') + ); }); - fillSelected = d; - if (d !== 'wireframe') { - fillDefault = d; - context.storage('area-fill', d); - } - update(); - } + labelEnter + .append('input') + .attr('type', 'checkbox') + .on('change', function(d) { toggleLayer(d.id); }); + + labelEnter + .append('span') + .text(function(d) { return t(d.id.replace('-', '_') + '.title'); }); - function showsLayer(which) { - var layer = layers.layer(which); - if (layer) { - return layer.enabled(); - } - return false; - } + // Update + li = li + .merge(liEnter); + + li + .classed('active', layerEnabled) + .selectAll('input') + .property('checked', layerEnabled); + } - function setLayer(which, enabled) { - var layer = layers.layer(which); - if (layer) { - layer.enabled(enabled); - update(); - } - } + function drawOsmItem(selection) { + var osm = layers.layer('osm'), + showsOsm = osm.enabled(); + + var ul = selection + .selectAll('.layer-list-osm') + .data(osm ? [0] : []); + + // Exit + ul.exit() + .remove(); + + // Enter + var ulEnter = ul.enter() + .append('ul') + .attr('class', 'layer-list layer-list-osm'); + + var liEnter = ulEnter + .append('li') + .attr('class', 'list-item-osm'); + + var labelEnter = liEnter + .append('label') + .call(tooltip() + .title(t('map_data.layers.osm.tooltip')) + .placement('top') + ); + + labelEnter + .append('input') + .attr('type', 'checkbox') + .on('change', function() { toggleLayer('osm'); }); + + labelEnter + .append('span') + .text(t('map_data.layers.osm.title')); + + // Update + ul = ul + .merge(ulEnter); + + ul.selectAll('.list-item-osm') + .classed('active', showsOsm) + .selectAll('input') + .property('checked', showsOsm); + } - function toggleLayer(which) { - setLayer(which, !showsLayer(which)); - } + function drawGpxItem(selection) { + var gpx = layers.layer('gpx'), + hasGpx = gpx && gpx.hasGpx(), + showsGpx = hasGpx && gpx.enabled(); + var ul = selection + .selectAll('.layer-list-gpx') + .data(gpx ? [0] : []); - function drawPhotoItems(selection) { - var photoKeys = ['mapillary-images', 'mapillary-signs', 'openstreetcam-images']; - var photoLayers = layers.all().filter(function(obj) { return photoKeys.indexOf(obj.id) !== -1; }); - var data = photoLayers.filter(function(obj) { return obj.layer.supported(); }); + // Exit + ul.exit() + .remove(); - function layerSupported(d) { - return d.layer && d.layer.supported(); - } - function layerEnabled(d) { - return layerSupported(d) && d.layer.enabled(); - } + // Enter + var ulEnter = ul.enter() + .append('ul') + .attr('class', 'layer-list layer-list-gpx'); - var ul = selection - .selectAll('.layer-list-photos') - .data([0]); + var liEnter = ulEnter + .append('li') + .attr('class', 'list-item-gpx'); - ul = ul.enter() - .append('ul') - .attr('class', 'layer-list layer-list-photos') - .merge(ul); + liEnter + .append('button') + .attr('class', 'list-item-gpx-extent') + .call(tooltip() + .title(t('gpx.zoom')) + .placement((textDirection === 'rtl') ? 'right' : 'left') + ) + .on('click', function() { + d3_event.preventDefault(); + d3_event.stopPropagation(); + gpx.fitZoom(); + }) + .call(svgIcon('#icon-search')); - var li = ul.selectAll('.list-item-photos') - .data(data); - - li.exit() - .remove(); - - var liEnter = li.enter() - .append('li') - .attr('class', function(d) { return 'list-item-photos list-item-' + d.id; }); - - var labelEnter = liEnter - .append('label') - .each(function(d) { - d3_select(this) - .call(tooltip() - .title(t(d.id.replace('-', '_') + '.tooltip')) - .placement('top') - ); - }); - - labelEnter - .append('input') - .attr('type', 'checkbox') - .on('change', function(d) { toggleLayer(d.id); }); - - labelEnter - .append('span') - .text(function(d) { return t(d.id.replace('-', '_') + '.title'); }); - - - // Update - li = li - .merge(liEnter); - - li - .classed('active', layerEnabled) - .selectAll('input') - .property('checked', layerEnabled); - } - - - function drawOsmItem(selection) { - var osm = layers.layer('osm'), - showsOsm = osm.enabled(); - - var ul = selection - .selectAll('.layer-list-osm') - .data(osm ? [0] : []); - - // Exit - ul.exit() - .remove(); - - // Enter - var ulEnter = ul.enter() - .append('ul') - .attr('class', 'layer-list layer-list-osm'); - - var liEnter = ulEnter - .append('li') - .attr('class', 'list-item-osm'); - - var labelEnter = liEnter - .append('label') - .call(tooltip() - .title(t('map_data.layers.osm.tooltip')) - .placement('top') - ); - - labelEnter - .append('input') - .attr('type', 'checkbox') - .on('change', function() { toggleLayer('osm'); }); - - labelEnter - .append('span') - .text(t('map_data.layers.osm.title')); - - // Update - ul = ul - .merge(ulEnter); - - ul.selectAll('.list-item-osm') - .classed('active', showsOsm) - .selectAll('input') - .property('checked', showsOsm); - } - - - function drawGpxItem(selection) { - var gpx = layers.layer('gpx'), - hasGpx = gpx && gpx.hasGpx(), - showsGpx = hasGpx && gpx.enabled(); - - var ul = selection - .selectAll('.layer-list-gpx') - .data(gpx ? [0] : []); - - // Exit - ul.exit() - .remove(); - - // Enter - var ulEnter = ul.enter() - .append('ul') - .attr('class', 'layer-list layer-list-gpx'); - - var liEnter = ulEnter - .append('li') - .attr('class', 'list-item-gpx'); - - liEnter - .append('button') - .attr('class', 'list-item-gpx-extent') - .call(tooltip() - .title(t('gpx.zoom')) - .placement((textDirection === 'rtl') ? 'right' : 'left')) - .on('click', function() { - d3_event.preventDefault(); - d3_event.stopPropagation(); - gpx.fitZoom(); - }) - .call(svgIcon('#icon-search')); - - liEnter - .append('button') - .attr('class', 'list-item-gpx-browse') - .call(tooltip() - .title(t('gpx.browse')) - .placement((textDirection === 'rtl') ? 'right' : 'left') - ) - .on('click', function() { - d3_select(document.createElement('input')) - .attr('type', 'file') - .on('change', function() { - gpx.files(d3_event.target.files); - }) - .node().click(); - }) - .call(svgIcon('#icon-geolocate')); - - var labelEnter = liEnter - .append('label') - .call(tooltip() - .title(t('gpx.drag_drop')) - .placement('top') - ); - - labelEnter - .append('input') - .attr('type', 'checkbox') - .on('change', function() { toggleLayer('gpx'); }); - - labelEnter - .append('span') - .text(t('gpx.local_layer')); - - // Update - ul = ul - .merge(ulEnter); - - ul.selectAll('.list-item-gpx') - .classed('active', showsGpx) - .selectAll('label') - .classed('deemphasize', !hasGpx) - .selectAll('input') - .property('disabled', !hasGpx) - .property('checked', showsGpx); - } - - - function drawList(selection, data, type, name, change, active) { - var items = selection.selectAll('li') - .data(data); - - // Exit - items.exit() - .remove(); - - // Enter - var enter = items.enter() - .append('li') - .attr('class', 'layer') - .call(tooltip() - .html(true) - .title(function(d) { - var tip = t(name + '.' + d + '.tooltip'), - key = (d === 'wireframe' ? t('area_fill.wireframe.key') : null); - - if (name === 'feature' && autoHiddenFeature(d)) { - var msg = showsLayer('osm') ? t('map_data.autohidden') : t('map_data.osmhidden'); - tip += '
' + msg + '
'; - } - return uiTooltipHtml(tip, key); + liEnter + .append('button') + .attr('class', 'list-item-gpx-browse') + .call(tooltip() + .title(t('gpx.browse')) + .placement((textDirection === 'rtl') ? 'right' : 'left') + ) + .on('click', function() { + d3_select(document.createElement('input')) + .attr('type', 'file') + .on('change', function() { + gpx.files(d3_event.target.files); }) - .placement('top') - ); + .node().click(); + }) + .call(svgIcon('#icon-geolocate')); - var label = enter - .append('label'); + var labelEnter = liEnter + .append('label') + .call(tooltip() + .title(t('gpx.drag_drop')) + .placement('top') + ); - label - .append('input') - .attr('type', type) - .attr('name', name) - .on('change', change); + labelEnter + .append('input') + .attr('type', 'checkbox') + .on('change', function() { toggleLayer('gpx'); }); - label - .append('span') - .text(function(d) { return t(name + '.' + d + '.description'); }); + labelEnter + .append('span') + .text(t('gpx.local_layer')); - // Update - items = items - .merge(enter); + // Update + ul = ul + .merge(ulEnter); - items - .classed('active', active) - .selectAll('input') - .property('checked', active) - .property('indeterminate', function(d) { - return (name === 'feature' && autoHiddenFeature(d)); - }); + ul.selectAll('.list-item-gpx') + .classed('active', showsGpx) + .selectAll('label') + .classed('deemphasize', !hasGpx) + .selectAll('input') + .property('disabled', !hasGpx) + .property('checked', showsGpx); + } + + + function drawListItems(selection, data, type, name, change, active) { + var items = selection.selectAll('li') + .data(data); + + // Exit + items.exit() + .remove(); + + // Enter + var enter = items.enter() + .append('li') + .attr('class', 'layer') + .call(tooltip() + .html(true) + .title(function(d) { + var tip = t(name + '.' + d + '.tooltip'), + key = (d === 'wireframe' ? t('area_fill.wireframe.key') : null); + + if (name === 'feature' && autoHiddenFeature(d)) { + var msg = showsLayer('osm') ? t('map_data.autohidden') : t('map_data.osmhidden'); + tip += '
' + msg + '
'; + } + return uiTooltipHtml(tip, key); + }) + .placement('top') + ); + + var label = enter + .append('label'); + + label + .append('input') + .attr('type', type) + .attr('name', name) + .on('change', change); + + label + .append('span') + .text(function(d) { return t(name + '.' + d + '.description'); }); + + // Update + items = items + .merge(enter); + + items + .classed('active', active) + .selectAll('input') + .property('checked', active) + .property('indeterminate', function(d) { + return (name === 'feature' && autoHiddenFeature(d)); + }); + } + + + function renderDataLayers(selection) { + var container = selection.selectAll('data-layer-container') + .data([0]); + + _dataLayerContainer = container.enter() + .append('div') + .attr('class', 'data-layer-container') + .merge(container); + } + + + function renderFillList(selection) { + var container = selection.selectAll('layer-fill-list') + .data([0]); + + _fillList = container.enter() + .append('ul') + .attr('class', 'layer-list layer-fill-list') + .merge(container); + } + + + function renderFeatureList(selection) { + var container = selection.selectAll('layer-feature-list') + .data([0]); + + _featureList = container.enter() + .append('ul') + .attr('class', 'layer-list layer-feature-list') + .merge(container); + } + + + function update() { + _dataLayerContainer + .call(drawOsmItem) + .call(drawPhotoItems) + .call(drawGpxItem); + + _fillList + .call(drawListItems, fills, 'radio', 'area_fill', setFill, showsFill); + + _featureList + .call(drawListItems, features, 'checkbox', 'feature', clickFeature, showsFeature); + } + + + function toggleWireframe() { + if (d3_event) { + d3_event.preventDefault(); + d3_event.stopPropagation(); } + setFill((_fillSelected === 'wireframe' ? _fillDefault : 'wireframe')); + context.map().pan([0,0]); // trigger a redraw + } - function update() { - dataLayerContainer - .call(drawOsmItem) - .call(drawPhotoItems) - .call(drawGpxItem); + function mapData(selection) { - fillList - .call(drawList, fills, 'radio', 'area_fill', setFill, showsFill); - - featureList - .call(drawList, features, 'checkbox', 'feature', clickFeature, showsFeature); - } - - - function hidePanel() { + function hidePane() { setVisible(false); } - - function togglePanel() { + function togglePane() { if (d3_event) d3_event.preventDefault(); - tooltipBehavior.hide(button); + paneTooltip.hide(button); setVisible(!button.classed('active')); } - - function toggleWireframe() { - if (d3_event) { - d3_event.preventDefault(); - d3_event.stopPropagation(); - } - setFill((fillSelected === 'wireframe' ? fillDefault : 'wireframe')); - context.map().pan([0,0]); // trigger a redraw - } - - function setVisible(show) { - if (show !== shown) { + if (show !== _shown) { button.classed('active', show); - shown = show; + _shown = show; if (show) { update(); + selection.on('mousedown.map_data-inside', function() { return d3_event.stopPropagation(); }); - content.style('display', 'block') + + pane + .style('display', 'block') .style('right', '-300px') .transition() .duration(200) .style('right', '0px'); + } else { - content.style('display', 'block') + pane + .style('display', 'block') .style('right', '0px') .transition() .duration(200) @@ -386,113 +430,78 @@ export function uiMapData(context) { .on('end', function() { d3_select(this).style('display', 'none'); }); + selection.on('mousedown.map_data-inside', null); } } } - 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('map_data.description'), key)), - button = selection - .append('button') - .attr('tabindex', -1) - .on('click', togglePanel) - .call(svgIcon('#icon-data', 'light')) - .call(tooltipBehavior), - shown = false; + var pane = selection + .append('div') + .attr('class', 'fillL map-overlay col3 content hide'); - content - .append('h4') + var paneTooltip = tooltip() + .placement((textDirection === 'rtl') ? 'right' : 'left') + .html(true) + .title(uiTooltipHtml(t('map_data.description'), key)); + + var button = selection + .append('button') + .attr('tabindex', -1) + .on('click', togglePane) + .call(svgIcon('#icon-data', 'light')) + .call(paneTooltip); + + + pane + .append('h3') .text(t('map_data.title')); // data layers - content - .append('a') - .text(t('map_data.data_layers')) - .attr('href', '#') - .classed('hide-toggle', true) - .classed('expanded', true) - .on('click', function() { - var exp = d3_select(this).classed('expanded'); - dataLayerContainer.style('display', exp ? 'none' : 'block'); - d3_select(this).classed('expanded', !exp); - d3_event.preventDefault(); - }); - - var dataLayerContainer = content + pane .append('div') - .attr('class', 'data-data-layers') - .style('display', 'block'); - + .attr('class', 'map-data-data-layers') + .call(uiDisclosure(context, 'data_layers', true) + .title(t('map_data.data_layers')) + .content(renderDataLayers) + ); // area fills - content - .append('a') - .text(t('map_data.fill_area')) - .attr('href', '#') - .classed('hide-toggle', true) - .classed('expanded', false) - .on('click', function() { - var exp = d3_select(this).classed('expanded'); - fillContainer.style('display', exp ? 'none' : 'block'); - d3_select(this).classed('expanded', !exp); - d3_event.preventDefault(); - }); - - var fillContainer = content + pane .append('div') - .attr('class', 'data-area-fills') - .style('display', 'none'); - - var fillList = fillContainer - .append('ul') - .attr('class', 'layer-list layer-fill-list'); - + .attr('class', 'map-data-area-fills') + .call(uiDisclosure(context, 'fill_area', false) + .title(t('map_data.fill_area')) + .content(renderFillList) + ); // feature filters - content - .append('a') - .text(t('map_data.map_features')) - .attr('href', '#') - .classed('hide-toggle', true) - .classed('expanded', false) - .on('click', function() { - var exp = d3_select(this).classed('expanded'); - featureContainer.style('display', exp ? 'none' : 'block'); - d3_select(this).classed('expanded', !exp); - d3_event.preventDefault(); - }); - - var featureContainer = content + pane .append('div') - .attr('class', 'data-feature-filters') - .style('display', 'none'); - - var featureList = featureContainer - .append('ul') - .attr('class', 'layer-list layer-feature-list'); + .attr('class', 'map-data-feature-filters') + .call(uiDisclosure(context, 'map_features', false) + .title(t('map_data.map_features')) + .content(renderFeatureList) + ); + // add listeners context.features() .on('change.map_data-update', update); - setFill(fillDefault); + update(); + setFill(_fillDefault); var keybinding = d3_keybinding('features') - .on(key, togglePanel) + .on(key, togglePane) .on(t('area_fill.wireframe.key'), toggleWireframe) - .on([t('background.key'), t('help.key')], hidePanel); + .on([t('background.key'), t('help.key')], hidePane); d3_select(document) .call(keybinding); } - return map_data; + return mapData; } From 5a9749c51642f512cc7bea03f4c3f8694d3dff5a Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Mon, 27 Nov 2017 23:42:02 -0500 Subject: [PATCH 05/18] Fix bug that caused clicking a uiDisclosure to change the url (closes #4570) --- modules/ui/disclosure.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/ui/disclosure.js b/modules/ui/disclosure.js index 45c6315a5..3b4c3a0a4 100644 --- a/modules/ui/disclosure.js +++ b/modules/ui/disclosure.js @@ -1,4 +1,5 @@ import { dispatch as d3_dispatch } from 'd3-dispatch'; +import { event as d3_event } from 'd3-selection'; import { utilRebind } from '../util/rebind'; import { uiToggle } from './toggle'; @@ -43,12 +44,15 @@ export function uiDisclosure(context, key, expandedDefault) { function toggle() { + d3_event.preventDefault(); + _expanded = !_expanded; if (_updatePreference) { context.storage('disclosure.' + key + '.expanded', _expanded); } hideToggle.classed('expanded', _expanded); wrap.call(uiToggle(_expanded)); + dispatch.call('toggled', this, _expanded); } }; From d7e8625d6b7d2f987db87dd363cc2f8f9f2a90db Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Tue, 28 Nov 2017 15:06:19 -0500 Subject: [PATCH 06/18] Use uiDisclosure for background pane subsections, move brightness --- css/80_app.css | 41 ++- data/core.yaml | 12 +- dist/locales/en.json | 14 +- modules/ui/background.js | 572 ++++++++++++++++++++++----------------- 4 files changed, 352 insertions(+), 287 deletions(-) diff --git a/css/80_app.css b/css/80_app.css index b8662d899..2e8b95715 100644 --- a/css/80_app.css +++ b/css/80_app.css @@ -2307,7 +2307,7 @@ div.full-screen > button:hover { padding: 5px 10px; cursor: pointer; color: #7092FF; - border-radius: 3px; + border-top: 1px solid #ccc; } .minimap-toggle.active { @@ -2463,42 +2463,39 @@ div.full-screen > button:hover { } .background-control .nudge.right::after { - border-top: 5px solid transparent; - border-bottom: 5px solid transparent; - border-left: 5px solid #222; + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + border-left: 5px solid #222; } .background-control .nudge.left::after { - border-top: 5px solid transparent; - border-bottom: 5px solid transparent; - border-right: 5px solid #222; + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + border-right: 5px solid #222; } .background-control .nudge.top::after { - border-right: 5px solid transparent; - border-left: 5px solid transparent; - border-bottom: 5px solid #222; + border-right: 5px solid transparent; + border-left: 5px solid transparent; + border-bottom: 5px solid #222; } .background-control .nudge.bottom::after { - border-right: 5px solid transparent; - border-left: 5px solid transparent; - border-top: 5px solid #222; + border-right: 5px solid transparent; + border-left: 5px solid transparent; + border-top: 5px solid #222; +} + +.opacity-options-wrapper { + padding: 10px; } .opacity-options { background: url(img/background-pattern-opacity.png) 0 0 repeat; height: 20px; width: 82px; - position: absolute; - right: 50px; - top: 20px; border: 1px solid #ccc; } -[dir='rtl'] .opacity-options { - left: 50px; - right: auto; -} .opacity-options li { height: 100%; @@ -2513,8 +2510,6 @@ div.full-screen > button:hover { z-index: 9999; } -.map-data-control li:hover .select-box, -.map-data-control li.selected .select-box, .background-control li:hover .select-box, .background-control li.selected .select-box { border: 2px solid #7092ff; @@ -2522,8 +2517,6 @@ div.full-screen > button:hover { opacity: .5; } -.map-data-control li.selected:hover .select-box, -.map-data-control li.selected .select-box, .background-control li.selected:hover .select-box, .background-control li.selected .select-box { opacity: 1; diff --git a/data/core.yaml b/data/core.yaml index e40b52190..1344b733a 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -372,21 +372,25 @@ en: title: Background description: Background settings key: B - percent_brightness: "{opacity}% brightness" + backgrounds: Backgrounds none: None best_imagery: Best known imagery source for this location switch: Switch back to this background custom: Custom custom_button: Edit custom background custom_prompt: "Enter a tile URL template. Valid tokens are:\n - {zoom}/{z}, {x}, {y} for Z/X/Y tile scheme\n - {ty} for flipped TMS-style Y coordinates\n - {u} for quadtile scheme\n - {switch:a,b,c} for DNS server multiplexing\n\nExample:\n{example}" - fix_misalignment: Adjust imagery offset + overlays: Overlays imagery_source_faq: Where does this imagery come from? reset: reset - offset: "Drag anywhere in the gray area below to adjust the imagery offset, or enter the offset values in meters." + display_options: Display Options + brightness: Brightness + percent_brightness: "{opacity}% brightness" minimap: - description: Minimap + description: Show Minimap tooltip: Show a zoomed out map to help locate the area currently displayed. key: '/' + fix_misalignment: Adjust imagery offset + offset: "Drag anywhere in the gray area below to adjust the imagery offset, or enter the offset values in meters." map_data: title: Map Data description: Map Data diff --git a/dist/locales/en.json b/dist/locales/en.json index 0cdefe708..c41d2f005 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -460,22 +460,26 @@ "title": "Background", "description": "Background settings", "key": "B", - "percent_brightness": "{opacity}% brightness", + "backgrounds": "Backgrounds", "none": "None", "best_imagery": "Best known imagery source for this location", "switch": "Switch back to this background", "custom": "Custom", "custom_button": "Edit custom background", "custom_prompt": "Enter a tile URL template. Valid tokens are:\n - {zoom}/{z}, {x}, {y} for Z/X/Y tile scheme\n - {ty} for flipped TMS-style Y coordinates\n - {u} for quadtile scheme\n - {switch:a,b,c} for DNS server multiplexing\n\nExample:\n{example}", - "fix_misalignment": "Adjust imagery offset", + "overlays": "Overlays", "imagery_source_faq": "Where does this imagery come from?", "reset": "reset", - "offset": "Drag anywhere in the gray area below to adjust the imagery offset, or enter the offset values in meters.", + "display_options": "Display Options", + "brightness": "Brightness", + "percent_brightness": "{opacity}% brightness", "minimap": { - "description": "Minimap", + "description": "Show Minimap", "tooltip": "Show a zoomed out map to help locate the area currently displayed.", "key": "/" - } + }, + "fix_misalignment": "Adjust imagery offset", + "offset": "Drag anywhere in the gray area below to adjust the imagery offset, or enter the offset values in meters." }, "map_data": { "title": "Map Data", diff --git a/modules/ui/background.js b/modules/ui/background.js index 732188deb..d23287a7f 100644 --- a/modules/ui/background.js +++ b/modules/ui/background.js @@ -16,6 +16,7 @@ import { t, textDirection } from '../util/locale'; import { svgIcon } from '../svg'; import { uiBackgroundOffset } from './background_offset'; import { uiCmd } from './cmd'; +import { uiDisclosure } from './disclosure'; import { uiMapInMap } from './map_in_map'; import { uiTooltipHtml } from './tooltipHtml'; import { utilDetect } from '../util/detect'; @@ -24,227 +25,325 @@ import { tooltip } from '../util/tooltip'; export function uiBackground(context) { - var key = t('background.key'), - detected = utilDetect(), - opacities = [1, 0.75, 0.5, 0.25], - opacityDefault = (context.storage('background-opacity') !== null) ? - (+context.storage('background-opacity')) : 1.0, - customSource = context.background().findSource('custom'), - previous; + var key = t('background.key'); + var detected = utilDetect(); + var opacities = [1, 0.75, 0.5, 0.25]; + + var _opacityDefault = (context.storage('background-opacity') !== null) ? + (+context.storage('background-opacity')) : 1.0; + var _customSource = context.background().findSource('custom'); + var _previousBackground; + var _shown = false; + + var _backgroundList = d3_select(null); + var _overlayList = d3_select(null); + var _displayOptions = d3_select(null); + var _offsetContainer = d3_select(null); var backgroundOffset = uiBackgroundOffset(context); // Can be 0 from <1.3.0 use or due to issue #1923. - if (opacityDefault === 0) opacityDefault = 1.0; + if (_opacityDefault === 0) _opacityDefault = 1.0; - function background(selection) { + function setOpacity(d) { + var bg = context.container().selectAll('.layer-background') + .transition() + .style('opacity', d) + .attr('data-opacity', d); + + if (!detected.opera) { + utilSetTransform(bg, 0, 0); + } + + _displayOptions.selectAll('opacity-options li') + .classed('active', function(_) { return _ === d; }); + + context.storage('background-opacity', d); + } + + + function setTooltips(selection) { + selection.each(function(d, i, nodes) { + var item = d3_select(this).select('label'), + span = item.select('span'), + placement = (i < nodes.length / 2) ? 'bottom' : 'top', + description = d.description(), + isOverflowing = (span.property('clientWidth') !== span.property('scrollWidth')); + + if (d === _previousBackground) { + item.call(tooltip() + .placement(placement) + .html(true) + .title(function() { + var tip = '
' + t('background.switch') + '
'; + return uiTooltipHtml(tip, uiCmd('⌘' + key)); + }) + ); + } else if (description || isOverflowing) { + item.call(tooltip() + .placement(placement) + .title(description || d.name()) + ); + } else { + item.call(tooltip().destroy); + } + }); + } + + + function updateLayerSelections(selection) { + function active(d) { + return context.background().showsLayer(d); + } + + selection.selectAll('.layer') + .classed('active', active) + .classed('switch', function(d) { return d === _previousBackground; }) + .call(setTooltips) + .selectAll('input') + .property('checked', active); + } + + + function chooseBackground(d) { + if (d.id === 'custom' && !d.template()) { + return editCustom(); + } + + d3_event.preventDefault(); + _previousBackground = context.background().baseLayerSource(); + context.background().baseLayerSource(d); + _backgroundList.call(updateLayerSelections); + document.activeElement.blur(); + } + + + function editCustom() { + d3_event.preventDefault(); + var example = 'https://{switch:a,b,c}.tile.openstreetmap.org/{zoom}/{x}/{y}.png'; + var template = window.prompt( + t('background.custom_prompt', { example: example }), + _customSource.template() || example + ); + + if (template) { + context.storage('background-custom-template', template); + _customSource.template(template); + chooseBackground(_customSource); + } else { + _backgroundList.call(updateLayerSelections); + } + } + + + function chooseOverlay(d) { + d3_event.preventDefault(); + context.background().toggleOverlayLayer(d); + _overlayList.call(updateLayerSelections); + document.activeElement.blur(); + } + + + function drawListItems(layerList, type, change, filter) { + var sources = context.background() + .sources(context.map().extent()) + .filter(filter); + + var layerLinks = layerList.selectAll('li.layer') + .data(sources, function(d) { return d.name(); }); + + layerLinks.exit() + .remove(); + + var enter = layerLinks.enter() + .append('li') + .attr('class', 'layer') + .classed('layer-custom', function(d) { return d.id === 'custom'; }) + .classed('best', function(d) { return d.best(); }); + + enter.filter(function(d) { return d.id === 'custom'; }) + .append('button') + .attr('class', 'layer-browse') + .call(tooltip() + .title(t('background.custom_button')) + .placement((textDirection === 'rtl') ? 'right' : 'left') + ) + .on('click', editCustom) + .call(svgIcon('#icon-search')); + + enter.filter(function(d) { return d.best(); }) + .append('div') + .attr('class', 'best') + .call(tooltip() + .title(t('background.best_imagery')) + .placement((textDirection === 'rtl') ? 'right' : 'left') + ) + .append('span') + .html('★'); + + var label = enter + .append('label'); + + label + .append('input') + .attr('type', type) + .attr('name', 'layers') + .on('change', change); + + label + .append('span') + .text(function(d) { return d.name(); }); + + + layerList.selectAll('li.layer') + .sort(sortSources) + .style('display', layerList.selectAll('li.layer').data().length > 0 ? 'block' : 'none'); + + layerList + .call(updateLayerSelections); + function sortSources(a, b) { return a.best() && !b.best() ? -1 : b.best() && !a.best() ? 1 : d3_descending(a.area(), b.area()) || d3_ascending(a.name(), b.name()) || 0; } + } - function setOpacity(d) { - var bg = context.container().selectAll('.layer-background') - .transition() - .style('opacity', d) - .attr('data-opacity', d); + function renderBackgroundList(selection) { + var container = selection.selectAll('layer-background-list') + .data([0]); - if (!detected.opera) { - utilSetTransform(bg, 0, 0); - } - - opacityList.selectAll('li') - .classed('active', function(_) { return _ === d; }); - - context.storage('background-opacity', d); - } + _backgroundList = container.enter() + .append('ul') + .attr('class', 'layer-list layer-background-list') + .attr('dir', 'auto') + .merge(container); + } - function setTooltips(selection) { - selection.each(function(d, i, nodes) { - var item = d3_select(this).select('label'), - span = item.select('span'), - placement = (i < nodes.length / 2) ? 'bottom' : 'top', - description = d.description(), - isOverflowing = (span.property('clientWidth') !== span.property('scrollWidth')); + function renderOverlayList(selection) { + var container = selection.selectAll('layer-overlay-list') + .data([0]); - if (d === previous) { - item.call(tooltip() - .placement(placement) - .html(true) - .title(function() { - var tip = '
' + t('background.switch') + '
'; - return uiTooltipHtml(tip, uiCmd('⌘' + key)); - }) - ); - } else if (description || isOverflowing) { - item.call(tooltip() - .placement(placement) - .title(description || d.name()) - ); - } else { - item.call(tooltip().destroy); - } - }); - } + _overlayList = container.enter() + .append('ul') + .attr('class', 'layer-list layer-overlay-list') + .attr('dir', 'auto') + .merge(container); + } - function selectLayer() { - function active(d) { - return context.background().showsLayer(d); - } + function renderDisplayOptions(selection) { + var container = selection.selectAll('display-options-container') + .data([0]); - content.selectAll('.layer') - .classed('active', active) - .classed('switch', function(d) { return d === previous; }) - .call(setTooltips) - .selectAll('input') - .property('checked', active); - } + var containerEnter = container.enter() + .append('div') + .attr('class', 'display-options-container controls-list'); + + /* add opacity switcher */ + var opacityDivEnter = containerEnter + .append('div') + .attr('class', 'opacity-options-wrapper'); + + opacityDivEnter + .append('h5') + .text(t('background.brightness')); + + var opacityUlEnter = opacityDivEnter.append('ul') + .attr('class', 'opacity-options'); + + opacityUlEnter.selectAll('div.opacity') + .data(opacities) + .enter() + .append('li') + .attr('data-original-title', function(d) { + return t('background.percent_brightness', { opacity: (d * 100) }); + }) + .on('click.set-opacity', setOpacity) + .html('
') + .call(tooltip() + .placement((textDirection === 'rtl') ? 'right' : 'left') + ) + .append('div') + .attr('class', 'opacity') + .style('opacity', function(d) { return 1.25 - d; }); - function clickSetSource(d) { - if (d.id === 'custom' && !d.template()) { - return editCustom(); - } + /* add minimap toggle */ + var minimapEnter = containerEnter + .append('div') + .attr('class', 'minimap-toggle-wrap'); - d3_event.preventDefault(); - previous = context.background().baseLayerSource(); - context.background().baseLayerSource(d); - selectLayer(); - document.activeElement.blur(); - } - - - function editCustom() { - d3_event.preventDefault(); - var example = 'https://{switch:a,b,c}.tile.openstreetmap.org/{zoom}/{x}/{y}.png'; - var template = window.prompt( - t('background.custom_prompt', { example: example }), - customSource.template() || example + var minimapLabelEnter = minimapEnter + .append('label') + .call(tooltip() + .html(true) + .title(uiTooltipHtml(t('background.minimap.tooltip'), t('background.minimap.key'))) + .placement('top') ); - if (template) { - context.storage('background-custom-template', template); - customSource.template(template); - clickSetSource(customSource); - } else { - selectLayer(); - } - } + minimapLabelEnter + .classed('minimap-toggle', true) + .append('input') + .attr('type', 'checkbox') + .on('change', function() { + uiMapInMap.toggle(); + d3_event.preventDefault(); + }); + + minimapLabelEnter + .append('span') + .text(t('background.minimap.description')); + + _displayOptions = containerEnter + .merge(container); + } - function clickSetOverlay(d) { + function update() { + _backgroundList + .call(drawListItems, 'radio', chooseBackground, function(d) { return !d.isHidden() && !d.overlay; }); + + _overlayList + .call(drawListItems, 'checkbox', chooseOverlay, function(d) { return !d.isHidden() && d.overlay; }); + + _offsetContainer + .call(backgroundOffset); + } + + + function quickSwitch() { + if (d3_event) { + d3_event.stopImmediatePropagation(); d3_event.preventDefault(); - context.background().toggleOverlayLayer(d); - selectLayer(); - document.activeElement.blur(); } - - - function drawListItems(layerList, type, change, filter) { - var sources = context.background() - .sources(context.map().extent()) - .filter(filter); - - var layerLinks = layerList.selectAll('li.layer') - .data(sources, function(d) { return d.name(); }); - - layerLinks.exit() - .remove(); - - var enter = layerLinks.enter() - .append('li') - .attr('class', 'layer') - .classed('layer-custom', function(d) { return d.id === 'custom'; }) - .classed('best', function(d) { return d.best(); }); - - enter.filter(function(d) { return d.id === 'custom'; }) - .append('button') - .attr('class', 'layer-browse') - .call(tooltip() - .title(t('background.custom_button')) - .placement((textDirection === 'rtl') ? 'right' : 'left')) - .on('click', editCustom) - .call(svgIcon('#icon-search')); - - enter.filter(function(d) { return d.best(); }) - .append('div') - .attr('class', 'best') - .call(tooltip() - .title(t('background.best_imagery')) - .placement((textDirection === 'rtl') ? 'right' : 'left')) - .append('span') - .html('★'); - - var label = enter - .append('label'); - - label - .append('input') - .attr('type', type) - .attr('name', 'layers') - .on('change', change); - - label - .append('span') - .text(function(d) { return d.name(); }); - - - layerList.selectAll('li.layer') - .sort(sortSources) - .style('display', layerList.selectAll('li.layer').data().length > 0 ? 'block' : 'none'); + if (_previousBackground) { + chooseBackground(_previousBackground); } + } - function update() { - backgroundList - .call(drawListItems, 'radio', clickSetSource, function(d) { return !d.isHidden() && !d.overlay; }); + function background(selection) { - overlayList - .call(drawListItems, 'checkbox', clickSetOverlay, function(d) { return !d.isHidden() && d.overlay; }); - - selectLayer(); - - offsetContainer - .call(backgroundOffset); - } - - - function hide() { + function hidePane() { setVisible(false); } - - function toggle() { - if (d3_event) { - d3_event.preventDefault(); - } - tooltipBehavior.hide(button); + function togglePane() { + if (d3_event) d3_event.preventDefault(); + paneTooltip.hide(button); setVisible(!button.classed('active')); } - - function quickSwitch() { - if (d3_event) { - d3_event.stopImmediatePropagation(); - d3_event.preventDefault(); - } - if (previous) { - clickSetSource(previous); - } - } - - function setVisible(show) { - if (show !== shown) { + if (show !== _shown) { button.classed('active', show); - shown = show; + _shown = show; if (show) { selection @@ -252,18 +351,18 @@ export function uiBackground(context) { d3_event.stopPropagation(); }); - content + pane .style('display', 'block') .style('right', '-300px') .transition() .duration(200) .style('right', '0px'); - content.selectAll('.layer') + pane.selectAll('.layer') .call(setTooltips); } else { - content + pane .style('display', 'block') .style('right', '0px') .transition() @@ -280,11 +379,11 @@ export function uiBackground(context) { } - var content = selection + var pane = selection .append('div') .attr('class', 'fillL map-overlay col3 content hide'); - var tooltipBehavior = tooltip() + var paneTooltip = tooltip() .placement((textDirection === 'rtl') ? 'right' : 'left') .html(true) .title(uiTooltipHtml(t('background.description'), key)); @@ -292,50 +391,30 @@ export function uiBackground(context) { var button = selection .append('button') .attr('tabindex', -1) - .on('click', toggle) + .on('click', togglePane) .call(svgIcon('#icon-layers', 'light')) - .call(tooltipBehavior); + .call(paneTooltip); - var shown = false; - - - /* add opacity switcher */ - var opawrap = content - .append('div') - .attr('class', 'opacity-options-wrapper'); - - opawrap - .append('h4') + pane + .append('h3') .text(t('background.title')); - var opacityList = opawrap - .append('ul') - .attr('class', 'opacity-options'); - - opacityList.selectAll('div.opacity') - .data(opacities) - .enter() - .append('li') - .attr('data-original-title', function(d) { - return t('background.percent_brightness', { opacity: (d * 100) }); - }) - .on('click.set-opacity', setOpacity) - .html('
') - .call(tooltip() - .placement((textDirection === 'rtl') ? 'right' : 'left')) + // background list + pane .append('div') - .attr('class', 'opacity') - .style('opacity', function(d) { return 1.25 - d; }); + .attr('class', 'background-background-list-container') + .call(uiDisclosure(context, 'background_list', true) + .title(t('background.backgrounds')) + .content(renderBackgroundList) + ); - - /* add background list */ - var backgroundList = content - .append('ul') - .attr('class', 'layer-list') - .attr('dir', 'auto'); + // _backgroundList = pane + // .append('ul') + // .attr('class', 'layer-list') + // .attr('dir', 'auto'); // "Where does this imagery come from?" - // content + // pane // .append('div') // .attr('class', 'imagery-faq') // .append('a') @@ -347,46 +426,31 @@ export function uiBackground(context) { // .text(t('background.imagery_source_faq')); - /* add overlay list */ - var overlayList = content - .append('ul') - .attr('class', 'layer-list'); - - var controls = content + // overlay list + pane .append('div') - .attr('class', 'controls-list'); - - - /* add minimap toggle */ - var minimapLabel = controls - .append('label') - .call(tooltip() - .html(true) - .title(uiTooltipHtml(t('background.minimap.tooltip'), t('background.minimap.key'))) - .placement('top') + .attr('class', 'background-overlay-list-container') + .call(uiDisclosure(context, 'overlay_list', true) + .title(t('background.overlays')) + .content(renderOverlayList) ); - minimapLabel - .classed('minimap-toggle', true) - .append('input') - .attr('type', 'checkbox') - .on('change', function() { - uiMapInMap.toggle(); - d3_event.preventDefault(); - }); + // display settings + pane + .append('div') + .attr('class', 'background-display-options-container') + .call(uiDisclosure(context, 'background_display_options', true) + .title(t('background.display_options')) + .content(renderDisplayOptions) + ); - minimapLabel - .append('span') - .text(t('background.minimap.description')); - - - /* add offset controls */ - var offsetContainer = content + // offset controls + _offsetContainer = pane .append('div') .attr('class', 'background-offset'); - /* add listeners */ + // add listeners context.map() .on('move.background-update', _debounce(utilCallWhenIdle(update), 1000)); @@ -395,12 +459,12 @@ export function uiBackground(context) { update(); - setOpacity(opacityDefault); + setOpacity(_opacityDefault); var keybinding = d3_keybinding('background') - .on(key, toggle) + .on(key, togglePane) .on(uiCmd('⌘' + key), quickSwitch) - .on([t('map_data.key'), t('help.key')], hide); + .on([t('map_data.key'), t('help.key')], hidePane); d3_select(document) .call(keybinding); From 61bb9d968dd5c8fa5a986f827c502e7a3619cb25 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Tue, 28 Nov 2017 21:33:46 -0500 Subject: [PATCH 07/18] Restyle uiDisclosures, larger text, svg expand/contract icon --- css/80_app.css | 206 ++++++++++++++++++--------------------- modules/ui/disclosure.js | 38 +++++++- 2 files changed, 127 insertions(+), 117 deletions(-) diff --git a/css/80_app.css b/css/80_app.css index 2e8b95715..76cb459c3 100644 --- a/css/80_app.css +++ b/css/80_app.css @@ -268,7 +268,7 @@ table th { } table.tags, table.tags td, table.tags th { - border: 1px solid #CCC; + border: 1px solid #ccc; padding: 4px; } @@ -304,7 +304,7 @@ ul li { list-style: none;} display: block; height: 30px; background-color: white; - color: #7092FF; + color: #7092ff; cursor: pointer; } @@ -740,6 +740,30 @@ button.save.has-count .count::before { position: absolute; } + +/* Hide-Toggle +------------------------------------------------------- */ + +.hide-toggle .icon.pre-text { + vertical-align: text-top; + width: 16px; + height: 16px; + margin-left: -3px; +} +[dir='rtl'] .hide-toggle .icon.pre-text { + margin-left: 0; + margin-right: -3px; +} + +a:visited.hide-toggle, +a.hide-toggle { + display: inline-block; + font-size: 14px; + font-weight: bold; + padding-bottom: 5px; +} + + /* Inspector ------------------------------------------------------- */ @@ -782,7 +806,6 @@ button.save.has-count .count::before { bottom: 0; } - .feature-list-pane .inspector-body { top: 120px; } @@ -1071,7 +1094,7 @@ button.save.has-count .count::before { .preset-list-item button.tag-reference-button { height: 100%; - border: 1px solid #CCC; + border: 1px solid #ccc; border-radius: 0 3px 3px 0; position: absolute; top: 0; @@ -1143,7 +1166,7 @@ button.save.has-count .count::before { } .preset-editor a.hide-toggle { - margin: 0 20px 10px 20px; + margin: 0 20px 5px 20px; } .preset-editor .form-fields-container { @@ -1218,7 +1241,7 @@ button.save.has-count .count::before { } [dir='rtl'] .form-label button { border-left: none; - border-right: 1px solid #CCC; + border-right: 1px solid #ccc; border-radius: 4px 0 0 0; width: 31px; } @@ -1574,13 +1597,13 @@ input[type=number] { float: left; height: 100%; width: 32px; - border-left: 1px solid #CCC; + border-left: 1px solid #ccc; border-radius: 0; background: rgba(0, 0, 0, 0); } [dir='rtl'] .spin-control button{ border-left: 0; - border-right: 1px solid #CCC; + border-right: 1px solid #ccc; } .spin-control button.decrement { @@ -1604,13 +1627,13 @@ input[type=number] { } .spin-control button.decrement::after { - border-top: 5px solid #CCC; + border-top: 5px solid #ccc; border-left: 5px solid transparent; border-right: 5px solid transparent; } .spin-control button.increment::after { - border-bottom: 5px solid #CCC; + border-bottom: 5px solid #ccc; border-left: 5px solid transparent; border-right: 5px solid transparent; } @@ -1622,7 +1645,7 @@ input[type=number] { display: block; background: white; padding: 5px 10px; - color: #7092FF; + color: #7092ff; } .checkselect label:hover { @@ -1720,7 +1743,7 @@ input[type=number] { right: 1px; width: 32px; margin-left: -32px; - border: 1px solid #CCC; + border: 1px solid #ccc; border-top-width: 0; border-right-width: 0; border-radius: 0 0 4px 0; @@ -1916,12 +1939,12 @@ div.combobox { height: 31px; border: 0; border-radius: 0; - border-bottom: 1px solid #CCC; - border-left: 1px solid #CCC; + border-bottom: 1px solid #ccc; + border-left: 1px solid #ccc; } [dir='rtl'] .tag-row input { border-left: none; - border-right: 1px solid #CCC; + border-right: 1px solid #ccc; } .tag-row .key-wrap, @@ -1941,14 +1964,14 @@ div.combobox { } .tag-row input.value { - border-right: 1px solid #CCC; + border-right: 1px solid #ccc; } [dir='rtl'] .tag-row input.value { - border-left: 1px solid #CCC; + border-left: 1px solid #ccc; } .tag-row:first-child input.key { - border-top: 1px solid #CCC; + border-top: 1px solid #ccc; border-top-left-radius: 4px; } [dir='rtl'] .tag-row:first-child input.key { @@ -1957,14 +1980,14 @@ div.combobox { } .tag-row:first-child input.value { - border-top: 1px solid #CCC; + border-top: 1px solid #ccc; } .tag-row button { position: absolute; height: 31px; right: 10%; - border: 1px solid #CCC; + border: 1px solid #ccc; border-top-width: 0; border-left-width: 0; } @@ -2226,14 +2249,9 @@ div.full-screen > button:hover { margin-bottom: 10px; } -.map-data-control .hide-toggle, -.background-control .hide-toggle { - padding-bottom: 10px; -} - .layer-list, .controls-list { margin-bottom: 10px; - border: 1px solid #CCC; + border: 1px solid #ccc; border-radius: 4px; } @@ -2241,7 +2259,7 @@ div.full-screen > button:hover { position: relative; height: 30px; background-color: white; - color: #7092FF; + color: #7092ff; } .layer-list:empty { @@ -2270,7 +2288,7 @@ div.full-screen > button:hover { .layer-list li.active, .layer-list li.switch { - background: #E8EBFF; + background: #e8ebff; } .layer-list li.best > div.best { @@ -2306,58 +2324,18 @@ div.full-screen > button:hover { display: block; padding: 5px 10px; cursor: pointer; - color: #7092FF; + color: #7092ff; border-top: 1px solid #ccc; } .minimap-toggle.active { - background: #E8EBFF; + background: #e8ebff; } .minimap-toggle:hover { background-color: #ececec; } -.hide-toggle { - display: block; - padding-left: 12px; - position: relative; -} -[dir='rtl'] .hide-toggle { - padding-left: 0; - padding-right: 12px; -} - -.hide-toggle:before { - content: ''; - display: block; - position: absolute; - height: 0; - width: 0; - left: 0; - top: 5px; - border-top: 4px solid transparent; - border-bottom: 4px solid transparent; - border-left: 8px solid #7092ff; -} -[dir='rtl'] .hide-toggle:before { - left: auto; - right: 0; - border-left: none; - border-right: 8px solid #7092ff; -} - -.hide-toggle.expanded:before { - border-top: 8px solid #7092ff; - border-bottom: 0; - border-right: 4px solid transparent; - border-left: 4px solid transparent; -} -[dir='rtl'] .hide-toggle.expanded:before { - border-left: 4px solid transparent; - border-right: 4px solid transparent; -} - /* Adjust Alignment controls */ @@ -2411,7 +2389,7 @@ div.full-screen > button:hover { } .nudge-container input.error { - border: 1px solid #FF7878; + border: 1px solid #ff7878; border-radius: 2px; background: #ffb; } @@ -2523,10 +2501,10 @@ div.full-screen > button:hover { } .background-control .opacity { - background:#222; - display:inline-block; - width:20px; - height:18px; + background: #222; + display: inline-block; + width: 20px; + height: 18px; } .map-data-control .layer-list button, @@ -2534,14 +2512,14 @@ div.full-screen > button:hover { float: right; height: 100%; width: 10%; - border-left: 1px solid #CCC; + border-left: 1px solid #ccc; border-radius: 0; } [dir='rtl'] .map-data-control .layer-list button, [dir='rtl'] .background-control .layer-list button { float: left; border-left: none; - border-right: 1px solid #CCC; + border-right: 1px solid #ccc; } .map-data-control .layer-list button .icon, @@ -2574,12 +2552,12 @@ div.full-screen > button:hover { border-radius: 0 0 0 4px; } [dir='rtl'] .geolocate-control button { - border-radius: 0 0 4px 0; + border-radius: 0 0 4px 0; } .map-overlay.content { position: fixed; - top:60px; + top: 60px; bottom: 30px; padding: 20px 50px 20px 20px; right: 0; @@ -2591,13 +2569,17 @@ div.full-screen > button:hover { right: auto !important; } +.map-overlay.content > div { + padding-bottom: 15px; +} + /* Help */ .help-control button { border-radius: 0 0 0 4px; } [dir='rtl'] .help-control button { - border-radius: 0 0 4px 0; + border-radius: 0 0 4px 0; } .help-wrap p { @@ -2626,7 +2608,7 @@ div.full-screen > button:hover { } .help-wrap .toc { - width:40%; + width: 40%; float:right; margin-left: 20px; margin-bottom: 20px; @@ -2703,12 +2685,12 @@ div.full-screen > button:hover { ------------------------------------------------------- */ img.tile { - position:absolute; - transform-origin:0 0; - -ms-transform-origin:0 0; - -webkit-transform-origin:0 0; - -moz-transform-origin:0 0; - -o-transform-origin:0 0; + position: absolute; + transform-origin: 0 0; + -ms-transform-origin: 0 0; + -webkit-transform-origin: 0 0; + -moz-transform-origin: 0 0; + -o-transform-origin: 0 0; -moz-user-select: none; -webkit-user-select: none; @@ -2734,11 +2716,11 @@ img.tile { margin-left: -50px; margin-top: -20px; - transform-origin:0 0; - -ms-transform-origin:0 0; - -webkit-transform-origin:0 0; - -moz-transform-origin:0 0; - -o-transform-origin:0 0; + transform-origin: 0 0; + -ms-transform-origin: 0 0; + -webkit-transform-origin: 0 0; + -moz-transform-origin: 0 0; + -o-transform-origin: 0 0; -moz-user-select: none; -webkit-user-select: none; @@ -2763,10 +2745,10 @@ img.tile-removing { ------------------------------------------------------- */ #map { - position:relative; - overflow:hidden; - height:100%; - background:#000; + position: relative; + overflow: hidden; + height: 100%; + background: #000; -moz-user-select: none; -webkit-user-select: none; -ms-user-select: none; @@ -2774,11 +2756,11 @@ img.tile-removing { } #supersurface { - transform-origin:0 0; - -ms-transform-origin:0 0; - -webkit-transform-origin:0 0; - -moz-transform-origin:0 0; - -o-transform-origin:0 0; + transform-origin: 0 0; + -ms-transform-origin: 0 0; + -webkit-transform-origin: 0 0; + -moz-transform-origin: 0 0; + -o-transform-origin: 0 0; } #supersurface, .layer { @@ -3307,7 +3289,7 @@ img.tile-removing { .modal-section { padding: 20px; - border-bottom: 1px solid #CCC; + border-bottom: 1px solid #ccc; } .modal-section.header h3 { @@ -3340,8 +3322,8 @@ img.tile-removing { .modal-actions button, .save-success a.button { font-weight: normal; - color: #7092FF; - border-bottom: 1px solid #CCC; + color: #7092ff; + border-bottom: 1px solid #ccc; border-radius: 0; height: 160px; text-align: center; @@ -3361,7 +3343,7 @@ img.tile-removing { } .modal-actions > :first-child { - border-right: 1px solid #CCC; + border-right: 1px solid #ccc; } .modal-section:last-child { @@ -3371,7 +3353,7 @@ img.tile-removing { /* Restore Modal ------------------------------------------------------- */ .modal-actions .logo-restore { - color: #7092FF; + color: #7092ff; } .modal-actions .logo-reset { color: #E06C5E; @@ -3389,7 +3371,7 @@ img.tile-removing { padding-top: 15px; } .save-success .logo-osm { - color: #7092FF; + color: #7092ff; margin-bottom: 10px; } .save-success a.button.social { @@ -3399,14 +3381,14 @@ img.tile-removing { .save-success .icon.social { height: 80px; width: 80px; - color: #7092FF; + color: #7092ff; } /* Splash Modal ------------------------------------------------------- */ .modal-actions .logo-walkthrough, .modal-actions .logo-features { - color: #7092FF; + color: #7092ff; } @@ -3597,7 +3579,7 @@ svg.mouseclick use.right { } .mode-save .commit-section .changeset-list button { - border-left: 1px solid #CCC; + border-left: 1px solid #ccc; } .changeset-list li span.count:before { content: '('; } @@ -4154,7 +4136,7 @@ li.hide + li.version .badge .tooltip .tooltip-arrow { .curtain-tooltip .tooltip-inner .instruction { font-weight: bold; display: block; - border-top: 1px solid #CCC; + border-top: 1px solid #ccc; margin-top: 10px; margin-left: -20px; margin-right: -20px; @@ -4232,5 +4214,5 @@ li.hide + li.version .badge .tooltip .tooltip-arrow { .huge-modal-button .illustration { height: 100px; width: 100px; - color: #7092FF; + color: #7092ff; } diff --git a/modules/ui/disclosure.js b/modules/ui/disclosure.js index 3b4c3a0a4..6c56e37c6 100644 --- a/modules/ui/disclosure.js +++ b/modules/ui/disclosure.js @@ -1,8 +1,10 @@ import { dispatch as d3_dispatch } from 'd3-dispatch'; import { event as d3_event } from 'd3-selection'; +import { svgIcon } from '../svg'; import { utilRebind } from '../util/rebind'; import { uiToggle } from './toggle'; +import { textDirection } from '../util/locale'; export function uiDisclosure(context, key, expandedDefault) { @@ -15,20 +17,36 @@ export function uiDisclosure(context, key, expandedDefault) { var disclosure = function(selection) { - var hideToggle = selection.selectAll('.hide-toggle') + var hideToggle = selection.selectAll('.hide-toggle-' + key) .data([0]); - hideToggle = hideToggle.enter() + // enter + var hideToggleEnter = hideToggle.enter() .append('a') .attr('href', '#') .attr('class', 'hide-toggle hide-toggle-' + key) + .call(svgIcon('', 'pre-text', 'hide-toggle-icon')); + + hideToggleEnter + .append('span') + .attr('class', 'hide-toggle-text'); + + // update + hideToggle = hideToggleEnter .merge(hideToggle); hideToggle - .text(_title) .on('click', toggle) .classed('expanded', _expanded); + hideToggle.selectAll('.hide-toggle-text') + .text(_title); + + hideToggle.selectAll('.hide-toggle-icon') + .attr('xlink:href', _expanded ? '#icon-down' + : (textDirection === 'rtl') ? '#icon-backward' : '#icon-forward' + ); + var wrap = selection.selectAll('.disclosure-wrap') .data([0]); @@ -47,11 +65,21 @@ export function uiDisclosure(context, key, expandedDefault) { d3_event.preventDefault(); _expanded = !_expanded; + if (_updatePreference) { context.storage('disclosure.' + key + '.expanded', _expanded); } - hideToggle.classed('expanded', _expanded); - wrap.call(uiToggle(_expanded)); + + hideToggle + .classed('expanded', _expanded); + + hideToggle.selectAll('.hide-toggle-icon') + .attr('xlink:href', _expanded ? '#icon-down' + : (textDirection === 'rtl') ? '#icon-backward' : '#icon-forward' + ); + + wrap + .call(uiToggle(_expanded)); dispatch.call('toggled', this, _expanded); } From 2e2dd5f02564183a8b2661fa657287789fc9c1c2 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Tue, 28 Nov 2017 22:49:46 -0500 Subject: [PATCH 08/18] Larger headings on Map Data and Background panes --- modules/ui/background.js | 2 +- modules/ui/help.js | 25 +++++++++++++++---------- modules/ui/map_data.js | 2 +- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/modules/ui/background.js b/modules/ui/background.js index d23287a7f..4b0dbfaf7 100644 --- a/modules/ui/background.js +++ b/modules/ui/background.js @@ -396,7 +396,7 @@ export function uiBackground(context) { .call(paneTooltip); pane - .append('h3') + .append('h2') .text(t('background.title')); // background list diff --git a/modules/ui/help.js b/modules/ui/help.js index 6b1e6b435..024515f77 100644 --- a/modules/ui/help.js +++ b/modules/ui/help.js @@ -260,12 +260,12 @@ export function uiHelp(context) { function help(selection) { - function hide() { + function hidePane() { setVisible(false); } - function toggle() { + function togglePane() { if (d3_event) d3_event.preventDefault(); tooltipBehavior.hide(button); setVisible(!button.classed('active')); @@ -375,13 +375,14 @@ export function uiHelp(context) { .title(uiTooltipHtml(t('help.title'), key)), button = selection.append('button') .attr('tabindex', -1) - .on('click', toggle) + .on('click', togglePane) .call(svgIcon('#icon-help', 'light')) .call(tooltipBehavior), shown = false; - var toc = pane.append('ul') + var toc = pane + .append('ul') .attr('class', 'toc'); var menuItems = toc.selectAll('li') @@ -424,23 +425,27 @@ export function uiHelp(context) { .text(t('splash.walkthrough')); - var content = pane.append('div') + var content = pane + .append('div') .attr('class', 'left-content'); - var doctitle = content.append('h2') + var doctitle = content + .append('h2') .text(t('help.title')); - var body = content.append('div') + var body = content + .append('div') .attr('class', 'body'); - var nav = content.append('div') + var nav = content + .append('div') .attr('class', 'nav'); clickHelp(docs[0], 0); var keybinding = d3_keybinding('help') - .on(key, toggle) - .on([t('background.key'), t('map_data.key')], hide); + .on(key, togglePane) + .on([t('background.key'), t('map_data.key')], hidePane); d3_select(document) .call(keybinding); diff --git a/modules/ui/map_data.js b/modules/ui/map_data.js index a928bd649..2cff0c842 100644 --- a/modules/ui/map_data.js +++ b/modules/ui/map_data.js @@ -455,7 +455,7 @@ export function uiMapData(context) { pane - .append('h3') + .append('h2') .text(t('map_data.title')); From 17809545c6b29516286d11a381a31d0ea982f25b Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Tue, 28 Nov 2017 23:21:52 -0500 Subject: [PATCH 09/18] Ensure only one pane shown at a time, remove unnecessary handlers --- modules/ui/background.js | 19 +++++++++---------- modules/ui/help.js | 14 ++++++++++---- modules/ui/map_data.js | 15 ++++++++------- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/modules/ui/background.js b/modules/ui/background.js index 4b0dbfaf7..680775eca 100644 --- a/modules/ui/background.js +++ b/modules/ui/background.js @@ -17,6 +17,8 @@ import { svgIcon } from '../svg'; import { uiBackgroundOffset } from './background_offset'; import { uiCmd } from './cmd'; import { uiDisclosure } from './disclosure'; +import { uiHelp } from './help'; +import { uiMapData } from './map_data'; import { uiMapInMap } from './map_in_map'; import { uiTooltipHtml } from './tooltipHtml'; import { utilDetect } from '../util/detect'; @@ -346,10 +348,9 @@ export function uiBackground(context) { _shown = show; if (show) { - selection - .on('mousedown.background-inside', function() { - d3_event.stopPropagation(); - }); + uiMapData.hidePane(); + uiHelp.hidePane(); + update(); pane .style('display', 'block') @@ -358,9 +359,6 @@ export function uiBackground(context) { .duration(200) .style('right', '0px'); - pane.selectAll('.layer') - .call(setTooltips); - } else { pane .style('display', 'block') @@ -371,9 +369,6 @@ export function uiBackground(context) { .on('end', function() { d3_select(this).style('display', 'none'); }); - - selection - .on('mousedown.background-inside', null); } } } @@ -468,6 +463,10 @@ export function uiBackground(context) { d3_select(document) .call(keybinding); + + uiBackground.hidePane = hidePane; + uiBackground.togglePane = togglePane; + uiBackground.setVisible = setVisible; } return background; diff --git a/modules/ui/help.js b/modules/ui/help.js index 024515f77..01196b3a7 100644 --- a/modules/ui/help.js +++ b/modules/ui/help.js @@ -9,7 +9,9 @@ import marked from 'marked'; import { t, textDirection } from '../util/locale'; import { svgIcon } from '../svg'; import { uiCmd } from './cmd'; +import { uiBackground } from './background'; import { uiIntro } from './intro'; +import { uiMapData } from './map_data'; import { uiShortcuts } from './shortcuts'; import { uiTooltipHtml } from './tooltipHtml'; import { tooltip } from '../util/tooltip'; @@ -278,14 +280,15 @@ export function uiHelp(context) { shown = show; if (show) { - selection.on('mousedown.help-inside', function() { - return d3_event.stopPropagation(); - }); + uiBackground.hidePane(); + uiMapData.hidePane(); + pane.style('display', 'block') .style('right', '-500px') .transition() .duration(200) .style('right', '0px'); + } else { pane.style('right', '0px') .transition() @@ -294,7 +297,6 @@ export function uiHelp(context) { .on('end', function() { d3_select(this).style('display', 'none'); }); - selection.on('mousedown.help-inside', null); } } } @@ -449,6 +451,10 @@ export function uiHelp(context) { d3_select(document) .call(keybinding); + + uiHelp.hidePane = hidePane; + uiHelp.togglePane = togglePane; + uiHelp.setVisible = setVisible; } return help; diff --git a/modules/ui/map_data.js b/modules/ui/map_data.js index 2cff0c842..aef91c01b 100644 --- a/modules/ui/map_data.js +++ b/modules/ui/map_data.js @@ -8,7 +8,9 @@ import { d3keybinding as d3_keybinding } from '../lib/d3.keybinding.js'; import { svgIcon } from '../svg'; import { t, textDirection } from '../util/locale'; import { tooltip } from '../util/tooltip'; +import { uiBackground } from './background'; import { uiDisclosure } from './disclosure'; +import { uiHelp } from './help'; import { uiTooltipHtml } from './tooltipHtml'; @@ -26,7 +28,6 @@ export function uiMapData(context) { var _featureList = d3_select(null); - function showsFeature(d) { return context.features().enabled(d); } @@ -407,12 +408,10 @@ export function uiMapData(context) { _shown = show; if (show) { + uiBackground.hidePane(); + uiHelp.hidePane(); update(); - selection.on('mousedown.map_data-inside', function() { - return d3_event.stopPropagation(); - }); - pane .style('display', 'block') .style('right', '-300px') @@ -430,8 +429,6 @@ export function uiMapData(context) { .on('end', function() { d3_select(this).style('display', 'none'); }); - - selection.on('mousedown.map_data-inside', null); } } } @@ -501,6 +498,10 @@ export function uiMapData(context) { d3_select(document) .call(keybinding); + + uiMapData.hidePane = hidePane; + uiMapData.togglePane = togglePane; + uiMapData.setVisible = setVisible; } return mapData; From d24e2663f2a2e6d264feb914ac8b9b28a46c3971 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Wed, 29 Nov 2017 16:49:58 -0500 Subject: [PATCH 10/18] Replace confusing opacity buttons with opacity slider --- css/80_app.css | 40 +------------- data/core.yaml | 1 - dist/img/background-pattern-1.png | Bin 89 -> 0 bytes dist/img/background-pattern-opacity.png | Bin 90 -> 0 bytes dist/locales/en.json | 1 - modules/ui/background.js | 69 +++++++++++------------- 6 files changed, 32 insertions(+), 79 deletions(-) delete mode 100644 dist/img/background-pattern-1.png delete mode 100644 dist/img/background-pattern-opacity.png diff --git a/css/80_app.css b/css/80_app.css index 76cb459c3..9c8045f4b 100644 --- a/css/80_app.css +++ b/css/80_app.css @@ -130,7 +130,6 @@ a, button, input, textarea { a, button, .checkselect label:hover, -.opacity-options li, .radial-menu-item { cursor: pointer; } @@ -2468,43 +2467,8 @@ div.full-screen > button:hover { padding: 10px; } -.opacity-options { - background: url(img/background-pattern-opacity.png) 0 0 repeat; - height: 20px; - width: 82px; - border: 1px solid #ccc; -} - -.opacity-options li { - height: 100%; - display: block; - float: left; -} - -.opacity-options li .select-box{ - position: absolute; - width: 20px; - height: 18px; - z-index: 9999; -} - -.background-control li:hover .select-box, -.background-control li.selected .select-box { - border: 2px solid #7092ff; - background: rgba(89, 123, 231, .5); - opacity: .5; -} - -.background-control li.selected:hover .select-box, -.background-control li.selected .select-box { - opacity: 1; -} - -.background-control .opacity { - background: #222; - display: inline-block; - width: 20px; - height: 18px; +.opacity-options-wrapper .opacity-value { + margin: 5px; } .map-data-control .layer-list button, diff --git a/data/core.yaml b/data/core.yaml index 1344b733a..3404c6454 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -384,7 +384,6 @@ en: reset: reset display_options: Display Options brightness: Brightness - percent_brightness: "{opacity}% brightness" minimap: description: Show Minimap tooltip: Show a zoomed out map to help locate the area currently displayed. diff --git a/dist/img/background-pattern-1.png b/dist/img/background-pattern-1.png deleted file mode 100644 index d2f2bcb125924fa13e23a9e4d657ef10198a6a5d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 89 zcmeAS@N?(olHy`uVBq!ia0vp^%plCc1|-8Yw(bW~3Z5>GAr-fBk8k8bP0l+XkKz}Xtc diff --git a/dist/img/background-pattern-opacity.png b/dist/img/background-pattern-opacity.png deleted file mode 100644 index f24376283025091b6fbe5b8e64b2dd4a38d470d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^Y#_`5A|IT2?*XI~JzX3_DsIi~G6X6UX!`a4N&oJb l2X-EC)Nzo{=MbCQR-mw') - .call(tooltip() - .placement((textDirection === 'rtl') ? 'right' : 'left') - ) - .append('div') - .attr('class', 'opacity') - .style('opacity', function(d) { return 1.25 - d; }); - - - /* add minimap toggle */ + // add minimap toggle var minimapEnter = containerEnter .append('div') .attr('class', 'minimap-toggle-wrap'); @@ -403,11 +399,6 @@ export function uiBackground(context) { .content(renderBackgroundList) ); - // _backgroundList = pane - // .append('ul') - // .attr('class', 'layer-list') - // .attr('dir', 'auto'); - // "Where does this imagery come from?" // pane // .append('div') @@ -454,7 +445,7 @@ export function uiBackground(context) { update(); - setOpacity(_opacityDefault); + setOpacity(_opacity); var keybinding = d3_keybinding('background') .on(key, togglePane) From 2946774e60ed0cff9afefc89707fd916bb7c117d Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Thu, 30 Nov 2017 01:56:23 -0500 Subject: [PATCH 11/18] WIP: Add unsharp-mask filter layer to sharpen background imagery --- modules/renderer/background.js | 118 ++++++++++++++++++++++++++------- modules/ui/background.js | 12 ++-- 2 files changed, 101 insertions(+), 29 deletions(-) diff --git a/modules/renderer/background.js b/modules/renderer/background.js index d1d59042e..8fd9720be 100644 --- a/modules/renderer/background.js +++ b/modules/renderer/background.js @@ -12,24 +12,79 @@ import { utilRebind } from '../util/rebind'; export function rendererBackground(context) { - var dispatch = d3_dispatch('change'), - baseLayer = rendererTileLayer(context).projection(context.projection), - overlayLayers = [], - backgroundSources; + var dispatch = d3_dispatch('change'); + var baseLayer = rendererTileLayer(context).projection(context.projection); + var _overlayLayers = []; + var _backgroundSources = []; + var _brightness = 1; + var _sharpness = 1; function background(selection) { + var baseFilter = ''; + if (_brightness !== 1) { + baseFilter += 'brightness(' + _brightness + ')'; + } + if (_sharpness !== 1) { + baseFilter += 'contrast(1.25)';// + _sharpness + ')'; + } + var base = selection.selectAll('.layer-background') .data([0]); - base.enter() + base = base.enter() .insert('div', '.layer-data') .attr('class', 'layer layer-background') .merge(base) + .style('-webkit-filter', baseFilter || null) + .style('filter', baseFilter || null); + + + var imagery = base.selectAll('.layer-imagery') + .data([0]); + + imagery.enter() + .append('div') + .attr('class', 'layer layer-imagery') + .merge(imagery) .call(baseLayer); + + var maskFilter = ''; + var mixBlendMode = ''; + if (_sharpness !== 1) { + var blur = Math.abs(_sharpness - 1) * 10; + maskFilter += 'blur(' + blur + 'px)'; + + if (_sharpness > 1) { + maskFilter += 'invert(1)'; + mixBlendMode = 'overlay'; + } else { + mixBlendMode = 'normal'; + } + + // var contrast = 1 / (_sharpness || 0.1); + maskFilter += 'contrast(0.75)';// + contrast + ')'; + } + + var mask = base.selectAll('.layer-unsharp-mask') + .data(_sharpness !== 1 ? [0] : []); + + mask.exit() + .remove(); + + mask.enter() + .append('div') + .attr('class', 'layer layer-unsharp-mask') + .merge(mask) + .call(baseLayer) + .style('-webkit-filter', maskFilter || null) + .style('filter', maskFilter || null) + .style('mix-blend-mode', mixBlendMode || null); + + var overlays = selection.selectAll('.layer-overlay') - .data(overlayLayers, function(d) { return d.source().name(); }); + .data(_overlayLayers, function(d) { return d.source().name(); }); overlays.exit() .remove(); @@ -46,7 +101,7 @@ export function rendererBackground(context) { if (context.inIntro()) return; var b = background.baseLayerSource(), - o = overlayLayers + o = _overlayLayers .filter(function (d) { return !d.source().isLocatorOverlay() && !d.source().isHidden(); }) .map(function (d) { return d.source().id; }) .join(','), @@ -85,7 +140,7 @@ export function rendererBackground(context) { var imageryUsed = [b.imageryUsed()]; - overlayLayers + _overlayLayers .filter(function (d) { return !d.source().isLocatorOverlay() && !d.source().isHidden(); }) .forEach(function (d) { imageryUsed.push(d.source().imageryUsed()); }); @@ -117,7 +172,7 @@ export function rendererBackground(context) { background.sources = function(extent) { - return backgroundSources.filter(function(source) { + return _backgroundSources.filter(function(source) { return source.intersects(extent); }); }; @@ -127,7 +182,7 @@ export function rendererBackground(context) { if (!_) return; baseLayer.dimensions(_); - overlayLayers.forEach(function(layer) { + _overlayLayers.forEach(function(layer) { layer.dimensions(_); }); }; @@ -172,7 +227,7 @@ export function rendererBackground(context) { background.findSource = function(id) { - return _find(backgroundSources, function(d) { + return _find(_backgroundSources, function(d) { return d.id && d.id === id; }); }; @@ -185,22 +240,22 @@ export function rendererBackground(context) { background.showsLayer = function(d) { return d.id === baseLayer.source().id || - overlayLayers.some(function(layer) { return d.id === layer.source().id; }); + _overlayLayers.some(function(layer) { return d.id === layer.source().id; }); }; background.overlayLayerSources = function() { - return overlayLayers.map(function (l) { return l.source(); }); + return _overlayLayers.map(function (l) { return l.source(); }); }; background.toggleOverlayLayer = function(d) { var layer; - for (var i = 0; i < overlayLayers.length; i++) { - layer = overlayLayers[i]; + for (var i = 0; i < _overlayLayers.length; i++) { + layer = _overlayLayers[i]; if (layer.source() === d) { - overlayLayers.splice(i, 1); + _overlayLayers.splice(i, 1); dispatch.call('change'); background.updateImagery(); return; @@ -210,9 +265,10 @@ export function rendererBackground(context) { layer = rendererTileLayer(context) .source(d) .projection(context.projection) - .dimensions(baseLayer.dimensions()); + .dimensions(baseLayer.dimensions() + ); - overlayLayers.push(layer); + _overlayLayers.push(layer); dispatch.call('change'); background.updateImagery(); }; @@ -235,6 +291,22 @@ export function rendererBackground(context) { }; + background.brightness = function(d) { + if (!arguments.length) return _brightness; + _brightness = d; + if (context.mode()) dispatch.call('change'); + return background; + }; + + + background.sharpness = function(d) { + if (!arguments.length) return _sharpness; + _sharpness = d; + if (context.mode()) dispatch.call('change'); + return background; + }; + + background.init = function() { function parseMap(qmap) { if (!qmap) return false; @@ -251,7 +323,7 @@ export function rendererBackground(context) { best; // Add all the available imagery sources - backgroundSources = dataImagery.map(function(source) { + _backgroundSources = dataImagery.map(function(source) { if (source.type === 'bing') { return rendererBackgroundSource.Bing(source, dispatch); } else if (source.id === 'EsriWorldImagery') { @@ -261,15 +333,15 @@ export function rendererBackground(context) { } }); - first = backgroundSources.length && backgroundSources[0]; + first = _backgroundSources.length && _backgroundSources[0]; // Add 'None' - backgroundSources.unshift(rendererBackgroundSource.None()); + _backgroundSources.unshift(rendererBackgroundSource.None()); // Add 'Custom' var template = context.storage('background-custom-template') || ''; var custom = rendererBackgroundSource.Custom(template); - backgroundSources.unshift(custom); + _backgroundSources.unshift(custom); // Decide which background layer to display @@ -290,7 +362,7 @@ export function rendererBackground(context) { ); } - var locator = _find(backgroundSources, function(d) { + var locator = _find(_backgroundSources, function(d) { return d.overlay && d.default; }); diff --git a/modules/ui/background.js b/modules/ui/background.js index e2ecfe678..3777975d6 100644 --- a/modules/ui/background.js +++ b/modules/ui/background.js @@ -51,12 +51,12 @@ export function uiBackground(context) { // Can be 0 from <1.3.0 use or due to issue #1923. if (!d) d = 1.0; - var bg = context.container().selectAll('.layer-background') - .style('opacity', d); - - if (!detected.opera) { - utilSetTransform(bg, 0, 0); - } + // var bg = context.container().selectAll('.layer-background') + // .style('opacity', d); + // if (!detected.opera) { + // utilSetTransform(bg, 0, 0); + // } + context.background().brightness(d); _displayOptions.selectAll('.opacity-input') .property('value', d); From b1efcf83b29b9489d4bc8adf7641b6647745dee3 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Thu, 30 Nov 2017 08:50:32 -0500 Subject: [PATCH 12/18] Add Sharpness slider --- css/80_app.css | 4 +-- data/core.yaml | 1 + dist/locales/en.json | 1 + modules/ui/background.js | 73 +++++++++++++++++++++++++++++----------- 4 files changed, 57 insertions(+), 22 deletions(-) diff --git a/css/80_app.css b/css/80_app.css index 9c8045f4b..0435e46c0 100644 --- a/css/80_app.css +++ b/css/80_app.css @@ -2463,11 +2463,11 @@ div.full-screen > button:hover { border-top: 5px solid #222; } -.opacity-options-wrapper { +.display-controls-wrapper { padding: 10px; } -.opacity-options-wrapper .opacity-value { +.display-controls-wrapper h5 span { margin: 5px; } diff --git a/data/core.yaml b/data/core.yaml index 3404c6454..1cd24fb73 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -384,6 +384,7 @@ en: reset: reset display_options: Display Options brightness: Brightness + sharpness: Sharpness minimap: description: Show Minimap tooltip: Show a zoomed out map to help locate the area currently displayed. diff --git a/dist/locales/en.json b/dist/locales/en.json index 9d1872d60..e41ba4849 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -472,6 +472,7 @@ "reset": "reset", "display_options": "Display Options", "brightness": "Brightness", + "sharpness": "Sharpness", "minimap": { "description": "Show Minimap", "tooltip": "Show a zoomed out map to help locate the area currently displayed.", diff --git a/modules/ui/background.js b/modules/ui/background.js index 3777975d6..0b4251c88 100644 --- a/modules/ui/background.js +++ b/modules/ui/background.js @@ -30,8 +30,9 @@ export function uiBackground(context) { var key = t('background.key'); var detected = utilDetect(); - var _opacity = (context.storage('background-opacity') !== null) ? + var _brightness = (context.storage('background-opacity') !== null) ? (+context.storage('background-opacity')) : 1.0; + var _sharpness = 1; var _customSource = context.background().findSource('custom'); var _previousBackground; var _shown = false; @@ -43,19 +44,15 @@ export function uiBackground(context) { var backgroundOffset = uiBackgroundOffset(context); + function clamp(x, min, max) { return Math.max(min, Math.min(x, max)); } - function setOpacity(d) { + + function setBrightness(d) { if (!d && d3_event && d3_event.target) { d = d3_event.target.value; } - // Can be 0 from <1.3.0 use or due to issue #1923. - if (!d) d = 1.0; - // var bg = context.container().selectAll('.layer-background') - // .style('opacity', d); - // if (!detected.opera) { - // utilSetTransform(bg, 0, 0); - // } + d = clamp(d, 0.25, 2); context.background().brightness(d); _displayOptions.selectAll('.opacity-input') @@ -65,7 +62,25 @@ export function uiBackground(context) { .text(Math.floor(d * 100) + '%'); context.storage('background-opacity', d); - _opacity = d; + _brightness = d; + } + + + function setSharpness(d) { + if (!d && d3_event && d3_event.target) { + d = d3_event.target.value; + } + + d = clamp(d, 0.5, 2); + context.background().sharpness(d); + + _displayOptions.selectAll('.sharpness-input') + .property('value', d); + + _displayOptions.selectAll('.sharpness-value') + .text(Math.floor(d * 100) + '%'); + + _sharpness = d; } @@ -250,27 +265,45 @@ export function uiBackground(context) { .append('div') .attr('class', 'display-options-container controls-list'); - // add opacity switcher - var opacityDivEnter = containerEnter + // brightness + var controlsEnter = containerEnter .append('div') - .attr('class', 'opacity-options-wrapper'); + .attr('class', 'display-controls-wrapper'); - opacityDivEnter + controlsEnter .append('h5') .text(t('background.brightness')) .append('span') .attr('class', 'opacity-value') - .text(Math.floor(_opacity * 100) + '%'); + .text(Math.floor(_brightness * 100) + '%'); - opacityDivEnter + controlsEnter .append('input') .attr('class', 'opacity-input') .attr('type', 'range') .attr('min', '0.25') - .attr('max', '1') + .attr('max', '2') .attr('step', '0.05') - .property('value', _opacity) - .on('input.set-opacity', setOpacity); + .property('value', _brightness) + .on('input.set-brightness', setBrightness); + + // sharpness + controlsEnter + .append('h5') + .text(t('background.sharpness')) + .append('span') + .attr('class', 'sharpness-value') + .text(Math.floor(_brightness * 100) + '%'); + + controlsEnter + .append('input') + .attr('class', 'sharpness-input') + .attr('type', 'range') + .attr('min', '0.5') + .attr('max', '2') + .attr('step', '0.05') + .property('value', _sharpness) + .on('input.set-sharpness', setSharpness); // add minimap toggle var minimapEnter = containerEnter @@ -445,7 +478,7 @@ export function uiBackground(context) { update(); - setOpacity(_opacity); + setBrightness(_brightness); var keybinding = d3_keybinding('background') .on(key, togglePane) From 53225f08ec7a7287646cecf955054e6d992f5df3 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Thu, 30 Nov 2017 16:19:06 -0500 Subject: [PATCH 13/18] more tweaks to unsharp mask --- css/80_app.css | 18 ++++++++++-------- modules/renderer/background.js | 33 ++++++++++++++++++--------------- modules/ui/background.js | 5 +++-- 3 files changed, 31 insertions(+), 25 deletions(-) diff --git a/css/80_app.css b/css/80_app.css index 0435e46c0..743d698bb 100644 --- a/css/80_app.css +++ b/css/80_app.css @@ -2662,7 +2662,17 @@ img.tile { user-select: none; opacity: 0; +} +img.tile-loaded { + opacity: 1; +} + +img.tile-removing { + opacity: 0; +} + +div:not(.layer-mask) > img.tile { -webkit-transition: opacity 200ms linear; transition: opacity 200ms linear; -moz-transition: opacity 200ms linear; @@ -2696,14 +2706,6 @@ img.tile-debug { outline: 1px solid red; } -img.tile-loaded { - opacity: 1; -} - -img.tile-removing { - opacity: 0; -} - /* Map ------------------------------------------------------- */ diff --git a/modules/renderer/background.js b/modules/renderer/background.js index 8fd9720be..bf3940e84 100644 --- a/modules/renderer/background.js +++ b/modules/renderer/background.js @@ -1,6 +1,7 @@ import _find from 'lodash-es/find'; import { dispatch as d3_dispatch } from 'd3-dispatch'; +import { interpolateNumber as d3_interpolateNumber } from 'd3-interpolate'; import { select as d3_select } from 'd3-selection'; import { data } from '../../data'; @@ -22,11 +23,17 @@ export function rendererBackground(context) { function background(selection) { var baseFilter = ''; + var blur, contrast; + if (_brightness !== 1) { baseFilter += 'brightness(' + _brightness + ')'; } - if (_sharpness !== 1) { - baseFilter += 'contrast(1.25)';// + _sharpness + ')'; + if (_sharpness < 1) { // gaussian blur + blur = d3_interpolateNumber(0.5, 5)(1 - _sharpness); + baseFilter += 'blur(' + blur + 'px)'; + } else if (_sharpness > 1) { + contrast = d3_interpolateNumber(1, 1.5)(_sharpness - 1); + baseFilter += 'contrast(' + contrast + ')'; } var base = selection.selectAll('.layer-background') @@ -52,30 +59,26 @@ export function rendererBackground(context) { var maskFilter = ''; var mixBlendMode = ''; - if (_sharpness !== 1) { - var blur = Math.abs(_sharpness - 1) * 10; - maskFilter += 'blur(' + blur + 'px)'; + if (_sharpness > 1) { // apply unsharp mask + mixBlendMode = 'overlay'; + maskFilter = 'saturate(0)'; - if (_sharpness > 1) { - maskFilter += 'invert(1)'; - mixBlendMode = 'overlay'; - } else { - mixBlendMode = 'normal'; - } + blur = d3_interpolateNumber(3, 0.5)(_sharpness - 1); + maskFilter += 'blur(' + blur + 'px) invert(1)'; - // var contrast = 1 / (_sharpness || 0.1); - maskFilter += 'contrast(0.75)';// + contrast + ')'; + contrast = d3_interpolateNumber(0, 1)(_sharpness - 1); + maskFilter += 'contrast(' + contrast + ')'; } var mask = base.selectAll('.layer-unsharp-mask') - .data(_sharpness !== 1 ? [0] : []); + .data(_sharpness > 1 ? [0] : []); mask.exit() .remove(); mask.enter() .append('div') - .attr('class', 'layer layer-unsharp-mask') + .attr('class', 'layer layer-mask layer-unsharp-mask') .merge(mask) .call(baseLayer) .style('-webkit-filter', maskFilter || null) diff --git a/modules/ui/background.js b/modules/ui/background.js index 0b4251c88..0739ba407 100644 --- a/modules/ui/background.js +++ b/modules/ui/background.js @@ -10,6 +10,7 @@ import { select as d3_select } from 'd3-selection'; + import { d3keybinding as d3_keybinding } from '../lib/d3.keybinding.js'; import { t, textDirection } from '../util/locale'; @@ -71,7 +72,7 @@ export function uiBackground(context) { d = d3_event.target.value; } - d = clamp(d, 0.5, 2); + d = clamp(d, 0.25, 2); context.background().sharpness(d); _displayOptions.selectAll('.sharpness-input') @@ -299,7 +300,7 @@ export function uiBackground(context) { .append('input') .attr('class', 'sharpness-input') .attr('type', 'range') - .attr('min', '0.5') + .attr('min', '0.25') .attr('max', '2') .attr('step', '0.05') .property('value', _sharpness) From 50c1b58bda19531328c4211287e11d6d7365953f Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Thu, 30 Nov 2017 23:24:55 -0500 Subject: [PATCH 14/18] Added saturation slider, simplify code, improved sharpen parameters --- css/80_app.css | 14 +++-- data/core.yaml | 2 + dist/locales/en.json | 2 + modules/renderer/background.js | 42 ++++++++++----- modules/ui/background.js | 94 +++++++++++++--------------------- 5 files changed, 75 insertions(+), 79 deletions(-) diff --git a/css/80_app.css b/css/80_app.css index 743d698bb..e0c851e13 100644 --- a/css/80_app.css +++ b/css/80_app.css @@ -2463,11 +2463,11 @@ div.full-screen > button:hover { border-top: 5px solid #222; } -.display-controls-wrapper { +.display-options-container { padding: 10px; } -.display-controls-wrapper h5 span { +.display-options-container h5 span { margin: 5px; } @@ -2662,6 +2662,10 @@ img.tile { user-select: none; opacity: 0; + + -webkit-transition: opacity 200ms linear; + -moz-transition: opacity 200ms linear; + transition: opacity 200ms linear; } img.tile-loaded { @@ -2672,12 +2676,6 @@ img.tile-removing { opacity: 0; } -div:not(.layer-mask) > img.tile { - -webkit-transition: opacity 200ms linear; - transition: opacity 200ms linear; - -moz-transition: opacity 200ms linear; -} - .tile-label-debug { font-size: 10px; background: rgba(0, 0, 0, 0.7); diff --git a/data/core.yaml b/data/core.yaml index 1cd24fb73..7b593ce3d 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -384,6 +384,8 @@ en: reset: reset display_options: Display Options brightness: Brightness + contrast: Contrast + saturation: Saturation sharpness: Sharpness minimap: description: Show Minimap diff --git a/dist/locales/en.json b/dist/locales/en.json index e41ba4849..306c8ac3d 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -472,6 +472,8 @@ "reset": "reset", "display_options": "Display Options", "brightness": "Brightness", + "contrast": "Contrast", + "saturation": "Saturation", "sharpness": "Sharpness", "minimap": { "description": "Show Minimap", diff --git a/modules/renderer/background.js b/modules/renderer/background.js index bf3940e84..3d9f645b4 100644 --- a/modules/renderer/background.js +++ b/modules/renderer/background.js @@ -18,22 +18,26 @@ export function rendererBackground(context) { var _overlayLayers = []; var _backgroundSources = []; var _brightness = 1; + var _contrast = 1; + var _saturation = 1; var _sharpness = 1; function background(selection) { var baseFilter = ''; - var blur, contrast; if (_brightness !== 1) { baseFilter += 'brightness(' + _brightness + ')'; } + if (_contrast !== 1) { + baseFilter += 'contrast(' + _contrast + ')'; + } + if (_saturation !== 1) { + baseFilter += 'saturate(' + _saturation + ')'; + } if (_sharpness < 1) { // gaussian blur - blur = d3_interpolateNumber(0.5, 5)(1 - _sharpness); + var blur = d3_interpolateNumber(0.5, 5)(1 - _sharpness); baseFilter += 'blur(' + blur + 'px)'; - } else if (_sharpness > 1) { - contrast = d3_interpolateNumber(1, 1.5)(_sharpness - 1); - baseFilter += 'contrast(' + contrast + ')'; } var base = selection.selectAll('.layer-background') @@ -43,7 +47,6 @@ export function rendererBackground(context) { .insert('div', '.layer-data') .attr('class', 'layer layer-background') .merge(base) - .style('-webkit-filter', baseFilter || null) .style('filter', baseFilter || null); @@ -61,13 +64,13 @@ export function rendererBackground(context) { var mixBlendMode = ''; if (_sharpness > 1) { // apply unsharp mask mixBlendMode = 'overlay'; - maskFilter = 'saturate(0)'; + maskFilter = 'saturate(0) blur(3px) invert(1)'; - blur = d3_interpolateNumber(3, 0.5)(_sharpness - 1); - maskFilter += 'blur(' + blur + 'px) invert(1)'; + var contrast = _sharpness - 1; + maskFilter += ' contrast(' + contrast + ')'; - contrast = d3_interpolateNumber(0, 1)(_sharpness - 1); - maskFilter += 'contrast(' + contrast + ')'; + var brightness = d3_interpolateNumber(1, 0.85)(_sharpness - 1); + maskFilter += ' brightness(' + brightness + ')'; } var mask = base.selectAll('.layer-unsharp-mask') @@ -81,7 +84,6 @@ export function rendererBackground(context) { .attr('class', 'layer layer-mask layer-unsharp-mask') .merge(mask) .call(baseLayer) - .style('-webkit-filter', maskFilter || null) .style('filter', maskFilter || null) .style('mix-blend-mode', mixBlendMode || null); @@ -302,6 +304,22 @@ export function rendererBackground(context) { }; + background.contrast = function(d) { + if (!arguments.length) return _contrast; + _contrast = d; + if (context.mode()) dispatch.call('change'); + return background; + }; + + + background.saturation = function(d) { + if (!arguments.length) return _saturation; + _saturation = d; + if (context.mode()) dispatch.call('change'); + return background; + }; + + background.sharpness = function(d) { if (!arguments.length) return _sharpness; _sharpness = d; diff --git a/modules/ui/background.js b/modules/ui/background.js index 0739ba407..2ddf0d81c 100644 --- a/modules/ui/background.js +++ b/modules/ui/background.js @@ -31,9 +31,13 @@ export function uiBackground(context) { var key = t('background.key'); var detected = utilDetect(); - var _brightness = (context.storage('background-opacity') !== null) ? - (+context.storage('background-opacity')) : 1.0; - var _sharpness = 1; + var _options = { + brightness: (context.storage('background-opacity') !== null) ? + (+context.storage('background-opacity')) : 1.0, + contrast: 1, + saturation: 1, + sharpness: 1 + }; var _customSource = context.background().findSource('custom'); var _previousBackground; var _shown = false; @@ -48,40 +52,23 @@ export function uiBackground(context) { function clamp(x, min, max) { return Math.max(min, Math.min(x, max)); } - function setBrightness(d) { - if (!d && d3_event && d3_event.target) { - d = d3_event.target.value; + function setDisplayOption(d, val) { + if (!val && d3_event && d3_event.target) { + val = d3_event.target.value; } - d = clamp(d, 0.25, 2); - context.background().brightness(d); + val = clamp(val, 0.25, 2); + _displayOptions.selectAll('.' + d + '-input') + .property('value', val); + _displayOptions.selectAll('.' + d + '-value') + .text(Math.floor(val * 100) + '%'); - _displayOptions.selectAll('.opacity-input') - .property('value', d); + _options[d] = val; + context.background()[d](val); - _displayOptions.selectAll('.opacity-value') - .text(Math.floor(d * 100) + '%'); - - context.storage('background-opacity', d); - _brightness = d; - } - - - function setSharpness(d) { - if (!d && d3_event && d3_event.target) { - d = d3_event.target.value; + if (d === 'brightness') { + context.storage('background-opacity', val); } - - d = clamp(d, 0.25, 2); - context.background().sharpness(d); - - _displayOptions.selectAll('.sharpness-input') - .property('value', d); - - _displayOptions.selectAll('.sharpness-value') - .text(Math.floor(d * 100) + '%'); - - _sharpness = d; } @@ -266,45 +253,34 @@ export function uiBackground(context) { .append('div') .attr('class', 'display-options-container controls-list'); - // brightness - var controlsEnter = containerEnter + var sliders = ['brightness', 'contrast', 'saturation', 'sharpness']; + + var controls = containerEnter.selectAll('.display-control') + .data(sliders); + + var controlsEnter = controls.enter() .append('div') - .attr('class', 'display-controls-wrapper'); + .attr('class', function(d) { return 'display-control display-control-' + d; }); controlsEnter .append('h5') - .text(t('background.brightness')) + .text(function(d) { return t('background.' + d); }) .append('span') - .attr('class', 'opacity-value') - .text(Math.floor(_brightness * 100) + '%'); + .attr('class', function(d) { return d + '-value'; }); controlsEnter .append('input') - .attr('class', 'opacity-input') + .attr('class', function(d) { return d + '-input'; }) .attr('type', 'range') .attr('min', '0.25') .attr('max', '2') .attr('step', '0.05') - .property('value', _brightness) - .on('input.set-brightness', setBrightness); + .property('value', function(d) { return _options[d]; }) + .on('input', function(d) { + var val = d3_select(this).property('value'); + setDisplayOption(d, val); + }); - // sharpness - controlsEnter - .append('h5') - .text(t('background.sharpness')) - .append('span') - .attr('class', 'sharpness-value') - .text(Math.floor(_brightness * 100) + '%'); - - controlsEnter - .append('input') - .attr('class', 'sharpness-input') - .attr('type', 'range') - .attr('min', '0.25') - .attr('max', '2') - .attr('step', '0.05') - .property('value', _sharpness) - .on('input.set-sharpness', setSharpness); // add minimap toggle var minimapEnter = containerEnter @@ -479,7 +455,7 @@ export function uiBackground(context) { update(); - setBrightness(_brightness); + setDisplayOption('brightness', _options.brightness); var keybinding = d3_keybinding('background') .on(key, togglePane) From 48834a0ea564b4b1575797912d689809831593c3 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Tue, 2 Jan 2018 23:41:21 -0500 Subject: [PATCH 15/18] Add reset buttons for display sliders --- modules/ui/background.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/modules/ui/background.js b/modules/ui/background.js index 2ddf0d81c..cf194a358 100644 --- a/modules/ui/background.js +++ b/modules/ui/background.js @@ -62,6 +62,8 @@ export function uiBackground(context) { .property('value', val); _displayOptions.selectAll('.' + d + '-value') .text(Math.floor(val * 100) + '%'); + _displayOptions.selectAll('.' + d + '-reset') + .classed('disabled', val === 1); _options[d] = val; context.background()[d](val); @@ -281,6 +283,16 @@ export function uiBackground(context) { setDisplayOption(d, val); }); + controlsEnter + .append('button') + .attr('title', t('background.reset')) + .attr('class', function(d) { return d + '-reset disabled'; }) + .on('click', function(d) { + if (d3_event.button !== 0) return; + setDisplayOption(d, 1); + }) + .call(svgIcon('#icon-' + (textDirection === 'rtl' ? 'redo' : 'undo'))); + // add minimap toggle var minimapEnter = containerEnter From 53aa2973e48a363e286261af8a6be5c561860ad2 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Wed, 3 Jan 2018 01:47:25 -0500 Subject: [PATCH 16/18] Refactor display options controls to uiBackgroundDisplayOptions --- modules/ui/background.js | 133 ++------------------ modules/ui/background_display_options.js | 152 +++++++++++++++++++++++ modules/ui/index.js | 1 + 3 files changed, 163 insertions(+), 123 deletions(-) create mode 100644 modules/ui/background_display_options.js diff --git a/modules/ui/background.js b/modules/ui/background.js index cf194a358..3fd003d3f 100644 --- a/modules/ui/background.js +++ b/modules/ui/background.js @@ -10,69 +10,36 @@ import { select as d3_select } from 'd3-selection'; - import { d3keybinding as d3_keybinding } from '../lib/d3.keybinding.js'; import { t, textDirection } from '../util/locale'; import { svgIcon } from '../svg'; +import { uiBackgroundDisplayOptions } from './background_display_options'; import { uiBackgroundOffset } from './background_offset'; import { uiCmd } from './cmd'; import { uiDisclosure } from './disclosure'; import { uiHelp } from './help'; import { uiMapData } from './map_data'; -import { uiMapInMap } from './map_in_map'; import { uiTooltipHtml } from './tooltipHtml'; -import { utilDetect } from '../util/detect'; -import { utilSetTransform, utilCallWhenIdle } from '../util'; +import { utilCallWhenIdle } from '../util'; import { tooltip } from '../util/tooltip'; export function uiBackground(context) { var key = t('background.key'); - var detected = utilDetect(); - var _options = { - brightness: (context.storage('background-opacity') !== null) ? - (+context.storage('background-opacity')) : 1.0, - contrast: 1, - saturation: 1, - sharpness: 1 - }; var _customSource = context.background().findSource('custom'); var _previousBackground; var _shown = false; var _backgroundList = d3_select(null); var _overlayList = d3_select(null); - var _displayOptions = d3_select(null); + var _displayOptionsContainer = d3_select(null); var _offsetContainer = d3_select(null); + var backgroundDisplayOptions = uiBackgroundDisplayOptions(context); var backgroundOffset = uiBackgroundOffset(context); - function clamp(x, min, max) { return Math.max(min, Math.min(x, max)); } - - - function setDisplayOption(d, val) { - if (!val && d3_event && d3_event.target) { - val = d3_event.target.value; - } - - val = clamp(val, 0.25, 2); - _displayOptions.selectAll('.' + d + '-input') - .property('value', val); - _displayOptions.selectAll('.' + d + '-value') - .text(Math.floor(val * 100) + '%'); - _displayOptions.selectAll('.' + d + '-reset') - .classed('disabled', val === 1); - - _options[d] = val; - context.background()[d](val); - - if (d === 'brightness') { - context.storage('background-opacity', val); - } - } - function setTooltips(selection) { selection.each(function(d, i, nodes) { @@ -247,84 +214,6 @@ export function uiBackground(context) { } - function renderDisplayOptions(selection) { - var container = selection.selectAll('display-options-container') - .data([0]); - - var containerEnter = container.enter() - .append('div') - .attr('class', 'display-options-container controls-list'); - - var sliders = ['brightness', 'contrast', 'saturation', 'sharpness']; - - var controls = containerEnter.selectAll('.display-control') - .data(sliders); - - var controlsEnter = controls.enter() - .append('div') - .attr('class', function(d) { return 'display-control display-control-' + d; }); - - controlsEnter - .append('h5') - .text(function(d) { return t('background.' + d); }) - .append('span') - .attr('class', function(d) { return d + '-value'; }); - - controlsEnter - .append('input') - .attr('class', function(d) { return d + '-input'; }) - .attr('type', 'range') - .attr('min', '0.25') - .attr('max', '2') - .attr('step', '0.05') - .property('value', function(d) { return _options[d]; }) - .on('input', function(d) { - var val = d3_select(this).property('value'); - setDisplayOption(d, val); - }); - - controlsEnter - .append('button') - .attr('title', t('background.reset')) - .attr('class', function(d) { return d + '-reset disabled'; }) - .on('click', function(d) { - if (d3_event.button !== 0) return; - setDisplayOption(d, 1); - }) - .call(svgIcon('#icon-' + (textDirection === 'rtl' ? 'redo' : 'undo'))); - - - // add minimap toggle - var minimapEnter = containerEnter - .append('div') - .attr('class', 'minimap-toggle-wrap'); - - var minimapLabelEnter = minimapEnter - .append('label') - .call(tooltip() - .html(true) - .title(uiTooltipHtml(t('background.minimap.tooltip'), t('background.minimap.key'))) - .placement('top') - ); - - minimapLabelEnter - .classed('minimap-toggle', true) - .append('input') - .attr('type', 'checkbox') - .on('change', function() { - uiMapInMap.toggle(); - d3_event.preventDefault(); - }); - - minimapLabelEnter - .append('span') - .text(t('background.minimap.description')); - - _displayOptions = containerEnter - .merge(container); - } - - function update() { _backgroundList .call(drawListItems, 'radio', chooseBackground, function(d) { return !d.isHidden() && !d.overlay; }); @@ -332,6 +221,9 @@ export function uiBackground(context) { _overlayList .call(drawListItems, 'checkbox', chooseOverlay, function(d) { return !d.isHidden() && d.overlay; }); + _displayOptionsContainer + .call(backgroundDisplayOptions); + _offsetContainer .call(backgroundOffset); } @@ -443,14 +335,10 @@ export function uiBackground(context) { .content(renderOverlayList) ); - // display settings - pane + // display options + _displayOptionsContainer = pane .append('div') - .attr('class', 'background-display-options-container') - .call(uiDisclosure(context, 'background_display_options', true) - .title(t('background.display_options')) - .content(renderDisplayOptions) - ); + .attr('class', 'background-display-options'); // offset controls _offsetContainer = pane @@ -467,7 +355,6 @@ export function uiBackground(context) { update(); - setDisplayOption('brightness', _options.brightness); var keybinding = d3_keybinding('background') .on(key, togglePane) diff --git a/modules/ui/background_display_options.js b/modules/ui/background_display_options.js new file mode 100644 index 000000000..ecfa8fdda --- /dev/null +++ b/modules/ui/background_display_options.js @@ -0,0 +1,152 @@ +import { + event as d3_event, + select as d3_select +} from 'd3-selection'; + + +import { t, textDirection } from '../util/locale'; +import { svgIcon } from '../svg'; +import { uiDisclosure } from './disclosure'; +import { uiMapInMap } from './map_in_map'; +import { uiTooltipHtml } from './tooltipHtml'; +import { tooltip } from '../util/tooltip'; + + +export function uiBackgroundDisplayOptions(context) { + var _selection = d3_select(null); + var sliders = ['brightness', 'contrast', 'saturation', 'sharpness']; + + var _options = { + brightness: (context.storage('background-opacity') !== null) ? + (+context.storage('background-opacity')) : 1.0, + contrast: 1, + saturation: 1, + sharpness: 1 + }; + + + function clamp(x, min, max) { + return Math.max(min, Math.min(x, max)); + } + + + function updateValue(d, val) { + if (!val && d3_event && d3_event.target) { + val = d3_event.target.value; + } + + val = clamp(val, 0.25, 2); + + _options[d] = val; + context.background()[d](val); + + if (d === 'brightness') { + context.storage('background-opacity', val); + } + + _selection + .call(render); + } + + + function render(selection) { + var container = selection.selectAll('.display-options-container') + .data([0]); + + var containerEnter = container.enter() + .append('div') + .attr('class', 'display-options-container controls-list'); + + // add slider controls + var slidersEnter = containerEnter.selectAll('.display-control') + .data(sliders) + .enter() + .append('div') + .attr('class', function(d) { return 'display-control display-control-' + d; }); + + slidersEnter + .append('h5') + .text(function(d) { return t('background.' + d); }) + .append('span') + .attr('class', function(d) { return 'display-option-value display-option-value-' + d; }); + + slidersEnter + .append('input') + .attr('class', function(d) { return 'display-option-input display-option-input-' + d; }) + .attr('type', 'range') + .attr('min', '0.25') + .attr('max', '2') + .attr('step', '0.05') + .on('input', function(d) { + var val = d3_select(this).property('value'); + updateValue(d, val); + }); + + slidersEnter + .append('button') + .attr('title', t('background.reset')) + .attr('class', function(d) { return 'display-option-reset display-option-reset-' + d; }) + .on('click', function(d) { + if (d3_event.button !== 0) return; + updateValue(d, 1); + }) + .call(svgIcon('#icon-' + (textDirection === 'rtl' ? 'redo' : 'undo'))); + + + // add minimap toggle + var minimapEnter = containerEnter + .append('div') + .attr('class', 'minimap-toggle-wrap'); + + var minimapLabelEnter = minimapEnter + .append('label') + .call(tooltip() + .html(true) + .title(uiTooltipHtml(t('background.minimap.tooltip'), t('background.minimap.key'))) + .placement('top') + ); + + minimapLabelEnter + .classed('minimap-toggle', true) + .append('input') + .attr('type', 'checkbox') + .on('change', function() { + uiMapInMap.toggle(); + d3_event.preventDefault(); + }); + + minimapLabelEnter + .append('span') + .text(t('background.minimap.description')); + + + // update + container = containerEnter + .merge(container); + + container.selectAll('.display-option-input') + .property('value', function(d) { return _options[d]; }); + + container.selectAll('.display-option-value') + .text(function(d) { return Math.floor(_options[d] * 100) + '%'; }); + + container.selectAll('.display-option-reset') + .classed('disabled', function(d) { return _options[d] === 1; }); + } + + + function backgroundDisplayOptions(selection) { + _selection = selection; + + selection + .call(uiDisclosure(context, 'background_display_options', true) + .title(t('background.display_options')) + .content(render) + ); + } + + // setDisplayOption('brightness', _options.brightness); + + + return backgroundDisplayOptions; +} diff --git a/modules/ui/index.js b/modules/ui/index.js index f1affc4fd..8a3dd80c6 100644 --- a/modules/ui/index.js +++ b/modules/ui/index.js @@ -2,6 +2,7 @@ export { uiInit } from './init'; export { uiAccount } from './account'; export { uiAttribution } from './attribution'; export { uiBackground } from './background'; +export { uiBackgroundDisplayOptions } from './background_display_options'; export { uiBackgroundOffset } from './background_offset'; export { uiChangesetEditor } from './changeset_editor'; export { uiCmd } from './cmd'; From 46ebe025555fe82930f5d27fa9866e19759f1cfc Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Wed, 3 Jan 2018 14:35:27 -0500 Subject: [PATCH 17/18] Clean up styles, move minimap toggle below background imagery list --- css/80_app.css | 45 +++++++++++++++--------- modules/ui/background.js | 42 +++++++++++++++++++--- modules/ui/background_display_options.js | 41 ++++----------------- modules/ui/map_in_map.js | 7 ++-- 4 files changed, 76 insertions(+), 59 deletions(-) diff --git a/css/80_app.css b/css/80_app.css index e0c851e13..a7e430fe3 100644 --- a/css/80_app.css +++ b/css/80_app.css @@ -2319,20 +2319,38 @@ div.full-screen > button:hover { text-overflow: ellipsis; } -.minimap-toggle { - display: block; - padding: 5px 10px; - cursor: pointer; - color: #7092ff; - border-top: 1px solid #ccc; + +/* Background Display Options */ + +.display-options-container { + padding: 10px; } -.minimap-toggle.active { - background: #e8ebff; +.display-control h5 { + padding-bottom: 0; + padding-top: 10px; } -.minimap-toggle:hover { - background-color: #ececec; +.display-control h5 span { + margin: 5px; +} + +.display-control .display-option-input { + height: 20px; + width: 160px; +} + +.display-control button { + height: 30px; + width: 30px; + margin-left: 5px; + margin-right: 0px; + vertical-align: text-bottom; + border-radius: 4px; +} +[dir='rtl'] .display-control button { + margin-left: 0px; + margin-right: 5px; } @@ -2463,13 +2481,6 @@ div.full-screen > button:hover { border-top: 5px solid #222; } -.display-options-container { - padding: 10px; -} - -.display-options-container h5 span { - margin: 5px; -} .map-data-control .layer-list button, .background-control .layer-list button { diff --git a/modules/ui/background.js b/modules/ui/background.js index 3fd003d3f..cbd5bfa15 100644 --- a/modules/ui/background.js +++ b/modules/ui/background.js @@ -20,6 +20,7 @@ import { uiCmd } from './cmd'; import { uiDisclosure } from './disclosure'; import { uiHelp } from './help'; import { uiMapData } from './map_data'; +import { uiMapInMap } from './map_in_map'; import { uiTooltipHtml } from './tooltipHtml'; import { utilCallWhenIdle } from '../util'; import { tooltip } from '../util/tooltip'; @@ -43,11 +44,11 @@ export function uiBackground(context) { function setTooltips(selection) { selection.each(function(d, i, nodes) { - var item = d3_select(this).select('label'), - span = item.select('span'), - placement = (i < nodes.length / 2) ? 'bottom' : 'top', - description = d.description(), - isOverflowing = (span.property('clientWidth') !== span.property('scrollWidth')); + var item = d3_select(this).select('label'); + var span = item.select('span'); + var placement = (i < nodes.length / 2) ? 'bottom' : 'top'; + var description = d.description(); + var isOverflowing = (span.property('clientWidth') !== span.property('scrollWidth')); if (d === _previousBackground) { item.call(tooltip() @@ -194,11 +195,42 @@ export function uiBackground(context) { var container = selection.selectAll('layer-background-list') .data([0]); + // the background list _backgroundList = container.enter() .append('ul') .attr('class', 'layer-list layer-background-list') .attr('dir', 'auto') .merge(container); + + + // add minimap toggle below list + var minimapEnter = selection.selectAll('minimap-toggle-list') + .data([0]) + .enter() + .append('ul') + .attr('class', 'layer-list minimap-toggle-list') + .append('li') + .attr('class', 'layer minimap-toggle-item'); + + var minimapLabelEnter = minimapEnter + .append('label') + .call(tooltip() + .html(true) + .title(uiTooltipHtml(t('background.minimap.tooltip'), t('background.minimap.key'))) + .placement('top') + ); + + minimapLabelEnter + .append('input') + .attr('type', 'checkbox') + .on('change', function() { + d3_event.preventDefault(); + uiMapInMap.toggle(); + }); + + minimapLabelEnter + .append('span') + .text(t('background.minimap.description')); } diff --git a/modules/ui/background_display_options.js b/modules/ui/background_display_options.js index ecfa8fdda..9dfbfc80c 100644 --- a/modules/ui/background_display_options.js +++ b/modules/ui/background_display_options.js @@ -7,18 +7,15 @@ import { import { t, textDirection } from '../util/locale'; import { svgIcon } from '../svg'; import { uiDisclosure } from './disclosure'; -import { uiMapInMap } from './map_in_map'; -import { uiTooltipHtml } from './tooltipHtml'; -import { tooltip } from '../util/tooltip'; export function uiBackgroundDisplayOptions(context) { var _selection = d3_select(null); var sliders = ['brightness', 'contrast', 'saturation', 'sharpness']; + var storedOpacity = context.storage('background-opacity'); var _options = { - brightness: (context.storage('background-opacity') !== null) ? - (+context.storage('background-opacity')) : 1.0, + brightness: (storedOpacity !== null ? (+storedOpacity) : 1), contrast: 1, saturation: 1, sharpness: 1 @@ -93,33 +90,6 @@ export function uiBackgroundDisplayOptions(context) { .call(svgIcon('#icon-' + (textDirection === 'rtl' ? 'redo' : 'undo'))); - // add minimap toggle - var minimapEnter = containerEnter - .append('div') - .attr('class', 'minimap-toggle-wrap'); - - var minimapLabelEnter = minimapEnter - .append('label') - .call(tooltip() - .html(true) - .title(uiTooltipHtml(t('background.minimap.tooltip'), t('background.minimap.key'))) - .placement('top') - ); - - minimapLabelEnter - .classed('minimap-toggle', true) - .append('input') - .attr('type', 'checkbox') - .on('change', function() { - uiMapInMap.toggle(); - d3_event.preventDefault(); - }); - - minimapLabelEnter - .append('span') - .text(t('background.minimap.description')); - - // update container = containerEnter .merge(container); @@ -132,6 +102,11 @@ export function uiBackgroundDisplayOptions(context) { container.selectAll('.display-option-reset') .classed('disabled', function(d) { return _options[d] === 1; }); + + // first time only, set brightness if needed + if (containerEnter.size() && _options.brightness !== 1) { + context.background().brightness(_options.brightness); + } } @@ -145,8 +120,6 @@ export function uiBackgroundDisplayOptions(context) { ); } - // setDisplayOption('brightness', _options.brightness); - return backgroundDisplayOptions; } diff --git a/modules/ui/map_in_map.js b/modules/ui/map_in_map.js index 4a6609c98..e7b70ec5e 100644 --- a/modules/ui/map_in_map.js +++ b/modules/ui/map_in_map.js @@ -285,9 +285,10 @@ export function uiMapInMap(context) { isHidden = !isHidden; - var label = d3_select('.minimap-toggle'); - label.classed('active', !isHidden) - .select('input').property('checked', !isHidden); + d3_select('.minimap-toggle-item') + .classed('active', !isHidden) + .select('input') + .property('checked', !isHidden); if (isHidden) { wrap From 04fa29cfe6b8c745e64964dee86aa6111a2e3746 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Wed, 3 Jan 2018 15:23:47 -0500 Subject: [PATCH 18/18] Move link to imagery faq, reword as "Imagery Info / Report a Problem" (closes #4546) --- css/80_app.css | 1 + data/core.yaml | 2 +- dist/locales/en.json | 2 +- modules/ui/background.js | 37 ++++++++++++++++++++----------------- 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/css/80_app.css b/css/80_app.css index a7e430fe3..a64c2d821 100644 --- a/css/80_app.css +++ b/css/80_app.css @@ -2246,6 +2246,7 @@ div.full-screen > button:hover { .imagery-faq { margin-bottom: 10px; + white-space: nowrap; } .layer-list, .controls-list { diff --git a/data/core.yaml b/data/core.yaml index 7b593ce3d..4ccb518fc 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -380,7 +380,7 @@ en: custom_button: Edit custom background custom_prompt: "Enter a tile URL template. Valid tokens are:\n - {zoom}/{z}, {x}, {y} for Z/X/Y tile scheme\n - {ty} for flipped TMS-style Y coordinates\n - {u} for quadtile scheme\n - {switch:a,b,c} for DNS server multiplexing\n\nExample:\n{example}" overlays: Overlays - imagery_source_faq: Where does this imagery come from? + imagery_source_faq: Imagery Info / Report a Problem reset: reset display_options: Display Options brightness: Brightness diff --git a/dist/locales/en.json b/dist/locales/en.json index 306c8ac3d..f944f7194 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -468,7 +468,7 @@ "custom_button": "Edit custom background", "custom_prompt": "Enter a tile URL template. Valid tokens are:\n - {zoom}/{z}, {x}, {y} for Z/X/Y tile scheme\n - {ty} for flipped TMS-style Y coordinates\n - {u} for quadtile scheme\n - {switch:a,b,c} for DNS server multiplexing\n\nExample:\n{example}", "overlays": "Overlays", - "imagery_source_faq": "Where does this imagery come from?", + "imagery_source_faq": "Imagery Info / Report a Problem", "reset": "reset", "display_options": "Display Options", "brightness": "Brightness", diff --git a/modules/ui/background.js b/modules/ui/background.js index cbd5bfa15..d41bd9d24 100644 --- a/modules/ui/background.js +++ b/modules/ui/background.js @@ -192,10 +192,11 @@ export function uiBackground(context) { function renderBackgroundList(selection) { - var container = selection.selectAll('layer-background-list') - .data([0]); // the background list + var container = selection.selectAll('.layer-background-list') + .data([0]); + _backgroundList = container.enter() .append('ul') .attr('class', 'layer-list layer-background-list') @@ -204,7 +205,7 @@ export function uiBackground(context) { // add minimap toggle below list - var minimapEnter = selection.selectAll('minimap-toggle-list') + var minimapEnter = selection.selectAll('.minimap-toggle-list') .data([0]) .enter() .append('ul') @@ -231,11 +232,26 @@ export function uiBackground(context) { minimapLabelEnter .append('span') .text(t('background.minimap.description')); + + + // "Info / Report a Problem" link + selection.selectAll('.imagery-faq') + .data([0]) + .enter() + .append('div') + .attr('class', 'imagery-faq') + .append('a') + .attr('target', '_blank') + .attr('tabindex', -1) + .call(svgIcon('#icon-out-link', 'inline')) + .attr('href', 'https://github.com/openstreetmap/iD/blob/master/FAQ.md#how-can-i-report-an-issue-with-background-imagery') + .append('span') + .text(t('background.imagery_source_faq')); } function renderOverlayList(selection) { - var container = selection.selectAll('layer-overlay-list') + var container = selection.selectAll('.layer-overlay-list') .data([0]); _overlayList = container.enter() @@ -345,19 +361,6 @@ export function uiBackground(context) { .content(renderBackgroundList) ); - // "Where does this imagery come from?" - // pane - // .append('div') - // .attr('class', 'imagery-faq') - // .append('a') - // .attr('target', '_blank') - // .attr('tabindex', -1) - // .call(svgIcon('#icon-out-link', 'inline')) - // .attr('href', 'https://github.com/openstreetmap/iD/blob/master/FAQ.md#how-can-i-report-an-issue-with-background-imagery') - // .append('span') - // .text(t('background.imagery_source_faq')); - - // overlay list pane .append('div')