diff --git a/css/80_app.css b/css/80_app.css index 45203d8d5..8163781a2 100644 --- a/css/80_app.css +++ b/css/80_app.css @@ -740,6 +740,12 @@ button.add-note svg.icon { [dir='rtl'] .search-add button.add-preset { margin-right: 1px; } +[dir='ltr'] .search-add button.add-preset.first-recent { + margin-left: 5px; +} +[dir='rtl'] .search-add button.add-preset.first-recent { + margin-right: 5px; +} .search-add button.add-preset:not(.add-generic-preset) { padding: 0; } diff --git a/modules/presets/index.js b/modules/presets/index.js index 585e2cbe1..16d443a74 100644 --- a/modules/presets/index.js +++ b/modules/presets/index.js @@ -24,7 +24,7 @@ export function presetIndex(context) { // a presetCollection with methods for // loading new data and returning defaults - var dispatch = d3_dispatch('favoritePreset'); + var dispatch = d3_dispatch('recentsChange', 'favoritePreset'); var all = presetCollection([]); var _defaults = { area: all, line: all, point: all, vertex: all, relation: all }; @@ -286,6 +286,12 @@ export function presetIndex(context) { item.geometry = geometry; item.source = source; + item.isFavorite = function() { + return item.source === 'favorite'; + }; + item.isRecent = function() { + return item.source === 'recent'; + }; item.matches = function(preset, geometry) { return item.preset.id === preset.id && item.geometry === geometry; }; @@ -340,8 +346,10 @@ export function presetIndex(context) { _recents = items; var minifiedItems = items.map(function(d) { return d.minified(); }); context.storage('preset_recents', JSON.stringify(minifiedItems)); + + dispatch.call('recentsChange'); } - + all.getRecents = function() { if (!_recents) { // fetch from local storage @@ -391,16 +399,22 @@ export function presetIndex(context) { return false; }; + all.moveItem = function(items, fromIndex, toIndex) { + if (fromIndex === toIndex || + fromIndex < 0 || toIndex < 0 || + fromIndex >= items.length || toIndex >= items.length) return null; + items.splice(toIndex, 0, items.splice(fromIndex, 1)[0]); + return items; + }; + all.moveFavorite = function(fromIndex, toIndex) { - if (fromIndex === toIndex) return; + var items = all.moveItem(all.getFavorites(), fromIndex, toIndex); + if (items) setFavorites(items); + }; - var favs = all.getFavorites(); - - if (fromIndex < 0 || toIndex < 0 || - fromIndex >= favs.length || toIndex >= favs.length) return; - - favs.splice(toIndex, 0, favs.splice(fromIndex, 1)[0]); - setFavorites(favs); + all.moveRecent = function(fromIndex, toIndex) { + var items = all.moveItem(all.getRecents(), fromIndex, toIndex); + if (items) setRecents(items); }; all.setMostRecent = function(preset, geometry) { @@ -422,6 +436,14 @@ export function presetIndex(context) { items.unshift(item); setRecents(items); }; + all.removeRecent = function(preset, geometry) { + var item = all.isRecent(preset, geometry); + if (item) { + var items = all.getRecents(); + items.splice(items.indexOf(item), 1); + setRecents(items); + } + }; return utilRebind(all, dispatch, 'on'); } diff --git a/modules/ui/modes.js b/modules/ui/modes.js index fb9957979..d1aee07c6 100644 --- a/modules/ui/modes.js +++ b/modules/ui/modes.js @@ -1,4 +1,6 @@ +import _clone from 'lodash-es/clone'; import _debounce from 'lodash-es/debounce'; +import _uniqWith from 'lodash-es/uniqWith'; import { drag as d3_drag } from 'd3-drag'; import { event as d3_event, select as d3_select } from 'd3-selection'; @@ -52,7 +54,9 @@ export function uiModes(context) { context .on('enter.modes', update) - .presets().on('favoritePreset.modes', update); + .presets() + .on('favoritePreset.modes', update) + .on('recentsChange.modes', update); update(); @@ -63,11 +67,21 @@ export function uiModes(context) { context.keybinding().off(i.toString()); } - var favoritePresets = context.presets().getFavorites(); - var favoriteModes = favoritePresets.map(function(d, index) { + var items = context.presets().getFavorites(); + + var favoritesCount = items.length; + + if (favoritesCount < 10) { + items = _uniqWith(items.concat(context.presets().getRecents()), function(item1, item2) { + return item1.matches(item2.preset, item2.geometry); + }); + items = items.slice(0, 10); + } + + var modes = items.map(function(d, index) { var presetName = d.preset.name().split(' – ')[0]; var markerClass = 'add-preset add-' + d.geometry + ' add-preset-' + presetName.replace(/\s+/g, '_') - + '-' + d.geometry; // replace spaces with underscores to avoid css interpretation + + '-' + d.geometry + ' add-' + d.source; // replace spaces with underscores to avoid css interpretation if (d.preset.isFallback()) { markerClass += ' add-generic-preset'; } @@ -88,13 +102,10 @@ export function uiModes(context) { tooltipTitleID = 'modes.add_preset.' + d.geometry + '.title'; } } - var favoriteMode = { - button: markerClass, - title: presetName, - description: t(tooltipTitleID, { feature: '' + presetName + '' }), - preset: d.preset, - geometry: d.geometry - }; + var protoMode = _clone(d); + protoMode.button = markerClass; + protoMode.title = presetName; + protoMode.description = t(tooltipTitleID, { feature: '' + presetName + '' }); var keyCode; if (textDirection === 'ltr') { // use number row order: 1 2 3 4 5 6 7 8 9 0 @@ -112,20 +123,20 @@ export function uiModes(context) { } } if (keyCode !== null) { - favoriteMode.key = keyCode.toString(); + protoMode.key = keyCode.toString(); } var mode; switch (d.geometry) { case 'point': case 'vertex': - mode = modeAddPoint(context, favoriteMode); + mode = modeAddPoint(context, protoMode); break; case 'line': - mode = modeAddLine(context, favoriteMode); + mode = modeAddLine(context, protoMode); break; case 'area': - mode = modeAddArea(context, favoriteMode); + mode = modeAddArea(context, protoMode); } if (mode.key) { @@ -135,6 +146,9 @@ export function uiModes(context) { if (mode.button === context.mode().button) { context.enter(modeBrowse(context)); } else { + if (mode.preset && mode.isFavorite()) { + context.presets().setMostRecent(mode.preset, mode.geometry); + } context.enter(mode); } }); @@ -143,10 +157,8 @@ export function uiModes(context) { return mode; }); - var data = favoriteModes; - var buttons = selection.selectAll('button.add-button') - .data(data, function(d, index) { return d.button + index; }); + .data(modes, function(d, index) { return d.button + index; }); // exit buttons.exit() @@ -156,7 +168,13 @@ export function uiModes(context) { var buttonsEnter = buttons.enter() .append('button') .attr('tabindex', -1) - .attr('class', function(d) { return d.button + ' add-button bar-button'; }) + .attr('class', function(d, index) { + var classes = d.button + ' add-button bar-button'; + if (index === favoritesCount && index !== 0) { + classes += ' first-recent'; + } + return classes; + }) .on('click.mode-buttons', function(d) { if (!enabled(d)) return; @@ -167,7 +185,7 @@ export function uiModes(context) { if (d.id === currMode) { context.enter(modeBrowse(context)); } else { - if (d.preset) { + if (d.preset && d.isFavorite()) { context.presets().setMostRecent(d.preset, d.geometry); } context.enter(d); @@ -223,22 +241,24 @@ export function uiModes(context) { if (index2 > index) { return 'translateX(' + (textDirection === 'rtl' ? '' : '-') + '100%)'; } - } else if (index2 > index && ( - (d3_event.x > node.offsetLeft && textDirection === 'ltr') || - (d3_event.x < node.offsetLeft + node.offsetWidth && textDirection === 'rtl') - )) { - if (targetIndex === null || index2 > targetIndex) { - targetIndex = index2; + } else if (d.source === 'favorite' && d.source === d2.source) { + if (index2 > index && ( + (d3_event.x > node.offsetLeft && textDirection === 'ltr') || + (d3_event.x < node.offsetLeft + node.offsetWidth && textDirection === 'rtl') + )) { + if (targetIndex === null || index2 > targetIndex) { + targetIndex = index2; + } + return 'translateX(' + (textDirection === 'rtl' ? '' : '-') + '100%)'; + } else if (index2 < index && ( + (d3_event.x < node.offsetLeft + node.offsetWidth && textDirection === 'ltr') || + (d3_event.x > node.offsetLeft && textDirection === 'rtl') + )) { + if (targetIndex === null || index2 < targetIndex) { + targetIndex = index2; + } + return 'translateX(' + (textDirection === 'rtl' ? '-' : '') + '100%)'; } - return 'translateX(' + (textDirection === 'rtl' ? '' : '-') + '100%)'; - } else if (index2 < index && ( - (d3_event.x < node.offsetLeft + node.offsetWidth && textDirection === 'ltr') || - (d3_event.x > node.offsetLeft && textDirection === 'rtl') - )) { - if (targetIndex === null || index2 < targetIndex) { - targetIndex = index2; - } - return 'translateX(' + (textDirection === 'rtl' ? '-' : '') + '100%)'; } return null; }); @@ -254,11 +274,19 @@ export function uiModes(context) { var y = d3_event.y - dragOrigin.y; if (y > 50) { - // dragged out of the top bar, remove the favorite - context.presets().toggleFavorite(d.preset, d.geometry); + // dragged out of the top bar, remove + if (d.isFavorite()) { + context.presets().toggleFavorite(d.preset, d.geometry); + } else if (d.isRecent()) { + context.presets().removeRecent(d.preset, d.geometry); + } } else if (targetIndex !== null) { // dragged to a new position, reorder - context.presets().moveFavorite(index, targetIndex); + if (d.isFavorite()) { + context.presets().moveFavorite(index, targetIndex); + } else if (d.isRecent()) { + //context.presets().moveRecent(index - favoritesCount, targetIndex - favoritesCount); + } } }) );