mirror of
https://github.com/muerwre/orchidmap-front.git
synced 2025-04-24 18:46:40 +07:00
nominatim search (without working dialog)
This commit is contained in:
parent
c3e136cebb
commit
b20a3445d1
27 changed files with 450 additions and 61 deletions
101
package-lock.json
generated
101
package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
16
src/components/dialogs/DialogLoader.tsx
Normal file
16
src/components/dialogs/DialogLoader.tsx
Normal 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 };
|
|
@ -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 && (
|
||||
|
|
46
src/components/dialogs/NominatimDialog.tsx
Normal file
46
src/components/dialogs/NominatimDialog.tsx
Normal 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 };
|
37
src/components/dialogs/NominatimSearchPanel.tsx
Normal file
37
src/components/dialogs/NominatimSearchPanel.tsx
Normal 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 };
|
21
src/components/nominatim/NominatimListItem.tsx
Normal file
21
src/components/nominatim/NominatimListItem.tsx
Normal 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 };
|
|
@ -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);
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 }) => (
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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,
|
||||
})
|
|
@ -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`,
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
import { LatLngLiteral } from 'leaflet';
|
||||
|
||||
export interface INominatimResult {
|
||||
id: number;
|
||||
title: string;
|
||||
latlng: LatLngLiteral;
|
||||
};
|
|
@ -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: {
|
||||
|
|
|
@ -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 |
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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 } }))
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue