diff --git a/src/index.tsx b/src/index.tsx index 30e849b..2e6eaca 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -23,10 +23,13 @@ export const Index = () => ( ReactDOM.render(, 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)); + } + })(); +} diff --git a/src/map/CurrentLocation/index.tsx b/src/map/CurrentLocation/index.tsx new file mode 100644 index 0000000..1710a18 --- /dev/null +++ b/src/map/CurrentLocation/index.tsx @@ -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 = ({ location }) => { + useEffect(() => { + if (!location) return; + + const item = new Marker(location, { + icon: new DivIcon({ + html: ` +
+ + + + +
+ + `, + }), + }).addTo(MainMap); + + return () => item.removeFrom(MainMap); + }, [MainMap, location]); + + return null; +}; + +export { CurrentLocation }; diff --git a/src/map/Map/index.tsx b/src/map/Map/index.tsx index b2ad195..95f3752 100644 --- a/src/map/Map/index.tsx +++ b/src/map/Map/index.tsx @@ -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 = ({ stickers, editing, mode, + location, mapClicked, mapSetSticker, @@ -78,6 +81,7 @@ const MapUnconnected: React.FC = ({ + , document.getElementById('canvas') ); diff --git a/src/redux/store.ts b/src/redux/store.ts index b2a9195..550ac22 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -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' && - (window).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ + typeof window === 'object' && (window).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? (window).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose; @@ -49,7 +49,7 @@ export const store = createStore( composeEnhancers(applyMiddleware(sagaMiddleware)) ); -export function configureStore(): { store: Store, persistor: Persistor } { +export function configureStore(): { store: Store; 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))); diff --git a/src/redux/user/actions.ts b/src/redux/user/actions.ts index 81f970f..0f4bec4 100644 --- a/src/redux/user/actions.ts +++ b/src/redux/user/actions.ts @@ -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 }); diff --git a/src/redux/user/constants.ts b/src/redux/user/constants.ts index e7cae5b..dbe4a00 100644 --- a/src/redux/user/constants.ts +++ b/src/redux/user/constants.ts @@ -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', diff --git a/src/redux/user/handlers.ts b/src/redux/user/handlers.ts index 3550434..5473031 100644 --- a/src/redux/user/handlers.ts +++ b/src/redux/user/handlers.ts @@ -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 extends (...args: any[]) => infer R ? R : any; @@ -17,7 +17,10 @@ const setUser: ActionHandler = (state, { user }) }, }); -const searchSetTitle: ActionHandler = (state, { title = '' }) => ({ +const searchSetTitle: ActionHandler = ( + state, + { title = '' } +) => ({ ...state, routes: { ...state.routes, @@ -25,33 +28,42 @@ const searchSetTitle: ActionHandler = (sta ...state.routes.filter, title, distance: [0, 10000], - } - } + }, + }, }); -const searchSetDistance: ActionHandler = (state, { distance = [0, 9999] }) => ({ +const searchSetDistance: ActionHandler = ( + state, + { distance = [0, 9999] } +) => ({ ...state, routes: { ...state.routes, filter: { ...state.routes.filter, distance, - } - } + }, + }, }); -const searchSetTab: ActionHandler = (state, { tab = TABS[Object.keys(TABS)[0]] }) => ({ +const searchSetTab: ActionHandler = ( + 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 = (state, { list = [], min, max, limit, step, shift }) => ({ +const searchPutRoutes: ActionHandler = ( + state, + { list = [], min, max, limit, step, shift } +) => ({ ...state, routes: { ...state.routes, @@ -61,50 +73,68 @@ const searchPutRoutes: ActionHandler = (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 = (state, { loading = false }) => ({ +const searchSetLoading: ActionHandler = ( + state, + { loading = false } +) => ({ ...state, routes: { ...state.routes, loading, - } + }, }); -const setStarred: ActionHandler = (state, { is_published = false }) => ({ ...state, is_published }); +const setStarred: ActionHandler = ( + state, + { is_published = false } +) => ({ ...state, is_published }); const mapsSetShift: ActionHandler = (state, { shift = 0 }) => ({ ...state, routes: { ...state.routes, shift, - } + }, }); -const setRouteStarred: ActionHandler = (state, { address, is_published }) => ({ +const setRouteStarred: ActionHandler = ( + 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 +): 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, -}); \ No newline at end of file +}; diff --git a/src/redux/user/index.ts b/src/redux/user/index.ts index ef444d4..162d8d0 100644 --- a/src/redux/user/index.ts +++ b/src/redux/user/index.ts @@ -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, - step: number, - shift: number, + limit: 0; + loading: boolean; + list: Array; + 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; 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, - } + }, }, }; diff --git a/src/redux/user/sagas.ts b/src/redux/user/sagas.ts index 744dd87..32c0893 100644 --- a/src/redux/user/sagas.ts +++ b/src/redux/user/sagas.ts @@ -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); diff --git a/src/redux/user/selectors.ts b/src/redux/user/selectors.ts index 6de1196..7a8cc68 100644 --- a/src/redux/user/selectors.ts +++ b/src/redux/user/selectors.ts @@ -1,4 +1,5 @@ import { IState } from '~/redux/store' export const selectUser = (state: IState) => state.user; -export const selectUserUser = (state: IState) => state.user.user; \ No newline at end of file +export const selectUserUser = (state: IState) => state.user.user; +export const selectUserLocation = (state: IState) => state.user.location; \ No newline at end of file diff --git a/src/styles/map.less b/src/styles/map.less index 608a19b..671303e 100644 --- a/src/styles/map.less +++ b/src/styles/map.less @@ -338,3 +338,12 @@ .leaflet-pane { user-select: none; } + +.current-location { + svg { + fill: @bar_background; + stroke: white; + stroke-width: 0.2px; + } +} + \ No newline at end of file diff --git a/src/utils/window.ts b/src/utils/window.ts index 8174964..0d60bb7 100644 --- a/src/utils/window.ts +++ b/src/utils/window.ts @@ -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, + } + ); +};