diff --git a/.eslintrc b/.eslintrc index 7cd2c9c..46e1265 100644 --- a/.eslintrc +++ b/.eslintrc @@ -12,6 +12,10 @@ "eslint:recommended", "plugin:react/recommended" ], + "globals": { + "Position": false, + "navigator": false + }, "rules": { // overrides "react/display-name": 1 diff --git a/js/apis/FoodItemsApi.js b/js/apis/FoodItemsApi.js new file mode 100644 index 0000000..80cc879 --- /dev/null +++ b/js/apis/FoodItemsApi.js @@ -0,0 +1,111 @@ +// @flow + +// TODO open a websoket and create observable from it +const DUMMY_DATA = [ + { + id: 1, + name: 'Big John Cookies', + placeId: 'ChIJf5QJFBK1RIYRjjfxZz9Z0O0', + latitude: 30.270667599999996, + longitude: -97.7532464, + distance: 0.5, + quantity: 'alot', + category: 'desserts', + images: [ + 'https://s-media-cache-ak0.pinimg.com/564x/e7/5f/08/e75f08b00c0bc7f2b01b3d1a636389f6.jpg', + 'http://images.media-allrecipes.com/userphotos/560x315/1107530.jpg', + ], + thumbImage: 'http://images.media-allrecipes.com/userphotos/560x315/1107530.jpg', + }, + { + id: 2, + name: 'Jelly Filled Donuts', + placeId: 'ChIJ72if-Qe1RIYRCzMucGEEdBA', + latitude: 30.263963300000004, + longitude: -97.742308, + distance: 1.0, + quantity: 'few', + category: 'desserts', + images: ['https://timenewsfeed.files.wordpress.com/2013/06/jellydonut.jpg'], + thumbImage: 'https://timenewsfeed.files.wordpress.com/2013/06/jellydonut.jpg', + }, + { + id: 3, + name: 'Spelt Brownies', + placeId: 'ChIJG44vBQi1RIYRvWGHdYUolZY', + latitude: 30.265019100000004, + longitude: -97.7419085, + distance: 1.7, + quantity: 'few', + category: 'desserts', + images: ['http://www.momshealthyeats.com/wp-content/uploads/2011/11/spelt-fudge-brownie.jpg'], + thumbImage: 'http://www.momshealthyeats.com/wp-content/uploads/2011/11/spelt-fudge-brownie.jpg', + }, + { + id: 4, + name: 'Oatmeal Raisin Cookies', + placeId: 'ChIJf5QJFBK1RIYRjjfxZz9Z0O0', + latitude: 30.270667599999996, + longitude: -97.7532464, + distance: 0.5, + quantity: 'few', + category: 'desserts', + images: ['http://cookingontheside.com/wp-content/uploads/2009/04/oatmeal_raisin_cookies_stack-400.jpg'], + thumbImage: 'http://cookingontheside.com/wp-content/uploads/2009/04/oatmeal_raisin_cookies_stack-400.jpg', + }, + { + id: 5, + name: 'Donuts with Sprinkles', + placeId: 'ChIJ72if-Qe1RIYRCzMucGEEdBA', + latitude: 30.263963300000004, + longitude: -97.742308, + distance: 1.0, + quantity: 'few', + category: 'desserts', + images: ['https://dannivee.files.wordpress.com/2013/10/img_1950.jpg'], + thumbImage: 'https://dannivee.files.wordpress.com/2013/10/img_1950.jpg', + }, + { + id: 6, + name: 'Powdered Donuts', + placeId: 'ChIJ72if-Qe1RIYRCzMucGEEdBA', + latitude: 30.263963300000004, + longitude: -97.742308, + distance: 1.0, + quantity: 'few', + category: 'desserts', + images: [ + 'http://3.bp.blogspot.com/-NUKSXr1qLHs/UQmsaEFgbTI/AAAAAAAAA_Y/l4psfVl4a5A/s1600/white-powdered-sugar-doughnuts-tracie-kaska.jpg', + ], + thumbImage: + 'http://3.bp.blogspot.com/-NUKSXr1qLHs/UQmsaEFgbTI/AAAAAAAAA_Y/l4psfVl4a5A/s1600/white-powdered-sugar-doughnuts-tracie-kaska.jpg', + }, + { + id: 7, + name: 'Snickerdoodles', + placeId: 'ChIJr3szW6a1RIYRkM7LRpnBIO0', + latitude: 30.266898599999998, + longitude: -97.73798459999999, + quantity: 'few', + category: 'desserts', + images: ['http://josephcphillips.com/wp-content/uploads/2015/02/snickerdoodles2.jpg'], + thumbImage: 'http://josephcphillips.com/wp-content/uploads/2015/02/snickerdoodles2.jpg', + }, + { + id: 8, + name: 'Pizza', + placeId: 'ChIJr3szW6a1RIYRkM7LRpnBIO0', + latitude: 30.266898599999998, + longitude: -97.73798459999999, + quantity: 'few', + category: 'entrees', + images: ['http://www.foodandhealth.co.uk/wp-content/uploads/2016/05/Hot-stone-Vegan-Pizza.jpg'], + thumbImage: 'http://www.foodandhealth.co.uk/wp-content/uploads/2016/05/Hot-stone-Vegan-Pizza.jpg', + }, +]; + +/* eslint-disable no-unused-vars */ +export const getFoodItemsForLocation = (location: Location) => { + return Promise.resolve(DUMMY_DATA); +}; +/* eslint-enable no-unused-vars */ diff --git a/js/apis/PlaceDetailsApi.js b/js/apis/PlaceDetailsApi.js index 9a1cef2..2ed30da 100644 --- a/js/apis/PlaceDetailsApi.js +++ b/js/apis/PlaceDetailsApi.js @@ -1,16 +1,20 @@ // @flow +import { type GooglePlaceObj } from '../records/PlaceRecord'; + const apiKey = 'AIzaSyBfMm1y6JayCbXrQmgAG1R3ka4ZOJno_5E'; const placesUrl = `https://maps.googleapis.com/maps/api/place/details/json?key=${apiKey}`; const photosUrl = `https://maps.googleapis.com/maps/api/place/photo?key=${apiKey}`; -export const getPlaceDetails = async (placeid: ?string) => { +type GooglePlaceDetailsResponse = { error_message: ?string, result: GooglePlaceObj }; + +export const getPlaceDetails = async (placeid: ?string): Promise => { if (!placeid || typeof placeid !== 'string') { throw new Error('placeid looks wrong'); } const res = await fetch(`${placesUrl}&placeid=${placeid}`); - const { error_message, result } = await res.json(); + const { error_message, result }: GooglePlaceDetailsResponse = await res.json(); if (error_message) { throw new Error(error_message); diff --git a/js/helpers/ImmutableHelpers.js b/js/helpers/ImmutableHelpers.js index a0bc4dd..ca87f20 100644 --- a/js/helpers/ImmutableHelpers.js +++ b/js/helpers/ImmutableHelpers.js @@ -3,8 +3,8 @@ import { type List, type Map } from 'immutable'; export const pushInto = (list: List, item: T): List => list.push(item); -export const setById = (map: Map, item: { id: string }): Map => { - if (!item.id) { +export const setById = (map: Map, item: ?{ id?: string }): Map => { + if (!item || !item.id) { return map; } return map.set(item.id, item); diff --git a/js/records/PlaceRecord.js b/js/records/PlaceRecord.js index c0064b0..9ee47db 100644 --- a/js/records/PlaceRecord.js +++ b/js/records/PlaceRecord.js @@ -76,7 +76,7 @@ const FoodRecordDefaults: Place = { const PlaceRecord = Record(FoodRecordDefaults, 'PlaceRecord'); -const getPhotos = pathOr([], ['photos']); +const getPhotos = pathOr([{}], ['photos']); const getPhotoRef = photo => photo.photo_reference || ''; @@ -85,8 +85,12 @@ const getThumb = pipe(getPhotos, head, getPhotoRef, getURLForPhotoReference({ ma const getPhotoUrls = pipe(getPhotos, map(getPhotoRef), map(getURLForPhotoReference({ maxheight: 600 }))); export const buildPlaceRecord = memoizeWith( - (place: GooglePlaceObj) => place.place_id, - (place: GooglePlaceObj) => { + (place: ?GooglePlaceObj) => place && place.place_id, + (place: ?GooglePlaceObj) => { + if (!place) { + return; + } + const { place_id, name, diff --git a/js/streams/FoodItemsStream.js b/js/streams/FoodItemsStream.js index 4f927df..73eb0de 100644 --- a/js/streams/FoodItemsStream.js +++ b/js/streams/FoodItemsStream.js @@ -1,113 +1,10 @@ //@flow import { createFoodItem } from '../records/FoodItemRecord'; -import { Observable } from 'rxjs'; import { setById } from '../helpers/ImmutableHelpers'; import { Map } from 'immutable'; +import location$ from './LocationStream'; +import { getFoodItemsForLocation } from '../apis/FoodItemsApi'; -// TODO open a websoket and create observable from it -const DUMMY_DATA = [ - { - id: 1, - name: 'Big John Cookies', - placeId: 'ChIJf5QJFBK1RIYRjjfxZz9Z0O0', - latitude: 30.270667599999996, - longitude: -97.7532464, - distance: 0.5, - quantity: 'alot', - category: 'desserts', - images: [ - 'https://s-media-cache-ak0.pinimg.com/564x/e7/5f/08/e75f08b00c0bc7f2b01b3d1a636389f6.jpg', - 'http://images.media-allrecipes.com/userphotos/560x315/1107530.jpg', - ], - thumbImage: 'http://images.media-allrecipes.com/userphotos/560x315/1107530.jpg', - }, - { - id: 2, - name: 'Jelly Filled Donuts', - placeId: 'ChIJ72if-Qe1RIYRCzMucGEEdBA', - latitude: 30.263963300000004, - longitude: -97.742308, - distance: 1.0, - quantity: 'few', - category: 'desserts', - images: ['https://timenewsfeed.files.wordpress.com/2013/06/jellydonut.jpg'], - thumbImage: 'https://timenewsfeed.files.wordpress.com/2013/06/jellydonut.jpg', - }, - { - id: 3, - name: 'Spelt Brownies', - placeId: 'ChIJG44vBQi1RIYRvWGHdYUolZY', - latitude: 30.265019100000004, - longitude: -97.7419085, - distance: 1.7, - quantity: 'few', - category: 'desserts', - images: ['http://www.momshealthyeats.com/wp-content/uploads/2011/11/spelt-fudge-brownie.jpg'], - thumbImage: 'http://www.momshealthyeats.com/wp-content/uploads/2011/11/spelt-fudge-brownie.jpg', - }, - { - id: 4, - name: 'Oatmeal Raisin Cookies', - placeId: 'ChIJf5QJFBK1RIYRjjfxZz9Z0O0', - latitude: 30.270667599999996, - longitude: -97.7532464, - distance: 0.5, - quantity: 'few', - category: 'desserts', - images: ['http://cookingontheside.com/wp-content/uploads/2009/04/oatmeal_raisin_cookies_stack-400.jpg'], - thumbImage: 'http://cookingontheside.com/wp-content/uploads/2009/04/oatmeal_raisin_cookies_stack-400.jpg', - }, - { - id: 5, - name: 'Donuts with Sprinkles', - placeId: 'ChIJ72if-Qe1RIYRCzMucGEEdBA', - latitude: 30.263963300000004, - longitude: -97.742308, - distance: 1.0, - quantity: 'few', - category: 'desserts', - images: ['https://dannivee.files.wordpress.com/2013/10/img_1950.jpg'], - thumbImage: 'https://dannivee.files.wordpress.com/2013/10/img_1950.jpg', - }, - { - id: 6, - name: 'Powdered Donuts', - placeId: 'ChIJ72if-Qe1RIYRCzMucGEEdBA', - latitude: 30.263963300000004, - longitude: -97.742308, - distance: 1.0, - quantity: 'few', - category: 'desserts', - images: [ - 'http://3.bp.blogspot.com/-NUKSXr1qLHs/UQmsaEFgbTI/AAAAAAAAA_Y/l4psfVl4a5A/s1600/white-powdered-sugar-doughnuts-tracie-kaska.jpg', - ], - thumbImage: - 'http://3.bp.blogspot.com/-NUKSXr1qLHs/UQmsaEFgbTI/AAAAAAAAA_Y/l4psfVl4a5A/s1600/white-powdered-sugar-doughnuts-tracie-kaska.jpg', - }, - { - id: 7, - name: 'Snickerdoodles', - placeId: 'ChIJr3szW6a1RIYRkM7LRpnBIO0', - latitude: 30.266898599999998, - longitude: -97.73798459999999, - quantity: 'few', - category: 'desserts', - images: ['http://josephcphillips.com/wp-content/uploads/2015/02/snickerdoodles2.jpg'], - thumbImage: 'http://josephcphillips.com/wp-content/uploads/2015/02/snickerdoodles2.jpg', - }, - { - id: 8, - name: 'Pizza', - placeId: 'ChIJr3szW6a1RIYRkM7LRpnBIO0', - latitude: 30.266898599999998, - longitude: -97.73798459999999, - quantity: 'few', - category: 'entrees', - images: ['http://www.foodandhealth.co.uk/wp-content/uploads/2016/05/Hot-stone-Vegan-Pizza.jpg'], - thumbImage: 'http://www.foodandhealth.co.uk/wp-content/uploads/2016/05/Hot-stone-Vegan-Pizza.jpg', - }, -]; - -export const foodItemsRaw$ = Observable.from(DUMMY_DATA); - -export default foodItemsRaw$.map(createFoodItem).scan(setById, Map()); +export default location$.mergeMap(getFoodItemsForLocation).map(foodItems => { + return foodItems.map(createFoodItem).reduce(setById, new Map()); +}); diff --git a/js/streams/LocationStream.js b/js/streams/LocationStream.js new file mode 100644 index 0000000..4c6340e --- /dev/null +++ b/js/streams/LocationStream.js @@ -0,0 +1,6 @@ +// @flow +import { Observable, type Observer } from 'rxjs'; + +export default Observable.create((obs: Observer): Observable => { + navigator.geolocation.getCurrentPosition((pos: Position) => obs.next(pos)); +}); diff --git a/js/streams/PlacesStream.js b/js/streams/PlacesStream.js index d0ba67d..fc7825d 100644 --- a/js/streams/PlacesStream.js +++ b/js/streams/PlacesStream.js @@ -1,26 +1,33 @@ // @flow import { buildPlaceRecord } from '../records/PlaceRecord'; import { Map } from 'immutable'; -import { foodItemsRaw$ } from './FoodItemsStream'; +import foodItems$ from './FoodItemsStream'; import { getPlaceDetails } from '../apis/PlaceDetailsApi'; import { memoize } from 'ramda'; +import { Observable } from 'rxjs'; +import typeof FoodItemRecord from '../records/FoodItemRecord'; +import PlaceRecord, { type GooglePlaceObj } from '../records/PlaceRecord'; +import { setById } from '../helpers/ImmutableHelpers'; /** * return a promise of a place details object * if already requested return existing promise * swallow exceptions so as not to break the stream */ -const safeGetPlaceDetails = memoize(placeId => { +const safeGetPlaceDetails = memoize((placeId: string): Promise => { return getPlaceDetails(placeId).catch(error => { console.log(error); // eslint-disable-line no-console - return {}; + return null; }); }); -const uniquePlaceIds$ = foodItemsRaw$.map(foodItem => foodItem.placeId).distinct(); - -const placeRecords$ = uniquePlaceIds$.mergeMap(safeGetPlaceDetails).map(buildPlaceRecord).distinct(); - -export default placeRecords$.scan((accMap, place) => { - return accMap.set(place.id, place); -}, new Map()); +export default foodItems$ + .map((foodItems: Map): Array => { + return foodItems.map(foodItem => foodItem.placeId).toArray(); + }) + .mergeMap((placeIds: Array): Observable>> => { + return Observable.forkJoin(placeIds.map(safeGetPlaceDetails)); + }) + .map((places: Array): Map => { + return places.map(buildPlaceRecord).reduce(setById, new Map()); + });