atriusmaps-node-sdk
Version:
This project provides an API to Atrius Personal Wayfinder maps within a Node environment. See the README.md for more information
249 lines (223 loc) • 9.06 kB
JavaScript
;
var R = require('ramda');
var Zousan = require('zousan');
var configUtils = require('../../../src/utils/configUtils.js');
var rand = require('../../../src/utils/rand.js');
var poiSearch = require('./poiSearch.js');
var searchTypeahead = require('./searchTypeahead.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var R__namespace = /*#__PURE__*/_interopNamespaceDefault(R);
function create (app, config) {
const state = {
poiSearch: null,
typeahead: null,
indexesCreated: new Zousan(), // re-initialize this when changing venues
defaultSearchTerms: null,
specialQueryTerms: {}
};
const init = async () => {
const pois = await app.bus.get('poi/getAll');
state.poiSearch = poiSearch(pois, app.i18n().language);
state.typeahead = searchTypeahead(pois, state.poiSearch.search, app.i18n().language);
state.defaultSearchTerms = configUtils.getLocalized(config, 'defaultSearchTerms', app.i18n().language);
state.indexesCreated.resolve();
};
/**
* Prepares and send an event with up to 50 non-portal POIs sorted by distance to user location.
*/
// todo check if async events (events which send a new event as a result) are still needed
app.bus.on('search/queryNearby', async () => {
const pois = await searchNearby();
app.bus.send('search/showNearby', { pois, term: 'Nearby' });
return pois
});
/**
* Returns max 50 non-portal POIs sorted by distance to user location.
* @returns Array.<POI>
*/
app.bus.on('search/queryNearbyAsync', searchNearby);
async function searchNearby () {
const startLocation = await app.bus.getFirst('user/getPhysicalLocation');
if (!startLocation?.floorId) return []
const poisSameFloor = await app.bus.get('poi/getByFloorId', { floorId: startLocation?.floorId });
const isNotPortal = poi => poi.category.indexOf('portal') === -1 && poi.category !== 'element.door';
const noPortalPois = Object.values(R__namespace.pickBy(isNotPortal, poisSameFloor));
const poisWithDistance = await app.bus.get('wayfinder/addPathTimeMultiple', { pois: noPortalPois, startLocation });
return R__namespace.sortBy(R__namespace.prop('distance'), Object.values(poisWithDistance)).slice(0, 50)
}
/**
* Searches for POIs with search term or category name
* and sends a new event 'search/showCategory' with search result POIs.
*
* @param {string} searchTerm - term to search POIs
* @param {string} category - category name, fallback term to search POIs
* @param {string} categoryName - label to display in search results input view
*/
app.bus.on('search/queryCategory', async ({ category, categoryName, searchTerm }) => {
const pois = await state.indexesCreated.then(() =>
state.poiSearch.search({ query: searchTerm || category }));
app.bus.send('search/showCategory', { pois, category, categoryName });
return pois
});
/**
* Searches for POIs with search term
* and sends a new event 'search/showCategory' with search result POIs.
*
* @param {string} term - search term
*/
// todo introduce consistent naming for parameters are return values (searchTem or term, pois or results, keywords or searchTerms)
app.bus.on('search/query', ({ term }) => {
return state.indexesCreated.then(() => {
const pois = state.poiSearch.search({ query: term });
app.bus.send('search/showSearchResults', { results: pois, term });
return pois
})
});
/**
* Search for POIs with search term.
*
* @param {string} term - search term
* @returns Array.<POI>
*/
app.bus.on('search/queryAsync', ({ term }) =>
state.indexesCreated.then(() => state.poiSearch.search({ query: term }))
);
/**
* If search term matches any registered special query term,
* then send an event with params for this special query,
* otherwise search for POIs with term and send the event 'search/query' with search result POIs
*
* @param {string} term - search term or special query
*/
app.bus.on('search/queryWithSpecial', ({ term }) => {
if (state.specialQueryTerms[term]) {
const { event, params } = state.specialQueryTerms[term];
// use "send" as we can't gaurentee this event is a "get" or even returns POIs
return app.bus.send(event, params)
} else {
return app.bus.get('search/query', { term })
}
});
/**
* Returns list of all localized default search terms declared in the configuration
* or list of up to 'limit' random unique POI categories as a fallback.
* Returns a list of keywords and sends an event 'search/showDefaultSearchKeywords' with same the list of keywords.
*
* @param {number} [limit=5] - Used to limit number of random unique categories list if default search terms are not defined in configuration.
* @returns Array.<string> - list of keywords
*/
app.bus.on('search/getDefaultSearchTerms', async ({ limit = 5 } = {}) => {
const defaultSearchTerms = state.defaultSearchTerms;
const hasDefaultSearchTerms = defaultSearchTerms && defaultSearchTerms.length;
const keywords = hasDefaultSearchTerms
? defaultSearchTerms
: await getUniqueRandomCategories(limit);
app.bus.send('search/showDefaultSearchKeywords', { keywords });
return keywords
});
async function getUniqueRandomCategories (limit) {
const allCategories = (await app.bus.send('poi/getAllCategories'))[0];
const uniqueCategories = Array.from(new Set(allCategories));
const shuffledUniqueCategories = rand.randomizeArray(uniqueCategories);
return shuffledUniqueCategories.slice(0, limit)
}
/**
* Returns list of up to 'limit' unique random navigable POIs.
*
* @param {number} [limit=5]
* @returns Array.<POI>
*/
app.bus.on('search/getDefaultSearchPois', async ({ limit = 5 } = {}) => {
const allPois = await app.bus.get('poi/getAll');
const navigablePred = (val, key) => val.isNavigable;
const navigablePois = R__namespace.pickBy(navigablePred, allPois);
return rand.arrayPick(Object.values(navigablePois), limit)
});
/**
* Registers an event and event params for special query term.
* Optionally adds passed special query term to keywords search index.
*
* @param {string} term - special query term
* @param {string} event - name of associated event
* @param {Object} params - parameters for associated event
* @param {boolean} addKeyword - indicates if term has to be added to keywords search index
*/
app.bus.on('search/registerSpecialQuery', ({ term, event, params, addKeyword = true }) => {
state.indexesCreated.then(() => {
if (addKeyword) {
state.typeahead.addKeyword(term);
}
state.specialQueryTerms[term] = { event, params };
});
});
/**
* Adds list of keywords to keywords search index.
*
* @param {Array.<string>} keywords - list of new keywords
*/
app.bus.on('search/addKeywords', ({ keywords }) =>
state.indexesCreated.then(() =>
keywords.forEach(keyword => state.typeahead.addKeyword(keyword))));
/**
* Returns lists of keywords and POIs matching search term.
*
* For term shorter than 3 characters only keywords index is queried.
* If no keywords are found or when term is longer,
* then POIs index is queried for the the remaining results.
*
* @param {string} term - search term
* @param {number} limit - maximum number of results
* @returns {{ pois: Array.<POI>, keywords: Array.<String>, term: string }} - list of keywords, list of POIs, search term
*/
app.bus.on('search/typeahead', ({ term, limit }) => state.indexesCreated.then(() => {
const { keywords, pois } = state.typeahead.query(term, limit);
return { keywords, pois, term }
}));
/**
* Resets plugin state.
*/
app.bus.on('venueData/loadNewVenue', () => {
state.indexesCreated = new Zousan();
init();
});
/**
* Updates POIs search index with dynamic Grab POIs.
*
* @param {string} plugin - type of dynamic data
* @param {Object<string, Object>} - dictionary of POI id to dynamic data object
*/
app.bus.on('poi/setDynamicData', async ({ plugin, idValuesMap }) => {
if (plugin !== 'grab') return
const dynamicPoisPromises = Object.keys(idValuesMap)
.map(id => app.bus.get('poi/getById', { id }));
return Promise.all(dynamicPoisPromises)
.then(pois =>
state.indexesCreated.then(() =>
state.poiSearch.updateMultiple(pois)))
});
const runTest = async (initialState, testRoutine) => {
// state = { ...initialState }
await testRoutine();
return state
};
return {
init,
runTest
}
}
exports.create = create;