mirror of
https://github.com/muerwre/orchidmap-front.git
synced 2025-04-24 18:46:40 +07:00
current user location
This commit is contained in:
parent
5e3aa587c7
commit
a574b7393d
12 changed files with 205 additions and 77 deletions
|
@ -23,10 +23,13 @@ export const Index = () => (
|
|||
|
||||
ReactDOM.render(<Index />, document.getElementById('index'));
|
||||
|
||||
(function () {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('./service-worker.js', { scope: '/' })
|
||||
.then(() => console.log('Service Worker registered successfully.'))
|
||||
.catch(error => console.log('Service Worker registration failed:', error));
|
||||
}
|
||||
}());
|
||||
if (process.env.NODE_ENV && process.env.NODE_ENV !== 'development') {
|
||||
(function() {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker
|
||||
.register('./service-worker.js', { scope: '/' })
|
||||
.then(() => console.log('Service Worker registered successfully.'))
|
||||
.catch(error => console.log('Service Worker registration failed:', error));
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
|
33
src/map/CurrentLocation/index.tsx
Normal file
33
src/map/CurrentLocation/index.tsx
Normal file
|
@ -0,0 +1,33 @@
|
|||
import React, { FC, useState, useEffect } from 'react';
|
||||
import { LatLngLiteral, marker, Marker, DivIcon } from 'leaflet';
|
||||
import { MainMap } from '~/constants/map';
|
||||
|
||||
interface IProps {
|
||||
location: LatLngLiteral;
|
||||
}
|
||||
|
||||
const CurrentLocation: FC<IProps> = ({ location }) => {
|
||||
useEffect(() => {
|
||||
if (!location) return;
|
||||
|
||||
const item = new Marker(location, {
|
||||
icon: new DivIcon({
|
||||
html: `
|
||||
<div class="current-location">
|
||||
<svg width="28" height="28" viewBox="0 0 20 20">
|
||||
<circle r="1.846" cy="1.846" cx="5.088"/>
|
||||
<path d="M3.004 4.326h4l2-3 1 1-3 4v10h-1l-1-7-1 7h-1v-10s-3.125-4-3-4l1-1z"/>
|
||||
<ellipse ry="1" rx="4" cy="16.326" cx="5.004" opacity=".262" fill="black" />
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
}).addTo(MainMap);
|
||||
|
||||
return () => item.removeFrom(MainMap);
|
||||
}, [MainMap, location]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export { CurrentLocation };
|
|
@ -11,11 +11,12 @@ import { Router } from '~/map/Router';
|
|||
import { TileLayer } from '~/map/TileLayer';
|
||||
import { Stickers } from '~/map/Stickers';
|
||||
import { KmMarks } from '~/map/KmMarks';
|
||||
import { Arrows } from '~/map/Arrows';
|
||||
import { CurrentLocation } from '~/map/CurrentLocation';
|
||||
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import { selectEditorEditing, selectEditorMode } from '~/redux/editor/selectors';
|
||||
import { MODES } from '~/constants/modes';
|
||||
import { selectUserLocation } from '~/redux/user/selectors';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
provider: selectMapProvider(state),
|
||||
|
@ -23,6 +24,7 @@ const mapStateToProps = state => ({
|
|||
stickers: selectMapStickers(state),
|
||||
editing: selectEditorEditing(state),
|
||||
mode: selectEditorMode(state),
|
||||
location: selectUserLocation(state),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
|
@ -41,6 +43,7 @@ const MapUnconnected: React.FC<IProps> = ({
|
|||
stickers,
|
||||
editing,
|
||||
mode,
|
||||
location,
|
||||
|
||||
mapClicked,
|
||||
mapSetSticker,
|
||||
|
@ -78,6 +81,7 @@ const MapUnconnected: React.FC<IProps> = ({
|
|||
<Router />
|
||||
|
||||
<KmMarks />
|
||||
<CurrentLocation location={location} />
|
||||
</div>,
|
||||
document.getElementById('canvas')
|
||||
);
|
||||
|
|
|
@ -4,11 +4,9 @@ import { persistStore, persistReducer } from 'redux-persist';
|
|||
import storage from 'redux-persist/lib/storage';
|
||||
import createSagaMiddleware from 'redux-saga';
|
||||
|
||||
|
||||
import { createBrowserHistory } from 'history';
|
||||
import { editorLocationChanged } from '~/redux/editor/actions';
|
||||
import { PersistConfig, Persistor } from "redux-persist/es/types";
|
||||
|
||||
import { PersistConfig, Persistor } from 'redux-persist/es/types';
|
||||
|
||||
import { userReducer, IRootReducer } from '~/redux/user';
|
||||
import { userSaga } from '~/redux/user/sagas';
|
||||
|
@ -18,6 +16,9 @@ import { editorSaga } from '~/redux/editor/sagas';
|
|||
|
||||
import { map, IMapReducer } from '~/redux/map';
|
||||
import { mapSaga } from '~/redux/map/sagas';
|
||||
import { watchLocation, getLocation } from '~/utils/window';
|
||||
import { LatLngLiteral } from 'leaflet';
|
||||
import { setUserLocation } from './user/actions';
|
||||
|
||||
const userPersistConfig: PersistConfig = {
|
||||
key: 'user',
|
||||
|
@ -26,17 +27,16 @@ const userPersistConfig: PersistConfig = {
|
|||
};
|
||||
|
||||
export interface IState {
|
||||
user: IRootReducer
|
||||
map: IMapReducer,
|
||||
editor: IEditorState,
|
||||
user: IRootReducer;
|
||||
map: IMapReducer;
|
||||
editor: IEditorState;
|
||||
}
|
||||
// create the saga middleware
|
||||
export const sagaMiddleware = createSagaMiddleware();
|
||||
|
||||
// redux extension composer
|
||||
const composeEnhancers =
|
||||
typeof window === 'object' &&
|
||||
(<any>window).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
|
||||
typeof window === 'object' && (<any>window).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
|
||||
? (<any>window).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
|
||||
: compose;
|
||||
|
||||
|
@ -49,7 +49,7 @@ export const store = createStore(
|
|||
composeEnhancers(applyMiddleware(sagaMiddleware))
|
||||
);
|
||||
|
||||
export function configureStore(): { store: Store<any>, persistor: Persistor } {
|
||||
export function configureStore(): { store: Store<any>; persistor: Persistor } {
|
||||
sagaMiddleware.run(userSaga);
|
||||
sagaMiddleware.run(mapSaga);
|
||||
sagaMiddleware.run(editorSaga);
|
||||
|
@ -65,3 +65,5 @@ history.listen((location, action) => {
|
|||
if (action === 'REPLACE') return;
|
||||
store.dispatch(editorLocationChanged(location.pathname));
|
||||
});
|
||||
|
||||
watchLocation((location: LatLngLiteral) => store.dispatch(setUserLocation(location)));
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { USER_ACTIONS } from '~/redux/user/constants';
|
||||
import { IUser } from "~/constants/auth";
|
||||
import { IRootReducer } from '.';
|
||||
|
||||
export const setUser = (user: IUser) => ({ type: USER_ACTIONS.SET_USER, user });
|
||||
export const userLogout = () => ({ type: USER_ACTIONS.USER_LOGOUT });
|
||||
|
@ -24,3 +25,5 @@ export const modifyRoute = (address: string, { title, is_public }: { title: stri
|
|||
});
|
||||
export const toggleRouteStarred = (address: string) => ({ type: USER_ACTIONS.TOGGLE_ROUTE_STARRED, address });
|
||||
export const setRouteStarred = (address: string, is_published: boolean) => ({ type: USER_ACTIONS.SET_ROUTE_STARRED, address, is_published });
|
||||
|
||||
export const setUserLocation = (location: IRootReducer['location']) => ({ type: USER_ACTIONS.SET_USER_LOCATION, location });
|
||||
|
|
|
@ -5,7 +5,8 @@ export const USER_ACTIONS = {
|
|||
GOT_VK_USER: 'GOT_VK_USER',
|
||||
|
||||
IFRAME_LOGIN_VK: 'IFRAME_LOGIN_VK',
|
||||
|
||||
SET_USER_LOCATION: 'SET_USER_LOCATION',
|
||||
|
||||
SEARCH_SET_TITLE: 'SEARCH_SET_TITLE',
|
||||
SEARCH_SET_DISTANCE: 'SEARCH_SET_DISTANCE',
|
||||
SEARCH_CHANGE_DISTANCE: 'SEARCH_CHANGE_DISTANCE',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { IRootState } from ".";
|
||||
import * as ActionCreators from './actions'
|
||||
import { TABS } from "~/constants/dialogs";
|
||||
import { USER_ACTIONS } from "./constants";
|
||||
import { IRootState, IRootReducer } from '.';
|
||||
import * as ActionCreators from './actions';
|
||||
import { TABS } from '~/constants/dialogs';
|
||||
import { USER_ACTIONS } from './constants';
|
||||
|
||||
type UnsafeReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
|
||||
|
||||
|
@ -17,7 +17,10 @@ const setUser: ActionHandler<typeof ActionCreators.setUser> = (state, { user })
|
|||
},
|
||||
});
|
||||
|
||||
const searchSetTitle: ActionHandler<typeof ActionCreators.searchSetTitle> = (state, { title = '' }) => ({
|
||||
const searchSetTitle: ActionHandler<typeof ActionCreators.searchSetTitle> = (
|
||||
state,
|
||||
{ title = '' }
|
||||
) => ({
|
||||
...state,
|
||||
routes: {
|
||||
...state.routes,
|
||||
|
@ -25,33 +28,42 @@ const searchSetTitle: ActionHandler<typeof ActionCreators.searchSetTitle> = (sta
|
|||
...state.routes.filter,
|
||||
title,
|
||||
distance: [0, 10000],
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const searchSetDistance: ActionHandler<typeof ActionCreators.searchSetDistance> = (state, { distance = [0, 9999] }) => ({
|
||||
const searchSetDistance: ActionHandler<typeof ActionCreators.searchSetDistance> = (
|
||||
state,
|
||||
{ distance = [0, 9999] }
|
||||
) => ({
|
||||
...state,
|
||||
routes: {
|
||||
...state.routes,
|
||||
filter: {
|
||||
...state.routes.filter,
|
||||
distance,
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const searchSetTab: ActionHandler<typeof ActionCreators.searchSetTab> = (state, { tab = TABS[Object.keys(TABS)[0]] }) => ({
|
||||
const searchSetTab: ActionHandler<typeof ActionCreators.searchSetTab> = (
|
||||
state,
|
||||
{ tab = TABS[Object.keys(TABS)[0]] }
|
||||
) => ({
|
||||
...state,
|
||||
routes: {
|
||||
...state.routes,
|
||||
filter: {
|
||||
...state.routes.filter,
|
||||
tab: Object.values(TABS).indexOf(tab) >= 0 ? tab : TABS[Object.values(TABS)[0]],
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const searchPutRoutes: ActionHandler<typeof ActionCreators.searchPutRoutes> = (state, { list = [], min, max, limit, step, shift }) => ({
|
||||
const searchPutRoutes: ActionHandler<typeof ActionCreators.searchPutRoutes> = (
|
||||
state,
|
||||
{ list = [], min, max, limit, step, shift }
|
||||
) => ({
|
||||
...state,
|
||||
routes: {
|
||||
...state.routes,
|
||||
|
@ -61,50 +73,68 @@ const searchPutRoutes: ActionHandler<typeof ActionCreators.searchPutRoutes> = (s
|
|||
shift,
|
||||
filter: {
|
||||
...state.routes.filter,
|
||||
distance: (state.routes.filter.min === state.routes.filter.max)
|
||||
? [min, max]
|
||||
: state.routes.filter.distance,
|
||||
distance:
|
||||
state.routes.filter.min === state.routes.filter.max
|
||||
? [min, max]
|
||||
: state.routes.filter.distance,
|
||||
min,
|
||||
max,
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const searchSetLoading: ActionHandler<typeof ActionCreators.searchSetLoading> = (state, { loading = false }) => ({
|
||||
const searchSetLoading: ActionHandler<typeof ActionCreators.searchSetLoading> = (
|
||||
state,
|
||||
{ loading = false }
|
||||
) => ({
|
||||
...state,
|
||||
routes: {
|
||||
...state.routes,
|
||||
loading,
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const setStarred: ActionHandler<typeof ActionCreators.setStarred> = (state, { is_published = false }) => ({ ...state, is_published });
|
||||
const setStarred: ActionHandler<typeof ActionCreators.setStarred> = (
|
||||
state,
|
||||
{ is_published = false }
|
||||
) => ({ ...state, is_published });
|
||||
|
||||
const mapsSetShift: ActionHandler<typeof ActionCreators.mapsSetShift> = (state, { shift = 0 }) => ({
|
||||
...state,
|
||||
routes: {
|
||||
...state.routes,
|
||||
shift,
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const setRouteStarred: ActionHandler<typeof ActionCreators.setRouteStarred> = (state, { address, is_published }) => ({
|
||||
const setRouteStarred: ActionHandler<typeof ActionCreators.setRouteStarred> = (
|
||||
state,
|
||||
{ address, is_published }
|
||||
) => ({
|
||||
...state,
|
||||
routes: {
|
||||
...state.routes,
|
||||
list: (
|
||||
state.routes.list
|
||||
.map(el => el.address === address ? { ...el, is_published } : el)
|
||||
.filter(el => (
|
||||
list: state.routes.list
|
||||
.map(el => (el.address === address ? { ...el, is_published } : el))
|
||||
.filter(
|
||||
el =>
|
||||
(state.routes.filter.tab === TABS.STARRED && el.is_published) ||
|
||||
(state.routes.filter.tab === TABS.PENDING && !el.is_published)
|
||||
))
|
||||
)
|
||||
}
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
export const USER_HANDLERS = ({
|
||||
const setLocation = (
|
||||
state: IRootReducer,
|
||||
{ location }: ReturnType<typeof ActionCreators.setUserLocation>
|
||||
): IRootReducer => ({
|
||||
...state,
|
||||
location,
|
||||
});
|
||||
|
||||
export const USER_HANDLERS = {
|
||||
[USER_ACTIONS.SET_USER]: setUser,
|
||||
[USER_ACTIONS.SET_USER_LOCATION]: setLocation,
|
||||
|
||||
[USER_ACTIONS.SEARCH_SET_TITLE]: searchSetTitle,
|
||||
[USER_ACTIONS.SEARCH_SET_DISTANCE]: searchSetDistance,
|
||||
|
@ -118,4 +148,4 @@ export const USER_HANDLERS = ({
|
|||
[USER_ACTIONS.SET_STARRED]: setStarred,
|
||||
|
||||
[USER_ACTIONS.SET_ROUTE_STARRED]: setRouteStarred,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,43 +1,44 @@
|
|||
import { createReducer } from '~/utils/reducer';
|
||||
import { DEFAULT_USER, IUser } from '~/constants/auth';
|
||||
import { USER_HANDLERS } from './handlers';
|
||||
import { LatLngLiteral } from 'leaflet';
|
||||
|
||||
export interface IRouteListItem {
|
||||
address: string,
|
||||
title: string,
|
||||
distance: number,
|
||||
is_public: boolean,
|
||||
is_published: boolean,
|
||||
updated_at: string,
|
||||
address: string;
|
||||
title: string;
|
||||
distance: number;
|
||||
is_public: boolean;
|
||||
is_published: boolean;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface IRootReducer {
|
||||
// ready: boolean,
|
||||
user: IUser,
|
||||
|
||||
user: IUser;
|
||||
location: LatLngLiteral;
|
||||
routes: {
|
||||
limit: 0,
|
||||
loading: boolean,
|
||||
list: Array<IRouteListItem>,
|
||||
step: number,
|
||||
shift: number,
|
||||
limit: 0;
|
||||
loading: boolean;
|
||||
list: Array<IRouteListItem>;
|
||||
step: number;
|
||||
shift: number;
|
||||
filter: {
|
||||
title: string,
|
||||
starred: boolean,
|
||||
distance: [number, number],
|
||||
author: string,
|
||||
tab: string,
|
||||
min: number,
|
||||
max: number,
|
||||
}
|
||||
},
|
||||
title: string;
|
||||
starred: boolean;
|
||||
distance: [number, number];
|
||||
author: string;
|
||||
tab: string;
|
||||
min: number;
|
||||
max: number;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export type IRootState = Readonly<IRootReducer>;
|
||||
|
||||
export const INITIAL_STATE: IRootReducer = {
|
||||
user: { ...DEFAULT_USER },
|
||||
|
||||
location: null,
|
||||
routes: {
|
||||
limit: 0,
|
||||
loading: false, // <-- maybe delete this
|
||||
|
@ -52,7 +53,7 @@ export const INITIAL_STATE: IRootReducer = {
|
|||
tab: '',
|
||||
min: 0,
|
||||
max: 10000,
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ import { selectUser, selectUserUser } from './selectors';
|
|||
import { mapInitSaga } from '~/redux/map/sagas';
|
||||
import { editorSetDialog, editorSetDialogActive } from '../editor/actions';
|
||||
import { selectEditor } from '../editor/selectors';
|
||||
import { getLocation, watchLocation } from '~/utils/window';
|
||||
|
||||
function* generateGuestSaga() {
|
||||
const {
|
||||
|
@ -343,8 +344,13 @@ export function* updateUserRoutes() {
|
|||
yield put(searchSetTab(TABS.MY));
|
||||
}
|
||||
|
||||
// function* getUserLocation() {
|
||||
// yield call(watchLocation, ActionCreators.setUserLocation);
|
||||
// }
|
||||
|
||||
export function* userSaga() {
|
||||
yield takeLatest(REHYDRATE, authCheckSaga);
|
||||
// yield takeLatest(REHYDRATE, getUserLocation);
|
||||
|
||||
yield takeEvery(USER_ACTIONS.USER_LOGOUT, userLogoutSaga);
|
||||
yield takeLatest(USER_ACTIONS.GOT_VK_USER, gotVkUserSaga);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { IState } from '~/redux/store'
|
||||
|
||||
export const selectUser = (state: IState) => state.user;
|
||||
export const selectUserUser = (state: IState) => state.user.user;
|
||||
export const selectUserUser = (state: IState) => state.user.user;
|
||||
export const selectUserLocation = (state: IState) => state.user.location;
|
|
@ -338,3 +338,12 @@
|
|||
.leaflet-pane {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.current-location {
|
||||
svg {
|
||||
fill: @bar_background;
|
||||
stroke: white;
|
||||
stroke-width: 0.2px;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,38 @@
|
|||
import { MOBILE_BREAKPOINT } from '~/config/frontend';
|
||||
import { LatLngLiteral } from 'leaflet';
|
||||
|
||||
export const isMobile = (): boolean => (window.innerWidth <= MOBILE_BREAKPOINT);
|
||||
export const isMobile = (): boolean => window.innerWidth <= MOBILE_BREAKPOINT;
|
||||
|
||||
export const getLocation = (callback: (pos: LatLngLiteral) => void) => {
|
||||
window.navigator.geolocation.getCurrentPosition(position => {
|
||||
console.log('getting pos');
|
||||
|
||||
if (!position || !position.coords || !position.coords.latitude || !position.coords.longitude)
|
||||
return callback(null);
|
||||
|
||||
const { latitude: lat, longitude: lng } = position.coords;
|
||||
|
||||
callback({ lat, lng });
|
||||
return;
|
||||
});
|
||||
};
|
||||
|
||||
export const watchLocation = (callback: (pos: LatLngLiteral) => void): number => {
|
||||
return window.navigator.geolocation.watchPosition(
|
||||
position => {
|
||||
console.log('Watch?');
|
||||
|
||||
if (!position || !position.coords || !position.coords.latitude || !position.coords.longitude)
|
||||
return callback(null);
|
||||
|
||||
const { latitude: lat, longitude: lng } = position.coords;
|
||||
|
||||
callback({ lat, lng });
|
||||
return;
|
||||
},
|
||||
() => callback(null),
|
||||
{
|
||||
timeout: 30,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue