diff --git a/src/actions/history.js b/src/actions/history.js
index 1cbc5bdb..414b3f2b 100644
--- a/src/actions/history.js
+++ b/src/actions/history.js
@@ -1,6 +1,6 @@
import { LOCATION_CHANGE } from 'connected-react-router';
-import { getDongleID, getZoom, getPrimeNav, getClipsNav } from '../url';
-import { primeNav, selectDevice, pushTimelineRange } from './index';
+import { getDongleID, getZoom, getPrimeNav, getClipsNav, getNavigationNav } from '../url';
+import { primeNav, selectDevice, pushTimelineRange, navigateToDestination } from './index';
import { clipsExit, fetchClipsDetails, fetchClipsList } from './clips';
export const onHistoryMiddleware = ({ dispatch, getState }) => (next) => (action) => {
@@ -28,6 +28,11 @@ export const onHistoryMiddleware = ({ dispatch, getState }) => (next) => (action
dispatch(primeNav(pathPrimeNav));
}
+ const pathNavigationNav = getNavigationNav(action.payload.location.pathname, action.payload.location.search);
+ if (pathNavigationNav && pathNavigationNav != state.navigationNav) {
+ dispatch(navigateToDestination(pathNavigationNav));
+ }
+
const pathClipsNav = getClipsNav(action.payload.location.pathname);
if (pathClipsNav === null && state.clips) {
dispatch(clipsExit());
diff --git a/src/actions/index.js b/src/actions/index.js
index a0f8bac7..05f0e8f3 100644
--- a/src/actions/index.js
+++ b/src/actions/index.js
@@ -9,6 +9,7 @@ import { resetPlayback, selectLoop } from '../timeline/playback';
import { getSegmentFetchRange, hasRoutesData } from '../timeline/segments';
import { getClipsNav } from '../url';
import { getDeviceFromState, deviceVersionAtLeast } from '../utils';
+import { coordinatesFromMapsUrl } from '../utils/maps';
let routesRequest = null;
@@ -150,6 +151,40 @@ export function primeNav(nav, allowPathChange = true) {
};
}
+export function navigateToDestination(destination) {
+ return (dispatch, getState) => {
+ const state = getState();
+ if (!state.dongleId) {
+ return;
+ }
+
+ console.log(destination);
+ let {url, lat, long} = destination;
+ var latLongPromise = null;
+ if (url) {
+ latLongPromise = coordinatesFromMapsUrl(url);
+ } else if (lat && long) {
+ latLongPromise = Promise.resolve([lat, long]);
+ } else {
+ return;
+ }
+
+ latLongPromise.then((coords) => {
+ let destination = {
+ latitude: coords[0],
+ longitude: coords[1],
+ };
+
+ if (state.navigationDestination !== destination) {
+ dispatch({
+ type: Types.ACTION_SET_NAVIGATION_DESTINATION,
+ navigationDestination: destination,
+ });
+ }
+ });
+ };
+}
+
export function fetchSharedDevice(dongleId) {
return async (dispatch) => {
try {
diff --git a/src/actions/types.js b/src/actions/types.js
index 06dd7b9a..d672e8c0 100644
--- a/src/actions/types.js
+++ b/src/actions/types.js
@@ -19,6 +19,9 @@ export const ACTION_PRIME_NAV = 'ACTION_PRIME_NAV';
export const ACTION_PRIME_SUBSCRIPTION = 'ACTION_PRIME_SUBSCRIPTION';
export const ACTION_PRIME_SUBSCRIBE_INFO = 'ACTION_PRIME_SUBSCRIBE_INFO';
+// map
+export const ACTION_SET_NAVIGATION_DESTINATION = 'ACTION_SET_NAVIGATION_DESTINATION';
+
// playback
export const ACTION_SEEK = 'action_seek';
export const ACTION_PAUSE = 'action_pause';
diff --git a/src/components/Dashboard/index.jsx b/src/components/Dashboard/index.jsx
index a0646e2a..e330a263 100644
--- a/src/components/Dashboard/index.jsx
+++ b/src/components/Dashboard/index.jsx
@@ -18,7 +18,7 @@ const DashboardLoading = () => (
);
-const Dashboard = ({ primeNav, device, dongleId }) => {
+const Dashboard = ({ primeNav, device, dongleId, navigationDestination }) => {
if (!device || !dongleId) {
return ;
}
@@ -30,7 +30,7 @@ const Dashboard = ({ primeNav, device, dongleId }) => {
?
: (
<>
-
+
>
@@ -44,6 +44,7 @@ const stateToProps = Obstruction({
dongleId: 'dongleId',
primeNav: 'primeNav',
device: 'device',
+ navigationDestination: 'navigationDestination',
});
export default connect(stateToProps)(Dashboard);
diff --git a/src/components/Navigation/index.jsx b/src/components/Navigation/index.jsx
index 09835e62..e8ab0042 100644
--- a/src/components/Navigation/index.jsx
+++ b/src/components/Navigation/index.jsx
@@ -345,7 +345,7 @@ class Navigation extends Component {
}
componentDidUpdate(prevProps, prevState) {
- const { dongleId, device } = this.props;
+ const { dongleId, device, navigationDestination } = this.props;
const { geoLocateCoords, search, carLastLocation, carNetworkLocation, searchSelect, favoriteLocations } = this.state;
if ((carLastLocation && !prevState.carLastLocation) || (carNetworkLocation && !prevState.carNetworkLocation)
@@ -354,6 +354,12 @@ class Navigation extends Component {
this.flyToMarkers();
}
+ if (prevProps.navigationDestination !== navigationDestination) {
+ this.getDeviceLastLocation().then(() => {
+ this.setDestination(navigationDestination.latitude, navigationDestination.longitude);
+ })
+ }
+
if (prevProps.dongleId !== dongleId) {
this.setState({
...initialState,
@@ -394,6 +400,40 @@ class Navigation extends Component {
}
}
+ setDestination(latitude, longitude) {
+ this.focus();
+
+ const item = {
+ favoriteId: null,
+ favoriteIcon: null,
+ address: {
+ label: '',
+ },
+ title: '',
+ resultType: 'place',
+ position: {
+ lat: latitude,
+ lng: longitude,
+ },
+ };
+ this.onSearchSelect(item, 'pin');
+ reverseLookup([longitude, latitude], false).then((location) => {
+ if (!location) {
+ return;
+ }
+
+ this.setState((prevState) => ({
+ searchSelect: {
+ ...prevState.searchSelect,
+ address: {
+ label: location.details,
+ },
+ title: location.place,
+ },
+ }));
+ });
+ }
+
updateDevice() {
if (Demo.isDemo()) {
return;
diff --git a/src/reducers/globalState.js b/src/reducers/globalState.js
index d18f3e17..e497ab68 100644
--- a/src/reducers/globalState.js
+++ b/src/reducers/globalState.js
@@ -438,6 +438,9 @@ export default function reducer(_state, action) {
end: action.end,
};
break;
+ case Types.ACTION_SET_NAVIGATION_DESTINATION:
+ state.navigationDestination = action.navigationDestination
+ break;
default:
return state;
}
diff --git a/src/url.js b/src/url.js
index c8dfea55..3dc56c97 100644
--- a/src/url.js
+++ b/src/url.js
@@ -48,3 +48,24 @@ export function getClipsNav(pathname) {
}
return null;
}
+
+export function getNavigationNav(pathname, search) {
+ let parts = pathname.split('/');
+ parts = parts.filter((m) => m.length);
+
+ if (parts.length >= 2 && parts[0] !== 'auth' && parts[1] == 'navigate') {
+ let params = new URLSearchParams(search);
+ if (params.has('url')) {
+ return {
+ url: params.get('url'),
+ }
+ } else if (params.has('lat') && params.has('long')) {
+ return {
+ lat: parseFloat(params.get('lat')),
+ long: parseFloat(params.get('long')),
+ };
+ }
+ return {};
+ }
+ return null;
+}
\ No newline at end of file
diff --git a/src/utils/maps.js b/src/utils/maps.js
new file mode 100644
index 00000000..b71445cf
--- /dev/null
+++ b/src/utils/maps.js
@@ -0,0 +1,29 @@
+import { forwardLookup } from "./geocode";
+
+export async function coordinatesFromMapsUrl(urlString) {
+ let url = new URL(urlString);
+ if (url.hostname.endsWith('maps.apple.com')) {
+ if (!url.searchParams.has('ll')) {
+ return null;
+ }
+
+ let ll = url.searchParams.get('ll');
+ let coords = ll.split(',').map((x) => parseFloat(x));
+ return coords;
+ } else if (['maps.google.com', 'google.com'].find((h) => url.hostname.endsWith(h))) {
+ if (url.searchParams.has('q')) { // maps.google.com?q=...
+ let query = url.searchParams.get('q');
+ let places = await forwardLookup(query, null, null);
+ if (places.length === 0) {
+ return null;
+ }
+
+ let position = places[0].position;
+ return position ? [position.lat, position.lng] : null;
+ } else {
+ return null;
+ }
+ }
+
+ return null;
+}
\ No newline at end of file
diff --git a/src/utils/maps.test.js b/src/utils/maps.test.js
new file mode 100644
index 00000000..18d5326e
--- /dev/null
+++ b/src/utils/maps.test.js
@@ -0,0 +1,21 @@
+/* eslint-env jest */
+import { coordinatesFromMapsUrl } from './maps';
+import * as geocode from './geocode';
+
+jest.mock('./geocode', () => ({
+ forwardLookup: jest.fn()
+}));
+
+describe('url parsing', () => {
+ it('should parse coordinates apple maps url', async () => {
+ let coords = await coordinatesFromMapsUrl('https://maps.apple.com/?ll=37.3319,-122.0312');
+ expect(coords).toEqual([37.3319, -122.0312]);
+ });
+
+ it('should reverse lookup coordinates based of query of maps.google.com?q=', async () => {
+ geocode.forwardLookup.mockResolvedValue([{position: {lat: 37.3319, lng: -122.0312}}]);
+
+ let coords = await coordinatesFromMapsUrl('https://www.google.com/maps?q=Some+place+somewhere');
+ expect(coords).toEqual([37.3319, -122.0312]);
+ });
+});