Use algolia for search

This commit is contained in:
Jordan Eldredge 2018-11-29 12:43:45 -08:00
parent 180827e27c
commit f2a544e64c
6 changed files with 76 additions and 11 deletions

View file

@ -7,6 +7,7 @@ import Skin from "./Skin";
import FocusedSkin from "./FocusedSkin";
import * as Utils from "./utils";
import * as Selectors from "./redux/selectors";
import * as Actions from "./redux/actionCreators";
import { SKIN_WIDTH, SKIN_HEIGHT, SKIN_RATIO } from "./constants";
const OVERSCAN_ROWS_LEADING = 10;
@ -166,7 +167,7 @@ const mapDispatchToProps = dispatch => ({
dispatch({ type: "SELECT_SKIN", hash, position });
},
setSearchQuery(query) {
dispatch({ type: "SET_SEARCH_QUERY", query });
dispatch(Actions.searchQueryChanged(query));
}
});
export default connect(

27
src/algolia.js Normal file
View file

@ -0,0 +1,27 @@
import algoliasearch from "algoliasearch";
var client = algoliasearch("HQ9I5Z6IM5", "6466695ec3f624a5fccf46ec49680e51");
var index = client.initIndex("Skins");
export function search(query) {
return new Promise((resolve, reject) => {
index.search(
{
query,
attributes: ["objectID"],
attributesToHighlight: [],
hitsPerPage: 1000,
// https://www.algolia.com/doc/api-reference/api-parameters/typoTolerance/
// min: Retrieve records with the smallest number of typos.
typoTolerance: "min"
},
(err, content) => {
if (err != null) {
reject(err);
return;
}
resolve(content);
}
);
});
}

View file

@ -1,3 +1,7 @@
export function closeModal() {
return { type: "CLOSE_MODAL" };
}
export function searchQueryChanged(query) {
return { type: "SEARCH_QUERY_CHANGED", query };
}

26
src/redux/epics.js Normal file
View file

@ -0,0 +1,26 @@
import { combineEpics } from "redux-observable";
import { of, from } from "rxjs";
import { filter, switchMap, map } from "rxjs/operators";
import { search } from "../algolia";
const searchEpic = actions =>
actions.pipe(
filter(action => action.type === "SEARCH_QUERY_CHANGED"),
switchMap(({ query }) => {
if (query.length === 0) {
return of({
type: "GOT_NEW_MATCHING_HASHES",
matchingHashes: null
});
}
return from(search(query)).pipe(
map(content => {
const matchingHashes = new Set(content.hits.map(hit => hit.objectID));
return { type: "GOT_NEW_MATCHING_HASHES", matchingHashes };
})
);
})
);
export default combineEpics(searchEpic);

View file

@ -22,14 +22,10 @@ export function getSearchQuery(state) {
export function getMatchingSkinHashes(state) {
const hashes = Object.keys(skins);
const searchQuery = getSearchQuery(state);
if (searchQuery == null) {
if (searchQuery == null || state.matchingHashes == null) {
return hashes;
}
const normalizedSearchQuery = searchQuery.toLowerCase();
return hashes.filter(hash => {
const { fileName } = skins[hash];
return fileName.toLowerCase().includes(normalizedSearchQuery);
});
return hashes.filter(hash => state.matchingHashes.has(hash));
}
export function getUrl(state) {

View file

@ -1,10 +1,13 @@
import { createStore as createReduxStore } from "redux";
import { createStore as createReduxStore, applyMiddleware } from "redux";
import { createEpicMiddleware } from "redux-observable";
import * as Selectors from "./selectors";
import rootEpic from "./epics";
const defaultState = {
searchQuery: null,
selectedSkinHash: null,
selectedSkinPosition: null
selectedSkinPosition: null,
matchingHashes: null
};
function reducer(state = defaultState, action) {
@ -35,17 +38,25 @@ function reducer(state = defaultState, action) {
};
}
return { ...defaultState, searchQuery };
case "SET_SEARCH_QUERY":
case "SEARCH_QUERY_CHANGED":
return {
...state,
searchQuery: action.query
};
case "GOT_NEW_MATCHING_HASHES":
return {
...state,
matchingHashes: action.matchingHashes
};
default:
return state;
}
}
export function createStore() {
const store = createReduxStore(reducer);
const epicMiddleware = createEpicMiddleware();
const store = createReduxStore(reducer, applyMiddleware(epicMiddleware));
epicMiddleware.run(rootEpic);
let lastUrl = null;
store.subscribe(() => {
const state = store.getState();