diff options
-rw-r--r-- | App.js | 16 | ||||
-rw-r--r-- | app.json | 2 | ||||
-rw-r--r-- | package.json | 1 | ||||
-rw-r--r-- | src/Actions/entity.ts | 16 | ||||
-rw-r--r-- | src/Actions/settings.ts | 6 | ||||
-rw-r--r-- | src/Components/Map.tsx | 17 | ||||
-rw-r--r-- | src/Components/MapObjects.tsx | 32 | ||||
-rw-r--r-- | src/Components/MapOverlay.tsx | 177 | ||||
-rw-r--r-- | src/Components/PortalPanel.tsx | 1 | ||||
-rw-r--r-- | src/Components/Settings.tsx | 49 | ||||
-rw-r--r-- | src/Store/store.ts | 53 | ||||
-rw-r--r-- | src/helper.js | 43 | ||||
-rw-r--r-- | yarn.lock | 5 |
13 files changed, 260 insertions, 158 deletions
@@ -1,8 +1,18 @@ import React from 'react'; import { Provider } from 'react-redux'; +import { PersistGate } from 'redux-persist/integration/react'; +import createStore from './src/Store/store'; import Main from './src/Main'; -import store from './src/Store/store' +import { ActivityIndicator, Text, View } from 'react-native'; -const App = () => (<Provider store={store}><Main /></Provider>) +const { store, persistor } = createStore(); -export default App
\ No newline at end of file +const loading = (<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}><ActivityIndicator /><Text>Загрузка состояния...</Text></View>); + +const App = () => (<Provider store={store}> + <PersistGate loading={loading} persistor={persistor}> + <Main /> + </PersistGate> +</Provider>); + +export default App;
\ No newline at end of file @@ -15,7 +15,7 @@ "android": { "package": "ru.neonxp.ingresshelper" }, - "version": "1.0.0", + "version": "1.0.1", "orientation": "portrait", "icon": "./assets/icon.png", "splash": { diff --git a/package.json b/package.json index 225cb47..e00b5ca 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "react-redux": "^6.0.1", "redux": "^4.0.1", "redux-devtools-extension": "^2.13.8", + "redux-persist": "^5.10.0", "redux-su": "^0.3.4", "redux-thunk": "^2.3.0" }, diff --git a/src/Actions/entity.ts b/src/Actions/entity.ts index 8b01ea6..2b85ffe 100644 --- a/src/Actions/entity.ts +++ b/src/Actions/entity.ts @@ -15,7 +15,7 @@ const entity = { ...queue, ] : [] const tiles = getTilesToLoad(region, width).filter(t => loadedAlready.indexOf(t) == -1) - dispatch(entity.setLoadQueue([...queue, ...tiles])) + dispatch(entity.setLoadQueue(tiles)) setImmediate(() => dispatch(entity.loadRutine())) }, @@ -52,21 +52,11 @@ const entity = { const portal = decodePortal(j.result) portal.fullLoad = true dispatch(entity.portalSet(guid, portal)) - let addr = "нет" - try { - addr = await fetch(`https://nominatim.openstreetmap.org/reverse?format=json&polygon_geojson=1&lat=${portal.coords.latitude}&lon=${portal.coords.longitude}`) - .then(r => r.json()) - .then(j => [j.address.city, j.address.road, j.address.house_number].filter(p => !!p).join(', ') || 'нет') - } catch (e) { - addr = "ошибка" - } - portal.address = addr - dispatch(entity.portalSet(guid, portal)) }) }, - 'setLoadQueue': (queue: string[][]) => ({ + 'setLoadQueue': (queue: string[]) => ({ type: 'setLoad', - queue + queue, }), 'portalsSet': (portals: { [guid: string]: Portal }) => ({ type: 'portalsSet', diff --git a/src/Actions/settings.ts b/src/Actions/settings.ts index 8dfc3fc..ef18c9e 100644 --- a/src/Actions/settings.ts +++ b/src/Actions/settings.ts @@ -1,3 +1,5 @@ +import { Region } from "react-native-maps"; + const settings = { 'setLevelFrom': (level: Number) => ({ type: 'setLevelFrom', @@ -6,6 +8,10 @@ const settings = { 'setLevelTo': (level: Number) => ({ type: 'setLevelTo', level + }), + 'setRegion': (region: Region) => ({ + type: 'setRegion', + region }) } export default settings
\ No newline at end of file diff --git a/src/Components/Map.tsx b/src/Components/Map.tsx index db0bed9..6c3ebab 100644 --- a/src/Components/Map.tsx +++ b/src/Components/Map.tsx @@ -1,5 +1,5 @@ import React, { Component } from 'react'; -import { StyleSheet, View, Dimensions, ActivityIndicator } from 'react-native'; +import { StyleSheet, View, Dimensions, ActivityIndicator, StatusBar } from 'react-native'; import MapView, { Marker, Region } from 'react-native-maps'; import { connect } from 'redux-su'; import { NavigationActions } from 'react-navigation'; @@ -7,7 +7,7 @@ import { NavigationActions } from 'react-navigation'; import MapOverlay from './MapOverlay'; import MapObjects from './MapObjects'; import PortalPanel from './PortalPanel'; -import { getBottomSpace } from '../helper'; +import debounce, { getBottomSpace } from '../helper'; import actions from '../Actions/actions'; import { LatLng } from '../Api/interfaces'; import { getZoomByRegion, getDataZoomForMapZoom } from '../Api/api'; @@ -35,6 +35,7 @@ class Map extends Component<Props, State> { region: null, dataZoom: 15, } + this.load = debounce(this.load, 300) } componentDidMount() { this.refreshTimer = setInterval(() => { @@ -68,6 +69,7 @@ class Map extends Component<Props, State> { const zoom = getZoomByRegion(width, region) const dataZoom = getDataZoomForMapZoom(zoom); this.setState({ region, dataZoom }) + this.props.actions.setRegion(region) setImmediate(() => this.load(false)) } @@ -110,9 +112,13 @@ class Map extends Component<Props, State> { if (!this.state.user) { return <View style={styles.spinnerContainer}><ActivityIndicator size={'large'} /></View> } - const initialRegion = { ...this.state.user, latitudeDelta: 0.002, longitudeDelta: 0.002 } + const initialRegion = this.props.settings.region || { ...this.state.user, latitudeDelta: 0.002, longitudeDelta: 0.002 } return ( <> + <StatusBar + backgroundColor="blue" + barStyle="dark-content" + /> <MapView ref={r => (r != null) ? this.map = r : null} style={styles.container} @@ -121,6 +127,7 @@ class Map extends Component<Props, State> { showsCompass={false} showsScale showsUserLocation + userLocationAnnotationTitle="" showsMyLocationButton loadingEnabled type={'hybrid'} @@ -131,9 +138,9 @@ class Map extends Component<Props, State> { onPortalClick={this.onPortalClick} region={this.state.region || initialRegion} levels={this.props.settings.filterLevel} + selectedPortal={this.state.selectedPortal} zoom={this.state.dataZoom} /> - {this.state.selectedPortal && <Marker cluster={false} coordinate={this.state.selectedPortal.coords} onPress={this.onPortalDismiss} />} </MapView> <MapOverlay goToMe={this.goToMe} @@ -141,6 +148,8 @@ class Map extends Component<Props, State> { loading={this.props.entities.loadQueue.length} selectedPortal={this.state.selectedPortal} onOpenPortal={this.onOpenPortal} + getPortalDetails={this.props.actions.getPortalDetails} + portal={this.state.selectedPortal && this.props.entities.portals[this.state.selectedPortal.guid]} /> </> ); diff --git a/src/Components/MapObjects.tsx b/src/Components/MapObjects.tsx index 2a5e1df..6e9b053 100644 --- a/src/Components/MapObjects.tsx +++ b/src/Components/MapObjects.tsx @@ -1,5 +1,5 @@ import React, { PureComponent } from 'react'; -import { View, StyleSheet, Text } from 'react-native'; +import { View, StyleSheet, Text, Image } from 'react-native'; import { Polyline, Polygon, Marker, Region } from 'react-native-maps'; import Icon from 'react-native-vector-icons/FontAwesome'; import { Link, Portal, Field } from '../Api/types'; @@ -43,27 +43,28 @@ type Props = { class MapObjects extends PureComponent<Props> { render() { - const region = this.props.region - const lat1 = region.latitude - region.latitudeDelta / 2 - const lat2 = region.latitude + region.latitudeDelta / 2 - const lng1 = region.longitude - region.longitudeDelta / 2 - const lng2 = region.longitude + region.longitudeDelta / 2 const portals = this.props.portals const fields = this.props.fields const links = this.props.links - const zoom = this.props.zoom - const renderPortal = this.props.renderPortal.map(guid => this.drawPortal(guid, portals[guid], this.props.onPortalClick)) + const renderPortal = this.props.renderPortal.map(guid => guid && this.drawPortal(guid, portals[guid], this.props.onPortalClick)) const renderLink = this.props.renderLink.map(guid => this.drawLink(guid, links[guid])) const renderField = this.props.renderField.map(guid => this.drawField(guid, fields[guid])) return [...renderField, ...renderLink, ...renderPortal] } drawPortal = (guid: string, entity: Portal, onPortalClick: Function) => { + const selected = this.props.selectedPortal && this.props.selectedPortal.guid == guid return (<Marker key={guid} coordinate={entity.coords} onPress={() => onPortalClick(guid, entity.coords)} - image={PORTALS[entity.fraction + entity.level]} - />); + style={[selected ? { borderColor: 'red', borderWidth: 4, borderRadius: 13 } : null, { height: 26, width: 26, flex: 1, justifyContent: "center", alignItems: 'center' }]} + zIndex={selected ? 10 : entity.level} + > + <Image + source={PORTALS[entity.fraction + entity.level]} + style={{ height: 22, width: 22 }} + /> + </Marker>); } drawField = (guid: string, entity: Field) => { return <Polygon @@ -96,11 +97,12 @@ export default connect((store, props: Props) => { const lng2 = region.longitude + region.longitudeDelta / 2 const minlvl = DEFAULT_ZOOM_TO_LEVEL[Math.min(props.zoom - 1, DEFAULT_ZOOM_TO_LEVEL.length - 1)] const minlen = DEFAULT_ZOOM_TO_LINK_LENGTH[Math.min(props.zoom - 1, DEFAULT_ZOOM_TO_LINK_LENGTH.length - 1)] / 1000 - const renderPortal = Object - .keys(store.entities.portals) - .filter(guid => store.entities.portals[guid].level >= props.levels[0] && store.entities.portals[guid].level <= props.levels[1]) - .filter(guid => store.entities.portals[guid].coords.latitude > lat1 && store.entities.portals[guid].coords.latitude < lat2 && store.entities.portals[guid].coords.longitude > lng1 && store.entities.portals[guid].coords.longitude < lng2) - + const renderPortal = props.zoom > 14 + ? Object + .keys(store.entities.portals) + .filter(guid => store.entities.portals[guid].level >= props.levels[0] && store.entities.portals[guid].level <= props.levels[1]) + .filter(guid => store.entities.portals[guid].coords.latitude > lat1 && store.entities.portals[guid].coords.latitude < lat2 && store.entities.portals[guid].coords.longitude > lng1 && store.entities.portals[guid].coords.longitude < lng2) + : [props.selectedPortal ? props.selectedPortal.guid : null] const renderLink = Object .keys(store.entities.links) .filter(guid => { diff --git a/src/Components/MapOverlay.tsx b/src/Components/MapOverlay.tsx index 0e3624b..28da324 100644 --- a/src/Components/MapOverlay.tsx +++ b/src/Components/MapOverlay.tsx @@ -1,87 +1,108 @@ import React from 'react'; -import { StyleSheet, View, Text, GestureResponderEvent, ActivityIndicator, Linking, TextInput, Slider } from 'react-native'; +import { StyleSheet, View, Text, GestureResponderEvent, ActivityIndicator, Linking, TextInput, Slider, Image, Dimensions } from 'react-native'; import { FontAwesome } from '@expo/vector-icons'; import { getStatusBarHeight, getBottomSpace } from '../helper'; import { LatLng } from '../Api/interfaces'; +import { COLORS_FRACTION, COLORS_LVL, RESO_NRG } from '../constants'; + +const { width, height } = Dimensions.get("screen") type Props = { goToMe: (e: GestureResponderEvent) => void; refresh: (e: GestureResponderEvent) => void; onOpenPortal: (guid: string, coords: LatLng) => void; + getPortalDetails: (guid: string) => void; loading: Number; selectedPortal: any; + portal: any; } -export default (props: Props) => ( - <> - <View style={styles.overlayLeft}> - {props.selectedPortal ? ( - <> +export default class extends React.Component<Props> { + componentWillReceiveProps(next) { + if (this.props.selectedPortal !== next.selectedPortal && next.selectedPortal && next.selectedPortal.guid) { + this.props.getPortalDetails(next.selectedPortal.guid) + } + } + render() { + const props = this.props + const portal = this.props.portal + return ( + <> + <View style={styles.overlayLeft}> + {props.loading > 0 ? ( + <View style={styles.loader}> + <ActivityIndicator color={"#000"} /> + </View> + ) : null} + </View> + + <View style={styles.overlayRight}> <View style={styles.buttonWrapper}> <FontAwesome.Button style={styles.button} - name={'info'} - onPress={() => { - props.onOpenPortal(props.selectedPortal.guid, props.selectedPortal.coords) - }} + name={'crosshairs'} + onPress={props.goToMe} iconStyle={styles.icon} color={"#000"} size={25} - backgroundColor={"#e3e3e3"} + backgroundColor={"#fff"} /> </View> - {/* <View style={styles.buttonWrapper}> + <View style={styles.buttonWrapper}> <FontAwesome.Button style={styles.button} - name={'location-arrow'} - onPress={() => Linking.openURL(`geo:${props.selectedPortal.coords.latitude},${props.selectedPortal.coords.longitude}`)} + name={'refresh'} + onPress={props.refresh} iconStyle={styles.icon} color={"#000"} size={25} - backgroundColor={"#e3e3e3"} + backgroundColor={"#fff"} /> - </View> */} - </> - ) : null} - {props.loading > 0 ? ( - <View style={styles.loader}> - <ActivityIndicator color={"#000"} /> + </View> </View> - ) : null} - </View> - - <View style={styles.overlayRight}> - <View style={styles.buttonWrapper}> - <FontAwesome.Button - style={styles.button} - name={'crosshairs'} - onPress={props.goToMe} - iconStyle={styles.icon} - color={"#000"} - size={25} - backgroundColor={"#e3e3e3"} - /> - </View> - <View style={styles.buttonWrapper}> - <FontAwesome.Button - style={styles.button} - name={'refresh'} - onPress={props.refresh} - iconStyle={styles.icon} - color={"#000"} - size={25} - backgroundColor={"#e3e3e3"} - /> - </View> - </View> - {props.selectedPortal ? ( - <View style={styles.panel}> - <Text>{JSON.stringify(props.selectedPortal)}</Text> - </View> - ) : null} - </> -); - + {props.selectedPortal ? ( + <View style={styles.panel} tint={'default'} intensity={90}> + <Image style={styles.photo} source={{ uri: portal.photo }} /> + <View style={styles.column}> + <Text style={[styles.title, { color: COLORS_FRACTION[portal.fraction] }]} ellipsizeMode={'tail'} numberOfLines={1}>{portal.name}</Text> + <Text style={styles.subtitle}>Уровeнь: {portal.level}, здоровье: {portal.power}</Text> + <Text style={styles.subtitle}>Владелец: <Text style={[styles.user, { color: COLORS_FRACTION[portal.fraction] }]}>{portal.owner || 'нет'}</Text></Text> + <Text style={styles.subtitle}>Резонаторы:</Text> + <Text style={styles.subtitle}> + {portal.resonators && portal + .resonators + .filter(r => !!r) + .map((r, idx) => (<Text key={idx} style={[styles.subtitle, { color: COLORS_LVL[r[1]] }]}>[{r[1]}]</Text>))} + </Text> + </View> + {/* <FontAwesome.Button + style={styles.panelButton} + name={'star-o'} + onPress={() => { + props.onOpenPortal(props.selectedPortal.guid, props.selectedPortal.coords) + }} + iconStyle={styles.icon} + color={"#000"} + size={32} + backgroundColor={"transparent"} + /> */} + <FontAwesome.Button + style={styles.panelButton} + name={'angle-right'} + onPress={() => { + props.onOpenPortal(props.selectedPortal.guid, props.selectedPortal.coords) + }} + iconStyle={styles.icon} + color={"#000"} + size={32} + backgroundColor={"transparent"} + /> + </View> + ) : null} + </> + ); + } +} const styles = StyleSheet.create({ overlayLeft: { position: 'absolute', @@ -98,7 +119,12 @@ const styles = StyleSheet.create({ alignItems: 'center', height: 48, width: 48, - + }, + panelButton: { + justifyContent: 'center', + alignItems: 'center', + height: 100, + width: 64, }, buttonWrapper: { margin: 8, @@ -114,15 +140,20 @@ const styles = StyleSheet.create({ }, panel: { position: 'absolute', - bottom: getBottomSpace(), - left: 8, - right: 8, - borderRadius: 8, - padding: 8, + bottom: 0, + left: 0, + right: 0, backgroundColor: '#fff', + flexDirection: 'row', + alignItems: 'center', + overflow: 'hidden', + borderTopWidth: StyleSheet.hairlineWidth, + borderTopColor: '#000', + padding: 0, + height: 100, //ios - shadowOpacity: 0.3, - shadowRadius: 3, + shadowOpacity: 0.6, + shadowRadius: 6, shadowOffset: { height: 0, width: 0 @@ -130,11 +161,29 @@ const styles = StyleSheet.create({ //android elevation: 1 }, + column: { + flexGrow: 1, + padding: 8, + width: (width - 124) + }, icon: { marginRight: 0, }, loader: { margin: 8, paddingHorizontal: 8, + }, + title: { + fontWeight: 'bold', + }, + subtitle: { + fontWeight: 'normal', + }, + user: { + fontWeight: 'bold', + }, + photo: { + width: 55, + height: 100, } });
\ No newline at end of file diff --git a/src/Components/PortalPanel.tsx b/src/Components/PortalPanel.tsx index e372259..30564ca 100644 --- a/src/Components/PortalPanel.tsx +++ b/src/Components/PortalPanel.tsx @@ -43,7 +43,6 @@ class PortalPanel extends PureComponent<Props> { <Text style={styles.subtitle}>Уровeнь: {portal.level}, здоровье: {portal.power}</Text> <Text style={styles.subtitle}>Владелец: <Text style={[styles.user, { color: COLORS_FRACTION[portal.fraction] }]}>{portal.owner || 'нет'}</Text></Text> <Text style={styles.subtitle}>Дата: {portal.timestamp && (new Date(portal.timestamp)).toLocaleString()}</Text> - <Text style={styles.subtitle}>Адрес: {portal.address || 'загрузка...'}</Text> </View> </View> <Text style={styles.title}>Резонаторы</Text> diff --git a/src/Components/Settings.tsx b/src/Components/Settings.tsx index ca481a9..ffc176c 100644 --- a/src/Components/Settings.tsx +++ b/src/Components/Settings.tsx @@ -23,28 +23,30 @@ class Settings extends PureComponent<Props> { const { filterLevel } = this.props.settings return ( <SafeAreaView style={styles.overlay}> - <Text>Мин. Ур.: {filterLevel[0]}</Text> - <Slider - style={{ width: 150, height: 40 }} - minimumValue={0} - maximumValue={filterLevel[1]} - value={filterLevel[0]} - step={1} - minimumTrackTintColor="#028ce3" - maximumTrackTintColor="#000000" - onSlidingComplete={this.props.setLevelFrom} - /> - <Text>Макс. Ур.: {filterLevel[1]}</Text> - <Slider - style={{ width: 150, height: 40 }} - minimumValue={filterLevel[0]} - maximumValue={8} - value={filterLevel[1]} - step={1} - minimumTrackTintColor="#028ce3" - maximumTrackTintColor="#000000" - onSlidingComplete={this.props.setLevelTo} - /> + <View style={styles.container}> + <Text>Мин. Ур.: {filterLevel[0]}</Text> + <Slider + style={{ height: 40 }} + minimumValue={0} + maximumValue={filterLevel[1]} + value={filterLevel[0]} + step={1} + minimumTrackTintColor="#028ce3" + maximumTrackTintColor="#000000" + onSlidingComplete={this.props.setLevelFrom} + /> + <Text>Макс. Ур.: {filterLevel[1]}</Text> + <Slider + style={{ height: 40 }} + minimumValue={filterLevel[0]} + maximumValue={8} + value={filterLevel[1]} + step={1} + minimumTrackTintColor="#028ce3" + maximumTrackTintColor="#000000" + onSlidingComplete={this.props.setLevelTo} + /> + </View> </SafeAreaView> ); } @@ -57,6 +59,9 @@ const styles = StyleSheet.create({ flex: 1, padding: 8, }, + container: { + margin: 8, + }, title: { fontWeight: 'bold', fontSize: 22, diff --git a/src/Store/store.ts b/src/Store/store.ts index 581f14b..2fb5a1f 100644 --- a/src/Store/store.ts +++ b/src/Store/store.ts @@ -1,8 +1,10 @@ import { createStore, combineReducers, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import { createReducer } from 'redux-su'; -import { composeWithDevTools } from 'redux-devtools-extension'; +import { AsyncStorage } from 'react-native'; +import { persistStore, persistReducer } from 'redux-persist'; import { Portal, Link, Field } from '../Api/types'; +import { Region } from '../Api/interfaces'; const reducers = { 'auth': createReducer({ @@ -32,8 +34,22 @@ const reducers = { ({ ...store, links: { ...store.links, ...action.links } }), 'fieldsSet': (store: any, action: { fields: { [guid: string]: Field } }) => ({ ...store, fields: { ...store.fields, ...action.fields } }), - 'setLoad': (store: any, action: { queue: string[][] }) => + 'setLoad': (store: any, action: { queue: string[] }) => ({ ...store, loadQueue: [...action.queue] }), + 'portalsGC': (store: any, action: { portals: { [tile: string]: string[] } }) => { + let portalsCache = store.portalsCache + let portals = store.portals + Object.keys(action.portals).forEach(tileId => { + const oldPortals = portalsCache[tileId] + if (oldPortals != undefined) { + oldPortals.forEach((guid: string) => { + delete portals[guid] + }) + } + portalsCache[tileId] = action.portals[tileId] + }) + return ({ ...store, portals: { ...portals }, portalsCache }) + }, 'linksGC': (store: any, action: { links: { [tile: string]: string[] } }) => { let linksCache = store.linksCache let links = store.links @@ -62,28 +78,31 @@ const reducers = { }) return ({ ...store, fields: { ...fields }, fieldsCache }) }, - }, { portals: {}, fields: {}, links: {}, loadQueue: [], linksCache: {}, fieldsCache: {} }), + }, { portals: {}, fields: {}, links: {}, loadQueue: [], linksCache: {}, fieldsCache: {}, portalsCache: {} }), 'settings': createReducer({ 'setLevelFrom': (store: any, action: { level: number }) => ({ ...store, filterLevel: [action.level, store.filterLevel[1]] }), 'setLevelTo': (store: any, action: { level: number }) => ({ ...store, filterLevel: [store.filterLevel[0], action.level] + }), + 'setRegion': (store: any, action: { region: Region }) => ({ + ...store, region: action.region }) - }, { filterLevel: [0, 8] }) + }, { filterLevel: [0, 8], region: null }) } -function extend(obj: any, src: any) { - for (var key in src) { - if (src.hasOwnProperty(key) && !!src[key]) { - obj[key] = src[key]; - } - } - return obj; -} +const rootReducer = combineReducers(reducers) -const store = createStore( - combineReducers(reducers), - composeWithDevTools(applyMiddleware(thunk)) -) -export default store
\ No newline at end of file +const persistConfig = { + key: 'root', + storage: AsyncStorage, + } + + const persistedReducer = persistReducer(persistConfig, rootReducer) + + export default () => { + let store = createStore(persistedReducer, applyMiddleware(thunk)) + let persistor = persistStore(store) + return { store, persistor } + }
\ No newline at end of file diff --git a/src/helper.js b/src/helper.js index 2242d91..3a4cca0 100644 --- a/src/helper.js +++ b/src/helper.js @@ -45,23 +45,30 @@ function formatInt(num) { return ("00" + num).slice(-2) } -export function calcCrow(lat1, lon1, lat2, lon2) - { - var R = 6371; // km - var dLat = toRad(lat2-lat1); - var dLon = toRad(lon2-lon1); - var lat1 = toRad(lat1); - var lat2 = toRad(lat2); +export function calcCrow(lat1, lon1, lat2, lon2) { + var R = 6371; // km + var dLat = toRad(lat2 - lat1); + var dLon = toRad(lon2 - lon1); + var lat1 = toRad(lat1); + var lat2 = toRad(lat2); - var a = Math.sin(dLat/2) * Math.sin(dLat/2) + - Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2); - var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); - var d = R * c; - return d; - } + var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(lat1) * Math.cos(lat2); + var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + var d = R * c; + return d; +} + +// Converts numeric degrees to radians +function toRad(Value) { + return Value * Math.PI / 180; +} - // Converts numeric degrees to radians - function toRad(Value) - { - return Value * Math.PI / 180; - }
\ No newline at end of file +export default function debounce(func, wait) { + let timeout + return function (...args) { + const context = this + clearTimeout(timeout) + timeout = setTimeout(() => func.apply(context, args), wait) + } +} @@ -4936,6 +4936,11 @@ redux-devtools-extension@^2.13.8: resolved "https://registry.yarnpkg.com/redux-devtools-extension/-/redux-devtools-extension-2.13.8.tgz#37b982688626e5e4993ff87220c9bbb7cd2d96e1" integrity sha512-8qlpooP2QqPtZHQZRhx3x3OP5skEV1py/zUdMY28WNAocbafxdG2tRD1MWE7sp8obGMNYuLWanhhQ7EQvT1FBg== +redux-persist@^5.10.0: + version "5.10.0" + resolved "https://registry.yarnpkg.com/redux-persist/-/redux-persist-5.10.0.tgz#5d8d802c5571e55924efc1c3a9b23575283be62b" + integrity sha512-sSJAzNq7zka3qVHKce1hbvqf0Vf5DuTVm7dr4GtsqQVOexnrvbV47RWFiPxQ8fscnyiuWyD2O92DOxPl0tGCRg== + redux-su@^0.3.4: version "0.3.4" resolved "https://registry.yarnpkg.com/redux-su/-/redux-su-0.3.4.tgz#27111cd5c502cd58988d3c171c13bc10142ce9c3" |