mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-12 16:52:50 +00:00
689 lines
20 KiB
JavaScript
689 lines
20 KiB
JavaScript
import { dispatch as d3_dispatch } from 'd3-dispatch';
|
|
|
|
import { prefs } from '../core/preferences';
|
|
import { fileFetcher } from '../core/file_fetcher';
|
|
import { locationManager } from '../core/LocationManager';
|
|
|
|
import { osmNodeGeometriesForTags, osmSetAreaKeys, osmSetLineTags, osmSetPointTags, osmSetVertexTags } from '../osm/tags';
|
|
import { presetCategory } from './category';
|
|
import { presetCollection } from './collection';
|
|
import { presetField } from './field';
|
|
import { presetPreset } from './preset';
|
|
import { utilArrayUniq, utilRebind } from '../util';
|
|
|
|
export { presetCategory };
|
|
export { presetCollection };
|
|
export { presetField };
|
|
export { presetPreset };
|
|
|
|
let _mainPresetIndex = presetIndex(); // singleton
|
|
export { _mainPresetIndex as presetManager };
|
|
|
|
//
|
|
// `presetIndex` wraps a `presetCollection`
|
|
// with methods for loading new data and returning defaults
|
|
//
|
|
export function presetIndex() {
|
|
const dispatch = d3_dispatch('favoritePreset', 'recentsChange');
|
|
const MAXRECENTS = 30;
|
|
|
|
// seed the preset lists with geometry fallbacks
|
|
const POINT = presetPreset('point', { name: 'Point', tags: {}, geometry: ['point', 'vertex'], matchScore: 0.1 } );
|
|
const LINE = presetPreset('line', { name: 'Line', tags: {}, geometry: ['line'], matchScore: 0.1 } );
|
|
const AREA = presetPreset('area', { name: 'Area', tags: { area: 'yes' }, geometry: ['area'], matchScore: 0.1 } );
|
|
const RELATION = presetPreset('relation', { name: 'Relation', tags: {}, geometry: ['relation'], matchScore: 0.1 } );
|
|
|
|
let _this = presetCollection([POINT, LINE, AREA, RELATION]);
|
|
let _presets = { point: POINT, line: LINE, area: AREA, relation: RELATION };
|
|
|
|
let _defaults = {
|
|
point: presetCollection([POINT]),
|
|
vertex: presetCollection([POINT]),
|
|
line: presetCollection([LINE]),
|
|
area: presetCollection([AREA]),
|
|
relation: presetCollection([RELATION])
|
|
};
|
|
|
|
let _fields = {};
|
|
let _categories = {};
|
|
let _universal = [];
|
|
let _addablePresetIDs = null; // Set of preset IDs that the user can add
|
|
let _recents;
|
|
let _favorites;
|
|
|
|
// Index of presets by (geometry, tag key).
|
|
let _geometryIndex = { point: {}, vertex: {}, line: {}, area: {}, relation: {} };
|
|
let _loadPromise;
|
|
|
|
|
|
/** @param {boolean=} bypassCache - used by unit tests */
|
|
_this.ensureLoaded = (bypassCache) => {
|
|
if (_loadPromise && !bypassCache) return _loadPromise;
|
|
|
|
return _loadPromise = Promise.all([
|
|
fileFetcher.get('preset_categories'),
|
|
fileFetcher.get('preset_defaults'),
|
|
fileFetcher.get('preset_presets'),
|
|
fileFetcher.get('preset_fields')
|
|
])
|
|
.then(vals => {
|
|
_this.merge({
|
|
categories: vals[0],
|
|
defaults: vals[1],
|
|
presets: vals[2],
|
|
fields: vals[3]
|
|
});
|
|
osmSetAreaKeys(_this.areaKeys());
|
|
osmSetLineTags(_this.lineTags());
|
|
osmSetPointTags(_this.pointTags());
|
|
osmSetVertexTags(_this.vertexTags());
|
|
});
|
|
};
|
|
|
|
|
|
// `merge` accepts an object containing new preset data (all properties optional):
|
|
// {
|
|
// fields: {},
|
|
// presets: {},
|
|
// categories: {},
|
|
// defaults: {},
|
|
// featureCollection: {}
|
|
//}
|
|
_this.merge = (d) => {
|
|
let newLocationSets = [];
|
|
|
|
// Merge Fields
|
|
if (d.fields) {
|
|
Object.keys(d.fields).forEach(fieldID => {
|
|
let f = d.fields[fieldID];
|
|
|
|
if (f) { // add or replace
|
|
f = presetField(fieldID, f, _fields);
|
|
if (f.locationSet) newLocationSets.push(f);
|
|
_fields[fieldID] = f;
|
|
|
|
} else { // remove
|
|
delete _fields[fieldID];
|
|
}
|
|
});
|
|
}
|
|
|
|
// Merge Presets
|
|
if (d.presets) {
|
|
Object.keys(d.presets).forEach(presetID => {
|
|
let p = d.presets[presetID];
|
|
|
|
if (p) { // add or replace
|
|
const isAddable = !_addablePresetIDs || _addablePresetIDs.has(presetID);
|
|
p = presetPreset(presetID, p, isAddable, _fields, _presets);
|
|
if (p.locationSet) newLocationSets.push(p);
|
|
_presets[presetID] = p;
|
|
|
|
} else { // remove (but not if it's a fallback)
|
|
const existing = _presets[presetID];
|
|
if (existing && !existing.isFallback()) {
|
|
delete _presets[presetID];
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Merge Categories
|
|
if (d.categories) {
|
|
Object.keys(d.categories).forEach(categoryID => {
|
|
let c = d.categories[categoryID];
|
|
|
|
if (c) { // add or replace
|
|
c = presetCategory(categoryID, c, _presets);
|
|
if (c.locationSet) newLocationSets.push(c);
|
|
_categories[categoryID] = c;
|
|
|
|
} else { // remove
|
|
delete _categories[categoryID];
|
|
}
|
|
});
|
|
}
|
|
|
|
// Rebuild _this.collection after changing presets and categories
|
|
_this.collection = Object.values(_presets).concat(Object.values(_categories));
|
|
|
|
// Merge Defaults
|
|
if (d.defaults) {
|
|
Object.keys(d.defaults).forEach(geometry => {
|
|
const def = d.defaults[geometry];
|
|
if (Array.isArray(def)) { // add or replace
|
|
_defaults[geometry] = presetCollection(
|
|
def.map(id => _presets[id] || _categories[id]).filter(Boolean)
|
|
);
|
|
} else { // remove
|
|
delete _defaults[geometry];
|
|
}
|
|
});
|
|
}
|
|
|
|
// Rebuild universal fields array
|
|
_universal = Object.values(_fields).filter(field => field.universal);
|
|
|
|
// Rebuild geometry index
|
|
_geometryIndex = { point: {}, vertex: {}, line: {}, area: {}, relation: {} };
|
|
_this.collection.forEach(preset => {
|
|
(preset.geometry || []).forEach(geometry => {
|
|
let g = _geometryIndex[geometry];
|
|
for (let key in preset.tags) {
|
|
g[key] = g[key] || {};
|
|
let value = preset.tags[key];
|
|
(g[key][value] = g[key][value] || []).push(preset);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Merge Custom Features
|
|
if (d.featureCollection && Array.isArray(d.featureCollection.features)) {
|
|
locationManager.mergeCustomGeoJSON(d.featureCollection);
|
|
}
|
|
|
|
// Resolve all locationSet features.
|
|
if (newLocationSets.length) {
|
|
locationManager.mergeLocationSets(newLocationSets);
|
|
}
|
|
|
|
return _this;
|
|
};
|
|
|
|
|
|
_this.match = (entity, resolver) => {
|
|
return resolver.transient(entity, 'presetMatch', () => {
|
|
let geometry = entity.geometry(resolver);
|
|
// Treat entities on addr:interpolation lines as points, not vertices - #3241
|
|
if (geometry === 'vertex' && entity.isOnAddressLine(resolver)) {
|
|
geometry = 'point';
|
|
}
|
|
const entityExtent = entity.extent(resolver);
|
|
return _this.matchTags(entity.tags, geometry, entityExtent.center());
|
|
});
|
|
};
|
|
|
|
|
|
_this.matchTags = (tags, geometry, loc) => {
|
|
const keyIndex = _geometryIndex[geometry];
|
|
let bestScore = -1;
|
|
let bestMatch;
|
|
let matchCandidates = [];
|
|
|
|
for (let k in tags) {
|
|
let indexMatches = [];
|
|
|
|
let valueIndex = keyIndex[k];
|
|
if (!valueIndex) continue;
|
|
|
|
let keyValueMatches = valueIndex[tags[k]];
|
|
if (keyValueMatches) indexMatches.push(...keyValueMatches);
|
|
let keyStarMatches = valueIndex['*'];
|
|
if (keyStarMatches) indexMatches.push(...keyStarMatches);
|
|
|
|
if (indexMatches.length === 0) continue;
|
|
|
|
for (let i = 0; i < indexMatches.length; i++) {
|
|
const candidate = indexMatches[i];
|
|
const score = candidate.matchScore(tags);
|
|
|
|
if (score === -1){
|
|
continue;
|
|
}
|
|
matchCandidates.push({score, candidate});
|
|
|
|
if (score > bestScore) {
|
|
bestScore = score;
|
|
bestMatch = candidate;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bestMatch && bestMatch.locationSetID && bestMatch.locationSetID !== '+[Q2]' && Array.isArray(loc)){
|
|
const validHere = locationManager.locationSetsAt(loc);
|
|
if (!validHere[bestMatch.locationSetID]) {
|
|
matchCandidates.sort((a, b) => (a.score < b.score) ? 1 : -1);
|
|
for (let i = 0; i < matchCandidates.length; i++) {
|
|
const candidateScore = matchCandidates[i];
|
|
if (!candidateScore.candidate.locationSetID || validHere[candidateScore.candidate.locationSetID]) {
|
|
bestMatch = candidateScore.candidate;
|
|
bestScore = candidateScore.score;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If any part of an address is present, allow fallback to "Address" preset - #4353
|
|
if (!bestMatch || bestMatch.isFallback()) {
|
|
for (let k in tags){
|
|
if (/^addr:/.test(k) && keyIndex['addr:*'] && keyIndex['addr:*']['*']) {
|
|
bestMatch = keyIndex['addr:*']['*'][0];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bestMatch || _this.fallback(geometry);
|
|
};
|
|
|
|
_this.allowsVertex = (entity, resolver) => {
|
|
if (entity.type !== 'node') return false;
|
|
if (Object.keys(entity.tags).length === 0) return true;
|
|
|
|
return resolver.transient(entity, 'vertexMatch', () => {
|
|
// address lines allow vertices to act as standalone points
|
|
if (entity.isOnAddressLine(resolver)) return true;
|
|
|
|
const geometries = osmNodeGeometriesForTags(entity.tags);
|
|
if (geometries.vertex) return true;
|
|
if (geometries.point) return false;
|
|
// allow vertices for unspecified points
|
|
return true;
|
|
});
|
|
};
|
|
|
|
|
|
// Because of the open nature of tagging, iD will never have a complete
|
|
// list of tags used in OSM, so we want it to have logic like "assume
|
|
// that a closed way with an amenity tag is an area, unless the amenity
|
|
// is one of these specific types". This function computes a structure
|
|
// that allows testing of such conditions, based on the presets designated
|
|
// as as supporting (or not supporting) the area geometry.
|
|
//
|
|
// The returned object L is a keeplist/discardlist of tags. A closed way
|
|
// with a tag (k, v) is considered to be an area if `k in L && !(v in L[k])`
|
|
// (see `Way#isArea()`). In other words, the keys of L form the keeplist,
|
|
// and the subkeys form the discardlist.
|
|
_this.areaKeys = () => {
|
|
// The ignore list is for keys that imply lines. (We always add `area=yes` for exceptions)
|
|
const ignore = {
|
|
barrier: true,
|
|
highway: true,
|
|
footway: true,
|
|
railway: true,
|
|
junction: true,
|
|
type: true
|
|
};
|
|
let areaKeys = {};
|
|
|
|
// ignore name-suggestion-index and deprecated presets
|
|
const presets = _this.collection.filter(p => !p.suggestion && !p.replacement);
|
|
|
|
// keeplist
|
|
presets.forEach(p => {
|
|
const keys = p.tags && Object.keys(p.tags);
|
|
const key = keys && keys.length && keys[0]; // pick the first tag
|
|
if (!key) return;
|
|
if (ignore[key]) return;
|
|
|
|
if (p.geometry.indexOf('area') !== -1) { // probably an area..
|
|
areaKeys[key] = areaKeys[key] || {};
|
|
}
|
|
});
|
|
|
|
// discardlist
|
|
presets.forEach(p => {
|
|
let key;
|
|
for (key in p.addTags) {
|
|
// examine all addTags to get a better sense of what can be tagged on lines - #6800
|
|
const value = p.addTags[key];
|
|
if (key in areaKeys && // probably an area...
|
|
p.geometry.indexOf('line') !== -1 && // but sometimes a line
|
|
value !== '*') {
|
|
areaKeys[key][value] = true;
|
|
}
|
|
}
|
|
});
|
|
|
|
return areaKeys;
|
|
};
|
|
|
|
|
|
_this.lineTags = () => {
|
|
return _this.collection.filter((lineTags, d) => {
|
|
// ignore name-suggestion-index, deprecated, and generic presets
|
|
if (d.suggestion || d.replacement || d.searchable === false) return lineTags;
|
|
|
|
// only care about the primary tag
|
|
const keys = d.tags && Object.keys(d.tags);
|
|
const key = keys && keys.length && keys[0]; // pick the first tag
|
|
if (!key) return lineTags;
|
|
|
|
// if this can be a line
|
|
if (d.geometry.indexOf('line') !== -1) {
|
|
lineTags[key] = lineTags[key] || [];
|
|
lineTags[key].push(d.tags);
|
|
}
|
|
return lineTags;
|
|
}, {});
|
|
};
|
|
|
|
|
|
_this.pointTags = () => {
|
|
return _this.collection.reduce((pointTags, d) => {
|
|
// ignore name-suggestion-index, deprecated, and generic presets
|
|
if (d.suggestion || d.replacement || d.searchable === false) return pointTags;
|
|
|
|
// only care about the primary tag
|
|
const keys = d.tags && Object.keys(d.tags);
|
|
const key = keys && keys.length && keys[0]; // pick the first tag
|
|
if (!key) return pointTags;
|
|
|
|
// if this can be a point
|
|
if (d.geometry.indexOf('point') !== -1) {
|
|
pointTags[key] = pointTags[key] || {};
|
|
pointTags[key][d.tags[key]] = true;
|
|
}
|
|
return pointTags;
|
|
}, {});
|
|
};
|
|
|
|
|
|
_this.vertexTags = () => {
|
|
return _this.collection.reduce((vertexTags, d) => {
|
|
// ignore name-suggestion-index, deprecated, and generic presets
|
|
if (d.suggestion || d.replacement || d.searchable === false) return vertexTags;
|
|
|
|
// only care about the primary tag
|
|
const keys = d.tags && Object.keys(d.tags);
|
|
const key = keys && keys.length && keys[0]; // pick the first tag
|
|
if (!key) return vertexTags;
|
|
|
|
// if this can be a vertex
|
|
if (d.geometry.indexOf('vertex') !== -1) {
|
|
vertexTags[key] = vertexTags[key] || {};
|
|
vertexTags[key][d.tags[key]] = true;
|
|
}
|
|
return vertexTags;
|
|
}, {});
|
|
};
|
|
|
|
|
|
_this.field = (id) => _fields[id];
|
|
|
|
_this.universal = () => _universal;
|
|
|
|
|
|
_this.defaults = (geometry, n, startWithRecents, loc, extraPresets) => {
|
|
let recents = [];
|
|
if (startWithRecents) {
|
|
recents = _this.recent().matchGeometry(geometry).collection.slice(0, 4);
|
|
}
|
|
|
|
let defaults;
|
|
if (_addablePresetIDs) {
|
|
defaults = Array.from(_addablePresetIDs).map(function(id) {
|
|
var preset = _this.item(id);
|
|
if (preset && preset.matchGeometry(geometry)) return preset;
|
|
return null;
|
|
}).filter(Boolean);
|
|
} else {
|
|
defaults = _defaults[geometry].collection.concat(_this.fallback(geometry));
|
|
}
|
|
|
|
let result = presetCollection(
|
|
utilArrayUniq(recents.concat(defaults).concat(extraPresets || [])).slice(0, n - 1)
|
|
);
|
|
|
|
if (Array.isArray(loc)) {
|
|
const validHere = locationManager.locationSetsAt(loc);
|
|
result.collection = result.collection.filter(a => !a.locationSetID || validHere[a.locationSetID]);
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
// pass a Set of addable preset ids
|
|
_this.addablePresetIDs = function(val) {
|
|
if (!arguments.length) return _addablePresetIDs;
|
|
|
|
// accept and convert arrays
|
|
if (Array.isArray(val)) val = new Set(val);
|
|
|
|
_addablePresetIDs = val;
|
|
if (_addablePresetIDs) { // reset all presets
|
|
_this.collection.forEach(p => {
|
|
// categories aren't addable
|
|
if (p.addable) p.addable(_addablePresetIDs.has(p.id));
|
|
});
|
|
} else {
|
|
_this.collection.forEach(p => {
|
|
if (p.addable) p.addable(true);
|
|
});
|
|
}
|
|
|
|
return _this;
|
|
};
|
|
|
|
|
|
_this.recent = () => {
|
|
return presetCollection(
|
|
utilArrayUniq(_this.getRecents()
|
|
.map(d => d.preset)
|
|
.filter(d => d.searchable !== false))
|
|
);
|
|
};
|
|
|
|
|
|
function RibbonItem(preset, source) {
|
|
let item = {};
|
|
item.preset = preset;
|
|
item.source = source;
|
|
|
|
item.isFavorite = () => item.source === 'favorite';
|
|
item.isRecent = () => item.source === 'recent';
|
|
item.matches = (preset) => item.preset.id === preset.id;
|
|
item.minified = () => ({ pID: item.preset.id });
|
|
|
|
return item;
|
|
}
|
|
|
|
|
|
function ribbonItemForMinified(d, source) {
|
|
if (d && d.pID) {
|
|
const preset = _this.item(d.pID);
|
|
if (!preset) return null;
|
|
return RibbonItem(preset, source);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
|
|
_this.getGenericRibbonItems = () => {
|
|
return ['point', 'line', 'area'].map(id => RibbonItem(_this.item(id), 'generic'));
|
|
};
|
|
|
|
|
|
_this.getAddable = () => {
|
|
if (!_addablePresetIDs) return [];
|
|
|
|
return _addablePresetIDs.map((id) => {
|
|
const preset = _this.item(id);
|
|
if (preset) return RibbonItem(preset, 'addable');
|
|
return null;
|
|
}).filter(Boolean);
|
|
};
|
|
|
|
|
|
function setRecents(items) {
|
|
_recents = items;
|
|
const minifiedItems = items.map(d => d.minified());
|
|
prefs('preset_recents', JSON.stringify(minifiedItems));
|
|
dispatch.call('recentsChange');
|
|
}
|
|
|
|
|
|
_this.getRecents = () => {
|
|
if (!_recents) {
|
|
// fetch from local storage
|
|
_recents = (JSON.parse(prefs('preset_recents')) || [])
|
|
.reduce((acc, d) => {
|
|
let item = ribbonItemForMinified(d, 'recent');
|
|
if (item && item.preset.addable()) acc.push(item);
|
|
return acc;
|
|
}, []);
|
|
}
|
|
return _recents;
|
|
};
|
|
|
|
|
|
_this.addRecent = (preset, besidePreset, after) => {
|
|
const recents = _this.getRecents();
|
|
|
|
const beforeItem = _this.recentMatching(besidePreset);
|
|
let toIndex = recents.indexOf(beforeItem);
|
|
if (after) toIndex += 1;
|
|
|
|
const newItem = RibbonItem(preset, 'recent');
|
|
recents.splice(toIndex, 0, newItem);
|
|
setRecents(recents);
|
|
};
|
|
|
|
|
|
_this.removeRecent = (preset) => {
|
|
const item = _this.recentMatching(preset);
|
|
if (item) {
|
|
let items = _this.getRecents();
|
|
items.splice(items.indexOf(item), 1);
|
|
setRecents(items);
|
|
}
|
|
};
|
|
|
|
|
|
_this.recentMatching = (preset) => {
|
|
const items = _this.getRecents();
|
|
for (let i in items) {
|
|
if (items[i].matches(preset)) {
|
|
return items[i];
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
|
|
|
|
_this.moveItem = (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;
|
|
};
|
|
|
|
|
|
_this.moveRecent = (item, beforeItem) => {
|
|
const recents = _this.getRecents();
|
|
const fromIndex = recents.indexOf(item);
|
|
const toIndex = recents.indexOf(beforeItem);
|
|
const items = _this.moveItem(recents, fromIndex, toIndex);
|
|
if (items) setRecents(items);
|
|
};
|
|
|
|
|
|
_this.setMostRecent = (preset) => {
|
|
if (preset.searchable === false) return;
|
|
|
|
let items = _this.getRecents();
|
|
let item = _this.recentMatching(preset);
|
|
if (item) {
|
|
items.splice(items.indexOf(item), 1);
|
|
} else {
|
|
item = RibbonItem(preset, 'recent');
|
|
}
|
|
|
|
// remove the last recent (first in, first out)
|
|
while (items.length >= MAXRECENTS) {
|
|
items.pop();
|
|
}
|
|
|
|
// prepend array
|
|
items.unshift(item);
|
|
setRecents(items);
|
|
};
|
|
|
|
function setFavorites(items) {
|
|
_favorites = items;
|
|
const minifiedItems = items.map(d => d.minified());
|
|
prefs('preset_favorites', JSON.stringify(minifiedItems));
|
|
|
|
// call update
|
|
dispatch.call('favoritePreset');
|
|
}
|
|
|
|
_this.addFavorite = (preset, besidePreset, after) => {
|
|
const favorites = _this.getFavorites();
|
|
|
|
const beforeItem = _this.favoriteMatching(besidePreset);
|
|
let toIndex = favorites.indexOf(beforeItem);
|
|
if (after) toIndex += 1;
|
|
|
|
const newItem = RibbonItem(preset, 'favorite');
|
|
favorites.splice(toIndex, 0, newItem);
|
|
setFavorites(favorites);
|
|
};
|
|
|
|
_this.toggleFavorite = (preset) => {
|
|
const favs = _this.getFavorites();
|
|
const favorite = _this.favoriteMatching(preset);
|
|
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, 'favorite'));
|
|
}
|
|
setFavorites(favs);
|
|
};
|
|
|
|
|
|
_this.removeFavorite = (preset) => {
|
|
const item = _this.favoriteMatching(preset);
|
|
if (item) {
|
|
const items = _this.getFavorites();
|
|
items.splice(items.indexOf(item), 1);
|
|
setFavorites(items);
|
|
}
|
|
};
|
|
|
|
|
|
_this.getFavorites = () => {
|
|
if (!_favorites) {
|
|
|
|
// fetch from local storage
|
|
let rawFavorites = JSON.parse(prefs('preset_favorites'));
|
|
|
|
if (!rawFavorites) {
|
|
rawFavorites = [];
|
|
prefs('preset_favorites', JSON.stringify(rawFavorites));
|
|
}
|
|
|
|
_favorites = rawFavorites.reduce((output, d) => {
|
|
const item = ribbonItemForMinified(d, 'favorite');
|
|
if (item && item.preset.addable()) output.push(item);
|
|
return output;
|
|
}, []);
|
|
}
|
|
return _favorites;
|
|
};
|
|
|
|
|
|
_this.favoriteMatching = (preset) => {
|
|
const favs = _this.getFavorites();
|
|
for (let index in favs) {
|
|
if (favs[index].matches(preset)) {
|
|
return favs[index];
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
|
|
|
|
return utilRebind(_this, dispatch, 'on');
|
|
}
|