nominatim search (without working dialog)

This commit is contained in:
Fedor Katurov 2020-01-20 16:42:46 +07:00
parent c3e136cebb
commit b20a3445d1
27 changed files with 450 additions and 61 deletions

101
package-lock.json generated
View file

@ -775,6 +775,77 @@
"resolved": "https://registry.npmjs.org/@mapbox/polyline/-/polyline-0.2.0.tgz",
"integrity": "sha1-biWYB0SqIjMflLZFpULALT/P7pc="
},
"@redux-saga/core": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@redux-saga/core/-/core-1.1.3.tgz",
"integrity": "sha512-8tInBftak8TPzE6X13ABmEtRJGjtK17w7VUs7qV17S8hCO5S3+aUTWZ/DBsBJPdE8Z5jOPwYALyvofgq1Ws+kg==",
"requires": {
"@babel/runtime": "7.8.3",
"@redux-saga/deferred": "1.1.2",
"@redux-saga/delay-p": "1.1.2",
"@redux-saga/is": "1.1.2",
"@redux-saga/symbols": "1.1.2",
"@redux-saga/types": "1.1.0",
"redux": "4.0.5",
"typescript-tuple": "2.2.1"
},
"dependencies": {
"@babel/runtime": {
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.3.tgz",
"integrity": "sha512-fVHx1rzEmwB130VTkLnxR+HmxcTjGzH12LYQcFFoBwakMd3aOMD4OsRN7tGG/UOYE2ektgFrS8uACAoRk1CY0w==",
"requires": {
"regenerator-runtime": "0.13.3"
}
},
"redux": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz",
"integrity": "sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==",
"requires": {
"loose-envify": "1.4.0",
"symbol-observable": "1.2.0"
}
},
"regenerator-runtime": {
"version": "0.13.3",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
"integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw=="
}
}
},
"@redux-saga/deferred": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@redux-saga/deferred/-/deferred-1.1.2.tgz",
"integrity": "sha512-908rDLHFN2UUzt2jb4uOzj6afpjgJe3MjICaUNO3bvkV/kN/cNeI9PMr8BsFXB/MR8WTAZQq/PlTq8Kww3TBSQ=="
},
"@redux-saga/delay-p": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@redux-saga/delay-p/-/delay-p-1.1.2.tgz",
"integrity": "sha512-ojc+1IoC6OP65Ts5+ZHbEYdrohmIw1j9P7HS9MOJezqMYtCDgpkoqB5enAAZrNtnbSL6gVCWPHaoaTY5KeO0/g==",
"requires": {
"@redux-saga/symbols": "1.1.2"
}
},
"@redux-saga/is": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@redux-saga/is/-/is-1.1.2.tgz",
"integrity": "sha512-OLbunKVsCVNTKEf2cH4TYyNbbPgvmZ52iaxBD4I1fTif4+MTXMa4/Z07L83zW/hTCXwpSZvXogqMqLfex2Tg6w==",
"requires": {
"@redux-saga/symbols": "1.1.2",
"@redux-saga/types": "1.1.0"
}
},
"@redux-saga/symbols": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@redux-saga/symbols/-/symbols-1.1.2.tgz",
"integrity": "sha512-EfdGnF423glv3uMwLsGAtE6bg+R9MdqlHEzExnfagXPrIiuxwr3bdiAwz3gi+PsrQ3yBlaBpfGLtDG8rf3LgQQ=="
},
"@redux-saga/types": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@redux-saga/types/-/types-1.1.0.tgz",
"integrity": "sha512-afmTuJrylUU/0OtqzaRkbyYFFNgCF73Bvel/sw90pvGrWIZ+vyoIJqA6eMSoA6+nb443kTmulmBtC9NerXboNg=="
},
"@types/classnames": {
"version": "2.2.7",
"resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.7.tgz",
@ -11242,9 +11313,12 @@
"integrity": "sha512-sSJAzNq7zka3qVHKce1hbvqf0Vf5DuTVm7dr4GtsqQVOexnrvbV47RWFiPxQ8fscnyiuWyD2O92DOxPl0tGCRg=="
},
"redux-saga": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-0.16.2.tgz",
"integrity": "sha512-iIjKnRThI5sKPEASpUvySemjzwqwI13e3qP7oLub+FycCRDysLSAOwt958niZW6LhxfmS6Qm1BzbU70w/Koc4w=="
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-1.1.3.tgz",
"integrity": "sha512-RkSn/z0mwaSa5/xH/hQLo8gNf4tlvT18qXDNvedihLcfzh+jMchDgaariQoehCpgRltEm4zHKJyINEz6aqswTw==",
"requires": {
"@redux-saga/core": "1.1.3"
}
},
"reduxsauce": {
"version": "1.0.1",
@ -13112,6 +13186,27 @@
"integrity": "sha512-A25xv5XCtarLwXpcDNZzCGvW2D1S3/bACratYBx2sax8PefsFhlYmkQicKHvpYflFS8if4zne5zT5kpJ7pzuvw==",
"dev": true
},
"typescript-compare": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/typescript-compare/-/typescript-compare-0.0.2.tgz",
"integrity": "sha512-8ja4j7pMHkfLJQO2/8tut7ub+J3Lw2S3061eJLFQcvs3tsmJKp8KG5NtpLn7KcY2w08edF74BSVN7qJS0U6oHA==",
"requires": {
"typescript-logic": "0.0.0"
}
},
"typescript-logic": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/typescript-logic/-/typescript-logic-0.0.0.tgz",
"integrity": "sha512-zXFars5LUkI3zP492ls0VskH3TtdeHCqu0i7/duGt60i5IGPIpAHE/DWo5FqJ6EjQ15YKXrt+AETjv60Dat34Q=="
},
"typescript-tuple": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/typescript-tuple/-/typescript-tuple-2.2.1.tgz",
"integrity": "sha512-Zcr0lbt8z5ZdEzERHAMAniTiIKerFCMgd7yjq1fPnDJ43et/k9twIFQMUYff9k5oXcsQ0WpvFcgzK2ZKASoW6Q==",
"requires": {
"typescript-compare": "0.0.2"
}
},
"uglifyjs-webpack-plugin": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.3.0.tgz",

