\n
onRouteColor(index)}\n />\n\n
onFocusRoute(index)}>\n {item.name}\n
\n\n
\n
onRouteReplace(index)}>\n \n
\n\n
onRouteDrop(index)}>\n \n
\n\n
\n onRouteToggle(index)} />\n
\n
\n
\n );\n }\n);\n\nexport { GpxDialogRow };\n","import React, { FC, useCallback, ChangeEvent } from 'react';\nimport { connect } from 'react-redux';\nimport { IState } from '~/redux/store';\nimport { selectEditorGpx } from '~/redux/editor/selectors';\nimport { GpxDialogRow } from '~/components/gpx/GpxDialogRow';\nimport { GpxConfirm } from '~/components/gpx/GpxConfirm';\nimport { MainMap } from '~/constants/map';\nimport { latLngBounds } from 'leaflet';\nimport { Switch } from '../Switch';\nimport { selectMapRoute, selectMapTitle, selectMapAddress } from '~/redux/map/selectors';\nimport classNames from 'classnames';\nimport uuid from 'uuid';\nimport { getUrlData } from '~/utils/history';\nimport { getRandomColor } from '~/utils/dom';\n\nimport * as EDITOR_ACTIONS from '~/redux/editor/actions';\nimport * as MAP_ACTIONS from '~/redux/map/actions';\nimport { simplify } from '~/utils/simplify';\n\nconst mapStateToProps = (state: IState) => ({\n gpx: selectEditorGpx(state),\n route: selectMapRoute(state),\n title: selectMapTitle(state),\n address: selectMapAddress(state),\n});\n\nconst mapDispatchToProps = {\n editorDropGpx: EDITOR_ACTIONS.editorDropGpx,\n editorUploadGpx: EDITOR_ACTIONS.editorUploadGpx,\n editorSetGpx: EDITOR_ACTIONS.editorSetGpx,\n editorGetGPXTrack: EDITOR_ACTIONS.editorGetGPXTrack,\n mapSetRoute: MAP_ACTIONS.mapSetRoute,\n};\n\ntype Props = ReturnType
& typeof mapDispatchToProps & {};\n\nconst GpxDialogUnconnected: FC = ({\n title,\n address,\n gpx,\n route,\n editorGetGPXTrack,\n editorSetGpx,\n editorUploadGpx,\n mapSetRoute,\n}) => {\n const toggleGpx = useCallback(() => {\n editorSetGpx({ enabled: !gpx.enabled });\n }, [gpx, editorSetGpx]);\n\n const onGpxUpload = useCallback(\n (event: ChangeEvent) => {\n if (!event.target || !event.target.files || event.target.files.length === 0) {\n return;\n }\n\n editorUploadGpx(event.target.files[0]);\n },\n [editorUploadGpx]\n );\n\n const onFocusRoute = useCallback(\n index => {\n if (!gpx.list[index] || !gpx.list[index].latlngs) return;\n\n const bounds = latLngBounds(gpx.list[index].latlngs);\n MainMap.fitBounds(bounds);\n },\n [gpx, MainMap]\n );\n\n const onRouteDrop = useCallback(\n index => {\n editorSetGpx({ list: gpx.list.filter((el, i) => i !== index) });\n },\n [gpx, editorSetGpx]\n );\n\n const onRouteColor = useCallback(\n index => {\n if (!gpx.enabled) return;\n editorSetGpx({\n list: gpx.list.map((el, i) => (i !== index ? el : { ...el, color: getRandomColor() })),\n });\n },\n [gpx, editorSetGpx]\n );\n\n const onRouteToggle = useCallback(\n index => {\n if (!gpx.enabled) return;\n\n editorSetGpx({\n list: gpx.list.map((el, i) => (i !== index ? el : { ...el, enabled: !el.enabled })),\n });\n },\n [gpx, editorSetGpx]\n );\n\n const addCurrent = useCallback(() => {\n if (!route.length) return;\n\n const { path } = getUrlData();\n\n editorSetGpx({\n list: [\n ...gpx.list,\n {\n latlngs: route,\n enabled: false,\n name: title || address || path,\n id: uuid(),\n color: getRandomColor(),\n },\n ],\n });\n }, [route, gpx, editorSetGpx]);\n\n const onRouteReplace = useCallback(\n (i: number) => {\n mapSetRoute(simplify(gpx.list[i].latlngs));\n\n editorSetGpx({\n list: gpx.list.map((el, index) => (i !== index ? el : { ...el, enabled: false })),\n });\n },\n [gpx, mapSetRoute, editorSetGpx]\n );\n\n return (\n \n
\n\n {gpx.list.map((item, index) => (\n
\n ))}\n\n
\n
\n\n
\n Добавить текущий\n
\n\n
\n Скачать текущий\n
\n
\n
\n );\n};\n\nconst GpxDialog = connect(mapStateToProps, mapDispatchToProps)(GpxDialogUnconnected);\n\nexport { GpxDialog };\n","import React from 'react';\nimport { PROVIDERS, replaceProviderUrl } from '~/constants/providers';\nimport { Icon } from '~/components/panels/Icon';\nimport classnames from 'classnames';\nimport * as MAP_ACTIONS from \"~/redux/map/actions\";\nimport { selectMapProvider } from '~/redux/map/selectors';\nimport { connect } from 'react-redux';\n\nconst mapStateToProps = state => ({\n provider: selectMapProvider(state),\n});\n\nconst mapDispatchToProps = {\n mapSetProvider: MAP_ACTIONS.mapSetProvider,\n};\n\ntype Props = ReturnType & typeof mapDispatchToProps & {};\n\nconst ProviderDialogUnconnected = ({ provider, mapSetProvider }: Props) => (\n \n
\n {\n Object.keys(PROVIDERS).map(item => (\n
mapSetProvider(item)}\n key={PROVIDERS[item]?.name}\n >\n {\n provider === item &&\n
\n \n
\n }\n
\n ))\n }\n
\n
\n);\n\nconst ProviderDialog = connect(mapStateToProps, mapDispatchToProps)(ProviderDialogUnconnected)\n\nexport { ProviderDialog }\n","import React from 'react';\nimport { connect } from 'react-redux';\nimport { selectEditorRenderer } from '~/redux/editor/selectors';\n\nconst mapStateToProps = state => ({\n renderer: selectEditorRenderer(state),\n});\n\ntype Props = ReturnType & {};\n\nconst ShotPrefetchDialogUnconnected = ({ renderer: { info, progress }}: Props) => (\n \n);\n\nconst ShotPrefetchDialog = connect(mapStateToProps)(ShotPrefetchDialogUnconnected);\n\nexport { ShotPrefetchDialog }","import React, { createElement } from 'react';\nimport { MODES } from '~/constants/modes';\n\nimport { RouterDialog } from '~/components/dialogs/RouterDialog';\nimport { PolylineDialog } from '~/components/dialogs/PolylineDialog';\nimport { StickersDialog } from '~/components/dialogs/StickersDialog';\nimport { TrashDialog } from '~/components/dialogs/TrashDialog';\nimport { LogoDialog } from '~/components/dialogs/LogoDialog';\nimport { SaveDialog } from '~/components/dialogs/SaveDialog';\nimport { CancelDialog } from '~/components/dialogs/CancelDialog';\nimport { GpxDialog } from '~/components/dialogs/GpxDialog';\n\nimport { connect } from 'react-redux';\n\nimport { ProviderDialog } from '~/components/dialogs/ProviderDialog';\nimport { ShotPrefetchDialog } from '~/components/dialogs/ShotPrefetchDialog';\nimport { selectEditorMode } from '~/redux/editor/selectors';\n\nconst mapStateToProps = state => ({ mode: selectEditorMode(state) });\n\ntype Props = ReturnType & {\n width: number;\n};\n\nconst DIALOG_CONTENTS: { [x: string]: any } = {\n [MODES.ROUTER]: RouterDialog,\n [MODES.STICKERS_SELECT]: StickersDialog,\n [MODES.TRASH]: TrashDialog,\n [MODES.LOGO]: LogoDialog,\n [MODES.SAVE]: SaveDialog,\n [MODES.CONFIRM_CANCEL]: CancelDialog,\n [MODES.PROVIDER]: ProviderDialog,\n [MODES.SHOT_PREFETCH]: ShotPrefetchDialog,\n [MODES.POLY]: PolylineDialog,\n [MODES.GPX]: GpxDialog,\n};\n\nconst EditorDialogUnconnected = (props: Props) =>\n props.mode && DIALOG_CONTENTS[props.mode] ? createElement(DIALOG_CONTENTS[props.mode]) : null;\n\nconst EditorDialog = connect(mapStateToProps)(EditorDialogUnconnected);\n\nexport { EditorDialog };\n","import React from 'react';\nimport classnames from 'classnames';\n\nexport const Tooltip = ({ children, position = 'bottom' }: { children: string, position?: string }) => (\n \n {children}\n
\n);\n","import React, { PureComponent } from 'react';\nimport { MODES } from '~/constants/modes';\nimport classnames from 'classnames';\n\nimport { Icon } from '~/components/panels/Icon';\nimport { EditorDialog } from '~/components/panels/EditorDialog';\nimport { connect } from 'react-redux';\nimport {\n editorChangeMode,\n editorKeyPressed,\n editorRedo,\n editorStartEditing,\n editorStopEditing,\n editorTakeAShot,\n editorUndo,\n} from '~/redux/editor/actions';\nimport { Tooltip } from '~/components/panels/Tooltip';\nimport { IState } from '~/redux/store';\nimport { selectEditor } from '~/redux/editor/selectors';\nimport { selectMap } from '~/redux/map/selectors';\n\nconst mapStateToProps = (state: IState) => {\n const { mode, changed, editing, features, history } = selectEditor(state);\n const { route, stickers } = selectMap(state);\n return {\n mode,\n changed,\n editing,\n features,\n history,\n route,\n stickers,\n };\n};\n\nconst mapDispatchToProps = {\n editorChangeMode,\n editorStartEditing,\n editorStopEditing,\n editorTakeAShot,\n editorKeyPressed,\n editorUndo,\n editorRedo,\n};\n\ntype Props = ReturnType & typeof mapDispatchToProps & {};\n\nclass EditorPanelUnconnected extends PureComponent {\n componentDidMount() {\n if (!this.panel) {\n return;\n }\n\n window.addEventListener('keydown', this.onKeyPress as any);\n\n const obj = document.getElementById('control-dialog');\n const { width } = this.panel.getBoundingClientRect();\n\n if (!this.panel || !obj) return;\n\n obj.style.width = String(width);\n }\n\n panel: HTMLDivElement | null = null;\n\n componentWillUnmount() {\n window.removeEventListener('keydown', this.onKeyPress as any);\n }\n\n onKeyPress = event => {\n if (event.target.tagName === 'TEXTAREA' || event.target.tagName === 'INPUT') return;\n\n this.props.editorKeyPressed(event);\n };\n\n startPolyMode = () => this.props.editorChangeMode(MODES.POLY);\n startStickerMode = () => this.props.editorChangeMode(MODES.STICKERS_SELECT);\n startRouterMode = () => this.props.editorChangeMode(MODES.ROUTER);\n startTrashMode = () => this.props.editorChangeMode(MODES.TRASH);\n startSaveMode = () => {\n this.props.editorChangeMode(MODES.SAVE);\n };\n\n render() {\n const {\n mode,\n changed,\n editing,\n features: { routing },\n history: { records, position },\n route,\n stickers,\n } = this.props;\n\n const can_undo = records.length > 0 && position > 0;\n const can_redo = records.length && records.length - 1 > position;\n const can_clear = route.length > 0 || stickers.length > 0;\n\n return (\n \n
{\n this.panel = el;\n }}\n >\n
\n \n\n \n\n \n
\n\n
\n {routing && (\n \n )}\n\n \n\n \n
\n\n
\n\n
\n \n\n \n
\n
\n\n
\n
\n \n
\n
\n\n
\n
\n );\n }\n}\n\nexport const EditorPanel = connect(mapStateToProps, mapDispatchToProps)(EditorPanelUnconnected);\n","import React from 'react';\n\nexport const Fills = () => (\n \n);\n","import React from 'react';\nimport { Icon } from '~/components/panels/Icon';\n\ntype Props = {\n onClick: () => void,\n}\n\nexport const GuestButton = ({ onClick }: Props) => (\n \n \n
\n);\n","import React from 'react';\n\nexport const UserPicture = ({ photo }) => (\n \n);\n","// @flow\nimport React, { FC, memo } from 'react';\nimport { UserPicture } from '~/components/user/UserPicture';\nimport { IUser } from '~/constants/auth';\n\ninterface Props {\n user: IUser;\n setMenuOpened: () => void;\n}\n\nexport const UserButton: FC = memo(({ setMenuOpened, user: { uid, photo, name } }) => (\n \n
\n
\n\n
\n
{name || uid || '...'}
\n
{uid || 'пользователь'}
\n
\n
\n
\n));\n","export const APP_INFO = {\n VERSION: 2,\n RELEASE: 1,\n CHANGELOG: {\n 2: [\n [\n 'Redux, redux-saga', // [26.11.18]\n 'Рисование карт на стороне клиента', // [28.11.18]\n 'Backend на expressjs + mongoose', // [30.11.18]\n 'Импорт данных из старых версий карт', // [06.12.18]\n 'Диалог со списком карт пользователя', // [07.12.18]\n 'Мобильный интерфейс', // [07.12.18]\n 'Приложение для vk', // [11.12.18]\n 'Фильтр в диалоге поиска карт', // [13.12.18]\n 'Экспорт GPX', // [18.02.19]\n 'Улучшенный редактор ломанных', // [23.02.19]\n 'Отметки расстояний и стрелки', // [04.03.19]\n 'Редактирование маршрутов', // [07.03.19]\n ],\n [\n 'Первый коммит', // [15.08.18]\n 'ReactJS для управления интерфейсом', // [15.08.18]\n 'Карта, роутер, стикеры, панели редактора', // [16.08.18]\n 'Выбор логотипа и стиля карты', // [27.08.18]\n 'Переключение режимов, сохранение', // [29.08.18]\n 'Загрузка карт, перерисовка данных, маршруты', // [04.09.18]\n ],\n ],\n 1: [\n [\n 'Первый работающий редактор карт'\n ]\n ]\n }\n};\n","import React from 'react';\nimport { APP_INFO } from '~/constants/app_info';\nimport { userLogout } from \"~/redux/user/actions\";\n\ninterface Props {\n userLogout: typeof userLogout,\n openAppInfoDialog: () => void,\n}\n\nexport const UserMenu = ({ userLogout, openAppInfoDialog }: Props) => (\n \n
\n ORCHID\n
\n MAP\n \n - {(APP_INFO.VERSION || 1)}.{(Object.keys(APP_INFO.CHANGELOG).length || 0)}\n \n
\n
\n О редакторе карт\n
\n
\n Выйти\n
\n
\n);\n\n/*\n \n
Мы храним следующие данные о вас:
\n { id &&
ID: {id}
}\n { agent &&
Браузер: {agent}
}\n { ip &&
Адрес: {ip}
}\n
Мы используем их для авторизации и исправления ошибок.
\n
\n */\n","import React from 'react';\nimport { connect } from 'react-redux';\n\nimport classnames from 'classnames';\nimport { getStyle } from '~/utils/dom';\nimport { nearestInt } from '~/utils/geom';\nimport { parseDesc } from '~/utils/format';\nimport { selectMap } from '~/redux/map/selectors';\nimport { selectEditor } from '~/redux/editor/selectors';\n\nconst mapStateToProps = state => ({\n editor: selectEditor(state),\n map: selectMap(state),\n});\n\ntype Props = ReturnType & {\n minLines?: number;\n maxLines?: number;\n};\n\ninterface State {\n raised: boolean;\n height: number;\n height_raised: number;\n}\n\nexport class TitleDialogUnconnected extends React.PureComponent {\n state = {\n raised: false,\n height: 0,\n height_raised: 0,\n };\n\n onHover = () => this.setState({ raised: true });\n onLeave = () => this.setState({ raised: false });\n\n componentDidMount() {\n this.setMaxHeight();\n }\n\n componentDidUpdate() {\n this.setMaxHeight();\n }\n\n setMaxHeight = () => {\n if (!this.ref_sizer || !this.ref_title || !this.ref_text) return 0;\n\n const { height: sizer_height } = this.ref_sizer.getBoundingClientRect();\n const { height: title_height } = this.ref_title.getBoundingClientRect();\n const { height: text_height } = this.ref_text.getBoundingClientRect();\n\n if (text_height === 0) {\n this.setState({ height: 0, height_raised: 0 });\n return;\n }\n\n const title_margin = parseInt(getStyle(this.ref_title, 'margin-bottom'), 10) || 0;\n const text_margins =\n (parseInt(getStyle(this.ref_text, 'margin-top'), 10) || 0) +\n parseInt(getStyle(this.ref_text, 'margin-bottom'), 10) || 0;\n const text_line = parseInt(getStyle(this.ref_text, 'line-height'), 10) || 0;\n\n const container_height = sizer_height - title_height - title_margin - text_margins;\n\n const min_height = (this.props.minLines || 5) * text_line;\n const max_height = (this.props.maxLines || 20) * text_line;\n\n const height =\n nearestInt(Math.min(container_height, Math.min(text_height, min_height)), text_line) +\n text_margins;\n const height_raised =\n nearestInt(Math.min(container_height, Math.min(text_height, max_height)), text_line) +\n text_margins;\n\n this.setState({\n height: height_raised - height < 2 * text_line ? height_raised : height,\n height_raised,\n });\n };\n\n render() {\n const {\n editor: { editing },\n map: { title, description },\n } = this.props;\n const { raised, height, height_raised } = this.state;\n\n return (\n \n
{\n this.ref_sizer = el;\n }}\n >\n
\n
{\n this.ref_title = el;\n }}\n >\n
{title}
\n \n\n
height,\n })}\n style={{\n height: raised ? height_raised : height,\n marginBottom: height === 0 ? 0 : 15,\n }}\n ref={el => {\n this.ref_overflow = el;\n }}\n >\n
{\n this.ref_text = el;\n }}\n >\n {parseDesc(description)}\n
\n
\n
\n
\n
\n );\n }\n\n ref_sizer;\n ref_title;\n ref_text;\n ref_overflow;\n}\n\nconst TitleDialog = connect(mapStateToProps)(TitleDialogUnconnected);\n\nexport { TitleDialog };\n","import React, { FC, useCallback, useState } from 'react';\nimport { Icon } from '../panels/Icon';\n\ninterface IProps {\n active: boolean;\n onSearch: (search: string) => void;\n}\n\nconst NominatimSearchPanel: FC = ({ active, onSearch }) => {\n const [search, setSearch] = useState('Колывань');\n\n const setValue = useCallback(({ target: { value } }) => setSearch(value), [setSearch]);\n\n const onSubmit = useCallback(event => {\n event.preventDefault();\n \n if (search.length < 3) return;\n\n onSearch(search);\n }, [search, onSearch]);\n\n return (\n \n );\n};\n\nexport { NominatimSearchPanel };\n","import React, { PureComponent } from 'react';\n\nimport { GuestButton } from '~/components/user/GuestButton';\nimport { DEFAULT_USER, ROLES } from '~/constants/auth';\nimport { UserButton } from '~/components/user/UserButton';\nimport { UserMenu } from '~/components/user/UserMenu';\nimport { setUser, userLogout, gotVkUser, openMapDialog } from '~/redux/user/actions';\nimport {\n editorTakeAShot,\n editorSetDialog,\n editorSetDialogActive,\n editorGetGPXTrack,\n editorSearchNominatim,\n editorChangeMode,\n} from '~/redux/editor/actions';\nimport { connect } from 'react-redux';\nimport { Icon } from '~/components/panels/Icon';\n\nimport classnames from 'classnames';\nimport { CLIENT } from '~/config/frontend';\nimport { DIALOGS, TABS } from '~/constants/dialogs';\nimport { Tooltip } from '~/components/panels/Tooltip';\nimport { TitleDialog } from '~/components/dialogs/TitleDialog';\nimport { NominatimSearchPanel } from '~/components/dialogs/NominatimSearchPanel';\nimport { IState } from '~/redux/store';\nimport { MODES } from '~/constants/modes';\n\nconst mapStateToProps = ({\n user: { user },\n editor: { dialog, dialog_active, features },\n map: { route, stickers },\n}: IState) => ({\n dialog,\n dialog_active,\n user,\n route,\n stickers,\n features,\n});\n\nconst mapDispatchToProps = {\n setUser,\n userLogout,\n editorTakeAShot,\n editorSetDialog,\n gotVkUser,\n editorSetDialogActive,\n openMapDialog,\n editorGetGPXTrack,\n editorSearchNominatim,\n editorChangeMode,\n};\n\ntype Props = ReturnType & typeof mapDispatchToProps & {};\n\ninterface State {\n menuOpened: boolean;\n}\n\nexport class UserPanelUnconnected extends PureComponent {\n state = {\n menuOpened: false,\n };\n\n componentDidMount() {\n window.addEventListener('message', (e) => {\n const { data } = e;\n\n if (\n !data ||\n !data.type ||\n data.type !== 'oauth_login' ||\n !data.user ||\n !data.user.id ||\n !data.user.token\n )\n return;\n\n const { id, token, role = 'vk', name = '', ip = '', photo = '', agent = '' } = data.user;\n\n const user = {\n ...DEFAULT_USER,\n role,\n id,\n token,\n userdata: {\n name,\n ip,\n agent,\n photo,\n },\n };\n\n this.setState({ menuOpened: false });\n this.props.gotVkUser(user);\n });\n }\n\n setMenuOpened = () => this.setState({ menuOpened: !this.state.menuOpened });\n\n openMapsDialog = () => {\n this.props.openMapDialog(TABS.MY);\n };\n\n openAppInfoDialog = () => {\n this.setMenuOpened();\n this.props.editorSetDialog(DIALOGS.APP_INFO);\n this.props.editorSetDialogActive(this.props.dialog !== DIALOGS.APP_INFO);\n };\n\n openOauthFrame = () => {\n const width = parseInt(String(window.innerWidth), 10);\n const height = parseInt(String(window.innerHeight), 10);\n const top = (height - 370) / 2;\n const left = (width - 700) / 2;\n\n window.open(\n `https://oauth.vk.com/authorize?client_id=5987644&scope=&redirect_uri=${CLIENT.API_ADDR}/api/auth/vk`,\n 'socialPopupWindow',\n `location=no,width=700,height=370,scrollbars=no,top=${top},left=${left},resizable=no`\n );\n };\n\n openGpxDialog = () => {\n this.props.editorChangeMode(MODES.GPX);\n };\n\n render() {\n const {\n props: { user, dialog, dialog_active, route, stickers, features },\n state: { menuOpened },\n } = this;\n\n // const is_empty = !route.length && !stickers.length;\n\n return (\n \n
\n\n {features.nominatim && (\n
\n )}\n\n
\n
\n {!user || user.role === ROLES.guest ? (\n \n ) : (\n \n )}\n {user && user.role && user.role !== 'guest' && menuOpened && (\n \n )}\n
\n\n
\n\n
\n \n
\n\n
\n \n\n \n \n
\n \n\n
\n\n
\n \n
\n
\n
\n );\n }\n}\n\nconst UserPanel = connect(mapStateToProps, mapDispatchToProps)(UserPanelUnconnected);\n\nexport { UserPanel };\n","import React from 'react';\nimport { Icon } from '~/components/panels/Icon';\n\ntype Props = {\n onCancel: () => void,\n onSubmit: () => void,\n};\n\nexport const RendererPanel = ({ onCancel, onSubmit }: Props) => (\n \n
\n\n
\n \n\n \n
\n
\n);\n","import React from 'react';\n\nimport { connect } from 'react-redux';\nimport Croppr from 'croppr';\nimport 'croppr/dist/croppr.css';\nimport { LOGOS } from '~/constants/logos';\nimport { RendererPanel } from '~/components/panels/RendererPanel';\nimport { selectEditor } from '~/redux/editor/selectors';\nimport * as EDITOR_ACTIONS from '~/redux/editor/actions';\nimport { selectMap } from '~/redux/map/selectors';\n\nconst mapStateToProps = state => ({\n editor: selectEditor(state),\n map: selectMap(state),\n});\n\nconst mapDispatchToProps = {\n editorHideRenderer: EDITOR_ACTIONS.editorHideRenderer,\n editorCropAShot: EDITOR_ACTIONS.editorCropAShot,\n};\n\ntype Props = ReturnType & typeof mapDispatchToProps & {};\n\ntype State = {\n opacity: number,\n};\n\nclass Component extends React.Component {\n state = {\n opacity: 0,\n };\n\n onImageLoaded = () => {\n if (!this.image) {\n return\n }\n\n this.croppr = new Croppr(this.image, {\n onInitialize: this.onCropInit,\n });\n\n this.setState({ opacity: 1 });\n };\n\n componentWillUnmount() {\n if (this.croppr) this.croppr.destroy();\n }\n\n onCropInit = (crop) => {\n const { regionEl, box } = crop;\n const scale = ((box.x2 - box.x1) / window.innerWidth);\n\n this.logo = document.createElement('div');\n this.logo.className = 'renderer-logo';\n this.logo.style.transform = `scale(${scale})`;\n\n this.logoImg = document.createElement('img');\n if (this.props.map.logo && LOGOS[this.props.map.logo][1]) this.logoImg.src = LOGOS[this.props.map.logo][1];\n\n this.logo.append(this.logoImg);\n regionEl.append(this.logo);\n };\n\n croppr?: Croppr;\n logo: HTMLDivElement | null = null;\n image: HTMLImageElement | null = null;\n logoImg: HTMLImageElement | null = null;\n\n getImage = () => this.props.editorCropAShot(this.croppr?.getValue());\n\n render() {\n const { data } = this.props.editor.renderer;\n const { opacity } = this.state;\n const { innerWidth, innerHeight } = window;\n const padding = 30;\n const paddingBottom = 80;\n\n let width;\n let height;\n\n // if (innerWidth > innerHeight) {\n height = innerHeight - padding - paddingBottom;\n width = height * (innerWidth / innerHeight);\n // }\n\n return (\n \n
\n
\n

{ this.image = el; }}\n onLoad={this.onImageLoaded}\n />\n
\n
\n\n
\n
\n );\n }\n}\n\nexport const Renderer = connect(mapStateToProps, mapDispatchToProps)(Component);\n","// @flow\nimport React from \"react\";\nimport { Icon } from \"~/components/panels/Icon\";\nimport { MapListDialog } from \"~/components/dialogs/MapListDialog\";\nimport { Tooltip } from \"~/components/panels/Tooltip\";\nimport { ReactElement } from \"react\";\nimport classnames from \"classnames\";\nimport { toggleRouteStarred } from \"~/redux/user/actions\";\nimport { TABS } from \"~/constants/dialogs\";\n\ninterface Props {\n tab: string;\n\n address: string;\n title: string;\n distance: number;\n is_public: boolean;\n is_admin: boolean;\n is_published: boolean;\n\n openRoute: typeof MapListDialog.openRoute;\n toggleStarred: typeof MapListDialog.toggleStarred;\n startEditing: typeof MapListDialog.startEditing;\n stopEditing: typeof MapListDialog.stopEditing;\n showMenu: typeof MapListDialog.showMenu;\n hideMenu: typeof MapListDialog.hideMenu;\n showDropCard: typeof MapListDialog.showDropCard;\n}\n\nexport const RouteRowView = ({\n title,\n distance,\n address,\n openRoute,\n tab,\n startEditing,\n showMenu,\n showDropCard,\n hideMenu,\n is_admin,\n is_published,\n toggleStarred\n}: Props): ReactElement => (\n \n {(tab === TABS.PENDING || tab === TABS.STARRED) && is_admin && (\n
\n {is_published ? (\n \n ) : (\n \n )}\n
\n )}\n
openRoute(address)}>\n
\n {(tab === \"my\" || !is_admin) && is_published && (\n
\n \n
\n )}\n
{title || address}\n
\n\n
\n \n \n {address}\n \n \n \n {(distance && `${distance} km`) || \"0 km\"}\n \n
\n
\n {tab === \"my\" && (\n
\n \n
\n
\n
\n Удалить\n \n
\n
\n Редактировать\n \n
\n
\n
\n \n )}\n
\n);\n","// @flow\nimport React from 'react';\nimport { Icon } from '~/components/panels/Icon';\nimport { Switch } from '~/components/Switch';\nimport { MapListDialog } from \"~/components/dialogs/MapListDialog\";\n\ninterface Props {\n title: string;\n address: string;\n is_public: boolean,\n modifyRoute: typeof MapListDialog.modifyRoute,\n}\n\ninterface State {\n title: string,\n is_public: boolean,\n}\n\nexport class RouteRowEditor extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n title: props.title,\n is_public: props.is_public,\n };\n }\n\n stopEditing = () => {\n const {\n state: { title, is_public },\n props: { address }\n } = this;\n\n this.props.modifyRoute({ address, title, is_public })\n };\n\n setPublic = () => this.setState({ is_public: !this.state.is_public });\n setTitle = ({ target: { value } }: { target: { value: string } }) => this.setState({ title: value });\n\n render() {\n const {\n state: { title, is_public },\n } = this;\n\n return (\n \n
\n
\n \n
\n
\n
\n
\n \n {\n is_public\n ? ' В каталоге карт'\n : ' Только по ссылке'\n }\n
\n
\n OK\n
\n
\n
\n
\n
\n );\n }\n}\n","// @flow\nimport React, { FC, memo } from 'react';\nimport { MapListDialog } from '~/components/dialogs/MapListDialog';\nimport { ReactElement } from 'react';\n\ninterface Props {\n address: string;\n stopEditing: typeof MapListDialog.stopEditing;\n dropRoute: typeof MapListDialog.dropRoute;\n}\n\nexport const RouteRowDrop: FC = memo(({ address, stopEditing, dropRoute }) => (\n \n
\n
\n
\n Удалить\n
\n
\n Отмена\n
\n
\n
\n
\n));\n","import React, { FC, memo } from 'react';\nimport classnames from 'classnames';\nimport { MapListDialog } from '~/components/dialogs/MapListDialog';\nimport { RouteRowView } from '~/components/maps/RouteRowView';\nimport { RouteRowEditor } from '~/components/maps/RouteRowEditor';\nimport { RouteRowDrop } from '~/components/maps/RouteRowDrop';\nimport { ReactElement } from 'react';\n\ninterface Props {\n address: string;\n tab: string;\n title: string;\n distance: number;\n is_public: boolean;\n is_published: boolean;\n\n is_admin: boolean;\n is_editing_target: boolean;\n is_menu_target: boolean;\n\n openRoute: typeof MapListDialog.openRoute;\n startEditing: typeof MapListDialog.startEditing;\n stopEditing: typeof MapListDialog.stopEditing;\n showMenu: typeof MapListDialog.showMenu;\n hideMenu: typeof MapListDialog.hideMenu;\n showDropCard: typeof MapListDialog.showDropCard;\n dropRoute: typeof MapListDialog.dropRoute;\n modifyRoute: typeof MapListDialog.modifyRoute;\n toggleStarred: typeof MapListDialog.toggleStarred;\n\n is_editing_mode: 'edit' | 'drop';\n}\n\nexport const RouteRowWrapper: FC = memo(\n ({\n title,\n distance,\n address,\n openRoute,\n tab,\n startEditing,\n showMenu,\n showDropCard,\n is_public,\n is_editing_target,\n is_menu_target,\n is_editing_mode,\n dropRoute,\n stopEditing,\n modifyRoute,\n hideMenu,\n is_admin,\n is_published,\n toggleStarred,\n }) => (\n \n {is_editing_target && is_editing_mode === 'edit' && (\n \n )}\n {is_editing_target && is_editing_mode === 'drop' && (\n \n )}\n {!is_editing_target && (\n \n )}\n
\n )\n);\n","import React from 'react';\nimport { Scrollbars } from 'tt-react-custom-scrollbars';\n\nexport const Scroll = props => (\n }\n renderTrackVertical={prop => }\n renderThumbHorizontal={prop => }\n renderThumbVertical={prop => }\n {...props}\n >\n {props.children}\n \n);\n","import React, { FC, memo, useMemo, ChangeEvent, ChangeEventHandler } from 'react';\nimport Range from 'rc-slider/lib/Range';\n\ninterface Props {\n ready: boolean;\n min: number;\n max: number;\n search: string;\n distance: [number, number];\n onDistanceChange: (val: number[]) => void;\n onSearchChange: ChangeEventHandler;\n}\n\nconst MapListDialogHead: FC = memo(\n ({ min, max, ready, distance, search, onSearchChange, onDistanceChange }) => {\n const marks = useMemo(\n () =>\n [...new Array(Math.floor((Math.max(min, max) - Math.min(min, max)) / 25) + 1)].reduce(\n (obj, el, i) => ({\n ...obj,\n [min + i * 25]: min + i * 25 < 200 ? ` ${min + i * 25}` : ` ${min + i * 25}+`,\n }),\n {}\n ),\n [max, min]\n );\n\n return (\n \n
\n
\n\n
\n\n {ready && Object.keys(marks).length > 2 ? (\n
= max}\n />\n ) : (\n \n )}\n \n
\n );\n }\n);\n\nexport { MapListDialogHead };\n","import React, { FC } from 'react';\nimport { Icon } from '~/components/panels/Icon';\n\ninterface IProps {}\n\nconst DialogLoader: FC = ({}) => {\n return (\n \n );\n};\n\nexport { DialogLoader };\n","import React, { PureComponent } from 'react';\nimport { connect } from 'react-redux';\nimport { RouteRowWrapper } from '~/components/maps/RouteRowWrapper';\nimport { Scroll } from '~/components/Scroll';\nimport {\n searchSetDistance,\n searchSetTitle,\n searchSetTab,\n mapsLoadMore,\n dropRoute,\n modifyRoute,\n toggleRouteStarred,\n} from '~/redux/user/actions';\n\nimport { editorSetDialogActive } from '~/redux/editor/actions';\n\nimport { isMobile } from '~/utils/window';\nimport classnames from 'classnames';\n\nimport { TABS, TABS_TITLES } from '~/constants/dialogs';\nimport { Icon } from '~/components/panels/Icon';\nimport { pushPath } from '~/utils/history';\nimport { IRouteListItem } from '~/redux/user';\nimport { ROLES } from '~/constants/auth';\nimport { IState } from '~/redux/store';\nimport { MapListDialogHead } from '~/components/search/MapListDialogHead';\nimport { DialogLoader } from '~/components/dialogs/DialogLoader';\n\nconst mapStateToProps = ({\n editor: { editing },\n user: {\n routes,\n user: { role },\n },\n}: IState) => {\n return {\n role,\n routes,\n editing,\n ready: routes.filter.max < 9999,\n };\n};\n\nconst mapDispatchToProps = {\n searchSetDistance,\n searchSetTitle,\n searchSetTab,\n editorSetDialogActive,\n mapsLoadMore,\n dropRoute,\n modifyRoute,\n toggleRouteStarred,\n};\n\ntype Props = ReturnType & typeof mapDispatchToProps & {};\n\nexport interface State {\n menu_target: IRouteListItem['address'];\n editor_target: IRouteListItem['address'];\n\n is_editing: boolean;\n is_dropping: boolean;\n}\n\nclass MapListDialogUnconnected extends PureComponent {\n state = {\n menu_target: '',\n editor_target: '',\n is_editing: false,\n is_dropping: false,\n };\n\n startEditing = (editor_target: IRouteListItem['address']): void =>\n this.setState({\n editor_target,\n menu_target: '',\n is_editing: true,\n is_dropping: false,\n });\n\n showMenu = (menu_target: IRouteListItem['address']): void =>\n this.setState({\n menu_target,\n });\n\n hideMenu = (): void =>\n this.setState({\n menu_target: '',\n });\n\n showDropCard = (editor_target: IRouteListItem['address']): void =>\n this.setState({\n editor_target,\n menu_target: '',\n is_editing: false,\n is_dropping: true,\n });\n\n stopEditing = (): void => {\n this.setState({ editor_target: '' });\n };\n\n setTitle = ({ target: { value } }: { target: { value: string } }): void => {\n this.props.searchSetTitle(value);\n };\n\n openRoute = (_id: string): void => {\n if (isMobile()) this.props.editorSetDialogActive(false);\n\n this.stopEditing();\n\n pushPath(`/${_id}`);\n };\n\n onScroll = (e: {\n target: { scrollHeight: number; scrollTop: number; clientHeight: number };\n }): void => {\n const {\n target: { scrollHeight, scrollTop, clientHeight },\n } = e;\n const delta = scrollHeight - scrollTop - clientHeight;\n\n if (\n delta < 500 &&\n this.props.routes.list.length < this.props.routes.limit &&\n !this.props.routes.loading\n ) {\n this.props.mapsLoadMore();\n }\n };\n\n dropRoute = (address: string): void => {\n this.props.dropRoute(address);\n };\n\n modifyRoute = ({\n address,\n title,\n is_public,\n }: {\n address: string;\n title: string;\n is_public: boolean;\n }): void => {\n this.props.modifyRoute(address, { title, is_public });\n this.stopEditing();\n };\n\n toggleStarred = (id: string) => this.props.toggleRouteStarred(id);\n\n render() {\n const {\n ready,\n role,\n routes: {\n list,\n loading,\n filter: { min, max, title, distance, tab },\n },\n }: // marks,\n Props = this.props;\n\n const { editor_target, menu_target, is_editing, is_dropping } = this.state;\n\n return (\n \n {list.length === 0 && loading && (\n
\n )}\n\n {ready && !loading && list.length === 0 && (\n
\n
\n \n
\n ТУТ ПУСТО
И ОДИНОКО\n
\n )}\n\n
\n {Object.values(TABS).map(\n item =>\n (role === ROLES.admin || item !== TABS.PENDING) && (\n
this.props.searchSetTab(item)}\n key={item}\n >\n {TABS_TITLES[item]}\n
\n )\n )}\n
\n\n
\n\n
\n \n {list.map(route => (\n \n ))}\n
\n \n\n
\n
\n );\n }\n}\n\nconst MapListDialog = connect(mapStateToProps, mapDispatchToProps)(MapListDialogUnconnected);\n\nexport { MapListDialog };\n","import React, { FC, useCallback } from 'react';\nimport { INominatimResult } from '~/redux/types';\nimport { MainMap } from '~/constants/map';\n\ninterface IProps {\n item: INominatimResult;\n}\n\nconst NominatimListItem: FC = ({ item }) => {\n const onClick = useCallback(() => {\n MainMap.panTo(item.latlng);\n }, [MainMap]);\n\n return (\n \n );\n};\n\nexport { NominatimListItem };\n","import React, { FC, Fragment, useCallback } from 'react';\nimport { connect } from 'react-redux';\nimport { IState } from '~/redux/store';\nimport { selectEditorNominatim } from '~/redux/editor/selectors';\nimport { DialogLoader } from './DialogLoader';\nimport { NominatimListItem } from '~/components/nominatim/NominatimListItem';\nimport { MainMap } from '~/constants/map';\nimport { Scroll } from '../Scroll';\n\nconst mapStateToProps = (state: IState) => ({\n nominatim: selectEditorNominatim(state),\n});\n\ntype Props = ReturnType & {};\n\nconst NominatimDialogUnconnected: FC = ({ nominatim: { loading, list } }) => {\n const onItemClick = useCallback(\n (index: number) => {\n if (!list[index]) return;\n\n MainMap.setView(list[index].latlng, 17);\n },\n [MainMap, list]\n );\n\n return (\n \n \n \n
\n\n
\n {loading && }\n {list.map((item, i) => (\n \n ))}\n
\n
\n \n \n );\n};\n\nconst NominatimDialog = connect(mapStateToProps)(NominatimDialogUnconnected);\n\nexport { NominatimDialog };\n","import React, { createElement, FC, memo } from 'react';\nimport { DIALOGS } from '~/constants/dialogs';\nimport classnames from 'classnames';\nimport { AppInfoDialog } from '~/components/dialogs/AppInfoDialog';\nimport { Icon } from '~/components/panels/Icon';\nimport { MapListDialog } from '~/components/dialogs/MapListDialog';\nimport { NominatimDialog } from '~/components/dialogs/NominatimDialog';\nimport * as EDITOR_ACTIONS from '~/redux/editor/actions';\n\ninterface Props {\n dialog: keyof typeof DIALOGS;\n dialog_active: Boolean;\n editorSetDialogActive: typeof EDITOR_ACTIONS.editorSetDialogActive;\n}\n\nconst LEFT_DIALOGS = {\n [DIALOGS.MAP_LIST]: MapListDialog,\n [DIALOGS.APP_INFO]: AppInfoDialog,\n [DIALOGS.NOMINATIM]: NominatimDialog,\n};\n\nconst LeftDialog: FC = memo(({ dialog, dialog_active, editorSetDialogActive }) => (\n \n {Object.keys(LEFT_DIALOGS).map(item => (\n \n {dialog && LEFT_DIALOGS[item] && createElement(LEFT_DIALOGS[item], {})}\n\n
editorSetDialogActive(false)}\n >\n \n
\n\n
editorSetDialogActive(false)}\n >\n \n
\n
\n ))}\n \n));\n\nexport { LeftDialog };\n","// @flow\nimport React, { Fragment } from 'react';\nimport { Scroll } from '~/components/Scroll';\nimport { APP_INFO } from '~/constants/app_info';\n\nexport const AppInfoDialog = () => (\n \n \n\n \n
\n
Orchid Map
\n
\n версия {APP_INFO.VERSION || 1}.{APP_INFO.CHANGELOG[APP_INFO.VERSION].length || 0}.\n {APP_INFO.CHANGELOG[APP_INFO.VERSION][0].length - 1 || 0}\n
\n
\n
\n
\n
\n \n);\n","import React, { FC, useCallback } from 'react';\nimport { IState } from '~/redux/store';\nimport { selectUserLocation } from '~/redux/user/selectors';\nimport { connect } from 'react-redux';\nimport { Tooltip } from './panels/Tooltip';\nimport { MainMap } from '~/constants/map';\n\nconst mapStateToProps = (state: IState) => ({\n location: selectUserLocation(state),\n});\n\ntype Props = ReturnType & {};\n\nconst UserLocationUnconnected: FC = ({ location }) => {\n const onClick = useCallback(() => {\n if (!location) return;\n\n MainMap.setView(location, 17);\n }, [MainMap, location]);\n\n return (\n \n
Где я?\n\n
\n
\n );\n};\n\nexport const UserLocation = connect(mapStateToProps)(UserLocationUnconnected);\n","import React from 'react';\nimport { toHours } from '~/utils/format';\nimport { Icon } from '~/components/panels/Icon';\nimport { connect } from 'react-redux';\nimport Slider from 'rc-slider/lib/Slider';\nimport { editorSetSpeed } from '~/redux/editor/actions';\nimport { Tooltip } from '~/components/panels/Tooltip';\nimport { isMobile } from '~/utils/window';\nimport { IState } from '~/redux/store';\nimport pick from 'ramda/es/pick';\nimport { selectEditor } from '~/redux/editor/selectors';\n\nconst mapStateToProps = (state: IState) =>\n pick(['distance', 'estimated', 'speed'], selectEditor(state));\n\nconst mapDispatchToProps = { editorSetSpeed };\n\ntype Props = ReturnType & typeof mapDispatchToProps & {};\n\ninterface State {\n dialogOpened: boolean;\n}\n\nclass DistanceBarUnconnected extends React.PureComponent {\n constructor(props) {\n super(props);\n this.state = {\n dialogOpened: false,\n };\n }\n\n step: number = 5;\n min: number = 5;\n max: number = 30;\n\n marks: { [x: number]: string } = [\n ...Array(Math.floor(this.max - this.min) / this.step + 1),\n ].reduce(\n (obj, el, index) => ({\n ...obj,\n [this.min + index * this.step]: String(this.min + index * this.step),\n }),\n {}\n );\n\n toggleDialog = () => {\n if (isMobile()) return;\n\n this.setState({ dialogOpened: !this.state.dialogOpened });\n };\n\n render() {\n const {\n props: { distance, estimated, speed },\n state: { dialogOpened },\n min,\n max,\n step,\n marks,\n } = this;\n\n return (\n \n \n {distance} км \n
Примерное время\n
\n \n \n
{toHours(estimated)}
\n
\n {dialogOpened && (\n \n )}\n \n );\n }\n}\n\nexport const DistanceBar = connect(mapStateToProps, mapDispatchToProps)(DistanceBarUnconnected);\n","import React, { memo } from 'react';\nimport { UserLocation } from '~/components/UserLocation';\nimport { DistanceBar } from '~/components/panels/DistanceBar';\n\nexport const TopLeftPanel = memo(() => (\n \n \n \n
\n));\n","import React, { useCallback } from 'react';\nimport { Icon } from '~/components/panels/Icon';\nimport { PROVIDERS } from '~/constants/providers';\nimport { LOGOS } from '~/constants/logos';\nimport * as EDITOR_ACTIONS from '~/redux/editor/actions';\nimport { connect } from 'react-redux';\nimport { MODES } from '~/constants/modes';\n\nimport { Tooltip } from '~/components/panels/Tooltip';\nimport { selectMap } from '~/redux/map/selectors';\nimport { selectEditor } from '~/redux/editor/selectors';\nimport { IState } from '~/redux/store';\n\nconst mapStateToProps = (state: IState) => {\n const { provider, logo } = selectMap(state);\n const { markers_shown, editing } = selectEditor(state);\n\n return { provider, logo, markers_shown, editing };\n};\n\nconst mapDispatchToProps = {\n editorChangeMode: EDITOR_ACTIONS.editorChangeMode,\n};\n\ntype Props = ReturnType & typeof mapDispatchToProps & {};\n\nconst TopRightPanelUnconnected = ({\n provider,\n logo,\n markers_shown,\n editing,\n editorChangeMode,\n}: Props) => {\n const startProviderMode = useCallback(() => editorChangeMode(MODES.PROVIDER), [editorChangeMode]);\n const startLogoMode = useCallback(() => editorChangeMode(MODES.LOGO), [editorChangeMode]);\n const clearMode = useCallback(() => editorChangeMode(MODES.NONE), [editorChangeMode]);\n\n return (\n \n {editing && !markers_shown && (\n
\n \n Приблизьте, чтобы редактировать кривую\n
\n )}\n
\n
Стиль карты\n
\n
\n
{(provider && PROVIDERS[provider] && PROVIDERS[provider].name) || '...'}\n
\n\n
\n
Логотип\n
\n
\n
{(logo && LOGOS[logo] && LOGOS[logo][0]) || '...'}\n
\n
\n );\n};\n\nconst TopRightPanel = connect(mapStateToProps, mapDispatchToProps)(TopRightPanelUnconnected);\n\nexport { TopRightPanel };\n","import React from 'react';\nimport { LOGOS } from '~/constants/logos';\nimport { connect } from 'react-redux';\nimport { IRootState } from '~/redux/user';\nimport { selectMapLogo } from '~/redux/map/selectors';\n\nconst mapStateToProps = state => ({ logo: selectMapLogo(state) });\ntype Props = ReturnType;\n\nconst LogoPreviewUnconnected = React.memo(({ logo }: Props) => (\n \n));\n\nconst LogoPreview = connect(mapStateToProps)(LogoPreviewUnconnected);\n\nexport { LogoPreview };\n","/*\n done IMPORTANT: select closest point on drag instead of first\n done add touch hint poly\n done approx radius for dragFindNearest\n*/\n\nimport {\n LatLngExpression,\n Marker,\n Polyline,\n PolylineOptions,\n marker,\n divIcon,\n LayerGroup,\n LatLng,\n LeafletMouseEvent,\n latLng,\n LatLngLiteral,\n} from 'leaflet';\n\nimport { distKmHaversine, distToSegment, getPolyLength, pointInArea } from '~/utils/geom';\n\ninterface InteractivePolylineOptions extends PolylineOptions {\n maxMarkers?: number;\n constraintsStyle?: PolylineOptions;\n kmMarksEnabled?: boolean;\n kmMarksStep?: number;\n}\n\nclass InteractivePoly extends Polyline {\n constructor(\n latlngs: LatLngExpression[] | LatLngExpression[][],\n options?: InteractivePolylineOptions\n ) {\n super(latlngs, options);\n\n this.constraintsStyle = {\n ...this.constraintsStyle,\n ...(options?.constraintsStyle || {}),\n };\n this.maxMarkers = options?.maxMarkers || this.maxMarkers;\n\n this.constrLine = new Polyline([], this.constraintsStyle);\n\n this.startDragHinting();\n }\n\n updateConstraintsToLatLngs = (latlngs: LatLngExpression[]) => {\n if (this.is_drawing) {\n // update mouse hinter on latlngs change\n const constraints = this.constrLine.getLatLngs() as LatLng[];\n const source = latlngs && latlngs.length > 0 &&\n this.drawing_direction === 'forward'\n ? latlngs[latlngs.length - 1]\n : latlngs[0];\n\n if (!constraints || constraints.length < 2 || !source) {\n this.setConstraints([]);\n return;\n }\n\n this.setConstraints([constraints[0], source as LatLng]);\n }\n }\n\n setLatLngs = (latlngs: LatLngExpression[] | LatLngExpression[][] | LatLngExpression[][][]) => {\n super.setLatLngs(latlngs);\n\n this.updateConstraintsToLatLngs(latlngs as LatLngExpression[]);\n\n return this;\n };\n\n updateTouchHinter = ({ latlngs }: { latlngs: LatLngLiteral[] }): void => {\n this.touchHinter.setLatLngs(latlngs);\n };\n\n setPoints = (latlngs: LatLng[], emitEvent = false) => {\n this.setLatLngs(latlngs);\n this.recreateMarkers();\n this.recalcDistance();\n this.touchHinter.setLatLngs(latlngs);\n\n if (emitEvent) {\n this.fire('latlngschange', { latlngs });\n }\n };\n\n createHintMarker = (latlng: LatLng): Marker =>\n marker(latlng, {\n draggable: false,\n icon: divIcon({\n className: 'leaflet-vertex-drag-helper',\n iconSize: [11, 11],\n iconAnchor: [6, 6],\n }),\n });\n\n createMarker = (latlng: LatLng): Marker =>\n marker(latlng, {\n draggable: true,\n icon: divIcon({\n className: 'leaflet-vertex-icon',\n iconSize: [11, 11],\n iconAnchor: [6, 6],\n }),\n })\n .on('contextmenu', this.dropMarker)\n .on('drag', this.onMarkerDrag)\n .on('dragstart', this.onMarkerDragStart)\n .on('dragend', this.onMarkerDragEnd)\n .addTo(this.markerLayer);\n\n recreateMarkers = () => {\n this.clearAllMarkers();\n const latlngs = this.getLatLngs();\n\n if (!latlngs || latlngs.length === 0) return;\n\n latlngs.forEach(latlng => this.markers.push(this.createMarker(latlng)));\n\n this.updateMarkers();\n };\n\n clearAllMarkers = (): void => {\n this.markerLayer.clearLayers();\n this.markers = [];\n };\n\n updateMarkers = (): void => {\n this.showVisibleMarkers();\n };\n\n showAllMarkers = (): void => {\n if (!this.is_editing) return;\n if (this._map.hasLayer(this.markerLayer)) return;\n\n this._map.addLayer(this.markerLayer);\n this.fire('allvertexshow');\n };\n\n hideAllMarkers = (): void => {\n if (!this._map || !this._map.hasLayer(this.markerLayer)) return;\n\n this._map.removeLayer(this.markerLayer);\n this.fire('allvertexhide');\n };\n\n showVisibleMarkers = (): void => {\n if (!this.is_editing) return;\n\n const northEast = this._map.getBounds().getNorthEast();\n const southWest = this._map.getBounds().getSouthWest();\n\n const { visible, hidden } = this.markers.reduce(\n (obj, marker) => {\n const { lat, lng } = marker.getLatLng();\n const is_hidden =\n lat > northEast.lat || lng > northEast.lng || lat < southWest.lat || lng < southWest.lng;\n\n return is_hidden\n ? { ...obj, hidden: [...obj.hidden, marker] }\n : { ...obj, visible: [...obj.visible, marker] };\n },\n { visible: [], hidden: [] } as Record<'visible' | 'hidden', Marker[]>\n );\n\n if (visible.length > (this.maxMarkers || 2)) {\n return this.hideAllMarkers();\n }\n\n this.showAllMarkers();\n\n visible.forEach(marker => {\n if (!this.markerLayer.hasLayer(marker)) this.markerLayer.addLayer(marker);\n });\n\n hidden.forEach(marker => {\n if (this.markerLayer.hasLayer(marker)) this.markerLayer.removeLayer(marker);\n });\n };\n\n editor = {\n disable: () => {\n this.hideAllMarkers();\n this.is_editing = false;\n this.stopDragHinting();\n this.stopDrawing();\n this.touchHinter.removeFrom(this._map);\n this.fire('editordisable');\n },\n enable: () => {\n this.is_editing = true;\n this.showVisibleMarkers();\n this.startDragHinting();\n this.touchHinter.addTo(this._map);\n\n this.fire('editorenable');\n },\n continue: () => {\n this.is_drawing = true;\n this.drawing_direction = 'forward';\n this.startDrawing();\n },\n prepend: () => {\n this.is_drawing = true;\n this.drawing_direction = 'backward';\n this.startDrawing();\n },\n stop: () => {\n this.stopDrawing();\n },\n };\n\n moveDragHint = ({ latlng }: LeafletMouseEvent): void => {\n this.hintMarker.setLatLng(latlng);\n };\n\n hideDragHint = (): void => {\n if (this._map.hasLayer(this.hintMarker)) this._map.removeLayer(this.hintMarker);\n };\n\n showDragHint = (): void => {\n this._map.addLayer(this.hintMarker);\n };\n\n startDragHinting = (): void => {\n this.touchHinter.on('mousemove', this.moveDragHint);\n this.touchHinter.on('mousedown', this.startDragHintMove);\n this.touchHinter.on('mouseover', this.showDragHint);\n this.touchHinter.on('mouseout', this.hideDragHint);\n };\n\n stopDragHinting = (): void => {\n this.touchHinter.off('mousemove', this.moveDragHint);\n this.touchHinter.off('mousedown', this.startDragHintMove);\n this.touchHinter.off('mouseover', this.showDragHint);\n this.touchHinter.off('mouseout', this.hideDragHint);\n };\n\n startDragHintMove = (event: LeafletMouseEvent): void => {\n event.originalEvent.stopPropagation();\n event.originalEvent.preventDefault();\n\n if (this.is_drawing) {\n this.stopDrawing();\n this.is_drawing = true;\n }\n\n const prev = this.dragHintFindNearest(event.latlng);\n\n if (prev < 0) return;\n\n this.hint_prev_marker = prev;\n\n this.constrLine.setLatLngs([]).addTo(this._map);\n\n this._map.dragging.disable();\n\n this.is_dragging = true;\n\n this._map.on('mousemove', this.dragHintMove);\n this._map.on('mouseup', this.dragHintAddMarker);\n this._map.on('mouseout', this.stopDragHintMove);\n\n this.fire('vertexaddstart');\n };\n\n stopDragHintMove = (): void => {\n this._map.dragging.enable();\n\n this.constrLine.removeFrom(this._map);\n\n this._map.off('mousemove', this.dragHintMove);\n this._map.off('mouseup', this.dragHintAddMarker);\n this._map.off('mouseout', this.stopDragHintMove);\n\n if (this.is_drawing) this.startDrawing();\n\n setTimeout(() => {\n this.is_dragging = false;\n this.fire('vertexaddend');\n }, 0);\n };\n\n dragHintAddMarker = ({ latlng }: LeafletMouseEvent): void => {\n this.dragHintChangeDistance(this.hint_prev_marker, latlng);\n\n this.markers.splice(this.hint_prev_marker + 1, 0, this.createMarker(latlng));\n this.insertLatLng(latlng, this.hint_prev_marker + 1);\n this.hideDragHint();\n this.stopDragHintMove();\n };\n\n dragHintChangeDistance = (index: number, current: LatLngLiteral): void => {\n const prev = this.markers[index];\n const next = this.markers[index + 1];\n\n const initial_distance = distKmHaversine(prev.getLatLng(), next.getLatLng());\n\n const current_distance =\n ((prev && distKmHaversine(prev.getLatLng(), current)) || 0) +\n ((next && distKmHaversine(next.getLatLng(), current)) || 0);\n\n this.distance += current_distance - initial_distance;\n\n this.fire('distancechange', { distance: this.distance });\n };\n\n dragHintFindNearest = (latlng: LatLng): any => {\n const latlngs = this.getLatLngs() as LatLng[];\n\n const neighbours = latlngs\n .filter((current, index) => {\n const next = latlngs[index + 1] as LatLng;\n\n return next && pointInArea(current, next, latlng);\n })\n .map(el => latlngs.indexOf(el))\n .sort(\n (a, b) =>\n distToSegment(latlngs[a], latlngs[a + 1], latlng) -\n distToSegment(latlngs[b], latlngs[b + 1], latlng)\n );\n\n return neighbours.length > 0 ? neighbours[0] : -1;\n };\n\n dragHintMove = (event: LeafletMouseEvent): void => {\n event.originalEvent.stopPropagation();\n event.originalEvent.preventDefault();\n\n this.setConstraints([\n this.markers[this.hint_prev_marker].getLatLng(),\n event.latlng,\n this.markers[this.hint_prev_marker + 1].getLatLng(),\n ]);\n };\n\n onMarkerDrag = ({ target }: { target: Marker }) => {\n const coords = new Array(0)\n .concat((this.vertex_index! > 0 && this.markers[this.vertex_index! - 1].getLatLng()) || [])\n .concat(target.getLatLng())\n .concat(\n (this.vertex_index! < this.markers.length - 1 &&\n this.markers[this.vertex_index! + 1].getLatLng()) ||\n []\n );\n\n this.setConstraints(coords);\n\n this.fire('vertexdrag', { index: this.vertex_index, vertex: target });\n };\n\n onMarkerDragStart = ({ target }: { target: Marker }) => {\n if (this.is_drawing) {\n this.stopDrawing();\n this.is_drawing = true;\n }\n\n if (this.is_dragging) this.stopDragHintMove();\n\n this.vertex_index = this.markers.indexOf(target);\n\n this.is_dragging = true;\n this.constrLine.addTo(this._map);\n\n this.fire('vertexdragstart', { index: this.vertex_index, vertex: target });\n };\n\n onMarkerDragEnd = ({ target }: { target: Marker }): void => {\n const latlngs = this.getLatLngs() as LatLngLiteral[];\n this.markerDragChangeDistance(\n this.vertex_index!,\n latlngs[this.vertex_index!],\n target.getLatLng()\n );\n\n this.replaceLatlng(target.getLatLng(), this.vertex_index!);\n\n this.is_dragging = false;\n this.constrLine.removeFrom(this._map);\n\n this.vertex_index = 0;\n\n if (this.is_drawing) this.startDrawing();\n\n this.fire('vertexdragend', { index: this.vertex_index, vertex: target });\n };\n\n markerDragChangeDistance = (\n index: number,\n initial: LatLngLiteral,\n current: LatLngLiteral\n ): void => {\n const prev = index > 0 ? this.markers[index - 1] : null;\n const next = index <= this.markers.length + 1 ? this.markers[index + 1] : null;\n\n const initial_distance =\n ((prev && distKmHaversine(prev.getLatLng(), initial)) || 0) +\n ((next && distKmHaversine(next.getLatLng(), initial)) || 0);\n\n const current_distance =\n ((prev && distKmHaversine(prev.getLatLng(), current)) || 0) +\n ((next && distKmHaversine(next.getLatLng(), current)) || 0);\n\n this.distance += current_distance - initial_distance;\n\n this.fire('distancechange', { distance: this.distance });\n };\n\n startDrawing = (): void => {\n this.is_drawing = true;\n this.setConstraints([]);\n this.constrLine.addTo(this._map);\n this._map.on('mousemove', this.onDrawingMove);\n this._map.on('click', this.onDrawingClick);\n };\n\n stopDrawing = (): void => {\n this.constrLine.removeFrom(this._map);\n this._map.off('mousemove', this.onDrawingMove);\n this._map.off('click', this.onDrawingClick);\n this.is_drawing = false;\n };\n\n onDrawingMove = ({ latlng }: LeafletMouseEvent): void => {\n if (this.markers.length === 0) {\n this.setConstraints([]);\n return;\n }\n\n if (!this._map.hasLayer(this.constrLine)) this._map.addLayer(this.constrLine);\n\n const marker =\n this.drawing_direction === 'forward'\n ? this.markers[this.markers.length - 1]\n : this.markers[0];\n\n this.setConstraints([latlng, marker.getLatLng()]);\n };\n\n onDrawingClick = (event: LeafletMouseEvent): void => {\n if (this.is_dragging) return;\n\n const { latlng } = event;\n\n this.stopDrawing();\n\n const latlngs = this.getLatLngs() as any[];\n\n this.drawingChangeDistance(latlng);\n\n if (this.drawing_direction === 'forward') {\n latlngs.push(latlng);\n this.markers.push(this.createMarker(latlng));\n } else {\n latlngs.unshift(latlng);\n this.markers.unshift(this.createMarker(latlng));\n }\n\n this.setLatLngs(latlngs);\n this.fire('latlngschange', { latlngs });\n this.showVisibleMarkers();\n this.startDrawing();\n };\n\n drawingChangeDistance = (latlng: LatLngLiteral): void => {\n const latlngs = this.getLatLngs() as LatLngLiteral[];\n\n if (latlngs.length < 1) {\n this.distance = 0;\n this.fire('distancechange', { distance: this.distance });\n return;\n }\n\n const point = this.drawing_direction === 'forward' ? latlngs[latlngs.length - 1] : latlngs[0];\n\n this.distance += distKmHaversine(point, latlng);\n this.fire('distancechange', { distance: this.distance });\n };\n\n replaceLatlng = (latlng: LatLng, index: number): void => {\n const latlngs = this.getLatLngs() as LatLngLiteral[];\n latlngs.splice(index, 1, latlng);\n this.setLatLngs(latlngs);\n this.fire('latlngschange', { latlngs });\n };\n\n insertLatLng = (latlng, index): void => {\n const latlngs = this.getLatLngs();\n latlngs.splice(index, 0, latlng);\n this.setLatLngs(latlngs);\n this.fire('latlngschange', { latlngs });\n };\n\n setConstraints = (coords: LatLng[]) => {\n this.constrLine.setLatLngs(coords);\n };\n\n setDirection = (direction: 'forward' | 'backward') => {\n this.drawing_direction = direction;\n this.updateConstraintsToLatLngs(this.getLatLngs() as LatLngExpression[]);\n }\n\n dropMarker = ({ target }: LeafletMouseEvent): void => {\n const index = this.markers.indexOf(target);\n const latlngs = this.getLatLngs();\n\n if (typeof index === 'undefined' || latlngs.length === 0) return;\n\n this.dropMarkerDistanceChange(index);\n this._map.removeLayer(this.markers[index]);\n this.markers.splice(index, 1);\n latlngs.splice(index, 1);\n\n this.setLatLngs(latlngs);\n this.fire('latlngschange', { latlngs });\n };\n\n dropMarkerDistanceChange = (index: number): void => {\n const latlngs = this.getLatLngs() as LatLngLiteral[];\n\n const prev = index > 0 ? latlngs[index - 1] : null;\n const current = latlngs[index];\n const next = index <= latlngs.length + 1 ? latlngs[index + 1] : null;\n\n const initial_distance =\n ((prev && distKmHaversine(prev, current)) || 0) +\n ((next && distKmHaversine(next, current)) || 0);\n\n const current_distance = (prev && next && distKmHaversine(prev, next)) || 0;\n\n this.distance += current_distance - initial_distance;\n\n this.fire('distancechange', { distance: this.distance });\n };\n\n recalcDistance = () => {\n const latlngs = this.getLatLngs() as LatLngLiteral[];\n this.distance = getPolyLength(latlngs);\n\n this.fire('distancechange', { distance: this.distance });\n };\n\n markers: Marker[] = [];\n maxMarkers: InteractivePolylineOptions['maxMarkers'] = 2;\n markerLayer: LayerGroup = new LayerGroup();\n\n constraintsStyle: InteractivePolylineOptions['constraintsStyle'] = {\n weight: 6,\n color: 'red',\n dashArray: '10, 12',\n opacity: 0.5,\n interactive: false,\n };\n\n touchHinter: Polyline = new Polyline([], {\n weight: 24,\n smoothFactor: 3,\n className: 'touch-hinter-poly',\n });\n\n hintMarker: Marker = this.createHintMarker(latLng({ lat: 0, lng: 0 }));\n\n constrLine: Polyline;\n\n is_editing: boolean = true;\n is_dragging: boolean = false;\n is_drawing: boolean = false;\n\n drawing_direction: 'forward' | 'backward' = 'forward';\n vertex_index: number = 0;\n\n hint_prev_marker: number = 0;\n distance: number = 0;\n}\n\nInteractivePoly.addInitHook(function(this: InteractivePoly) {\n this.once('add', event => {\n if (event.target instanceof InteractivePoly) {\n this._map = event.target._map;\n\n this._map.on('touch', console.log);\n\n this.markerLayer.addTo(event.target._map);\n this.hintMarker.addTo(event.target._map);\n this.constrLine.addTo(event.target._map);\n this.touchHinter.addTo(event.target._map);\n\n this._map.on('moveend', this.updateMarkers);\n\n this.on('latlngschange' as any, this.updateTouchHinter as any);\n\n if (this.touchHinter && window.innerWidth < 768) {\n try {\n this.touchHinter.setStyle({ weight: 32 });\n } catch (e) {}\n }\n }\n });\n\n this.once('remove', event => {\n if (event.target instanceof InteractivePoly) {\n this.markerLayer.removeFrom(this._map);\n this.hintMarker.removeFrom(this._map);\n this.constrLine.removeFrom(this._map);\n this.touchHinter.removeFrom(this._map);\n\n this._map.off('moveend', this.updateMarkers);\n }\n });\n});\n\nexport { InteractivePoly };\n/*\n events:\n vertexdragstart,\n vertexdragend,\n vertexdrag,\n vertexaddstart\n vertexaddend\n\n allvertexhide\n allvertexshow\n\n editordisable\n editorenable\n\n distancechange\n\n latlngschange\n */\n","import React, { FC, memo, useCallback, useEffect, useState } from 'react';\nimport { InteractivePoly } from '~/utils/map/InteractivePoly';\nimport { isMobile } from '~/utils/window';\nimport { LatLng } from 'leaflet';\nimport { selectEditorDirection, selectEditorEditing, selectEditorMode } from '~/redux/editor/selectors';\nimport * as MAP_ACTIONS from '~/redux/map/actions';\nimport { connect } from 'react-redux';\nimport { selectMapRoute } from '~/redux/map/selectors';\nimport { MainMap } from '~/constants/map';\nimport { MODES } from '~/constants/modes';\nimport * as EDITOR_ACTIONS from '~/redux/editor/actions';\nimport { IState } from '~/redux/store';\n\nconst mapStateToProps = (state: IState) => ({\n mode: selectEditorMode(state),\n editing: selectEditorEditing(state),\n route: selectMapRoute(state),\n drawing_direction: selectEditorDirection(state),\n});\n\nconst mapDispatchToProps = {\n mapSetRoute: MAP_ACTIONS.mapSetRoute,\n editorSetDistance: EDITOR_ACTIONS.editorSetDistance,\n editorSetMarkersShown: EDITOR_ACTIONS.editorSetMarkersShown,\n};\n\ntype Props = ReturnType & typeof mapDispatchToProps & {};\n\nconst RouteUnconnected: FC = memo(\n ({ route, editing, mode, drawing_direction, mapSetRoute, editorSetDistance, editorSetMarkersShown }) => {\n const [layer, setLayer] = useState(null);\n\n const onDistanceChange = useCallback(({ distance }) => editorSetDistance(distance), [\n editorSetDistance,\n ]);\n\n useEffect(() => {\n if (!MainMap) return;\n\n const interactive = new InteractivePoly([], {\n color: 'url(#activePathGradient)',\n weight: 6,\n maxMarkers: isMobile() ? 50 : 150,\n smoothFactor: 3,\n })\n .addTo(MainMap.routeLayer)\n .on('distancechange', onDistanceChange)\n .on('vertexdragstart', MainMap.disableClicks)\n .on('vertexdragend', MainMap.enableClicks)\n .on('vertexaddstart', MainMap.disableClicks)\n .on('vertexaddend', MainMap.enableClicks)\n .on('allvertexhide', () => editorSetMarkersShown(false))\n .on('allvertexshow', () => editorSetMarkersShown(true));\n\n setLayer(interactive);\n\n return () => {\n MainMap.routeLayer.removeLayer(interactive);\n };\n }, [MainMap, onDistanceChange]);\n\n const onRouteChanged = useCallback(\n ({ latlngs }) => {\n mapSetRoute(latlngs);\n },\n [mapSetRoute]\n );\n\n useEffect(() => {\n if (!layer) return;\n\n layer.on('latlngschange', onRouteChanged);\n\n return () => layer.off('latlngschange', onRouteChanged);\n }, [layer, onRouteChanged]);\n\n useEffect(() => {\n if (!layer) return;\n\n const points = (route && route.length > 0 && route) || [];\n\n layer.setPoints(points as LatLng[]);\n }, [route, layer]);\n\n useEffect(() => {\n if (!layer) return;\n\n if (editing) {\n layer.editor.enable();\n } else {\n layer.editor.disable();\n }\n }, [editing, layer]);\n\n useEffect(() => {\n if (!layer) return;\n\n if (mode === MODES.POLY && !layer.is_drawing) {\n layer.editor.continue();\n }\n\n if (mode !== MODES.POLY && layer.is_drawing) {\n layer.editor.stop();\n }\n }, [mode, layer]);\n\n useEffect(() => {\n if (!layer) return;\n\n layer.setDirection(drawing_direction);\n }, [drawing_direction, layer]);\n\n return null;\n }\n);\n\nconst Route = connect(mapStateToProps, mapDispatchToProps)(RouteUnconnected);\n\nexport { Route };\n","import { FC, memo, useCallback, useEffect, useState } from 'react';\nimport { OsrmRouter } from '~/utils/map/OsrmRouter';\nimport { connect } from 'react-redux';\nimport { selectMapRoute } from '~/redux/map/selectors';\nimport { selectEditorDistance, selectEditorMode, selectEditorRouter } from '~/redux/editor/selectors';\nimport { MainMap } from '~/constants/map';\nimport * as EDITOR_ACTIONS from '~/redux/editor/actions';\nimport { MODES } from '~/constants/modes';\nimport { divIcon, LatLngLiteral, marker } from 'leaflet';\nimport classNames from 'classnames';\nimport { angleBetweenPoints } from '~/utils/geom';\n\nconst mapStateToProps = state => ({\n route: selectMapRoute(state),\n router: selectEditorRouter(state),\n mode: selectEditorMode(state),\n distance: selectEditorDistance(state),\n});\n\nconst mapDispatchToProps = {\n editorSetRouter: EDITOR_ACTIONS.editorSetRouter,\n};\n\ntype Props = ReturnType & typeof mapDispatchToProps & {};\n\nconst RouterUnconnected: FC = memo(\n ({ route, mode, router: { waypoints }, editorSetRouter, distance }) => {\n const [dist, setDist] = useState(0);\n const [end, setEnd] = useState(null);\n const [direction, setDirection] = useState(false);\n\n const updateWaypoints = useCallback(\n ({ waypoints }) => {\n const filtered = waypoints.filter(wp => !!wp.latLng);\n\n if (filtered.length < 2) {\n setDist(0);\n }\n\n editorSetRouter({ waypoints: filtered });\n },\n [editorSetRouter, setDist]\n );\n\n const updateDistance = useCallback(\n ({ routes, waypoints }) => {\n if (!routes || !routes.length || waypoints.length < 2) {\n setDist(0);\n return;\n }\n\n const { summary, coordinates } = routes[0];\n\n if (coordinates.length <= 1) {\n setDist(0);\n return;\n }\n\n const totalDistance = parseFloat((summary.totalDistance / 1000).toFixed(1)) || 0;\n const latlng =\n (waypoints[waypoints.length - 1] && waypoints[waypoints.length - 1].latLng) || null;\n\n const angle = angleBetweenPoints(\n MainMap.latLngToContainerPoint(waypoints[waypoints.length - 1].latLng),\n MainMap.latLngToContainerPoint(coordinates[coordinates.length - 1])\n );\n\n setDist(totalDistance);\n setEnd(latlng);\n setDirection(angle > -90 && angle < 90);\n },\n [setDist, setEnd]\n );\n\n useEffect(() => {\n OsrmRouter.on('waypointschanged', updateWaypoints)\n .on('routesfound', updateDistance)\n .addTo(MainMap);\n\n return () => {\n OsrmRouter.off('waypointschanged', updateWaypoints).setWaypoints([]);\n };\n }, [MainMap, updateWaypoints, mode]);\n\n useEffect(() => {\n if (!dist || !end) {\n return;\n }\n\n const item = marker(end, {\n draggable: false,\n interactive: false,\n icon: divIcon({\n html: `\n \n ${parseFloat((distance + dist).toFixed(1))}\n
\n `,\n className: classNames('router-marker', { right: !direction }),\n iconSize: [11, 11],\n iconAnchor: [6, 6],\n }),\n zIndexOffset: -100,\n });\n\n item.addTo(MainMap);\n\n return () => {\n item.removeFrom(MainMap).remove();\n };\n }, [dist, end, direction, distance]);\n\n useEffect(() => {\n if (mode !== MODES.ROUTER) {\n setDist(0);\n return;\n }\n\n const wp = OsrmRouter.getWaypoints()\n .filter(point => point.latLng)\n .map(point => point.latLng);\n\n if (\n !route.length ||\n !wp.length ||\n (route[route.length - 1].lat === wp[0].lat && route[route.length - 1].lng === wp[0].lng)\n )\n return;\n\n OsrmRouter.setWaypoints([route[route.length - 1], ...wp]);\n }, [route, mode, waypoints]);\n\n return null;\n }\n);\n\nconst Router = connect(mapStateToProps, mapDispatchToProps)(RouterUnconnected);\n\nexport { Router };\n","import React from 'react';\nimport { Map, TileLayer } from 'leaflet';\n\nexport const MapContext = React.createContext