From 213d0ee3e8052fadb184aa8cfb50a9074c842470 Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Thu, 7 Mar 2019 16:49:29 -0500 Subject: [PATCH] Move preset favoriting functions from core/context.js to presets/index.js Make recent presets persistent and compatible with favorite presets --- modules/core/context.js | 67 +------------- modules/presets/index.js | 169 ++++++++++++++++++++++++++++++---- modules/ui/modes.js | 31 +++---- modules/ui/preset_favorite.js | 6 +- modules/ui/preset_list.js | 2 +- modules/ui/search_add.js | 4 +- 6 files changed, 173 insertions(+), 106 deletions(-) diff --git a/modules/core/context.js b/modules/core/context.js index 7db177159..b20b2d1dc 100644 --- a/modules/core/context.js +++ b/modules/core/context.js @@ -57,7 +57,7 @@ export function coreContext() { addTranslation('en', dataEn); setLocale('en'); - var dispatch = d3_dispatch('enter', 'exit', 'change', 'favoritePreset'); + var dispatch = d3_dispatch('enter', 'exit', 'change'); // https://github.com/openstreetmap/iD/issues/772 // http://mathiasbynens.be/notes/localstorage-pattern#comment-9 @@ -313,69 +313,6 @@ export function coreContext() { var presets; context.presets = function() { return presets; }; - context.getFavoritePresets = function() { - // get favorites from local storage - var favs = JSON.parse(context.storage('favorite_presets')) || [ - // use the generic presets as the default favorites - { id: 'point', geom: 'point'}, - { id: 'line', geom: 'line'}, - { id: 'area', geom: 'area'} - ]; - return favs.filter(function(d) { - // iD's presets could have changed since this favorite was saved, - // so make sure it's still valid. - var preset = presets.item(d.id); - if (preset === null) { - return false; - } else if (preset.geometry.indexOf(d.geom) === -1) { - return false; - } - return true; - }); - }; - function setFavoritePresets(favs) { - context.storage('favorite_presets', JSON.stringify(favs)); - - //and call update on modes - dispatch.call('favoritePreset'); - } - context.favoritePreset = function(preset, geom) { - var favs = context.getFavoritePresets(); - - //add/remove favorites from local storage - if (context.isFavoritePreset(preset, geom)) { - favs = favs.filter(function(d) { - return !(d.id === preset.id && d.geom === geom); - }); - } else { - // only allow 10 favorites - if (favs.length === 10) { - // remove the last favorite (last in, first out) - favs.pop(); - } - // append array - favs.push({id: preset.id, geom: geom}); - } - setFavoritePresets(favs); - }; - - context.isFavoritePreset = function(preset, geom) { - var favs = context.getFavoritePresets(); - return favs.some(function(d) { - return d.id === preset.id && d.geom === geom; - }); - }; - context.moveFavoritePreset = function(fromIndex, toIndex) { - if (fromIndex === toIndex) return; - - var favs = context.getFavoritePresets(); - - if (fromIndex < 0 || toIndex < 0 || - fromIndex >= favs.length || toIndex >= favs.length) return; - - favs.splice(toIndex, 0, favs.splice(fromIndex, 1)[0]); - setFavoritePresets(favs); - }; /* Map */ var map; @@ -564,7 +501,7 @@ export function coreContext() { connection = services.osm; background = rendererBackground(context); features = rendererFeatures(context); - presets = presetIndex(); + presets = presetIndex(context); if (services.maprules && utilStringQs(window.location.hash).maprules) { var maprules = utilStringQs(window.location.hash).maprules; diff --git a/modules/presets/index.js b/modules/presets/index.js index e92039222..585e2cbe1 100644 --- a/modules/presets/index.js +++ b/modules/presets/index.js @@ -3,8 +3,8 @@ import _forEach from 'lodash-es/forEach'; import _isEmpty from 'lodash-es/isEmpty'; import _reject from 'lodash-es/reject'; import _uniq from 'lodash-es/uniq'; -import _uniqWith from 'lodash-es/uniqWith'; +import { dispatch as d3_dispatch } from 'd3-dispatch'; import { json as d3_json } from 'd3-request'; import { data } from '../../data/index'; @@ -12,6 +12,7 @@ import { presetCategory } from './category'; import { presetCollection } from './collection'; import { presetField } from './field'; import { presetPreset } from './preset'; +import { utilRebind } from '../util'; export { presetCategory }; export { presetCollection }; @@ -19,15 +20,17 @@ export { presetField }; export { presetPreset }; -export function presetIndex() { +export function presetIndex(context) { // a presetCollection with methods for // loading new data and returning defaults + var dispatch = d3_dispatch('favoritePreset'); + var all = presetCollection([]); var _defaults = { area: all, line: all, point: all, vertex: all, relation: all }; var _fields = {}; var _universal = []; - var _recentWithGeometry = []; + var _favorites, _recents; // Index of presets by (geometry, tag key). var _index = { @@ -214,7 +217,8 @@ export function presetIndex() { all.init = function() { all.collection = []; - _recentWithGeometry = []; + _favorites = null; + _recents = null; _fields = {}; _universal = []; _index = { point: {}, vertex: {}, line: {}, area: {}, relation: {} }; @@ -228,7 +232,8 @@ export function presetIndex() { _defaults = { area: all, line: all, point: all, vertex: all, relation: all }; _fields = {}; _universal = []; - _recentWithGeometry = []; + _favorites = null; + _recents = null; // Index of presets by (geometry, tag key). _index = { @@ -270,27 +275,153 @@ export function presetIndex() { }; all.recent = function() { - return presetCollection(_uniq(_recentWithGeometry.map(function(d) { + return presetCollection(_uniq(all.getRecents().map(function(d) { return d.preset; }))); }; - all.recentWithGeometry = function() { - return _recentWithGeometry; - }; + function RibbonItem(preset, geometry, source) { + var item = {}; + item.preset = preset; + item.geometry = geometry; + item.source = source; - all.choose = function(preset, geometry) { - if (preset.searchable !== false) { - var newWithGeometry = { - preset: preset, - geometry: geometry + item.matches = function(preset, geometry) { + return item.preset.id === preset.id && item.geometry === geometry; + }; + item.minified = function() { + return { + pID: item.preset.id, + geom: item.geometry }; - _recentWithGeometry = _uniqWith([newWithGeometry].concat(_recentWithGeometry), function(d1, d2) { - return d1.preset === d2.preset && d1.geometry === d2.geometry; - }); + }; + return item; + } + + function ribbonItemForMinified(d, source) { + if (d && d.pID && d.geom) { + var preset = all.item(d.pID); + // iD's presets could have changed since this was saved, + // so make sure it's still valid. + if (preset && preset.matchGeometry(d.geom)) { + return RibbonItem(preset, d.geom, source); + } } - return all; + return null; + } + + function setFavorites(items) { + _favorites = items; + var minifiedItems = items.map(function(d) { return d.minified(); }); + context.storage('preset_favorites', JSON.stringify(minifiedItems)); + + // call update + dispatch.call('favoritePreset'); + } + + all.getFavorites = function() { + if (!_favorites) { + // fetch from local storage + _favorites = (JSON.parse(context.storage('preset_favorites')) || [ + // use the generic presets as the default favorites + { pID: 'point', geom: 'point'}, + { pID: 'line', geom: 'line'}, + { pID: 'area', geom: 'area'} + ]).reduce(function(output, d) { + var item = ribbonItemForMinified(d, 'favorite'); + if (item) output.push(item); + return output; + }, []); + } + return _favorites; }; - return all; + function setRecents(items) { + _recents = items; + var minifiedItems = items.map(function(d) { return d.minified(); }); + context.storage('preset_recents', JSON.stringify(minifiedItems)); + } + + all.getRecents = function() { + if (!_recents) { + // fetch from local storage + _recents = (JSON.parse(context.storage('preset_recents')) || []) + .reduce(function(output, d) { + var item = ribbonItemForMinified(d, 'recent'); + if (item) output.push(item); + return output; + }, []); + } + return _recents; + }; + + all.toggleFavorite = function(preset, geometry) { + var favs = all.getFavorites(); + var favorite = all.isFavorite(preset, geometry); + if (favorite) { + favs.splice(favs.indexOf(favorite), 1); + } else { + // only allow 10 favorites + if (favs.length === 10) { + // remove the last favorite (last in, first out) + favs.pop(); + } + // append array + favs.push(RibbonItem(preset, geometry, 'favorite')); + } + setFavorites(favs); + }; + + all.isFavorite = function(preset, geometry) { + var favs = all.getFavorites(); + for (var index in favs) { + if (favs[index].matches(preset, geometry)) { + return favs[index]; + } + } + return false; + }; + all.isRecent = function(preset, geometry) { + var items = all.getRecents(); + for (var index in items) { + if (items[index].matches(preset, geometry)) { + return items[index]; + } + } + return false; + }; + + all.moveFavorite = function(fromIndex, toIndex) { + if (fromIndex === toIndex) return; + + 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.setMostRecent = function(preset, geometry) { + if (preset.searchable === false) return; + + var items = all.getRecents(); + var item = all.isRecent(preset, geometry); + if (item) { + items.splice(items.indexOf(item), 1); + } else { + item = RibbonItem(preset, geometry, 'recent'); + } + // allow 30 recents + if (items.length === 30) { + // remove the last favorite (first in, first out) + items.pop(); + } + // prepend array + items.unshift(item); + setRecents(items); + }; + + return utilRebind(all, dispatch, 'on'); } diff --git a/modules/ui/modes.js b/modules/ui/modes.js index 0f2147d3c..fb9957979 100644 --- a/modules/ui/modes.js +++ b/modules/ui/modes.js @@ -52,7 +52,7 @@ export function uiModes(context) { context .on('enter.modes', update) - .on('favoritePreset.modes', update); + .presets().on('favoritePreset.modes', update); update(); @@ -63,17 +63,16 @@ export function uiModes(context) { context.keybinding().off(i.toString()); } - var favoritePresets = context.getFavoritePresets(); + var favoritePresets = context.presets().getFavorites(); var favoriteModes = favoritePresets.map(function(d, index) { - var preset = context.presets().item(d.id); - var presetName = preset.name().split(' – ')[0]; - var markerClass = 'add-preset add-' + d.geom + ' add-preset-' + presetName.replace(/\s+/g, '_') - + '-' + d.geom; // replace spaces with underscores to avoid css interpretation - if (preset.isFallback()) { + 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 + if (d.preset.isFallback()) { markerClass += ' add-generic-preset'; } - var supportedGeometry = preset.geometry.filter(function(geometry) { + var supportedGeometry = d.preset.geometry.filter(function(geometry) { return ['vertex', 'point', 'line', 'area'].indexOf(geometry) !== -1; }); var vertexIndex = supportedGeometry.indexOf('vertex'); @@ -83,18 +82,18 @@ export function uiModes(context) { } var tooltipTitleID = 'modes.add_preset.title'; if (supportedGeometry.length !== 1) { - if (preset.setTags({}, d.geom).building) { + if (d.preset.setTags({}, d.geometry).building) { tooltipTitleID = 'modes.add_preset.building.title'; } else { - tooltipTitleID = 'modes.add_preset.' + d.geom + '.title'; + tooltipTitleID = 'modes.add_preset.' + d.geometry + '.title'; } } var favoriteMode = { button: markerClass, title: presetName, description: t(tooltipTitleID, { feature: '' + presetName + '' }), - preset: preset, - geometry: d.geom + preset: d.preset, + geometry: d.geometry }; var keyCode; if (textDirection === 'ltr') { @@ -117,7 +116,7 @@ export function uiModes(context) { } var mode; - switch (d.geom) { + switch (d.geometry) { case 'point': case 'vertex': mode = modeAddPoint(context, favoriteMode); @@ -169,7 +168,7 @@ export function uiModes(context) { context.enter(modeBrowse(context)); } else { if (d.preset) { - context.presets().choose(d.preset, d.geometry); + context.presets().setMostRecent(d.preset, d.geometry); } context.enter(d); } @@ -256,10 +255,10 @@ export function uiModes(context) { var y = d3_event.y - dragOrigin.y; if (y > 50) { // dragged out of the top bar, remove the favorite - context.favoritePreset(d.preset, d.geometry); + context.presets().toggleFavorite(d.preset, d.geometry); } else if (targetIndex !== null) { // dragged to a new position, reorder - context.moveFavoritePreset(index, targetIndex); + context.presets().moveFavorite(index, targetIndex); } }) ); diff --git a/modules/ui/preset_favorite.js b/modules/ui/preset_favorite.js index 2cc7b43b4..a0ae2bf5c 100644 --- a/modules/ui/preset_favorite.js +++ b/modules/ui/preset_favorite.js @@ -35,7 +35,7 @@ export function uiPresetFavorite(preset, geom, context, klass) { d3_event.stopPropagation(); d3_event.preventDefault(); - context.favoritePreset(preset, geom); + context.presets().toggleFavorite(preset, geom); update(); }); @@ -45,10 +45,10 @@ export function uiPresetFavorite(preset, geom, context, klass) { function update() { _button - .classed('active', context.isFavoritePreset(preset, geom)); + .classed('active', context.presets().isFavorite(preset, geom)); } - context.on('favoritePreset.button-' + preset.id.replace(/[^a-zA-Z\d:]/g, '-') + '-' + geom, update); + context.presets().on('favoritePreset.button-' + preset.id.replace(/[^a-zA-Z\d:]/g, '-') + '-' + geom, update); return presetFavorite; } diff --git a/modules/ui/preset_list.js b/modules/ui/preset_list.js index 8e89c01ce..c15e32fcf 100644 --- a/modules/ui/preset_list.js +++ b/modules/ui/preset_list.js @@ -388,7 +388,7 @@ export function uiPresetList(context) { item.choose = function() { if (d3_select(this).classed('disabled')) return; - context.presets().choose(preset, context.geometry(_entityID)); + context.presets().setMostRecent(preset, context.geometry(_entityID)); context.perform( actionChangePreset(_entityID, _currentPreset, preset), t('operations.change_tags.annotation') diff --git a/modules/ui/search_add.js b/modules/ui/search_add.js index 27fe49094..b2509cba4 100644 --- a/modules/ui/search_add.js +++ b/modules/ui/search_add.js @@ -207,7 +207,7 @@ export function uiSearchAdd(context) { if (value.length) { results = presets.search(value, shownGeometry).collection; } else { - var recents = context.presets().recentWithGeometry(); + var recents = context.presets().getRecents(); recents = recents.filter(function(d) { return shownGeometry.indexOf(d.geometry) !== -1; }); @@ -504,7 +504,7 @@ export function uiSearchAdd(context) { mode = modeAddArea(context, modeInfo); } search.node().blur(); - context.presets().choose(preset, geometry); + context.presets().setMostRecent(preset, geometry); context.enter(mode); }; return item;