View file

@ -95,7 +95,7 @@
"reactrangeslider": "^3.0.6",
"redux": "^4.0.1",
"redux-persist": "^5.10.0",
"redux-saga": "^0.16.2",
"redux-saga": "^1.0.0",
"reduxsauce": "^1.0.0",
"scrypt": "^6.0.3",
"throttle-debounce": "^2.1.0",

View file

@ -0,0 +1,16 @@
import React, { FC } from 'react';
import { Icon } from '~/components/panels/Icon';
interface IProps {}
const DialogLoader: FC<IProps> = ({}) => {
return (
<div className="dialog-maplist-loader">
<div className="dialog-maplist-icon spin">
<Icon icon="icon-sync-1" />
</div>
</div>
);
};
export { DialogLoader };

View file

@ -24,6 +24,7 @@ import { IRouteListItem } from '~/redux/user';
import { ROLES } from '~/constants/auth';
import { IState } from '~/redux/store';
import { MapListDialogHead } from '~/components/search/MapListDialogHead';
import { DialogLoader } from '~/components/dialogs/DialogLoader';
const mapStateToProps = ({
editor: { editing },
@ -165,11 +166,7 @@ class MapListDialogUnconnected extends PureComponent<Props, State> {
return (
<div className="dialog-content full">
{list.length === 0 && loading && (
<div className="dialog-maplist-loader">
<div className="dialog-maplist-icon spin">
<Icon icon="icon-sync-1" />
</div>
</div>
<DialogLoader />
)}
{ready && !loading && list.length === 0 && (

View file

@ -0,0 +1,46 @@
import React, { FC, Fragment, useCallback } from 'react';
import { connect } from 'react-redux';
import { IState } from '~/redux/store';
import { selectEditorNominatim } from '~/redux/editor/selectors';
import { DialogLoader } from './DialogLoader';
import { NominatimListItem } from '~/components/nominatim/NominatimListItem';
import { MainMap } from '~/constants/map';
import { Scroll } from '../Scroll';
const mapStateToProps = (state: IState) => ({
nominatim: selectEditorNominatim(state),
});
type Props = ReturnType<typeof mapStateToProps> & {};
const NominatimDialogUnconnected: FC<Props> = ({ nominatim: { loading, list } }) => {
const onItemClick = useCallback(
(index: number) => {
if (!list[index]) return;
MainMap.setView(list[index].latlng, 17);
},
[MainMap, list]
);
return (
<Fragment>
<div style={{ flex: 1 }} />
<div className="dialog-flex-scroll">
<Scroll>
<div className="dialog-content nominatim-dialog-content">
{loading && <DialogLoader />}
{list.map((item, i) => (
<NominatimListItem item={item} key={item.id} />
))}
</div>
</Scroll>
</div>
</Fragment>
);
};
const NominatimDialog = connect(mapStateToProps)(NominatimDialogUnconnected);
export { NominatimDialog };

View file

@ -0,0 +1,37 @@
import React, { FC, useCallback, useState } from 'react';
import { Icon } from '../panels/Icon';
interface IProps {
active: boolean;
onSearch: (search: string) => void;
}
const NominatimSearchPanel: FC<IProps> = ({ active, onSearch }) => {
const [search, setSearch] = useState('Колывань');
const setValue = useCallback(({ target: { value } }) => setSearch(value), [setSearch]);
const onSubmit = useCallback(event => {
event.preventDefault();
if (search.length < 3) return;
onSearch(search);
}, [search, onSearch]);
return (
<form className="panel nominatim-panel active" onSubmit={onSubmit}>
<div className="control-bar">
<div className="nominatim-search-input">
<input type="text" placeholder="Поиск на карте" value={search} onChange={setValue} />
</div>
<button>
<Icon icon="icon-search" />
</button>
</div>
</form>
);
};
export { NominatimSearchPanel };

View file

@ -0,0 +1,21 @@
import React, { FC, useCallback } from 'react';
import { INominatimResult } from '~/redux/types';
import { MainMap } from '~/constants/map';
interface IProps {
item: INominatimResult;
}
const NominatimListItem: FC<IProps> = ({ item }) => {
const onClick = useCallback(() => {
MainMap.panTo(item.latlng);
}, [MainMap]);
return (
<div onClick={onClick} className="nominatim-list-item">
<div className="title">{item.title}</div>
</div>
);
};
export { NominatimListItem };

View file

@ -32,7 +32,7 @@ type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {}
class EditorPanelUnconnected extends PureComponent<Props, void> {
componentDidMount() {
window.addEventListener('keydown', this.props.editorKeyPressed as any);
window.addEventListener('keydown', this.onKeyPress as any);
const obj = document.getElementById('control-dialog');
const { width } = this.panel.getBoundingClientRect();
@ -45,9 +45,15 @@ class EditorPanelUnconnected extends PureComponent<Props, void> {
panel: HTMLElement = null;
componentWillUnmount() {
window.removeEventListener('keydown', this.props.editorKeyPressed as any);
window.removeEventListener('keydown', this.onKeyPress as any);
}
onKeyPress = event => {
if (event.target.tagName === 'TEXTAREA' || event.target.tagName === 'INPUT') return;
this.props.editorKeyPressed(event);
};
startPolyMode = () => this.props.editorSetMode(MODES.POLY);
startStickerMode = () => this.props.editorSetMode(MODES.STICKERS_SELECT);
startRouterMode = () => this.props.editorSetMode(MODES.ROUTER);

View file

@ -10,6 +10,7 @@ import {
editorSetDialog,
editorSetDialogActive,
editorGetGPXTrack,
editorSearchNominatim,
} from '~/redux/editor/actions';
import { connect } from 'react-redux';
import { Icon } from '~/components/panels/Icon';
@ -19,11 +20,12 @@ import { CLIENT } from '~/config/frontend';
import { DIALOGS, TABS } from '~/constants/dialogs';
import { Tooltip } from '~/components/panels/Tooltip';
import { TitleDialog } from '~/components/dialogs/TitleDialog';
import { NominatimSearchPanel } from '~/components/dialogs/NominatimSearchPanel';
import { IState } from '~/redux/store';
const mapStateToProps = ({
user: { user },
editor: { dialog, dialog_active },
editor: { dialog, dialog_active, features },
map: { route, stickers },
}: IState) => ({
dialog,
@ -31,6 +33,7 @@ const mapStateToProps = ({
user,
route,
stickers,
features,
});
const mapDispatchToProps = {
@ -42,6 +45,7 @@ const mapDispatchToProps = {
editorSetDialogActive,
openMapDialog,
editorGetGPXTrack,
editorSearchNominatim,
};
type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
@ -90,6 +94,7 @@ export class UserPanelUnconnected extends PureComponent<Props, State> {
}
setMenuOpened = () => this.setState({ menuOpened: !this.state.menuOpened });
openMapsDialog = () => {
this.props.openMapDialog(TABS.MY);
};
@ -115,7 +120,7 @@ export class UserPanelUnconnected extends PureComponent<Props, State> {
render() {
const {
props: { user, dialog, dialog_active, route, stickers },
props: { user, dialog, dialog_active, route, stickers, features },
state: { menuOpened },
} = this;
@ -124,6 +129,7 @@ export class UserPanelUnconnected extends PureComponent<Props, State> {
return (
<div>
<TitleDialog />
<NominatimSearchPanel active={features.nominatim} onSearch={this.props.editorSearchNominatim} />
<div className="panel active panel-user">
<div className="user-panel">

View file

@ -1,22 +1,11 @@
export interface IDialogs {
NONE: string,
MAP_LIST: string,
APP_INFO: string,
}
export interface IMapTabs {
MY: string,
PENDING: string,
STARRED: string,
}
export const DIALOGS: IDialogs = ({
export const DIALOGS = ({
NONE: 'NONE',
MAP_LIST: 'MAP_LIST',
APP_INFO: 'APP_INFO',
NOMINATIM: 'NOMINATIM',
});
export const TABS: IMapTabs = {
export const TABS = {
MY: 'my',
PENDING: 'pending',
STARRED: 'starred',

View file

@ -4,7 +4,6 @@ import { EditorPanel } from '~/components/panels/EditorPanel';
import { Fills } from '~/components/Fills';
import { UserPanel } from '~/components/panels/UserPanel';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { hot } from 'react-hot-loader';
import { Renderer } from '~/components/renderer/Renderer';
@ -14,7 +13,7 @@ import { TopLeftPanel } from '~/components/panels/TopLeftPanel';
import { TopRightPanel } from '~/components/panels/TopRightPanel';
import { LogoPreview } from '~/components/logo/LogoPreview';
import { IStickerPack } from '~/constants/stickers';
import { IDialogs } from '~/constants/dialogs';
import { DIALOGS } from '~/constants/dialogs';
import { Map } from '~/map/Map';
import { IEditorState } from '~/redux/editor';
@ -25,7 +24,7 @@ type Props = {
renderer_active: boolean;
mode: IEditorState['mode'];
dialog: keyof IDialogs;
dialog: keyof typeof DIALOGS;
dialog_active: boolean;
set: keyof IStickerPack;
editorHideRenderer: typeof editorHideRenderer;

View file

@ -1,13 +1,14 @@
import React, { createElement, FC, memo } from 'react';
import { DIALOGS, IDialogs } from '~/constants/dialogs';
import { DIALOGS } from '~/constants/dialogs';
import classnames from 'classnames';
import { AppInfoDialog } from '~/components/dialogs/AppInfoDialog';
import { Icon } from '~/components/panels/Icon';
import { MapListDialog } from '~/components/dialogs/MapListDialog';
import { NominatimDialog } from '~/components/dialogs/NominatimDialog';
import * as EDITOR_ACTIONS from '~/redux/editor/actions';
interface Props {
dialog: keyof IDialogs;
dialog: keyof typeof DIALOGS;
dialog_active: Boolean;
editorSetDialogActive: typeof EDITOR_ACTIONS.editorSetDialogActive;
}
@ -15,6 +16,7 @@ interface Props {
const LEFT_DIALOGS = {
[DIALOGS.MAP_LIST]: MapListDialog,
[DIALOGS.APP_INFO]: AppInfoDialog,
[DIALOGS.NOMINATIM]: NominatimDialog,
};
const LeftDialog: FC<Props> = memo(({ dialog, dialog_active, editorSetDialogActive }) => (

View file

@ -51,8 +51,12 @@ const MapUnconnected: React.FC<IProps> = ({
}) => {
const onClick = React.useCallback(
event => {
if (!MainMap.clickable || mode === MODES.NONE) return;
if (
!MainMap.clickable ||
mode === MODES.NONE
)
return;
mapClicked(event.latlng);
},
[mapClicked, mode]

View file

@ -112,3 +112,13 @@ export const editorSetRouter = (router: Partial<IEditorState['router']>) => ({
type: EDITOR_ACTIONS.SET_ROUTER,
router,
});
export const editorSetNominatim = (nominatim: Partial<IEditorState['nominatim']>) => ({
type: EDITOR_ACTIONS.SET_NOMINATIM,
nominatim,
})
export const editorSearchNominatim = (search: IEditorState['nominatim']['search']) => ({
type: EDITOR_ACTIONS.SEARCH_NOMINATIM,
search,
})

View file

@ -44,4 +44,6 @@ export const EDITOR_ACTIONS = {
KEY_PRESSED: `${P}-KEY_PRESSED`,
SET_ROUTER: `${P}-SET_ROUTER`,
SET_NOMINATIM: `${P}-SET_NOMINATIM`,
SEARCH_NOMINATIM: `${P}-SEARCH_NOMINATIM`,
};

View file

@ -157,6 +157,17 @@ const setRouter = (
},
});
const setNominatim = (
state,
{ nominatim }: ReturnType<typeof ACTIONS.editorSetNominatim>
): IEditorState => ({
...state,
nominatim: {
...state.nominatim,
...nominatim,
},
});
export const EDITOR_HANDLERS = {
[EDITOR_ACTIONS.SET_EDITING]: setEditing,
[EDITOR_ACTIONS.SET_CHANGED]: setChanged,
@ -184,4 +195,5 @@ export const EDITOR_HANDLERS = {
[EDITOR_ACTIONS.SET_FEATURE]: setFeature,
[EDITOR_ACTIONS.SET_IS_ROUTING]: setIsRouting,
[EDITOR_ACTIONS.SET_ROUTER]: setRouter,
[EDITOR_ACTIONS.SET_NOMINATIM]: setNominatim,
};

View file

@ -1,8 +1,9 @@
import { createReducer } from '~/utils/reducer';
import { IDialogs } from '~/constants/dialogs';
import { DIALOGS } from '~/constants/dialogs';
import { MODES } from '~/constants/modes';
import { EDITOR_HANDLERS } from './handlers';
import { ILatLng } from '../map/types';
import { INominatimResult } from '~/redux/types';
export interface IEditorState {
changed: boolean;
@ -11,13 +12,13 @@ export interface IEditorState {
markers_shown: boolean;
router: {
points: ILatLng[];
points: ILatLng[];
waypoints: ILatLng[];
};
mode: typeof MODES[keyof typeof MODES];
dialog: IDialogs[keyof IDialogs];
dialog: typeof DIALOGS[keyof typeof DIALOGS];
dialog_active: boolean;
routerPoints: number;
@ -31,6 +32,13 @@ export interface IEditorState {
features: {
routing: boolean;
nominatim: boolean;
};
nominatim: {
search: string;
loading: boolean;
list: INominatimResult[];
};
renderer: {
@ -76,6 +84,13 @@ export const EDITOR_INITIAL_STATE = {
features: {
routing: false,
nominatim: false,
},
nominatim: {
search: '',
loading: false,
list: [],
},
renderer: {

View file

@ -1,5 +1,13 @@
import { call, put, takeEvery, takeLatest, select, race } from 'redux-saga/effects';
import { delay, SagaIterator } from 'redux-saga';
import {
call,
put,
takeEvery,
takeLatest,
select,
race,
takeLeading,
delay,
} from 'redux-saga/effects';
import { selectEditor, selectEditorMode } from '~/redux/editor/selectors';
import { simplify } from '~/utils/simplify';
import {
@ -14,13 +22,16 @@ import {
editorLocationChanged,
editorKeyPressed,
editorSetSave,
editorSearchNominatim,
editorSetDialog,
editorSetNominatim,
} from '~/redux/editor/actions';
import { getUrlData, pushPath } from '~/utils/history';
import { MODES } from '~/constants/modes';
import { checkOSRMService } from '~/utils/api';
import { checkOSRMService, checkNominatimService, searchNominatim } from '~/utils/api';
import { LatLng } from 'leaflet';
import { searchSetTab } from '../user/actions';
import { TABS } from '~/constants/dialogs';
import { TABS, DIALOGS } from '~/constants/dialogs';
import { EDITOR_ACTIONS } from './constants';
import { getGPXString, downloadGPXTrack } from '~/utils/gpx';
import {
@ -76,11 +87,17 @@ function* checkOSRMServiceSaga() {
yield put(editorSetFeature({ routing }));
}
function* checkNominatimSaga() {
const nominatim = yield call(checkNominatimService);
yield put(editorSetFeature({ nominatim }));
}
export function* setReadySaga() {
yield put(editorSetReady(true));
hideLoader();
yield call(checkOSRMServiceSaga);
yield call(checkNominatimSaga);
yield put(searchSetTab(TABS.MY));
}
@ -217,7 +234,7 @@ function* keyPressedSaga({ key, target }: ReturnType<typeof editorKeyPressed>) {
}
}
function* getGPXTrackSaga(): SagaIterator {
function* getGPXTrackSaga() {
const { route, stickers, title, address }: ReturnType<typeof selectMap> = yield select(selectMap);
if (!route.length && !stickers.length) return;
@ -259,6 +276,22 @@ function* cancelSave() {
);
}
function* searchNominatimSaga({ search }: ReturnType<typeof editorSearchNominatim>) {
const { dialog, dialog_active }: ReturnType<typeof selectEditor> = yield select(selectEditor);
if (dialog !== DIALOGS.NOMINATIM || !dialog_active) {
yield put(editorSetDialog(DIALOGS.NOMINATIM));
yield put(editorSetDialogActive(true));
}
yield put(editorSetNominatim({ loading: true, search }));
const list = yield call(searchNominatim, search);
yield put(editorSetNominatim({ list }));
yield delay(1000); // safely wait for 1s to prevent from ddosing nominatim
yield put(editorSetNominatim({ loading: false }));
}
export function* editorSaga() {
yield takeEvery(EDITOR_ACTIONS.LOCATION_CHANGED, locationChangeSaga);
@ -271,4 +304,5 @@ export function* editorSaga() {
yield takeLatest(MAP_ACTIONS.MAP_CLICKED, mapClick);
yield takeLatest(EDITOR_ACTIONS.ROUTER_SUBMIT, routerSubmit);
yield takeLatest(EDITOR_ACTIONS.CANCEL_SAVE, cancelSave);
yield takeLeading(EDITOR_ACTIONS.SEARCH_NOMINATIM, searchNominatimSaga);
}

View file

@ -8,3 +8,4 @@ export const selectEditorActiveSticker = (state: IState) => state.editor.activeS
export const selectEditorRenderer = (state: IState) => state.editor.renderer;
export const selectEditorRouter = (state: IState) => state.editor.router;
export const selectEditorDistance = (state: IState) => state.editor.distance;
export const selectEditorNominatim = (state: IState) => state.editor.nominatim;

View file

@ -7,6 +7,7 @@ import {
race,
take,
takeLatest,
delay,
} from 'redux-saga/effects';
import { MAP_ACTIONS } from './constants';
import {
@ -36,7 +37,6 @@ import { getStoredMap, postMap } from '~/utils/api';
import { Unwrap } from '~/utils/middleware';
import { selectMap, selectMapProvider, selectMapRoute, selectMapStickers } from './selectors';
import { TIPS } from '~/constants/tips';
import { delay } from 'redux-saga';
import { setReadySaga } from '../editor/sagas';
import { selectEditor } from '../editor/selectors';
import { EDITOR_ACTIONS } from '../editor/constants';

View file

@ -0,0 +1,7 @@
import { LatLngLiteral } from 'leaflet';
export interface INominatimResult {
id: number;
title: string;
latlng: LatLngLiteral;
};

View file

@ -1,6 +1,5 @@
import { REHYDRATE, RehydrateAction } from 'redux-persist';
import { delay, SagaIterator } from 'redux-saga';
import { takeLatest, select, call, put, takeEvery } from 'redux-saga/effects';
import { takeLatest, select, call, put, takeEvery, delay } from 'redux-saga/effects';
import {
checkIframeToken,
checkUserToken,
@ -34,7 +33,6 @@ 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 {
@ -198,7 +196,7 @@ function* searchSetTabSaga() {
yield put(searchSetTitle(''));
}
function* userLogoutSaga(): SagaIterator {
function* userLogoutSaga() {
yield put(setUser(DEFAULT_USER));
yield call(generateGuestSaga);
}
@ -256,7 +254,7 @@ function* mapsLoadMoreSaga() {
yield put(searchSetLoading(false));
}
function* dropRouteSaga({ address }: ReturnType<typeof ActionCreators.dropRoute>): SagaIterator {
function* dropRouteSaga({ address }: ReturnType<typeof ActionCreators.dropRoute>) {
const { token }: ReturnType<typeof selectUserUser> = yield select(selectUserUser);
const {
routes: {
@ -290,7 +288,7 @@ function* modifyRouteSaga({
address,
title,
is_public,
}: ReturnType<typeof ActionCreators.modifyRoute>): SagaIterator {
}: ReturnType<typeof ActionCreators.modifyRoute>) {
const { token }: ReturnType<typeof selectUserUser> = yield select(selectUserUser);
const {
routes: {

View file

@ -422,6 +422,13 @@
<path d="M12 14c-2.33 0-4.32 1.45-5.12 3.5h1.67c.69-1.19 1.97-2 3.45-2s2.75.81 3.45 2h1.67c-.8-2.05-2.79-3.5-5.12-3.5zm-.01-12C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z" fill="white" stroke="none" stroke-width="0"/>
</g>
</g>
<g id="icon-search" stroke="none">
<path stroke="none" fill="black"/>
<g transform="translate(4 4)">
<path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" />
</g>
</g>
</svg>
</defs>

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Before After
Before After

View file

@ -36,7 +36,6 @@
background: rgba(19, 45, 53, 0.95);
}
}
}
.dialog-close-button {
@ -94,8 +93,41 @@
}
}
.dialog-flex-scroll {
display: flex;
align-items: center;
justify-content: center;
}
.nominatim-dialog-content {
padding-bottom: 48px;
min-height: 25vh;
}
.nominatim-list-item {
padding: 10px;
color: white;
cursor: pointer;
transition: background-color 0.25s;
display: flex;
align-items: center;
justify-content: center;
padding-bottom: 200px;
&:hover {
background: rgba(255, 255, 255, 0.1);
}
.title {
text-overflow: hidden;
font-size: 12px;
-webkit-line-clamp: 2;
}
}
.dialog-shader {
&::before, &::after {
&::before,
&::after {
content: ' ';
height: 40px;
width: 100%;
@ -120,13 +152,21 @@
}
@keyframes pulse {
0% { opacity: 1; }
100% { opacity: 0.5; }
0% {
opacity: 1;
}
100% {
opacity: 0.5;
}
}
@keyframes spin {
0% { transform: rotate(0); }
100% { transform: rotate(360deg); }
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
.dialog-maplist-pulse {
@ -195,11 +235,14 @@
&.has_edit {
//transform: translateY(-2px);
.route-row { background: fade(@green_secondary, 30%); }
.route-row {
background: fade(@green_secondary, 30%);
}
}
&.is_menu_target {
.route-row, .route-row-fav {
.route-row,
.route-row-fav {
transform: translateX(-120px);
}
@ -337,7 +380,6 @@
}
}
}
}
.route-title {
@ -414,7 +456,7 @@
}
}
@media(max-width: @mobile_breakpoint) {
@media (max-width: @mobile_breakpoint) {
height: 48px;
.dialog-tab {

View file

@ -3,6 +3,8 @@
border-radius: @panel_radius;
display: flex;
box-shadow: @bar_shadow;
align-items: center;
justify-content: center;
@media (max-width: @mobile_breakpoint) {
box-shadow: none;
@ -723,4 +725,16 @@
.location-bar {
width: 32px;
}
.nominatim-panel {
position: fixed;
bottom: 53px;
left: 10px;
width: 272px
}
.nominatim-search-input {
padding-left: 10px;
flex: 1;
}

View file

@ -11,6 +11,7 @@ import {
configWithToken,
} from './middleware';
import { IRoute } from '~/redux/map/types';
import { INominatimResult } from '~/redux/types';
const arrayToObject = (array: any[], key: string): {} =>
array.reduce((obj, el) => ({ ...obj, [el[key]]: el }), {});
@ -180,6 +181,36 @@ export const checkOSRMService = (bounds: LatLngLiteral[]): Promise<boolean> =>
.then(() => true)
.catch(() => false);
export const checkNominatimService = (): Promise<boolean> =>
CLIENT &&
CLIENT.NOMINATIM_TEST_URL &&
axios
.get(CLIENT.NOMINATIM_TEST_URL)
.then(() => true)
.catch(() => false);
export const searchNominatim = (query: string) =>
CLIENT &&
CLIENT.NOMINATIM_URL &&
axios
.get(`${CLIENT.NOMINATIM_URL}${query}`, { params: { format: 'json', country_code: 'ru', 'accept-language': 'ru_RU' } })
.then(
data =>
data &&
data.data &&
data.data.map(
(item): INominatimResult => ({
id: item.place_id,
latlng: {
lat: item.lat,
lng: item.lon,
},
title: item.display_name,
})
)
)
.catch(() => []);
export const dropRoute = ({ address, token }: { address: string; token: string }): Promise<any> =>
axios
.delete(API.DROP_ROUTE, configWithToken(token, { data: { address } }))

View file

@ -20,8 +20,6 @@ export const getLocation = (callback: (pos: LatLngLiteral) => void) => {
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);