mirror of
https://github.com/muerwre/orchidmap-front.git
synced 2025-04-25 02:56:41 +07:00
commit
bc5892120f
56 changed files with 909 additions and 891 deletions
19
.drone.yml
19
.drone.yml
|
@ -64,21 +64,4 @@ steps:
|
||||||
- cp -a $${ENV_PATH}/${DRONE_BRANCH}/. $${BUILD_PATH}/${DRONE_BRANCH}
|
- cp -a $${ENV_PATH}/${DRONE_BRANCH}/. $${BUILD_PATH}/${DRONE_BRANCH}
|
||||||
- docker-compose build
|
- docker-compose build
|
||||||
- docker-compose up -d
|
- docker-compose up -d
|
||||||
# - name: telgram_notify
|
|
||||||
# image: appleboy/drone-telegram
|
|
||||||
# when:
|
|
||||||
# status:
|
|
||||||
# - success
|
|
||||||
# - failure
|
|
||||||
# settings:
|
|
||||||
# token:
|
|
||||||
# from_secret: telegram_token
|
|
||||||
# to:
|
|
||||||
# from_secret: telegram_chat_id
|
|
||||||
# format: markdown
|
|
||||||
# message: >
|
|
||||||
# {{#success build.status}}🤓{{else}}😨{{/success}}
|
|
||||||
# [{{repo.name}} / {{commit.branch}}]({{ build.link }})
|
|
||||||
# ```
|
|
||||||
# {{ commit.message }}
|
|
||||||
# ```
|
|
||||||
|
|
2
.env
2
.env
|
@ -1,4 +1,4 @@
|
||||||
REACT_APP_PUBLIC_PATH = https://localhost:3000/
|
REACT_APP_PUBLIC_PATH = https://localhost:3000/
|
||||||
REACT_APP_API_ADDR = https://backend.alpha-map.vault48.org/
|
REACT_APP_API_ADDR = https://backend.map.vault48.org
|
||||||
REACT_APP_OSRM_URL = https://vault48.org:5001/route/v1
|
REACT_APP_OSRM_URL = https://vault48.org:5001/route/v1
|
||||||
REACT_APP_OSRM_PROFILE = bike
|
REACT_APP_OSRM_PROFILE = bike
|
||||||
|
|
|
@ -32,7 +32,6 @@
|
||||||
"gpx-parser-builder": "^1.0.2",
|
"gpx-parser-builder": "^1.0.2",
|
||||||
"leaflet": "1.6.0",
|
"leaflet": "1.6.0",
|
||||||
"leaflet-editable": "^1.1.0",
|
"leaflet-editable": "^1.1.0",
|
||||||
"leaflet-geometryutil": "^0.9.0",
|
|
||||||
"leaflet-routing-machine": "^3.2.12",
|
"leaflet-routing-machine": "^3.2.12",
|
||||||
"leaflet.markercluster": "^1.4.1",
|
"leaflet.markercluster": "^1.4.1",
|
||||||
"node-sass": "^5.0.0",
|
"node-sass": "^5.0.0",
|
||||||
|
|
|
@ -23,10 +23,15 @@ class StickerDesc extends React.PureComponent<Props, State> {
|
||||||
blockMouse = e => {
|
blockMouse = e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
|
if (!this.input) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
this.input.focus();
|
this.input.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
input: HTMLTextAreaElement;
|
input: HTMLTextAreaElement | null = null;
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { value: text } = this.props;
|
const { value: text } = this.props;
|
||||||
|
|
|
@ -64,9 +64,8 @@ export interface State {
|
||||||
|
|
||||||
class MapListDialogUnconnected extends PureComponent<Props, State> {
|
class MapListDialogUnconnected extends PureComponent<Props, State> {
|
||||||
state = {
|
state = {
|
||||||
menu_target: null,
|
menu_target: '',
|
||||||
editor_target: null,
|
editor_target: '',
|
||||||
|
|
||||||
is_editing: false,
|
is_editing: false,
|
||||||
is_dropping: false,
|
is_dropping: false,
|
||||||
};
|
};
|
||||||
|
@ -74,7 +73,7 @@ class MapListDialogUnconnected extends PureComponent<Props, State> {
|
||||||
startEditing = (editor_target: IRouteListItem['address']): void =>
|
startEditing = (editor_target: IRouteListItem['address']): void =>
|
||||||
this.setState({
|
this.setState({
|
||||||
editor_target,
|
editor_target,
|
||||||
menu_target: null,
|
menu_target: '',
|
||||||
is_editing: true,
|
is_editing: true,
|
||||||
is_dropping: false,
|
is_dropping: false,
|
||||||
});
|
});
|
||||||
|
@ -86,19 +85,19 @@ class MapListDialogUnconnected extends PureComponent<Props, State> {
|
||||||
|
|
||||||
hideMenu = (): void =>
|
hideMenu = (): void =>
|
||||||
this.setState({
|
this.setState({
|
||||||
menu_target: null,
|
menu_target: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
showDropCard = (editor_target: IRouteListItem['address']): void =>
|
showDropCard = (editor_target: IRouteListItem['address']): void =>
|
||||||
this.setState({
|
this.setState({
|
||||||
editor_target,
|
editor_target,
|
||||||
menu_target: null,
|
menu_target: '',
|
||||||
is_editing: false,
|
is_editing: false,
|
||||||
is_dropping: true,
|
is_dropping: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
stopEditing = (): void => {
|
stopEditing = (): void => {
|
||||||
this.setState({ editor_target: null });
|
this.setState({ editor_target: '' });
|
||||||
};
|
};
|
||||||
|
|
||||||
setTitle = ({ target: { value } }: { target: { value: string } }): void => {
|
setTitle = ({ target: { value } }: { target: { value: string } }): void => {
|
||||||
|
|
|
@ -27,7 +27,7 @@ const ProviderDialogUnconnected = ({ provider, mapSetProvider }: Props) => (
|
||||||
backgroundImage: `url(${replaceProviderUrl(item, { x: 5980, y: 2589, zoom: 13 })})`,
|
backgroundImage: `url(${replaceProviderUrl(item, { x: 5980, y: 2589, zoom: 13 })})`,
|
||||||
}}
|
}}
|
||||||
onMouseDown={() => mapSetProvider(item)}
|
onMouseDown={() => mapSetProvider(item)}
|
||||||
key={PROVIDERS[item].name}
|
key={PROVIDERS[item]?.name}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
provider === item &&
|
provider === item &&
|
||||||
|
|
|
@ -42,7 +42,7 @@ export class TitleDialogUnconnected extends React.PureComponent<Props, State> {
|
||||||
this.setMaxHeight();
|
this.setMaxHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
setMaxHeight = (): number => {
|
setMaxHeight = () => {
|
||||||
if (!this.ref_sizer || !this.ref_title || !this.ref_text) return 0;
|
if (!this.ref_sizer || !this.ref_title || !this.ref_text) return 0;
|
||||||
|
|
||||||
const { height: sizer_height } = this.ref_sizer.getBoundingClientRect();
|
const { height: sizer_height } = this.ref_sizer.getBoundingClientRect();
|
||||||
|
|
|
@ -40,7 +40,7 @@ export const RouteRowView = ({
|
||||||
is_admin,
|
is_admin,
|
||||||
is_published,
|
is_published,
|
||||||
toggleStarred
|
toggleStarred
|
||||||
}: Props): ReactElement<Props, null> => (
|
}: Props): ReactElement<Props> => (
|
||||||
<div className={classnames("route-row-view", { has_menu: tab === "my" })}>
|
<div className={classnames("route-row-view", { has_menu: tab === "my" })}>
|
||||||
{(tab === TABS.PENDING || tab === TABS.STARRED) && is_admin && (
|
{(tab === TABS.PENDING || tab === TABS.STARRED) && is_admin && (
|
||||||
<div className="route-row-fav" onClick={toggleStarred.bind(null, address)}>
|
<div className="route-row-fav" onClick={toggleStarred.bind(null, address)}>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { PureComponent, useState, useCallback } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { MODES } from '~/constants/modes';
|
import { MODES } from '~/constants/modes';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
|
@ -7,12 +7,12 @@ import { EditorDialog } from '~/components/panels/EditorDialog';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
editorChangeMode,
|
editorChangeMode,
|
||||||
|
editorKeyPressed,
|
||||||
|
editorRedo,
|
||||||
editorStartEditing,
|
editorStartEditing,
|
||||||
editorStopEditing,
|
editorStopEditing,
|
||||||
editorTakeAShot,
|
editorTakeAShot,
|
||||||
editorKeyPressed,
|
|
||||||
editorUndo,
|
editorUndo,
|
||||||
editorRedo,
|
|
||||||
} from '~/redux/editor/actions';
|
} from '~/redux/editor/actions';
|
||||||
import { Tooltip } from '~/components/panels/Tooltip';
|
import { Tooltip } from '~/components/panels/Tooltip';
|
||||||
import { IState } from '~/redux/store';
|
import { IState } from '~/redux/store';
|
||||||
|
@ -47,6 +47,10 @@ type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {}
|
||||||
|
|
||||||
class EditorPanelUnconnected extends PureComponent<Props, void> {
|
class EditorPanelUnconnected extends PureComponent<Props, void> {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
if (!this.panel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
window.addEventListener('keydown', this.onKeyPress as any);
|
window.addEventListener('keydown', this.onKeyPress as any);
|
||||||
|
|
||||||
const obj = document.getElementById('control-dialog');
|
const obj = document.getElementById('control-dialog');
|
||||||
|
@ -57,7 +61,7 @@ class EditorPanelUnconnected extends PureComponent<Props, void> {
|
||||||
obj.style.width = String(width);
|
obj.style.width = String(width);
|
||||||
}
|
}
|
||||||
|
|
||||||
panel: HTMLElement = null;
|
panel: HTMLDivElement | null = null;
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
window.removeEventListener('keydown', this.onKeyPress as any);
|
window.removeEventListener('keydown', this.onKeyPress as any);
|
||||||
|
|
|
@ -31,6 +31,10 @@ class Component extends React.Component<Props, State> {
|
||||||
};
|
};
|
||||||
|
|
||||||
onImageLoaded = () => {
|
onImageLoaded = () => {
|
||||||
|
if (!this.image) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
this.croppr = new Croppr(this.image, {
|
this.croppr = new Croppr(this.image, {
|
||||||
onInitialize: this.onCropInit,
|
onInitialize: this.onCropInit,
|
||||||
});
|
});
|
||||||
|
@ -57,12 +61,12 @@ class Component extends React.Component<Props, State> {
|
||||||
regionEl.append(this.logo);
|
regionEl.append(this.logo);
|
||||||
};
|
};
|
||||||
|
|
||||||
croppr: Croppr;
|
croppr?: Croppr;
|
||||||
logo: HTMLDivElement;
|
logo: HTMLDivElement | null = null;
|
||||||
image: HTMLImageElement;
|
image: HTMLImageElement | null = null;
|
||||||
logoImg: HTMLImageElement;
|
logoImg: HTMLImageElement | null = null;
|
||||||
|
|
||||||
getImage = () => this.props.editorCropAShot(this.croppr.getValue());
|
getImage = () => this.props.editorCropAShot(this.croppr?.getValue());
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { data } = this.props.editor.renderer;
|
const { data } = this.props.editor.renderer;
|
||||||
|
|
|
@ -7,7 +7,7 @@ interface Props {
|
||||||
max: number;
|
max: number;
|
||||||
search: string;
|
search: string;
|
||||||
distance: [number, number];
|
distance: [number, number];
|
||||||
onDistanceChange: (val: [number, number]) => void;
|
onDistanceChange: (val: number[]) => void;
|
||||||
onSearchChange: ChangeEventHandler<HTMLInputElement>;
|
onSearchChange: ChangeEventHandler<HTMLInputElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
import { CLIENT } from '~/config/frontend';
|
|
||||||
|
|
||||||
export const API = {
|
export const API = {
|
||||||
GET_GUEST: `${CLIENT.API_ADDR}/api/auth/`,
|
GET_GUEST: `/api/auth/`,
|
||||||
CHECK_TOKEN: `${CLIENT.API_ADDR}/api/auth/`,
|
CHECK_TOKEN: `/api/auth/`,
|
||||||
IFRAME_LOGIN_VK: `${CLIENT.API_ADDR}/api/auth/vk`,
|
IFRAME_LOGIN_VK: `/api/auth/vk`,
|
||||||
GET_MAP: `${CLIENT.API_ADDR}/api/route/`,
|
GET_MAP: `/api/route/`,
|
||||||
POST_MAP: `${CLIENT.API_ADDR}/api/route/`,
|
POST_MAP: `/api/route/`,
|
||||||
GET_ROUTE_LIST: tab => `${CLIENT.API_ADDR}/api/route/list/${tab}`,
|
GET_ROUTE_LIST: tab => `/api/route/list/${tab}`,
|
||||||
|
|
||||||
DROP_ROUTE: `${CLIENT.API_ADDR}/api/route/`,
|
DROP_ROUTE: `/api/route/`,
|
||||||
MODIFY_ROUTE: `${CLIENT.API_ADDR}/api/route/`,
|
MODIFY_ROUTE: `/api/route/`,
|
||||||
SET_STARRED: `${CLIENT.API_ADDR}/api/route/publish`,
|
SET_STARRED: `/api/route/publish`,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const API_RETRY_INTERVAL = 10;
|
export const API_RETRY_INTERVAL = 10;
|
||||||
|
|
|
@ -11,7 +11,7 @@ export interface IUser {
|
||||||
role: IRoles[keyof IRoles];
|
role: IRoles[keyof IRoles];
|
||||||
routes: {};
|
routes: {};
|
||||||
success: boolean;
|
success: boolean;
|
||||||
id?: string;
|
id: string;
|
||||||
uid: string;
|
uid: string;
|
||||||
token?: string;
|
token?: string;
|
||||||
photo: string;
|
photo: string;
|
||||||
|
@ -31,9 +31,9 @@ export const DEFAULT_USER: IUser = {
|
||||||
role: ROLES.guest,
|
role: ROLES.guest,
|
||||||
routes: {},
|
routes: {},
|
||||||
success: false,
|
success: false,
|
||||||
id: null,
|
id: '',
|
||||||
token: null,
|
token: undefined,
|
||||||
photo: null,
|
photo: '',
|
||||||
name: null,
|
name: '',
|
||||||
uid: null,
|
uid: '',
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,21 +5,6 @@ export interface IProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ITileMaps = Record<string, IProvider>
|
export type ITileMaps = Record<string, IProvider>
|
||||||
// {
|
|
||||||
// WATERCOLOR: IProvider,
|
|
||||||
// DGIS: IProvider,
|
|
||||||
// DEFAULT: IProvider,
|
|
||||||
// DARQ: IProvider,
|
|
||||||
// BLANK: IProvider,
|
|
||||||
// HOT: IProvider,
|
|
||||||
// YSAT: IProvider,
|
|
||||||
// YMAP: IProvider,
|
|
||||||
// SAT: IProvider,
|
|
||||||
// ESAT: IProvider,
|
|
||||||
// CACHE_OSM: IProvider,
|
|
||||||
// CACHE_CARTO: IProvider,
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// Стили карт
|
// Стили карт
|
||||||
const TILEMAPS: ITileMaps = {
|
const TILEMAPS: ITileMaps = {
|
||||||
|
@ -53,7 +38,7 @@ const TILEMAPS: ITileMaps = {
|
||||||
const ENABLED: Array<keyof ITileMaps> = ['BLANK', 'DEFAULT', 'DGIS', 'HOT', 'ESAT'];
|
const ENABLED: Array<keyof ITileMaps> = ['BLANK', 'DEFAULT', 'DGIS', 'HOT', 'ESAT'];
|
||||||
|
|
||||||
export const DEFAULT_PROVIDER: keyof ITileMaps = ENABLED[1];
|
export const DEFAULT_PROVIDER: keyof ITileMaps = ENABLED[1];
|
||||||
export const PROVIDERS: Partial<ITileMaps> = ENABLED.reduce((obj, provider) => ({
|
export const PROVIDERS: ITileMaps = ENABLED.reduce((obj, provider) => ({
|
||||||
...obj,
|
...obj,
|
||||||
[provider]: TILEMAPS[provider],
|
[provider]: TILEMAPS[provider],
|
||||||
}), {});
|
}), {});
|
||||||
|
|
|
@ -14,7 +14,7 @@ const mapDispatchToProps = {};
|
||||||
type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
|
type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
|
||||||
|
|
||||||
const ArrowsUnconnected: FC<Props> = memo(({ route }) => {
|
const ArrowsUnconnected: FC<Props> = memo(({ route }) => {
|
||||||
const [layer, setLayer] = useState(null);
|
const [layer, setLayer] = useState<ArrowsLayer | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const item = new ArrowsLayer({}).addTo(MainMap);
|
const item = new ArrowsLayer({}).addTo(MainMap);
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import React, { FC, useState, useEffect, useCallback } from 'react';
|
import React, { FC, useCallback, useEffect } from 'react';
|
||||||
import { LatLngLiteral, marker, Marker, DivIcon } from 'leaflet';
|
import { DivIcon, LatLngLiteral, Marker } from 'leaflet';
|
||||||
import { MainMap } from '~/constants/map';
|
import { MainMap } from '~/constants/map';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
location: LatLngLiteral;
|
location?: LatLngLiteral;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CurrentLocation: FC<IProps> = ({ location }) => {
|
const CurrentLocation: FC<IProps> = ({ location }) => {
|
||||||
|
|
|
@ -8,7 +8,7 @@ interface IProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const GpxPolyline: FC<IProps> = ({ latlngs, color }) => {
|
const GpxPolyline: FC<IProps> = ({ latlngs, color }) => {
|
||||||
const [layer, setLayer] = useState<Polyline>(null);
|
const [layer, setLayer] = useState<Polyline | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const item = new Polyline([], {
|
const item = new Polyline([], {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { FC, useEffect, useState, memo } from 'react';
|
import React, { FC, memo, useEffect, useState } from 'react';
|
||||||
import { KmMarksLayer } from '~/utils/marks';
|
import { KmMarksLayer } from '~/utils/marks';
|
||||||
import { MainMap } from '~/constants/map';
|
import { MainMap } from '~/constants/map';
|
||||||
import { selectMap } from '~/redux/map/selectors';
|
import { selectMap } from '~/redux/map/selectors';
|
||||||
|
@ -14,14 +14,14 @@ const mapDispatchToProps = {};
|
||||||
type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
|
type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
|
||||||
|
|
||||||
const KmMarksUnconnected: FC<Props> = memo(({ map: { route } }) => {
|
const KmMarksUnconnected: FC<Props> = memo(({ map: { route } }) => {
|
||||||
const [layer, setLayer] = useState(null);
|
const [layer, setLayer] = useState<KmMarksLayer | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const layer = new KmMarksLayer([]);
|
const layer = new KmMarksLayer([]);
|
||||||
layer.addTo(MainMap);
|
layer.addTo(MainMap);
|
||||||
setLayer(layer);
|
setLayer(layer);
|
||||||
return () => MainMap.removeLayer(layer);
|
return () => MainMap.removeLayer(layer);
|
||||||
}, [MainMap]);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!layer) return;
|
if (!layer) return;
|
||||||
|
|
|
@ -93,7 +93,7 @@ const MapUnconnected: React.FC<IProps> = memo(
|
||||||
enabled && <GpxPolyline latlngs={latlngs} color={color} key={index} />
|
enabled && <GpxPolyline latlngs={latlngs} color={color} key={index} />
|
||||||
)}
|
)}
|
||||||
</div>,
|
</div>,
|
||||||
document.getElementById('canvas')
|
document.getElementById('canvas')!
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import React, { FC, useEffect, memo, useState, useCallback } from 'react';
|
import React, { FC, memo, useCallback, useEffect, useState } from 'react';
|
||||||
import { InteractivePoly } from '~/utils/map/InteractivePoly';
|
import { InteractivePoly } from '~/utils/map/InteractivePoly';
|
||||||
import { isMobile } from '~/utils/window';
|
import { isMobile } from '~/utils/window';
|
||||||
import { LatLng } from 'leaflet';
|
import { LatLng } from 'leaflet';
|
||||||
import { selectEditorMode, selectEditorEditing, selectEditorDirection } from '~/redux/editor/selectors';
|
import { selectEditorDirection, selectEditorEditing, selectEditorMode } from '~/redux/editor/selectors';
|
||||||
import * as MAP_ACTIONS from '~/redux/map/actions';
|
import * as MAP_ACTIONS from '~/redux/map/actions';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { selectMapRoute } from '~/redux/map/selectors';
|
import { selectMapRoute } from '~/redux/map/selectors';
|
||||||
|
@ -28,7 +28,7 @@ type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {}
|
||||||
|
|
||||||
const RouteUnconnected: FC<Props> = memo(
|
const RouteUnconnected: FC<Props> = memo(
|
||||||
({ route, editing, mode, drawing_direction, mapSetRoute, editorSetDistance, editorSetMarkersShown }) => {
|
({ route, editing, mode, drawing_direction, mapSetRoute, editorSetDistance, editorSetMarkersShown }) => {
|
||||||
const [layer, setLayer] = useState<InteractivePoly>(null);
|
const [layer, setLayer] = useState<InteractivePoly | null>(null);
|
||||||
|
|
||||||
const onDistanceChange = useCallback(({ distance }) => editorSetDistance(distance), [
|
const onDistanceChange = useCallback(({ distance }) => editorSetDistance(distance), [
|
||||||
editorSetDistance,
|
editorSetDistance,
|
||||||
|
|
|
@ -1,16 +1,12 @@
|
||||||
import { FC, useEffect, useCallback, memo, useState } from 'react';
|
import { FC, memo, useCallback, useEffect, useState } from 'react';
|
||||||
import { OsrmRouter } from '~/utils/map/OsrmRouter';
|
import { OsrmRouter } from '~/utils/map/OsrmRouter';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { selectMapRoute } from '~/redux/map/selectors';
|
import { selectMapRoute } from '~/redux/map/selectors';
|
||||||
import {
|
import { selectEditorDistance, selectEditorMode, selectEditorRouter } from '~/redux/editor/selectors';
|
||||||
selectEditorRouter,
|
|
||||||
selectEditorMode,
|
|
||||||
selectEditorDistance,
|
|
||||||
} from '~/redux/editor/selectors';
|
|
||||||
import { MainMap } from '~/constants/map';
|
import { MainMap } from '~/constants/map';
|
||||||
import * as EDITOR_ACTIONS from '~/redux/editor/actions';
|
import * as EDITOR_ACTIONS from '~/redux/editor/actions';
|
||||||
import { MODES } from '~/constants/modes';
|
import { MODES } from '~/constants/modes';
|
||||||
import { LatLngLiteral, marker, divIcon } from 'leaflet';
|
import { divIcon, LatLngLiteral, marker } from 'leaflet';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { angleBetweenPoints } from '~/utils/geom';
|
import { angleBetweenPoints } from '~/utils/geom';
|
||||||
|
|
||||||
|
@ -30,7 +26,7 @@ type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {}
|
||||||
const RouterUnconnected: FC<Props> = memo(
|
const RouterUnconnected: FC<Props> = memo(
|
||||||
({ route, mode, router: { waypoints }, editorSetRouter, distance }) => {
|
({ route, mode, router: { waypoints }, editorSetRouter, distance }) => {
|
||||||
const [dist, setDist] = useState(0);
|
const [dist, setDist] = useState(0);
|
||||||
const [end, setEnd] = useState<LatLngLiteral>(null);
|
const [end, setEnd] = useState<LatLngLiteral | null>(null);
|
||||||
const [direction, setDirection] = useState<boolean>(false);
|
const [direction, setDirection] = useState<boolean>(false);
|
||||||
|
|
||||||
const updateWaypoints = useCallback(
|
const updateWaypoints = useCallback(
|
||||||
|
|
|
@ -20,8 +20,8 @@ interface IProps {
|
||||||
mapDropSticker: (index: number) => void;
|
mapDropSticker: (index: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getLabelDirection = (angle: number): 'left' | 'right' =>
|
export const getLabelDirection = (angle?: number): 'left' | 'right' =>
|
||||||
angle % Math.PI >= -(Math.PI / 2) && angle % Math.PI <= Math.PI / 2 ? 'left' : 'right';
|
!!angle && angle % Math.PI >= -(Math.PI / 2) && angle % Math.PI <= Math.PI / 2 ? 'left' : 'right';
|
||||||
|
|
||||||
const getX = e =>
|
const getX = e =>
|
||||||
e.touches && e.touches.length > 0
|
e.touches && e.touches.length > 0
|
||||||
|
@ -36,50 +36,58 @@ const Sticker: React.FC<IProps> = ({
|
||||||
mapSetSticker,
|
mapSetSticker,
|
||||||
mapDropSticker,
|
mapDropSticker,
|
||||||
}) => {
|
}) => {
|
||||||
const [text, setText] = useState(sticker.text);
|
const [text, setText] = useState(sticker.text || '');
|
||||||
const [layer, setLayer] = React.useState<Marker>(null);
|
const [layer, setLayer] = React.useState<Marker | null>(null);
|
||||||
const [dragging, setDragging] = React.useState(false);
|
const [dragging, setDragging] = React.useState(false);
|
||||||
const wrapper = useRef(null);
|
const wrapper = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
let angle = useRef(sticker.angle);
|
let angle = useRef(sticker.angle);
|
||||||
|
|
||||||
const element = React.useMemo(() => document.createElement('div'), []);
|
const element = React.useMemo(() => document.createElement('div'), []);
|
||||||
|
|
||||||
const stickerArrow = React.useRef(null);
|
const stickerArrow = React.useRef<HTMLDivElement>(null);
|
||||||
const stickerImage = React.useRef(null);
|
const stickerImage = React.useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const onChange = React.useCallback(state => mapSetSticker(index, state), [mapSetSticker, index]);
|
const onChange = React.useCallback(state => mapSetSticker(index, state), [mapSetSticker, index]);
|
||||||
const onDelete = React.useCallback(state => mapDropSticker(index), [mapSetSticker, index]);
|
const onDelete = React.useCallback(() => setTimeout(() => mapDropSticker(index), 0), [mapDropSticker, index]);
|
||||||
|
|
||||||
const updateAngle = useCallback(
|
const updateAngle = useCallback(
|
||||||
ang => {
|
ang => {
|
||||||
if (!stickerImage.current || !stickerArrow.current) return;
|
|
||||||
|
|
||||||
const x = Math.cos(ang + Math.PI) * 56 - 30;
|
const x = Math.cos(ang + Math.PI) * 56 - 30;
|
||||||
const y = Math.sin(ang + Math.PI) * 56 - 30;
|
const y = Math.sin(ang + Math.PI) * 56 - 30;
|
||||||
|
|
||||||
|
if (!stickerImage.current || !stickerArrow.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
stickerImage.current.style.left = String(6 + x);
|
stickerImage.current.style.left = String(6 + x);
|
||||||
stickerImage.current.style.top = String(6 + y);
|
stickerImage.current.style.top = String(6 + y);
|
||||||
|
|
||||||
stickerArrow.current.style.transform = `rotate(${ang + Math.PI}rad)`;
|
stickerArrow.current.style.transform = `rotate(${ang + Math.PI}rad)`;
|
||||||
},
|
},
|
||||||
[stickerArrow, stickerImage, angle]
|
[stickerArrow, stickerImage],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onDragStart = React.useCallback(() => {
|
const onDragStart = React.useCallback(() => {
|
||||||
|
if (!layer?.dragging) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
layer.dragging.disable();
|
layer.dragging.disable();
|
||||||
MainMap.dragging.disable();
|
MainMap.dragging.disable();
|
||||||
MainMap.disableClicks();
|
MainMap.disableClicks();
|
||||||
|
|
||||||
setDragging(true);
|
setDragging(true);
|
||||||
}, [setDragging, layer, MainMap]);
|
}, [setDragging, layer]);
|
||||||
|
|
||||||
const onDragStop = React.useCallback(
|
const onDragStop = React.useCallback(
|
||||||
event => {
|
event => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (!layer) return;
|
if (!layer?.dragging) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setDragging(false);
|
setDragging(false);
|
||||||
onChange({
|
onChange({
|
||||||
|
@ -92,7 +100,7 @@ const Sticker: React.FC<IProps> = ({
|
||||||
|
|
||||||
setTimeout(MainMap.enableClicks, 100);
|
setTimeout(MainMap.enableClicks, 100);
|
||||||
},
|
},
|
||||||
[setDragging, layer, MainMap, sticker, angle]
|
[setDragging, layer, MainMap, sticker, angle],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onMoveStarted = React.useCallback(() => {
|
const onMoveStarted = React.useCallback(() => {
|
||||||
|
@ -110,7 +118,7 @@ const Sticker: React.FC<IProps> = ({
|
||||||
|
|
||||||
MainMap.enableClicks();
|
MainMap.enableClicks();
|
||||||
},
|
},
|
||||||
[onChange, sticker]
|
[onChange, sticker],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onDrag = React.useCallback(
|
const onDrag = React.useCallback(
|
||||||
|
@ -122,7 +130,7 @@ const Sticker: React.FC<IProps> = ({
|
||||||
angle.current = parseFloat(Math.atan2(y - pageY, x - pageX).toFixed(2));
|
angle.current = parseFloat(Math.atan2(y - pageY, x - pageX).toFixed(2));
|
||||||
updateAngle(angle.current);
|
updateAngle(angle.current);
|
||||||
},
|
},
|
||||||
[element, updateAngle, angle]
|
[element, updateAngle, angle],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onTextChange = React.useCallback(text => setText(text), [sticker, onChange]);
|
const onTextChange = React.useCallback(text => setText(text), [sticker, onChange]);
|
||||||
|
@ -134,7 +142,9 @@ const Sticker: React.FC<IProps> = ({
|
||||||
});
|
});
|
||||||
}, [text, onChange, sticker]);
|
}, [text, onChange, sticker]);
|
||||||
|
|
||||||
const direction = React.useMemo(() => getLabelDirection(sticker.angle), [sticker.angle]);
|
const direction = React.useMemo(() => {
|
||||||
|
getLabelDirection(sticker?.angle);
|
||||||
|
}, [sticker.angle]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateAngle(sticker.angle);
|
updateAngle(sticker.angle);
|
||||||
|
@ -148,15 +158,16 @@ const Sticker: React.FC<IProps> = ({
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!layer) return;
|
if (!layer) return;
|
||||||
setText(sticker.text);
|
|
||||||
|
setText(sticker.text || '');
|
||||||
}, [layer, sticker.text]);
|
}, [layer, sticker.text]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!wrapper || !wrapper.current) return;
|
if (!wrapper || !wrapper.current) return;
|
||||||
|
|
||||||
const scale = getAdaptiveScale(zoom) // adaptive zoom :-)
|
const scale = getAdaptiveScale(zoom); // adaptive zoom :-)
|
||||||
|
|
||||||
wrapper.current.style.transform = `scale(${scale}) perspective(1px)`
|
wrapper.current.style.transform = `scale(${scale}) perspective(1px)`;
|
||||||
}, [zoom, wrapper]);
|
}, [zoom, wrapper]);
|
||||||
|
|
||||||
// Attaches onMoveFinished event to item
|
// Attaches onMoveFinished event to item
|
||||||
|
@ -235,7 +246,7 @@ const Sticker: React.FC<IProps> = ({
|
||||||
<div className="sticker-delete" onMouseDown={onDelete} onTouchStart={onDelete} />
|
<div className="sticker-delete" onMouseDown={onDelete} onTouchStart={onDelete} />
|
||||||
</div>
|
</div>
|
||||||
</div>,
|
</div>,
|
||||||
element
|
element,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ interface IProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Stickers: FC<IProps> = memo(({ stickers, is_editing, mapSetSticker, mapDropSticker }) => {
|
const Stickers: FC<IProps> = memo(({ stickers, is_editing, mapSetSticker, mapDropSticker }) => {
|
||||||
const [layer, setLayer] = useState<FeatureGroup>(null);
|
const [layer, setLayer] = useState<FeatureGroup | null>(null);
|
||||||
const [zoom, setZoom] = useState(MainMap.getZoom());
|
const [zoom, setZoom] = useState(MainMap.getZoom());
|
||||||
|
|
||||||
const onZoomChange = useCallback(
|
const onZoomChange = useCallback(
|
||||||
|
|
|
@ -10,7 +10,7 @@ type IProps = React.HTMLAttributes<HTMLDivElement> & {
|
||||||
};
|
};
|
||||||
|
|
||||||
const TileLayer: React.FC<IProps> = React.memo(({ children, provider, map }) => {
|
const TileLayer: React.FC<IProps> = React.memo(({ children, provider, map }) => {
|
||||||
const [layer, setLayer] = React.useState<TileLayerInterface>(null);
|
const [layer, setLayer] = React.useState<TileLayerInterface | undefined>(undefined);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!map) return;
|
if (!map) return;
|
||||||
|
@ -34,7 +34,11 @@ const TileLayer: React.FC<IProps> = React.memo(({ children, provider, map }) =>
|
||||||
layer.setUrl(url);
|
layer.setUrl(url);
|
||||||
}, [layer, provider]);
|
}, [layer, provider]);
|
||||||
|
|
||||||
return <TileContext.Provider value={layer}>{children}</TileContext.Provider>;
|
return (
|
||||||
|
<TileContext.Provider value={layer}>
|
||||||
|
{children}
|
||||||
|
</TileContext.Provider>
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export { TileLayer };
|
export { TileLayer };
|
||||||
|
|
|
@ -41,7 +41,7 @@ export interface IEditorState {
|
||||||
distance: number;
|
distance: number;
|
||||||
estimated: number;
|
estimated: number;
|
||||||
speed: number;
|
speed: number;
|
||||||
activeSticker: { set?: string; sticker?: string };
|
activeSticker: { set: string; sticker: string };
|
||||||
is_empty: boolean;
|
is_empty: boolean;
|
||||||
is_published: boolean;
|
is_published: boolean;
|
||||||
is_routing: boolean;
|
is_routing: boolean;
|
||||||
|
@ -134,7 +134,7 @@ export const EDITOR_INITIAL_STATE = {
|
||||||
},
|
},
|
||||||
|
|
||||||
save: {
|
save: {
|
||||||
error: null,
|
error: '',
|
||||||
finished: false,
|
finished: false,
|
||||||
overwriting: false,
|
overwriting: false,
|
||||||
processing: false,
|
processing: false,
|
||||||
|
|
|
@ -69,8 +69,14 @@ import uuid from 'uuid';
|
||||||
import { getRandomColor, getAdaptiveScale } from '~/utils/dom';
|
import { getRandomColor, getAdaptiveScale } from '~/utils/dom';
|
||||||
|
|
||||||
const hideLoader = () => {
|
const hideLoader = () => {
|
||||||
document.getElementById('loader').style.opacity = String(0);
|
const el = document.getElementById('loader');
|
||||||
document.getElementById('loader').style.pointerEvents = 'none';
|
|
||||||
|
if (!el) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
el.style.opacity = String(0);
|
||||||
|
el.style.pointerEvents = 'none';
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
@ -125,6 +131,10 @@ function* getRenderData() {
|
||||||
canvas.height = window.innerHeight;
|
canvas.height = window.innerHeight;
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
if (!ctx) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const geometry = getTilePlacement();
|
const geometry = getTilePlacement();
|
||||||
const points = getPolyPlacement(route);
|
const points = getPolyPlacement(route);
|
||||||
const sticker_points = getStickersPlacement(stickers);
|
const sticker_points = getStickersPlacement(stickers);
|
||||||
|
@ -184,6 +194,11 @@ function* getCropData({ x, y, width, height }) {
|
||||||
canvas.width = width;
|
canvas.width = width;
|
||||||
canvas.height = height;
|
canvas.height = height;
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
if (!ctx) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const image = yield imageFetcher(data);
|
const image = yield imageFetcher(data);
|
||||||
|
|
||||||
ctx.drawImage(image, -x, -y);
|
ctx.drawImage(image, -x, -y);
|
||||||
|
@ -281,7 +296,7 @@ function* mapClick({ latlng }: ReturnType<typeof mapClicked>) {
|
||||||
|
|
||||||
function* routerSubmit() {
|
function* routerSubmit() {
|
||||||
const route: ReturnType<typeof selectMapRoute> = yield select(selectMapRoute);
|
const route: ReturnType<typeof selectMapRoute> = yield select(selectMapRoute);
|
||||||
const latlngs: LatLng[] = path(['_routes', 0, 'coordinates'], OsrmRouter);
|
const latlngs: LatLng[] = path(['_routes', 0, 'coordinates'], OsrmRouter) || [];
|
||||||
|
|
||||||
const coordinates = simplify(latlngs);
|
const coordinates = simplify(latlngs);
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ export const MAP_INITIAL_STATE: IMapReducer = {
|
||||||
address: '',
|
address: '',
|
||||||
address_origin: '',
|
address_origin: '',
|
||||||
description: '',
|
description: '',
|
||||||
owner: { id: null },
|
owner: { id: '' },
|
||||||
is_public: false,
|
is_public: false,
|
||||||
zoom: 13,
|
zoom: 13,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,40 +1,30 @@
|
||||||
import {
|
import { call, delay, put, race, select, take, TakeEffect, takeEvery, takeLatest } from 'redux-saga/effects';
|
||||||
takeEvery,
|
|
||||||
select,
|
|
||||||
put,
|
|
||||||
call,
|
|
||||||
TakeEffect,
|
|
||||||
race,
|
|
||||||
take,
|
|
||||||
takeLatest,
|
|
||||||
delay,
|
|
||||||
} from 'redux-saga/effects';
|
|
||||||
import { MAP_ACTIONS } from './constants';
|
import { MAP_ACTIONS } from './constants';
|
||||||
import {
|
import {
|
||||||
mapClicked,
|
|
||||||
mapAddSticker,
|
mapAddSticker,
|
||||||
mapSetProvider,
|
mapClicked,
|
||||||
mapSet,
|
mapSet,
|
||||||
mapSetTitle,
|
|
||||||
mapSetAddressOrigin,
|
mapSetAddressOrigin,
|
||||||
|
mapSetProvider,
|
||||||
mapSetRoute,
|
mapSetRoute,
|
||||||
mapSetStickers,
|
mapSetStickers,
|
||||||
|
mapSetTitle,
|
||||||
} from './actions';
|
} from './actions';
|
||||||
import { selectUser, selectUserUser } from '~/redux/user/selectors';
|
import { selectUser } from '~/redux/user/selectors';
|
||||||
import { MODES } from '~/constants/modes';
|
import { MODES } from '~/constants/modes';
|
||||||
import {
|
import {
|
||||||
|
editorCaptureHistory,
|
||||||
editorChangeMode,
|
editorChangeMode,
|
||||||
|
editorClearAll,
|
||||||
|
editorSendSaveRequest,
|
||||||
|
editorSetActiveSticker,
|
||||||
editorSetChanged,
|
editorSetChanged,
|
||||||
editorSetEditing,
|
editorSetEditing,
|
||||||
editorSetReady,
|
|
||||||
editorSetActiveSticker,
|
|
||||||
editorSendSaveRequest,
|
|
||||||
editorSetSave,
|
|
||||||
editorClearAll,
|
|
||||||
editorSetHistory,
|
editorSetHistory,
|
||||||
editorCaptureHistory,
|
editorSetReady,
|
||||||
|
editorSetSave,
|
||||||
} from '~/redux/editor/actions';
|
} from '~/redux/editor/actions';
|
||||||
import { pushLoaderState, getUrlData, pushPath } from '~/utils/history';
|
import { getUrlData, pushLoaderState, pushPath } from '~/utils/history';
|
||||||
import { getStoredMap, postMap } from '~/utils/api';
|
import { getStoredMap, postMap } from '~/utils/api';
|
||||||
import { Unwrap } from '~/utils/middleware';
|
import { Unwrap } from '~/utils/middleware';
|
||||||
import { selectMap, selectMapProvider, selectMapRoute, selectMapStickers } from './selectors';
|
import { selectMap, selectMapProvider, selectMapRoute, selectMapStickers } from './selectors';
|
||||||
|
@ -70,8 +60,11 @@ export function* replaceAddressIfItsBusy(destination, original) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* loadMapSaga(path) {
|
export function* loadMapSaga(path) {
|
||||||
|
try {
|
||||||
const {
|
const {
|
||||||
data: { route, error, random_url },
|
data: {
|
||||||
|
route, error, random_url,
|
||||||
|
},
|
||||||
}: Unwrap<typeof getStoredMap> = yield call(getStoredMap, { name: path });
|
}: Unwrap<typeof getStoredMap> = yield call(getStoredMap, { name: path });
|
||||||
|
|
||||||
if (route && !error) {
|
if (route && !error) {
|
||||||
|
@ -85,7 +78,7 @@ export function* loadMapSaga(path) {
|
||||||
description: route.description,
|
description: route.description,
|
||||||
is_public: route.is_public,
|
is_public: route.is_public,
|
||||||
logo: route.logo,
|
logo: route.logo,
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
yield put(editorSetHistory({ records: [{ route: route.route, stickers: route.stickers }] }));
|
yield put(editorSetHistory({ records: [{ route: route.route, stickers: route.stickers }] }));
|
||||||
|
@ -93,6 +86,10 @@ export function* loadMapSaga(path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
yield call(startEmptyEditorSaga);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* startEmptyEditorSaga() {
|
export function* startEmptyEditorSaga() {
|
||||||
|
@ -142,10 +139,10 @@ export function* mapInitSaga() {
|
||||||
yield put(mapSetProvider(provider));
|
yield put(mapSetProvider(provider));
|
||||||
|
|
||||||
if (hash && /^#map/.test(hash)) {
|
if (hash && /^#map/.test(hash)) {
|
||||||
const [, newUrl] = hash.match(/^#map[:/?!](.*)$/);
|
const matches = hash.match(/^#map[:/?!](.*)$/);
|
||||||
|
|
||||||
if (newUrl) {
|
if (matches && matches[1]) {
|
||||||
yield pushPath(`/${newUrl}`);
|
yield pushPath(`/${matches[1]}`);
|
||||||
yield call(setReadySaga);
|
yield call(setReadySaga);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -161,7 +158,7 @@ function* setActiveStickerSaga() {
|
||||||
yield put(editorChangeMode(MODES.STICKERS));
|
yield put(editorChangeMode(MODES.STICKERS));
|
||||||
}
|
}
|
||||||
|
|
||||||
function* setTitleSaga({ title }: ReturnType<typeof mapSetTitle>) {
|
function setTitleSaga({ title }: ReturnType<typeof mapSetTitle>) {
|
||||||
if (title) {
|
if (title) {
|
||||||
document.title = `${title} | Редактор маршрутов`;
|
document.title = `${title} | Редактор маршрутов`;
|
||||||
}
|
}
|
||||||
|
@ -216,7 +213,7 @@ function* clearSaga({ type }) {
|
||||||
const { mode, activeSticker }: ReturnType<typeof selectEditor> = yield select(selectEditor);
|
const { mode, activeSticker }: ReturnType<typeof selectEditor> = yield select(selectEditor);
|
||||||
|
|
||||||
if (activeSticker && activeSticker.set && activeSticker.sticker) {
|
if (activeSticker && activeSticker.set && activeSticker.sticker) {
|
||||||
yield put(editorSetActiveSticker(null));
|
yield put(editorSetActiveSticker({ set: '', sticker: '' }));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode !== MODES.NONE) {
|
if (mode !== MODES.NONE) {
|
||||||
|
@ -231,19 +228,19 @@ function* sendSaveRequestSaga({
|
||||||
is_public,
|
is_public,
|
||||||
description,
|
description,
|
||||||
}: ReturnType<typeof editorSendSaveRequest>) {
|
}: ReturnType<typeof editorSendSaveRequest>) {
|
||||||
|
try {
|
||||||
const { route, stickers, provider }: ReturnType<typeof selectMap> = yield select(selectMap);
|
const { route, stickers, provider }: ReturnType<typeof selectMap> = yield select(selectMap);
|
||||||
|
|
||||||
if (!route.length && !stickers.length) {
|
if (!route.length && !stickers.length) {
|
||||||
return yield put(
|
return yield put(
|
||||||
editorSetSave({ error: TIPS.SAVE_EMPTY, loading: false, overwriting: false, finished: false })
|
editorSetSave({ error: TIPS.SAVE_EMPTY, loading: false, overwriting: false, finished: false }),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { logo }: ReturnType<typeof selectMap> = yield select(selectMap);
|
const { logo }: ReturnType<typeof selectMap> = yield select(selectMap);
|
||||||
const { distance }: ReturnType<typeof selectEditor> = yield select(selectEditor);
|
const { distance }: ReturnType<typeof selectEditor> = yield select(selectEditor);
|
||||||
const { token }: ReturnType<typeof selectUserUser> = yield select(selectUserUser);
|
|
||||||
|
|
||||||
yield put(editorSetSave({ loading: true, overwriting: false, finished: false, error: null }));
|
yield put(editorSetSave({ loading: true, overwriting: false, finished: false, error: '' }));
|
||||||
|
|
||||||
const {
|
const {
|
||||||
result,
|
result,
|
||||||
|
@ -255,7 +252,6 @@ function* sendSaveRequestSaga({
|
||||||
cancel: TakeEffect;
|
cancel: TakeEffect;
|
||||||
} = yield race({
|
} = yield race({
|
||||||
result: postMap({
|
result: postMap({
|
||||||
token,
|
|
||||||
route,
|
route,
|
||||||
stickers,
|
stickers,
|
||||||
title,
|
title,
|
||||||
|
@ -285,7 +281,7 @@ function* sendSaveRequestSaga({
|
||||||
loading: false,
|
loading: false,
|
||||||
overwriting: false,
|
overwriting: false,
|
||||||
finished: false,
|
finished: false,
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (timeout || !result || !result.data.route || !result.data.route.address)
|
if (timeout || !result || !result.data.route || !result.data.route.address)
|
||||||
|
@ -295,7 +291,7 @@ function* sendSaveRequestSaga({
|
||||||
loading: false,
|
loading: false,
|
||||||
overwriting: false,
|
overwriting: false,
|
||||||
finished: false,
|
finished: false,
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
yield put(
|
yield put(
|
||||||
|
@ -304,7 +300,7 @@ function* sendSaveRequestSaga({
|
||||||
title: result.data.route.title,
|
title: result.data.route.title,
|
||||||
is_public: result.data.route.is_public,
|
is_public: result.data.route.is_public,
|
||||||
description: result.data.route.description,
|
description: result.data.route.description,
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
yield put(editorSetReady(false));
|
yield put(editorSetReady(false));
|
||||||
|
@ -317,8 +313,11 @@ function* sendSaveRequestSaga({
|
||||||
loading: false,
|
loading: false,
|
||||||
overwriting: false,
|
overwriting: false,
|
||||||
finished: true,
|
finished: true,
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function* setChanged() {
|
function* setChanged() {
|
||||||
|
@ -328,14 +327,10 @@ function* setChanged() {
|
||||||
yield put(editorSetChanged(true));
|
yield put(editorSetChanged(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
function* onZoomChange() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export function* mapSaga() {
|
export function* mapSaga() {
|
||||||
yield takeEvery(
|
yield takeEvery(
|
||||||
[MAP_ACTIONS.SET_ROUTE, MAP_ACTIONS.SET_STICKER, MAP_ACTIONS.SET_STICKERS],
|
[MAP_ACTIONS.SET_ROUTE, MAP_ACTIONS.SET_STICKER, MAP_ACTIONS.SET_STICKERS, MAP_ACTIONS.ADD_STICKER],
|
||||||
setChanged
|
setChanged,
|
||||||
);
|
);
|
||||||
|
|
||||||
yield takeEvery(EDITOR_ACTIONS.START_EDITING, startEditingSaga);
|
yield takeEvery(EDITOR_ACTIONS.START_EDITING, startEditingSaga);
|
||||||
|
@ -351,6 +346,6 @@ export function* mapSaga() {
|
||||||
EDITOR_ACTIONS.CLEAR_ALL,
|
EDITOR_ACTIONS.CLEAR_ALL,
|
||||||
EDITOR_ACTIONS.CLEAR_CANCEL,
|
EDITOR_ACTIONS.CLEAR_CANCEL,
|
||||||
],
|
],
|
||||||
clearSaga
|
clearSaga,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { createStore, applyMiddleware, combineReducers, compose, Store } from 'redux';
|
import { applyMiddleware, combineReducers, compose, createStore, Store } from 'redux';
|
||||||
|
|
||||||
import { persistStore, persistReducer } from 'redux-persist';
|
import { persistReducer, persistStore } from 'redux-persist';
|
||||||
import storage from 'redux-persist/lib/storage';
|
import storage from 'redux-persist/lib/storage';
|
||||||
import createSagaMiddleware from 'redux-saga';
|
import createSagaMiddleware from 'redux-saga';
|
||||||
|
|
||||||
|
@ -8,19 +8,28 @@ import { createBrowserHistory } from 'history';
|
||||||
import { editorLocationChanged } from '~/redux/editor/actions';
|
import { editorLocationChanged } from '~/redux/editor/actions';
|
||||||
import { PersistConfig, Persistor } from 'redux-persist/es/types';
|
import { PersistConfig, Persistor } from 'redux-persist/es/types';
|
||||||
|
|
||||||
import { userReducer, IRootReducer } from '~/redux/user';
|
import { IRootReducer, userReducer } from '~/redux/user';
|
||||||
import { userSaga } from '~/redux/user/sagas';
|
import { userSaga } from '~/redux/user/sagas';
|
||||||
|
|
||||||
import { editor, IEditorState } from '~/redux/editor';
|
import { editor, IEditorState } from '~/redux/editor';
|
||||||
import { editorSaga } from '~/redux/editor/sagas';
|
import { editorSaga } from '~/redux/editor/sagas';
|
||||||
|
|
||||||
import { map, IMapReducer } from '~/redux/map';
|
import { IMapReducer, map } from '~/redux/map';
|
||||||
import { mapSaga } from '~/redux/map/sagas';
|
import { mapSaga } from '~/redux/map/sagas';
|
||||||
import { watchLocation, getLocation } from '~/utils/window';
|
import { watchLocation } from '~/utils/window';
|
||||||
import { LatLngLiteral } from 'leaflet';
|
import { LatLngLiteral } from 'leaflet';
|
||||||
import { setUserLocation } from './user/actions';
|
import { setUserLocation, userLogout } from './user/actions';
|
||||||
import { MainMap } from '~/constants/map';
|
import { MainMap } from '~/constants/map';
|
||||||
import { mapZoomChange } from './map/actions';
|
import { mapZoomChange } from './map/actions';
|
||||||
|
import { assocPath } from 'ramda';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
import { api } from '~/utils/api/instance';
|
||||||
|
|
||||||
|
const mapPersistConfig: PersistConfig = {
|
||||||
|
key: 'map',
|
||||||
|
whitelist: ['logo', 'provider'],
|
||||||
|
storage,
|
||||||
|
};
|
||||||
|
|
||||||
const userPersistConfig: PersistConfig = {
|
const userPersistConfig: PersistConfig = {
|
||||||
key: 'user',
|
key: 'user',
|
||||||
|
@ -52,7 +61,7 @@ export const store = createStore(
|
||||||
combineReducers({
|
combineReducers({
|
||||||
user: persistReducer(userPersistConfig, userReducer),
|
user: persistReducer(userPersistConfig, userReducer),
|
||||||
editor: persistReducer(editorPersistConfig, editor),
|
editor: persistReducer(editorPersistConfig, editor),
|
||||||
map,
|
map: persistReducer(mapPersistConfig, map),
|
||||||
}),
|
}),
|
||||||
composeEnhancers(applyMiddleware(sagaMiddleware))
|
composeEnhancers(applyMiddleware(sagaMiddleware))
|
||||||
);
|
);
|
||||||
|
@ -64,6 +73,28 @@ export function configureStore(): { store: Store<any>; persistor: Persistor } {
|
||||||
|
|
||||||
const persistor = persistStore(store);
|
const persistor = persistStore(store);
|
||||||
|
|
||||||
|
// Pass token to axios
|
||||||
|
api.interceptors.request.use(options => {
|
||||||
|
const token = store.getState().user.user.token;
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
return assocPath(['headers', 'authorization'], token, options);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Logout on 401
|
||||||
|
api.interceptors.response.use(undefined, (error: AxiosError<{ error: string }>) => {
|
||||||
|
if (error.response?.status === 401) {
|
||||||
|
store.dispatch(userLogout());
|
||||||
|
}
|
||||||
|
|
||||||
|
error.message = error?.response?.data?.error || error?.response?.statusText || error.message;
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
|
||||||
return { store, persistor };
|
return { store, persistor };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,5 +105,5 @@ history.listen((location, action) => {
|
||||||
store.dispatch(editorLocationChanged(location.pathname));
|
store.dispatch(editorLocationChanged(location.pathname));
|
||||||
});
|
});
|
||||||
|
|
||||||
watchLocation((location: LatLngLiteral) => store.dispatch(setUserLocation(location)));
|
watchLocation((location: LatLngLiteral | undefined) => store.dispatch(setUserLocation(location)));
|
||||||
MainMap.on('zoomend', event => store.dispatch(mapZoomChange(event.target._zoom)))
|
MainMap.on('zoomend', event => store.dispatch(mapZoomChange(event.target._zoom)))
|
||||||
|
|
|
@ -15,7 +15,7 @@ export interface IRouteListItem {
|
||||||
export interface IRootReducer {
|
export interface IRootReducer {
|
||||||
// ready: boolean,
|
// ready: boolean,
|
||||||
user: IUser;
|
user: IUser;
|
||||||
location: LatLngLiteral;
|
location?: LatLngLiteral;
|
||||||
routes: {
|
routes: {
|
||||||
limit: 0;
|
limit: 0;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
|
@ -38,7 +38,7 @@ export type IRootState = Readonly<IRootReducer>;
|
||||||
|
|
||||||
export const INITIAL_STATE: IRootReducer = {
|
export const INITIAL_STATE: IRootReducer = {
|
||||||
user: { ...DEFAULT_USER },
|
user: { ...DEFAULT_USER },
|
||||||
location: null,
|
location: undefined,
|
||||||
routes: {
|
routes: {
|
||||||
limit: 0,
|
limit: 0,
|
||||||
loading: false, // <-- maybe delete this
|
loading: false, // <-- maybe delete this
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { REHYDRATE, RehydrateAction } from 'redux-persist';
|
import { REHYDRATE, RehydrateAction } from 'redux-persist';
|
||||||
import { takeLatest, select, call, put, takeEvery, delay } from 'redux-saga/effects';
|
import { call, delay, put, select, takeEvery, takeLatest } from 'redux-saga/effects';
|
||||||
import {
|
import {
|
||||||
checkIframeToken,
|
checkIframeToken,
|
||||||
checkUserToken,
|
checkUserToken,
|
||||||
|
@ -9,15 +9,16 @@ import {
|
||||||
modifyRoute,
|
modifyRoute,
|
||||||
sendRouteStarred,
|
sendRouteStarred,
|
||||||
} from '~/utils/api';
|
} from '~/utils/api';
|
||||||
|
import * as ActionCreators from '~/redux/user/actions';
|
||||||
import {
|
import {
|
||||||
searchSetTab,
|
|
||||||
setUser,
|
|
||||||
mapsSetShift,
|
mapsSetShift,
|
||||||
searchChangeDistance,
|
searchChangeDistance,
|
||||||
searchPutRoutes,
|
searchPutRoutes,
|
||||||
searchSetLoading,
|
searchSetLoading,
|
||||||
|
searchSetTab,
|
||||||
searchSetTitle,
|
searchSetTitle,
|
||||||
setRouteStarred,
|
setRouteStarred,
|
||||||
|
setUser,
|
||||||
userLogin,
|
userLogin,
|
||||||
} from '~/redux/user/actions';
|
} from '~/redux/user/actions';
|
||||||
|
|
||||||
|
@ -26,8 +27,6 @@ import { USER_ACTIONS } from '~/redux/user/constants';
|
||||||
import { DEFAULT_USER } from '~/constants/auth';
|
import { DEFAULT_USER } from '~/constants/auth';
|
||||||
|
|
||||||
import { DIALOGS, TABS } from '~/constants/dialogs';
|
import { DIALOGS, TABS } from '~/constants/dialogs';
|
||||||
|
|
||||||
import * as ActionCreators from '~/redux/user/actions';
|
|
||||||
import { Unwrap } from '~/utils/middleware';
|
import { Unwrap } from '~/utils/middleware';
|
||||||
import { selectUser, selectUserUser } from './selectors';
|
import { selectUser, selectUserUser } from './selectors';
|
||||||
import { mapInitSaga } from '~/redux/map/sagas';
|
import { mapInitSaga } from '~/redux/map/sagas';
|
||||||
|
@ -35,6 +34,7 @@ import { editorSetDialog, editorSetDialogActive } from '../editor/actions';
|
||||||
import { selectEditor } from '../editor/selectors';
|
import { selectEditor } from '../editor/selectors';
|
||||||
|
|
||||||
function* generateGuestSaga() {
|
function* generateGuestSaga() {
|
||||||
|
try {
|
||||||
const {
|
const {
|
||||||
data: { user, random_url },
|
data: { user, random_url },
|
||||||
}: Unwrap<typeof getGuestToken> = yield call(getGuestToken);
|
}: Unwrap<typeof getGuestToken> = yield call(getGuestToken);
|
||||||
|
@ -42,9 +42,13 @@ function* generateGuestSaga() {
|
||||||
yield put(setUser({ ...user, random_url }));
|
yield put(setUser({ ...user, random_url }));
|
||||||
|
|
||||||
return { ...user, random_url };
|
return { ...user, random_url };
|
||||||
|
} catch(e) {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function* authCheckSaga({ key }: RehydrateAction) {
|
function* authCheckSaga({ key }: RehydrateAction) {
|
||||||
|
try {
|
||||||
if (key !== 'user') return;
|
if (key !== 'user') return;
|
||||||
|
|
||||||
pushLoaderState(70);
|
pushLoaderState(70);
|
||||||
|
@ -93,6 +97,9 @@ function* authCheckSaga({ key }: RehydrateAction) {
|
||||||
pushLoaderState(80);
|
pushLoaderState(80);
|
||||||
|
|
||||||
return yield call(mapInitSaga);
|
return yield call(mapInitSaga);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function* gotVkUserSaga({ user: u }: ReturnType<typeof ActionCreators.gotVkUser>) {
|
function* gotVkUserSaga({ user: u }: ReturnType<typeof ActionCreators.gotVkUser>) {
|
||||||
|
@ -105,8 +112,7 @@ function* gotVkUserSaga({ user: u }: ReturnType<typeof ActionCreators.gotVkUser>
|
||||||
}
|
}
|
||||||
|
|
||||||
function* searchGetRoutes() {
|
function* searchGetRoutes() {
|
||||||
const { token }: ReturnType<typeof selectUserUser> = yield select(selectUserUser);
|
try {
|
||||||
|
|
||||||
const {
|
const {
|
||||||
routes: {
|
routes: {
|
||||||
step,
|
step,
|
||||||
|
@ -116,7 +122,6 @@ function* searchGetRoutes() {
|
||||||
}: ReturnType<typeof selectUser> = yield select(selectUser);
|
}: ReturnType<typeof selectUser> = yield select(selectUser);
|
||||||
|
|
||||||
const result: Unwrap<typeof getRouteList> = yield getRouteList({
|
const result: Unwrap<typeof getRouteList> = yield getRouteList({
|
||||||
token,
|
|
||||||
search: title,
|
search: title,
|
||||||
min: distance[0],
|
min: distance[0],
|
||||||
max: distance[1],
|
max: distance[1],
|
||||||
|
@ -126,9 +131,13 @@ function* searchGetRoutes() {
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* searchSetSagaWorker() {
|
export function* searchSetSagaWorker() {
|
||||||
|
try {
|
||||||
const {
|
const {
|
||||||
routes: { filter },
|
routes: { filter },
|
||||||
}: ReturnType<typeof selectUser> = yield select(selectUser);
|
}: ReturnType<typeof selectUser> = yield select(selectUser);
|
||||||
|
@ -152,11 +161,14 @@ export function* searchSetSagaWorker() {
|
||||||
searchChangeDistance([
|
searchChangeDistance([
|
||||||
filter.min > min && filter.distance[0] <= filter.min ? min : filter.distance[0],
|
filter.min > min && filter.distance[0] <= filter.min ? min : filter.distance[0],
|
||||||
filter.max < max && filter.distance[1] >= filter.max ? max : filter.distance[1],
|
filter.max < max && filter.distance[1] >= filter.max ? max : filter.distance[1],
|
||||||
])
|
]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return yield put(searchSetLoading(false));
|
return yield put(searchSetLoading(false));
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function* searchSetSaga() {
|
function* searchSetSaga() {
|
||||||
|
@ -167,6 +179,7 @@ function* searchSetSaga() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function* openMapDialogSaga({ tab }: ReturnType<typeof ActionCreators.openMapDialog>) {
|
function* openMapDialogSaga({ tab }: ReturnType<typeof ActionCreators.openMapDialog>) {
|
||||||
|
try {
|
||||||
const {
|
const {
|
||||||
routes: {
|
routes: {
|
||||||
filter: { tab: current },
|
filter: { tab: current },
|
||||||
|
@ -187,6 +200,9 @@ function* openMapDialogSaga({ tab }: ReturnType<typeof ActionCreators.openMapDia
|
||||||
yield put(editorSetDialogActive(true));
|
yield put(editorSetDialogActive(true));
|
||||||
|
|
||||||
return tab;
|
return tab;
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function* searchSetTabSaga() {
|
function* searchSetTabSaga() {
|
||||||
|
@ -210,6 +226,7 @@ function* setUserSaga() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function* mapsLoadMoreSaga() {
|
function* mapsLoadMoreSaga() {
|
||||||
|
try {
|
||||||
const {
|
const {
|
||||||
routes: { limit, list, shift, step, loading, filter },
|
routes: { limit, list, shift, step, loading, filter },
|
||||||
}: ReturnType<typeof selectUser> = yield select(selectUser);
|
}: ReturnType<typeof selectUser> = yield select(selectUser);
|
||||||
|
@ -237,7 +254,7 @@ function* mapsLoadMoreSaga() {
|
||||||
searchChangeDistance([
|
searchChangeDistance([
|
||||||
filter.min > min && filter.distance[0] <= filter.min ? min : filter.distance[0],
|
filter.min > min && filter.distance[0] <= filter.min ? min : filter.distance[0],
|
||||||
filter.max < max && filter.distance[1] >= filter.max ? max : filter.distance[1],
|
filter.max < max && filter.distance[1] >= filter.max ? max : filter.distance[1],
|
||||||
])
|
]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,13 +266,16 @@ function* mapsLoadMoreSaga() {
|
||||||
shift: resp_shift,
|
shift: resp_shift,
|
||||||
step: resp_step,
|
step: resp_step,
|
||||||
list: [...list, ...routes],
|
list: [...list, ...routes],
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
yield put(searchSetLoading(false));
|
yield put(searchSetLoading(false));
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function* dropRouteSaga({ address }: ReturnType<typeof ActionCreators.dropRoute>) {
|
function* dropRouteSaga({ address }: ReturnType<typeof ActionCreators.dropRoute>) {
|
||||||
const { token }: ReturnType<typeof selectUserUser> = yield select(selectUserUser);
|
try {
|
||||||
const {
|
const {
|
||||||
routes: {
|
routes: {
|
||||||
list,
|
list,
|
||||||
|
@ -277,11 +297,14 @@ function* dropRouteSaga({ address }: ReturnType<typeof ActionCreators.dropRoute>
|
||||||
step,
|
step,
|
||||||
shift: shift > 0 ? shift - 1 : 0,
|
shift: shift > 0 ? shift - 1 : 0,
|
||||||
limit: limit > 0 ? limit - 1 : limit,
|
limit: limit > 0 ? limit - 1 : limit,
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return yield call(dropRoute, { address, token });
|
return yield call(dropRoute, { address });
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function* modifyRouteSaga({
|
function* modifyRouteSaga({
|
||||||
|
@ -289,7 +312,7 @@ function* modifyRouteSaga({
|
||||||
title,
|
title,
|
||||||
is_public,
|
is_public,
|
||||||
}: ReturnType<typeof ActionCreators.modifyRoute>) {
|
}: ReturnType<typeof ActionCreators.modifyRoute>) {
|
||||||
const { token }: ReturnType<typeof selectUserUser> = yield select(selectUserUser);
|
try {
|
||||||
const {
|
const {
|
||||||
routes: {
|
routes: {
|
||||||
list,
|
list,
|
||||||
|
@ -311,31 +334,37 @@ function* modifyRouteSaga({
|
||||||
step,
|
step,
|
||||||
shift: shift > 0 ? shift - 1 : 0,
|
shift: shift > 0 ? shift - 1 : 0,
|
||||||
limit: limit > 0 ? limit - 1 : limit,
|
limit: limit > 0 ? limit - 1 : limit,
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return yield call(modifyRoute, { address, token, title, is_public });
|
return yield call(modifyRoute, { address, title, is_public });
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function* toggleRouteStarredSaga({
|
function* toggleRouteStarredSaga({
|
||||||
address,
|
address,
|
||||||
}: ReturnType<typeof ActionCreators.toggleRouteStarred>) {
|
}: ReturnType<typeof ActionCreators.toggleRouteStarred>) {
|
||||||
const { token }: ReturnType<typeof selectUserUser> = yield select(selectUserUser);
|
try {
|
||||||
const {
|
const {
|
||||||
routes: { list },
|
routes: { list },
|
||||||
}: ReturnType<typeof selectUser> = yield select(selectUser);
|
}: ReturnType<typeof selectUser> = yield select(selectUser);
|
||||||
|
|
||||||
const route = list.find(el => el.address === address);
|
const route = list.find(el => el.address === address);
|
||||||
|
|
||||||
yield put(setRouteStarred(address, !route.is_published));
|
yield put(setRouteStarred(address, !route?.is_published));
|
||||||
|
|
||||||
const result = yield sendRouteStarred({
|
const result = yield sendRouteStarred({
|
||||||
token,
|
|
||||||
address,
|
address,
|
||||||
is_published: !route.is_published,
|
is_published: !route?.is_published,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result) return yield put(setRouteStarred(address, route.is_published));
|
if (!result) return yield put(setRouteStarred(address, !!route?.is_published));
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* updateUserRoutes() {
|
export function* updateUserRoutes() {
|
||||||
|
@ -350,7 +379,7 @@ export function* userSaga() {
|
||||||
|
|
||||||
yield takeLatest(
|
yield takeLatest(
|
||||||
[USER_ACTIONS.SEARCH_SET_TITLE, USER_ACTIONS.SEARCH_SET_DISTANCE],
|
[USER_ACTIONS.SEARCH_SET_TITLE, USER_ACTIONS.SEARCH_SET_DISTANCE],
|
||||||
searchSetSaga
|
searchSetSaga,
|
||||||
);
|
);
|
||||||
|
|
||||||
yield takeLatest(USER_ACTIONS.OPEN_MAP_DIALOG, openMapDialogSaga);
|
yield takeLatest(USER_ACTIONS.OPEN_MAP_DIALOG, openMapDialogSaga);
|
||||||
|
|
|
@ -32,4 +32,4 @@ $tooltip_background: #123740;
|
||||||
$loading_shade: darken($blue_secondary, 20%);
|
$loading_shade: darken($blue_secondary, 20%);
|
||||||
$cluster_small: #0069a7;
|
$cluster_small: #0069a7;
|
||||||
|
|
||||||
$title_dialog_color: fade(#111111, 85%);
|
$title_dialog_color: darken(#111111, 85%);
|
||||||
|
|
|
@ -178,7 +178,7 @@
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
background: linear-gradient(fade($loading_shade, 0%), $loading_shade 70%);
|
background: linear-gradient(darken($loading_shade, 0%), $loading_shade 70%);
|
||||||
height: 100px;
|
height: 100px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
transition: opacity 100ms;
|
transition: opacity 100ms;
|
||||||
|
@ -240,7 +240,7 @@
|
||||||
&.has_edit {
|
&.has_edit {
|
||||||
//transform: translateY(-2px);
|
//transform: translateY(-2px);
|
||||||
.route-row {
|
.route-row {
|
||||||
background: fade($green_secondary, 30%);
|
background: darken($green_secondary, 30%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,11 +280,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.route-row-edit {
|
.route-row-edit {
|
||||||
background: fade($green_secondary, 30%);
|
background: darken($green_secondary, 30%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.route-row-drop {
|
.route-row-drop {
|
||||||
background: fade($red_secondary, 20%);
|
background: darken($red_secondary, 20%);
|
||||||
|
|
||||||
.route-row {
|
.route-row {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -341,13 +341,13 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
fill: fade(white, 30%);
|
fill: darken(white, 30%);
|
||||||
background: fade(white, 8%);
|
background: transparentize(white, 0.9);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 250ms, transform 500ms;
|
transition: background 250ms, transform 500ms;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: fade(white, 10%);
|
background: transparentize(white, 0.95);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -361,7 +361,7 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: all 500ms;
|
transition: all 500ms;
|
||||||
display: flex;
|
display: flex;
|
||||||
fill: fade(white, 30%);
|
fill: darken(white, 30%);
|
||||||
|
|
||||||
div {
|
div {
|
||||||
width: 60px;
|
width: 60px;
|
||||||
|
@ -371,16 +371,16 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
box-shadow: fade(black, 30%) 1px 0;
|
box-shadow: darken(black, 30%) 1px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: fade($red_secondary, 30%);
|
background: darken($red_secondary, 30%);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.modify-button {
|
&.modify-button {
|
||||||
&:hover {
|
&:hover {
|
||||||
background: fade($green_secondary, 30%);
|
background: darken($green_secondary, 30%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -399,7 +399,7 @@
|
||||||
|
|
||||||
.route-row-corner {
|
.route-row-corner {
|
||||||
svg {
|
svg {
|
||||||
fill: fade(white, 50%);
|
fill: darken(white, 50%);
|
||||||
margin-right: 2px;
|
margin-right: 2px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,28 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.leaflet-control-zoom {
|
.leaflet-control-zoom {
|
||||||
display: none;
|
width: 32px;
|
||||||
|
opacity: 0.5;
|
||||||
|
transition: opacity 0.1s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a.leaflet-control-zoom-in, a.leaflet-control-zoom-out {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.leaflet-touch .leaflet-bar a {
|
.leaflet-touch .leaflet-bar a {
|
||||||
|
@ -308,7 +329,7 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: white;
|
color: white;
|
||||||
box-shadow: fade($cluster_small, 70%) 0 0 0 5px;
|
box-shadow: darken($cluster_small, 70%) 0 0 0 5px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
transform: translate(-12px, -12px);
|
transform: translate(-12px, -12px);
|
||||||
|
@ -317,7 +338,7 @@
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: fade($cluster_small, 70%) 0 0 0 7px;
|
box-shadow: darken($cluster_small, 70%) 0 0 0 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
|
|
|
@ -729,7 +729,7 @@
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background: $title_dialog_color;
|
background: $title_dialog_color;
|
||||||
color: fade(white, 50%);
|
color: darken(white, 50%);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-radius: $panel_radius;
|
border-radius: $panel_radius;
|
||||||
|
@ -761,7 +761,7 @@
|
||||||
content: ' ';
|
content: ' ';
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
background: linear-gradient(fade($title_dialog_color, 0), $title_dialog_color);
|
background: linear-gradient(darken($title_dialog_color, 0), $title_dialog_color);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
color: fade(white, 70%);
|
color: darken(white, 70%);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
fill: white;
|
fill: white;
|
||||||
|
|
258
src/utils/api.ts
258
src/utils/api.ts
|
@ -1,258 +0,0 @@
|
||||||
import axios from 'axios/index';
|
|
||||||
import { API } from '~/constants/api';
|
|
||||||
import { IRootState, IRouteListItem } from '~/redux/user';
|
|
||||||
import { IUser } from '~/constants/auth';
|
|
||||||
import { CLIENT } from '~/config/frontend';
|
|
||||||
import { LatLngLiteral } from 'leaflet';
|
|
||||||
import {
|
|
||||||
resultMiddleware,
|
|
||||||
errorMiddleware,
|
|
||||||
IResultWithStatus,
|
|
||||||
configWithToken,
|
|
||||||
} from './middleware';
|
|
||||||
import { IRoute } from '~/redux/map/types';
|
|
||||||
import { INominatimResult } from '~/redux/types';
|
|
||||||
import { MainMap } from '~/constants/map';
|
|
||||||
|
|
||||||
const arrayToObject = (array: any[], key: string): {} =>
|
|
||||||
array.reduce((obj, el) => ({ ...obj, [el[key]]: el }), {});
|
|
||||||
|
|
||||||
interface IGetRouteList {
|
|
||||||
min: number;
|
|
||||||
max: number;
|
|
||||||
tab: string;
|
|
||||||
search: string;
|
|
||||||
step: IRootState['routes']['step'];
|
|
||||||
shift: IRootState['routes']['step'];
|
|
||||||
token: IRootState['user']['token'];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IGetRouteListResult {
|
|
||||||
min: IRootState['routes']['filter']['min'];
|
|
||||||
max: IRootState['routes']['filter']['max'];
|
|
||||||
limit: IRootState['routes']['limit'];
|
|
||||||
step: IRootState['routes']['step'];
|
|
||||||
shift: IRootState['routes']['shift'];
|
|
||||||
list: IRootState['routes']['list'];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const checkUserToken = ({
|
|
||||||
id,
|
|
||||||
token,
|
|
||||||
}: {
|
|
||||||
id: IRootState['user']['id'];
|
|
||||||
token: IRootState['user']['token'];
|
|
||||||
}): Promise<IResultWithStatus<{
|
|
||||||
user: IUser;
|
|
||||||
random_url: string;
|
|
||||||
routes: IRouteListItem[];
|
|
||||||
}>> =>
|
|
||||||
axios
|
|
||||||
.get(API.CHECK_TOKEN, {
|
|
||||||
params: { id, token },
|
|
||||||
})
|
|
||||||
.then(resultMiddleware)
|
|
||||||
.catch(errorMiddleware);
|
|
||||||
|
|
||||||
export const getGuestToken = (): Promise<IResultWithStatus<{
|
|
||||||
user: IUser;
|
|
||||||
random_url: string;
|
|
||||||
}>> =>
|
|
||||||
axios
|
|
||||||
.get(API.GET_GUEST)
|
|
||||||
.then(resultMiddleware)
|
|
||||||
.catch(errorMiddleware);
|
|
||||||
|
|
||||||
export const getStoredMap = ({
|
|
||||||
name,
|
|
||||||
}: {
|
|
||||||
name: IRoute['address'];
|
|
||||||
}): Promise<IResultWithStatus<{
|
|
||||||
route: IRoute;
|
|
||||||
error?: string;
|
|
||||||
random_url: string;
|
|
||||||
}>> =>
|
|
||||||
axios
|
|
||||||
.get(API.GET_MAP, {
|
|
||||||
params: { name },
|
|
||||||
})
|
|
||||||
.then(resultMiddleware)
|
|
||||||
.catch(errorMiddleware);
|
|
||||||
|
|
||||||
export const postMap = ({
|
|
||||||
title,
|
|
||||||
address,
|
|
||||||
route,
|
|
||||||
stickers,
|
|
||||||
force,
|
|
||||||
logo,
|
|
||||||
distance,
|
|
||||||
provider,
|
|
||||||
is_public,
|
|
||||||
description,
|
|
||||||
token,
|
|
||||||
}: Partial<IRoute> & {
|
|
||||||
force: boolean;
|
|
||||||
token: string;
|
|
||||||
}): Promise<IResultWithStatus<{
|
|
||||||
route: IRoute;
|
|
||||||
error?: string;
|
|
||||||
code?: string;
|
|
||||||
}>> =>
|
|
||||||
axios
|
|
||||||
.post(
|
|
||||||
API.POST_MAP,
|
|
||||||
{
|
|
||||||
route: {
|
|
||||||
title,
|
|
||||||
address,
|
|
||||||
route,
|
|
||||||
stickers,
|
|
||||||
logo,
|
|
||||||
distance,
|
|
||||||
provider,
|
|
||||||
is_public,
|
|
||||||
description,
|
|
||||||
},
|
|
||||||
force,
|
|
||||||
},
|
|
||||||
configWithToken(token)
|
|
||||||
)
|
|
||||||
.then(resultMiddleware)
|
|
||||||
.catch(errorMiddleware);
|
|
||||||
|
|
||||||
export const checkIframeToken = ({
|
|
||||||
viewer_id,
|
|
||||||
auth_key,
|
|
||||||
}: {
|
|
||||||
viewer_id: string;
|
|
||||||
auth_key: string;
|
|
||||||
}) =>
|
|
||||||
axios
|
|
||||||
.get(API.IFRAME_LOGIN_VK, {
|
|
||||||
params: { viewer_id, auth_key },
|
|
||||||
})
|
|
||||||
.then(result => result && result.data && result.data.success && result.data.user)
|
|
||||||
.catch(() => false);
|
|
||||||
|
|
||||||
export const getRouteList = ({
|
|
||||||
search,
|
|
||||||
min,
|
|
||||||
max,
|
|
||||||
tab,
|
|
||||||
token,
|
|
||||||
step,
|
|
||||||
shift,
|
|
||||||
}: IGetRouteList): Promise<IResultWithStatus<{
|
|
||||||
routes: IRoute[];
|
|
||||||
limits: {
|
|
||||||
min: number;
|
|
||||||
max: number;
|
|
||||||
count: number;
|
|
||||||
};
|
|
||||||
filter: {
|
|
||||||
min: number;
|
|
||||||
max: number;
|
|
||||||
shift: number;
|
|
||||||
step: number;
|
|
||||||
};
|
|
||||||
}>> =>
|
|
||||||
axios
|
|
||||||
.get(
|
|
||||||
API.GET_ROUTE_LIST(tab),
|
|
||||||
configWithToken(token, {
|
|
||||||
params: {
|
|
||||||
search,
|
|
||||||
min,
|
|
||||||
max,
|
|
||||||
token,
|
|
||||||
step,
|
|
||||||
shift,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.then(resultMiddleware)
|
|
||||||
.catch(errorMiddleware);
|
|
||||||
|
|
||||||
export const checkOSRMService = (bounds: LatLngLiteral[]): Promise<boolean> =>
|
|
||||||
CLIENT &&
|
|
||||||
CLIENT.OSRM_URL &&
|
|
||||||
axios
|
|
||||||
.get(CLIENT.OSRM_TEST_URL(bounds))
|
|
||||||
.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',
|
|
||||||
dedupe: 1,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.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 } }))
|
|
||||||
.then(resultMiddleware)
|
|
||||||
.catch(errorMiddleware);
|
|
||||||
|
|
||||||
export const modifyRoute = ({
|
|
||||||
address,
|
|
||||||
token,
|
|
||||||
title,
|
|
||||||
is_public,
|
|
||||||
}: {
|
|
||||||
address: string;
|
|
||||||
token: string;
|
|
||||||
title: string;
|
|
||||||
is_public: boolean;
|
|
||||||
}): Promise<IResultWithStatus<{
|
|
||||||
route: IRoute;
|
|
||||||
}>> =>
|
|
||||||
axios
|
|
||||||
.patch(API.MODIFY_ROUTE, { address, token, is_public, title }, configWithToken(token))
|
|
||||||
.then(resultMiddleware)
|
|
||||||
.catch(errorMiddleware);
|
|
||||||
|
|
||||||
export const sendRouteStarred = ({
|
|
||||||
token,
|
|
||||||
address,
|
|
||||||
is_published,
|
|
||||||
}: {
|
|
||||||
token: string;
|
|
||||||
address: string;
|
|
||||||
is_published: boolean;
|
|
||||||
}): Promise<IResultWithStatus<{ route: IRoute }>> =>
|
|
||||||
axios
|
|
||||||
.post(API.SET_STARRED, { address, is_published }, configWithToken(token))
|
|
||||||
.then(resultMiddleware)
|
|
||||||
.catch(errorMiddleware);
|
|
180
src/utils/api/index.ts
Normal file
180
src/utils/api/index.ts
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
import { API } from '~/constants/api';
|
||||||
|
import { IRootState } from '~/redux/user';
|
||||||
|
import { IUser } from '~/constants/auth';
|
||||||
|
import { CLIENT } from '~/config/frontend';
|
||||||
|
import { LatLngLiteral } from 'leaflet';
|
||||||
|
import { IRoute } from '~/redux/map/types';
|
||||||
|
import { INominatimResult } from '~/redux/types';
|
||||||
|
import { api } from './instance';
|
||||||
|
import { postMapInterceptor } from '~/utils/api/interceptors';
|
||||||
|
import {
|
||||||
|
CheckTokenRequest,
|
||||||
|
CheckTokenResult,
|
||||||
|
GetGuestTokenResult, GetRouteListRequest, GetRouteListResponse, GetStoredMapRequest, GetStoredMapResult,
|
||||||
|
PostMapRequest,
|
||||||
|
PostMapResponse,
|
||||||
|
} from '~/utils/api/types';
|
||||||
|
|
||||||
|
export const checkUserToken = ({
|
||||||
|
id,
|
||||||
|
token,
|
||||||
|
}: CheckTokenRequest) =>
|
||||||
|
api
|
||||||
|
.get<CheckTokenResult>(API.CHECK_TOKEN, {
|
||||||
|
params: { id, token },
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getGuestToken = () =>
|
||||||
|
api
|
||||||
|
.get<GetGuestTokenResult>(API.GET_GUEST);
|
||||||
|
|
||||||
|
export const getStoredMap = ({
|
||||||
|
name,
|
||||||
|
}: GetStoredMapRequest) =>
|
||||||
|
api
|
||||||
|
.get<GetStoredMapResult>(API.GET_MAP, {
|
||||||
|
params: { name },
|
||||||
|
});
|
||||||
|
|
||||||
|
export const postMap = ({
|
||||||
|
title,
|
||||||
|
address,
|
||||||
|
route,
|
||||||
|
stickers,
|
||||||
|
force,
|
||||||
|
logo,
|
||||||
|
distance,
|
||||||
|
provider,
|
||||||
|
is_public,
|
||||||
|
description,
|
||||||
|
}: PostMapRequest) =>
|
||||||
|
api
|
||||||
|
.post<PostMapResponse>(
|
||||||
|
API.POST_MAP,
|
||||||
|
{
|
||||||
|
route: {
|
||||||
|
title,
|
||||||
|
address,
|
||||||
|
route,
|
||||||
|
stickers,
|
||||||
|
logo,
|
||||||
|
distance,
|
||||||
|
provider,
|
||||||
|
is_public,
|
||||||
|
description,
|
||||||
|
},
|
||||||
|
force,
|
||||||
|
},
|
||||||
|
).catch(postMapInterceptor);
|
||||||
|
|
||||||
|
export const checkIframeToken = ({
|
||||||
|
viewer_id,
|
||||||
|
auth_key,
|
||||||
|
}: {
|
||||||
|
viewer_id: string;
|
||||||
|
auth_key: string;
|
||||||
|
}) =>
|
||||||
|
api
|
||||||
|
.get<{
|
||||||
|
success: boolean,
|
||||||
|
user: IUser,
|
||||||
|
}>(API.IFRAME_LOGIN_VK, {
|
||||||
|
params: { viewer_id, auth_key },
|
||||||
|
})
|
||||||
|
.then(result => !!result.data.success && !!result.data.user)
|
||||||
|
.catch(() => false);
|
||||||
|
|
||||||
|
export const getRouteList = ({
|
||||||
|
search,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
tab,
|
||||||
|
step,
|
||||||
|
shift,
|
||||||
|
}: GetRouteListRequest) =>
|
||||||
|
api
|
||||||
|
.get<GetRouteListResponse>(
|
||||||
|
API.GET_ROUTE_LIST(tab),
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
search,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
step,
|
||||||
|
shift,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const checkOSRMService = (bounds: LatLngLiteral[]) =>
|
||||||
|
!!CLIENT &&
|
||||||
|
!!CLIENT.OSRM_URL &&
|
||||||
|
api
|
||||||
|
.get<boolean>(CLIENT.OSRM_TEST_URL(bounds))
|
||||||
|
.then(() => true)
|
||||||
|
.catch(() => false);
|
||||||
|
|
||||||
|
export const checkNominatimService = () =>
|
||||||
|
!!CLIENT &&
|
||||||
|
!!CLIENT.NOMINATIM_TEST_URL &&
|
||||||
|
api
|
||||||
|
.get<boolean>(CLIENT.NOMINATIM_TEST_URL)
|
||||||
|
.then(() => true)
|
||||||
|
.catch(() => false);
|
||||||
|
|
||||||
|
export const searchNominatim = (query: string) =>
|
||||||
|
CLIENT &&
|
||||||
|
CLIENT.NOMINATIM_URL &&
|
||||||
|
api
|
||||||
|
.get(`${CLIENT.NOMINATIM_URL} ${query}`, {
|
||||||
|
params: {
|
||||||
|
format: 'json',
|
||||||
|
country_code: 'ru',
|
||||||
|
'accept-language': 'ru_RU',
|
||||||
|
dedupe: 1,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.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 }: { address: string }) =>
|
||||||
|
api
|
||||||
|
.delete(API.DROP_ROUTE, { data: { address } });
|
||||||
|
|
||||||
|
export const modifyRoute = ({
|
||||||
|
address,
|
||||||
|
title,
|
||||||
|
is_public,
|
||||||
|
}: {
|
||||||
|
address: string;
|
||||||
|
title: string;
|
||||||
|
is_public: boolean;
|
||||||
|
}) =>
|
||||||
|
api
|
||||||
|
.patch<{
|
||||||
|
route: IRoute;
|
||||||
|
}>(API.MODIFY_ROUTE, { address, is_public, title });
|
||||||
|
|
||||||
|
export const sendRouteStarred = ({
|
||||||
|
address,
|
||||||
|
is_published,
|
||||||
|
}: {
|
||||||
|
address: string;
|
||||||
|
is_published: boolean;
|
||||||
|
}) =>
|
||||||
|
api
|
||||||
|
.post<{ route: IRoute }>(API.SET_STARRED, { address, is_published });
|
6
src/utils/api/instance.ts
Normal file
6
src/utils/api/instance.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import axios from 'axios';
|
||||||
|
import { CLIENT } from '~/config/frontend';
|
||||||
|
|
||||||
|
export const api = axios.create({
|
||||||
|
baseURL: CLIENT.API_ADDR,
|
||||||
|
})
|
14
src/utils/api/interceptors.ts
Normal file
14
src/utils/api/interceptors.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
import { PostMapResponse } from '~/utils/api/types';
|
||||||
|
|
||||||
|
export const postMapInterceptor = (res: AxiosError<PostMapResponse>) => {
|
||||||
|
if (res.response?.data.code) {
|
||||||
|
return res.response;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.response?.data.error) {
|
||||||
|
throw new Error(res.response?.data.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw res;
|
||||||
|
};
|
63
src/utils/api/types.ts
Normal file
63
src/utils/api/types.ts
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
import { IRoute } from '~/redux/map/types';
|
||||||
|
import { IUser } from '~/constants/auth';
|
||||||
|
import { IRootState, IRouteListItem } from '~/redux/user';
|
||||||
|
|
||||||
|
export interface PostMapResponse {
|
||||||
|
route: IRoute;
|
||||||
|
error?: string;
|
||||||
|
code?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PostMapRequest = Partial<IRoute> & {
|
||||||
|
force: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CheckTokenResult {
|
||||||
|
user: IUser;
|
||||||
|
random_url: string;
|
||||||
|
routes: IRouteListItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CheckTokenRequest {
|
||||||
|
id: IRootState['user']['id'];
|
||||||
|
token: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetGuestTokenResult {
|
||||||
|
user: IUser;
|
||||||
|
random_url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetStoredMapResult {
|
||||||
|
route: IRoute;
|
||||||
|
error?: string;
|
||||||
|
random_url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetStoredMapRequest {
|
||||||
|
name: IRoute['address'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetRouteListRequest {
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
tab: string;
|
||||||
|
search: string;
|
||||||
|
step: IRootState['routes']['step'];
|
||||||
|
shift: IRootState['routes']['step'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetRouteListResponse {
|
||||||
|
routes: IRoute[];
|
||||||
|
limits: {
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
count: number;
|
||||||
|
};
|
||||||
|
filter: {
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
shift: number;
|
||||||
|
step: number;
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Map, TileLayer } from 'leaflet';
|
import { Map, TileLayer } from 'leaflet';
|
||||||
|
|
||||||
export const MapContext = React.createContext<Map>(null);
|
export const MapContext = React.createContext<Map | undefined>(undefined);
|
||||||
export const TileContext = React.createContext<TileLayer>(null)
|
export const TileContext = React.createContext<TileLayer | undefined>(undefined)
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
import { LatLng, LatLngLiteral, point, Point, PointExpression, latLng } from 'leaflet';
|
import { LatLng, latLng, LatLngLiteral, Point, point } from 'leaflet';
|
||||||
|
|
||||||
// interface LatLng {
|
|
||||||
// lat: number;
|
|
||||||
// lng: number;
|
|
||||||
// }
|
|
||||||
|
|
||||||
export const middleCoord = (l1: LatLng, l2: LatLng): LatLng => latLng({
|
export const middleCoord = (l1: LatLng, l2: LatLng): LatLng => latLng({
|
||||||
lat: l2.lat + (l1.lat - l2.lat) / 2,
|
lat: l2.lat + (l1.lat - l2.lat) / 2,
|
||||||
|
@ -46,14 +41,14 @@ export const findDistance = (t1: number, n1: number, t2: number, n2: number): nu
|
||||||
export const findDistanceHaversine = (t1: number, n1: number, t2: number, n2: number): number => {
|
export const findDistanceHaversine = (t1: number, n1: number, t2: number, n2: number): number => {
|
||||||
const R = 6371; // km
|
const R = 6371; // km
|
||||||
const dLat = ((t2 - t1) * Math.PI) / 180;
|
const dLat = ((t2 - t1) * Math.PI) / 180;
|
||||||
var dLon = ((n2 - n1) * Math.PI) / 180;
|
const dLon = ((n2 - n1) * Math.PI) / 180;
|
||||||
var a =
|
const a =
|
||||||
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
||||||
Math.cos((t1 * Math.PI) / 180) *
|
Math.cos((t1 * Math.PI) / 180) *
|
||||||
Math.cos((t2 * Math.PI) / 180) *
|
Math.cos((t2 * Math.PI) / 180) *
|
||||||
Math.sin(dLon / 2) *
|
Math.sin(dLon / 2) *
|
||||||
Math.sin(dLon / 2);
|
Math.sin(dLon / 2);
|
||||||
var c = 2 * Math.asin(Math.sqrt(a));
|
const c = 2 * Math.asin(Math.sqrt(a));
|
||||||
return R * c;
|
return R * c;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -88,7 +83,7 @@ export const dist2 = (A: LatLngLiteral, B: LatLngLiteral): number =>
|
||||||
|
|
||||||
const distToSegmentSquared = (A: LatLng, B: LatLng, C: LatLng): number => {
|
const distToSegmentSquared = (A: LatLng, B: LatLng, C: LatLng): number => {
|
||||||
const l2 = dist2(A, B);
|
const l2 = dist2(A, B);
|
||||||
if (l2 == 0) return dist2(C, A);
|
if (l2 === 0) return dist2(C, A);
|
||||||
|
|
||||||
const t = Math.max(
|
const t = Math.max(
|
||||||
0,
|
0,
|
||||||
|
|
|
@ -39,7 +39,7 @@ export const getGPXString = ({
|
||||||
<metadata>
|
<metadata>
|
||||||
<name>${title || 'GPX Track'}</name>
|
<name>${title || 'GPX Track'}</name>
|
||||||
</metadata>
|
</metadata>
|
||||||
${stickers.reduce(
|
${(stickers || []).reduce(
|
||||||
(cat, { latlng: { lat, lng }, text }) =>
|
(cat, { latlng: { lat, lng }, text }) =>
|
||||||
`${cat}
|
`${cat}
|
||||||
<wpt lat="${lat}" lon="${lng}">
|
<wpt lat="${lat}" lon="${lng}">
|
||||||
|
@ -93,12 +93,12 @@ export const importGpxTrack = async (file: File) => {
|
||||||
return trkseg.trkpt
|
return trkseg.trkpt
|
||||||
? [
|
? [
|
||||||
...trkseg_res,
|
...trkseg_res,
|
||||||
...trkseg.trkpt.map(pnt => ({ lat: pnt['$'].lat, lng: pnt['$'].lon })),
|
...trkseg.trkpt.map(pnt => new LatLng(pnt['$'].lat, pnt['$'].lon)),
|
||||||
]
|
]
|
||||||
: trkseg_res;
|
: trkseg_res;
|
||||||
}, trk_res)
|
}, trk_res)
|
||||||
: trk_res;
|
: trk_res;
|
||||||
}, []);
|
}, [] as LatLng[]);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,13 +1,5 @@
|
||||||
import { history } from '~/redux/store';
|
import { history } from '~/redux/store';
|
||||||
import {API_RETRY_INTERVAL} from "~/constants/api";
|
import { API_RETRY_INTERVAL } from '~/constants/api';
|
||||||
|
|
||||||
interface IUrlData {
|
|
||||||
path: string,
|
|
||||||
mode: 'edit' | '',
|
|
||||||
host: string,
|
|
||||||
hash: string,
|
|
||||||
protocol: 'http' | 'https',
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getPath = (): string => (window.location && window.location.pathname);
|
export const getPath = (): string => (window.location && window.location.pathname);
|
||||||
export const pushPath = (url: string): string => history.push(url);
|
export const pushPath = (url: string): string => history.push(url);
|
||||||
|
@ -36,20 +28,20 @@ export const parseQuery = (queryString: string) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pushLoaderState = (state: number) => {
|
export const pushLoaderState = (state: number) => {
|
||||||
document.getElementById('loader-bar').style.width = `${state}%`;
|
document.getElementById('loader-bar')!.style.width = `${state}%`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const countDownToRefresh = (left: number = API_RETRY_INTERVAL): void => {
|
export const countDownToRefresh = (left: number = API_RETRY_INTERVAL): void => {
|
||||||
if (left <= 0) return document.location.reload();
|
if (left <= 0) return document.location.reload();
|
||||||
|
|
||||||
document.getElementById('loader-bar').style.width = `${(left / API_RETRY_INTERVAL) * 100}%`;
|
document.getElementById('loader-bar')!.style.width = `${(left / API_RETRY_INTERVAL) * 100}%`;
|
||||||
setTimeout(() => countDownToRefresh(left - 0.25), 1000);
|
setTimeout(() => countDownToRefresh(left - 0.25), 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pushNetworkInitError = () => {
|
export const pushNetworkInitError = () => {
|
||||||
document.getElementById('loader-bar').classList.add('is_failed');
|
document.getElementById('loader-bar')!.classList.add('is_failed');
|
||||||
document.getElementById('loader-bar').style.width = '100%';
|
document.getElementById('loader-bar')!.style.width = '100%';
|
||||||
document.getElementById('loader-error').style.opacity = String(1);
|
document.getElementById('loader-error')!.style.opacity = String(1);
|
||||||
|
|
||||||
countDownToRefresh();
|
countDownToRefresh();
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { LatLng, LatLngLiteral, LayerGroup, Map, Marker } from 'leaflet';
|
||||||
import { arrowClusterIcon, createArrow } from '~/utils/arrow';
|
import { arrowClusterIcon, createArrow } from '~/utils/arrow';
|
||||||
import { MarkerClusterGroup } from 'leaflet.markercluster/dist/leaflet.markercluster-src.js';
|
import { MarkerClusterGroup } from 'leaflet.markercluster/dist/leaflet.markercluster-src.js';
|
||||||
import { angleBetweenPoints, dist2, middleCoord } from '~/utils/geom';
|
import { angleBetweenPoints, dist2, middleCoord } from '~/utils/geom';
|
||||||
|
import { MainMap } from '~/constants/map';
|
||||||
|
|
||||||
class ArrowsLayer extends LayerGroup {
|
class ArrowsLayer extends LayerGroup {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -46,13 +47,13 @@ class ArrowsLayer extends LayerGroup {
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
: res,
|
: res,
|
||||||
[]
|
[] as Marker[]
|
||||||
);
|
);
|
||||||
|
|
||||||
this.arrowLayer.addLayers(midpoints);
|
this.arrowLayer.addLayers(midpoints);
|
||||||
};
|
};
|
||||||
|
|
||||||
map: Map;
|
map: Map = MainMap;
|
||||||
arrowLayer = new MarkerClusterGroup({
|
arrowLayer = new MarkerClusterGroup({
|
||||||
spiderfyOnMaxZoom: false,
|
spiderfyOnMaxZoom: false,
|
||||||
showCoverageOnHover: false,
|
showCoverageOnHover: false,
|
||||||
|
@ -62,10 +63,10 @@ class ArrowsLayer extends LayerGroup {
|
||||||
iconCreateFunction: arrowClusterIcon,
|
iconCreateFunction: arrowClusterIcon,
|
||||||
});
|
});
|
||||||
|
|
||||||
layers: Marker<any>[] = [];
|
layers: Marker[] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
ArrowsLayer.addInitHook(function() {
|
ArrowsLayer.addInitHook(function(this: ArrowsLayer) {
|
||||||
this.once('add', event => {
|
this.once('add', event => {
|
||||||
if (event.target instanceof ArrowsLayer) {
|
if (event.target instanceof ArrowsLayer) {
|
||||||
this.map = event.target._map;
|
this.map = event.target._map;
|
||||||
|
|
|
@ -36,9 +36,9 @@ class InteractivePoly extends Polyline {
|
||||||
|
|
||||||
this.constraintsStyle = {
|
this.constraintsStyle = {
|
||||||
...this.constraintsStyle,
|
...this.constraintsStyle,
|
||||||
...options.constraintsStyle,
|
...(options?.constraintsStyle || {}),
|
||||||
};
|
};
|
||||||
this.maxMarkers = options.maxMarkers || this.maxMarkers;
|
this.maxMarkers = options?.maxMarkers || this.maxMarkers;
|
||||||
|
|
||||||
this.constrLine = new Polyline([], this.constraintsStyle);
|
this.constrLine = new Polyline([], this.constraintsStyle);
|
||||||
|
|
||||||
|
@ -162,10 +162,12 @@ class InteractivePoly extends Polyline {
|
||||||
? { ...obj, hidden: [...obj.hidden, marker] }
|
? { ...obj, hidden: [...obj.hidden, marker] }
|
||||||
: { ...obj, visible: [...obj.visible, marker] };
|
: { ...obj, visible: [...obj.visible, marker] };
|
||||||
},
|
},
|
||||||
{ visible: [], hidden: [] }
|
{ visible: [], hidden: [] } as Record<'visible' | 'hidden', Marker[]>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (visible.length > this.maxMarkers) return this.hideAllMarkers();
|
if (visible.length > (this.maxMarkers || 2)) {
|
||||||
|
return this.hideAllMarkers();
|
||||||
|
}
|
||||||
|
|
||||||
this.showAllMarkers();
|
this.showAllMarkers();
|
||||||
|
|
||||||
|
@ -337,11 +339,11 @@ class InteractivePoly extends Polyline {
|
||||||
|
|
||||||
onMarkerDrag = ({ target }: { target: Marker }) => {
|
onMarkerDrag = ({ target }: { target: Marker }) => {
|
||||||
const coords = new Array(0)
|
const coords = new Array(0)
|
||||||
.concat((this.vertex_index > 0 && this.markers[this.vertex_index - 1].getLatLng()) || [])
|
.concat((this.vertex_index! > 0 && this.markers[this.vertex_index! - 1].getLatLng()) || [])
|
||||||
.concat(target.getLatLng())
|
.concat(target.getLatLng())
|
||||||
.concat(
|
.concat(
|
||||||
(this.vertex_index < this.markers.length - 1 &&
|
(this.vertex_index! < this.markers.length - 1 &&
|
||||||
this.markers[this.vertex_index + 1].getLatLng()) ||
|
this.markers[this.vertex_index! + 1].getLatLng()) ||
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -369,17 +371,17 @@ class InteractivePoly extends Polyline {
|
||||||
onMarkerDragEnd = ({ target }: { target: Marker }): void => {
|
onMarkerDragEnd = ({ target }: { target: Marker }): void => {
|
||||||
const latlngs = this.getLatLngs() as LatLngLiteral[];
|
const latlngs = this.getLatLngs() as LatLngLiteral[];
|
||||||
this.markerDragChangeDistance(
|
this.markerDragChangeDistance(
|
||||||
this.vertex_index,
|
this.vertex_index!,
|
||||||
latlngs[this.vertex_index],
|
latlngs[this.vertex_index!],
|
||||||
target.getLatLng()
|
target.getLatLng()
|
||||||
);
|
);
|
||||||
|
|
||||||
this.replaceLatlng(target.getLatLng(), this.vertex_index);
|
this.replaceLatlng(target.getLatLng(), this.vertex_index!);
|
||||||
|
|
||||||
this.is_dragging = false;
|
this.is_dragging = false;
|
||||||
this.constrLine.removeFrom(this._map);
|
this.constrLine.removeFrom(this._map);
|
||||||
|
|
||||||
this.vertex_index = null;
|
this.vertex_index = 0;
|
||||||
|
|
||||||
if (this.is_drawing) this.startDrawing();
|
if (this.is_drawing) this.startDrawing();
|
||||||
|
|
||||||
|
@ -505,7 +507,7 @@ class InteractivePoly extends Polyline {
|
||||||
const index = this.markers.indexOf(target);
|
const index = this.markers.indexOf(target);
|
||||||
const latlngs = this.getLatLngs();
|
const latlngs = this.getLatLngs();
|
||||||
|
|
||||||
if (typeof index === 'undefined' || latlngs.length == 0) return;
|
if (typeof index === 'undefined' || latlngs.length === 0) return;
|
||||||
|
|
||||||
this.dropMarkerDistanceChange(index);
|
this.dropMarkerDistanceChange(index);
|
||||||
this._map.removeLayer(this.markers[index]);
|
this._map.removeLayer(this.markers[index]);
|
||||||
|
@ -568,27 +570,27 @@ class InteractivePoly extends Polyline {
|
||||||
is_drawing: boolean = false;
|
is_drawing: boolean = false;
|
||||||
|
|
||||||
drawing_direction: 'forward' | 'backward' = 'forward';
|
drawing_direction: 'forward' | 'backward' = 'forward';
|
||||||
vertex_index?: number = null;
|
vertex_index: number = 0;
|
||||||
|
|
||||||
hint_prev_marker: number = null;
|
hint_prev_marker: number = 0;
|
||||||
distance: number = 0;
|
distance: number = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
InteractivePoly.addInitHook(function() {
|
InteractivePoly.addInitHook(function(this: InteractivePoly) {
|
||||||
this.once('add', event => {
|
this.once('add', event => {
|
||||||
if (event.target instanceof InteractivePoly) {
|
if (event.target instanceof InteractivePoly) {
|
||||||
this.map = event.target._map;
|
this._map = event.target._map;
|
||||||
|
|
||||||
this.map.on('touch', console.log);
|
this._map.on('touch', console.log);
|
||||||
|
|
||||||
this.markerLayer.addTo(event.target._map);
|
this.markerLayer.addTo(event.target._map);
|
||||||
this.hintMarker.addTo(event.target._map);
|
this.hintMarker.addTo(event.target._map);
|
||||||
this.constrLine.addTo(event.target._map);
|
this.constrLine.addTo(event.target._map);
|
||||||
this.touchHinter.addTo(event.target._map);
|
this.touchHinter.addTo(event.target._map);
|
||||||
|
|
||||||
this.map.on('moveend', this.updateMarkers);
|
this._map.on('moveend', this.updateMarkers);
|
||||||
|
|
||||||
this.on('latlngschange', this.updateTouchHinter);
|
this.on('latlngschange' as any, this.updateTouchHinter as any);
|
||||||
|
|
||||||
if (this.touchHinter && window.innerWidth < 768) {
|
if (this.touchHinter && window.innerWidth < 768) {
|
||||||
try {
|
try {
|
||||||
|
@ -605,7 +607,7 @@ InteractivePoly.addInitHook(function() {
|
||||||
this.constrLine.removeFrom(this._map);
|
this.constrLine.removeFrom(this._map);
|
||||||
this.touchHinter.removeFrom(this._map);
|
this.touchHinter.removeFrom(this._map);
|
||||||
|
|
||||||
this.map.off('moveend', this.updateMarkers);
|
this._map.off('moveend', this.updateMarkers);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -35,7 +35,7 @@ export const OsrmRouter = Routing.control({
|
||||||
show: false,
|
show: false,
|
||||||
plan: Routing.plan([], {
|
plan: Routing.plan([], {
|
||||||
createMarker: (_, wp) => {
|
createMarker: (_, wp) => {
|
||||||
const marker = new Marker(wp.latLng, {
|
return new Marker(wp.latLng, {
|
||||||
draggable: true,
|
draggable: true,
|
||||||
icon: createWaypointMarker(),
|
icon: createWaypointMarker(),
|
||||||
})
|
})
|
||||||
|
@ -45,12 +45,10 @@ export const OsrmRouter = Routing.control({
|
||||||
OsrmRouter.setWaypoints(
|
OsrmRouter.setWaypoints(
|
||||||
OsrmRouter.getWaypoints().filter(
|
OsrmRouter.getWaypoints().filter(
|
||||||
point =>
|
point =>
|
||||||
!point.latLng || (point.latLng.lat != latlng.lat && point.latLng.lng != latlng.lng)
|
!point.latLng || (point.latLng.lat !== latlng.lat && point.latLng.lng !== latlng.lng)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return marker;
|
|
||||||
},
|
},
|
||||||
routeWhileDragging: false,
|
routeWhileDragging: false,
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { arrowClusterIcon } from '~/utils/arrow';
|
||||||
import { MarkerClusterGroup } from 'leaflet.markercluster/dist/leaflet.markercluster-src.js';
|
import { MarkerClusterGroup } from 'leaflet.markercluster/dist/leaflet.markercluster-src.js';
|
||||||
import { allwaysPositiveAngleDeg, angleBetweenPoints, distKmHaversine } from '~/utils/geom';
|
import { allwaysPositiveAngleDeg, angleBetweenPoints, distKmHaversine } from '~/utils/geom';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { MainMap } from '~/constants/map';
|
||||||
|
|
||||||
const arrow_image = '/images/arrow.svg';
|
const arrow_image = '/images/arrow.svg';
|
||||||
|
|
||||||
|
@ -43,8 +44,7 @@ class KmMarksLayer extends LayerGroup {
|
||||||
};
|
};
|
||||||
|
|
||||||
drawMiddleMarkers = (latlngs: LatLngLiteral[]) => {
|
drawMiddleMarkers = (latlngs: LatLngLiteral[]) => {
|
||||||
const marks = [];
|
const marks: Marker[] = [];
|
||||||
const arrows = [];
|
|
||||||
let last_km_mark = 0;
|
let last_km_mark = 0;
|
||||||
|
|
||||||
this.distance = latlngs.reduce((dist, current, index) => {
|
this.distance = latlngs.reduce((dist, current, index) => {
|
||||||
|
@ -160,7 +160,7 @@ class KmMarksLayer extends LayerGroup {
|
||||||
};
|
};
|
||||||
|
|
||||||
options: KmMarksOptions;
|
options: KmMarksOptions;
|
||||||
map: Map;
|
map: Map = MainMap;
|
||||||
marksLayer: MarkerClusterGroup = new MarkerClusterGroup({
|
marksLayer: MarkerClusterGroup = new MarkerClusterGroup({
|
||||||
spiderfyOnMaxZoom: false,
|
spiderfyOnMaxZoom: false,
|
||||||
showCoverageOnHover: false,
|
showCoverageOnHover: false,
|
||||||
|
@ -173,7 +173,7 @@ class KmMarksLayer extends LayerGroup {
|
||||||
distance: number = 0;
|
distance: number = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
KmMarksLayer.addInitHook(function() {
|
KmMarksLayer.addInitHook(function(this: KmMarksLayer) {
|
||||||
this.once('add', event => {
|
this.once('add', event => {
|
||||||
if (event.target instanceof KmMarksLayer) {
|
if (event.target instanceof KmMarksLayer) {
|
||||||
this.map = event.target._map;
|
this.map = event.target._map;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { AxiosRequestConfig } from "axios";
|
import { AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||||
|
|
||||||
export type Unwrap<T> = T extends (...args: any[]) => Promise<infer U> ? U : T;
|
export type Unwrap<T> = T extends (...args: any[]) => Promise<infer U> ? U : T;
|
||||||
|
|
||||||
|
@ -23,28 +23,3 @@ export const HTTP_RESPONSES = {
|
||||||
NOT_FOUND: 404,
|
NOT_FOUND: 404,
|
||||||
TOO_MANY_REQUESTS: 429,
|
TOO_MANY_REQUESTS: 429,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const resultMiddleware = (<T extends {}>({
|
|
||||||
status,
|
|
||||||
data,
|
|
||||||
}: {
|
|
||||||
status: number;
|
|
||||||
data: T;
|
|
||||||
}): { status: number; data: T } => ({ status, data }));
|
|
||||||
|
|
||||||
export const errorMiddleware = <T extends any>(debug): IResultWithStatus<T> => (debug && debug.response
|
|
||||||
? debug.response
|
|
||||||
: {
|
|
||||||
status: HTTP_RESPONSES.CONNECTION_REFUSED,
|
|
||||||
data: {},
|
|
||||||
debug,
|
|
||||||
error: 'Ошибка сети',
|
|
||||||
});
|
|
||||||
|
|
||||||
export const configWithToken = (
|
|
||||||
token: string,
|
|
||||||
config: AxiosRequestConfig = {},
|
|
||||||
): AxiosRequestConfig => ({
|
|
||||||
...config,
|
|
||||||
headers: { ...(config.headers || {}), Authorization: `${token}` },
|
|
||||||
});
|
|
|
@ -1,10 +1,3 @@
|
||||||
// create-reducer.ts
|
|
||||||
import { Action } from 'redux';
|
|
||||||
|
|
||||||
type Handlers<State, Types extends string, Actions extends Action<Types>> = {
|
|
||||||
readonly [Type in Types]: (state: State, action: Actions) => State
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createReducer = (
|
export const createReducer = (
|
||||||
initialState,
|
initialState,
|
||||||
handlers,
|
handlers,
|
||||||
|
|
|
@ -1,18 +1,10 @@
|
||||||
// import { editor } from '~/modules/Editor';
|
import { CLIENT, COLORS } from '~/config/frontend';
|
||||||
import { COLORS, CLIENT } from '~/config/frontend';
|
|
||||||
import saveAs from 'file-saver';
|
import saveAs from 'file-saver';
|
||||||
import { replaceProviderUrl } from '~/constants/providers';
|
import { replaceProviderUrl } from '~/constants/providers';
|
||||||
import { STICKERS } from '~/constants/stickers';
|
import { STICKERS } from '~/constants/stickers';
|
||||||
import { IRoute } from '~/redux/map/types';
|
import { IRoute, IStickerDump } from '~/redux/map/types';
|
||||||
import { IStickerDump } from '~/redux/map/types';
|
import { angleBetweenPoints, angleBetweenPointsRad, findDistancePx, middleCoordPx } from '~/utils/geom';
|
||||||
import { IRootState } from '~/redux/user';
|
import { LatLng, latLng, Point } from 'leaflet';
|
||||||
import {
|
|
||||||
angleBetweenPoints,
|
|
||||||
angleBetweenPointsRad,
|
|
||||||
findDistancePx,
|
|
||||||
middleCoordPx,
|
|
||||||
} from '~/utils/geom';
|
|
||||||
import { Point, LatLng, latLng } from 'leaflet';
|
|
||||||
import { MainMap } from '~/constants/map';
|
import { MainMap } from '~/constants/map';
|
||||||
|
|
||||||
export interface ITilePlacement {
|
export interface ITilePlacement {
|
||||||
|
@ -37,8 +29,7 @@ const latLngToTile = (latlng: {
|
||||||
lat: number;
|
lat: number;
|
||||||
lng: number;
|
lng: number;
|
||||||
}): { x: number; y: number; z: number } => {
|
}): { x: number; y: number; z: number } => {
|
||||||
const map = MainMap;
|
const zoom = MainMap.getZoom();
|
||||||
const zoom = map.getZoom();
|
|
||||||
const xtile = Number(Math.floor(((latlng.lng + 180) / 360) * (1 << zoom)));
|
const xtile = Number(Math.floor(((latlng.lng + 180) / 360) * (1 << zoom)));
|
||||||
const ytile = Number(
|
const ytile = Number(
|
||||||
Math.floor(
|
Math.floor(
|
||||||
|
@ -56,8 +47,7 @@ const latLngToTile = (latlng: {
|
||||||
};
|
};
|
||||||
|
|
||||||
const tileToLatLng = (point: { x: number; y: number }): LatLng => {
|
const tileToLatLng = (point: { x: number; y: number }): LatLng => {
|
||||||
const map = MainMap;
|
const z = MainMap.getZoom();
|
||||||
const z = map.getZoom();
|
|
||||||
const lng = (point.x / Math.pow(2, z)) * 360 - 180;
|
const lng = (point.x / Math.pow(2, z)) * 360 - 180;
|
||||||
const n = Math.PI - (2 * Math.PI * point.y) / Math.pow(2, z);
|
const n = Math.PI - (2 * Math.PI * point.y) / Math.pow(2, z);
|
||||||
const lat = (180 / Math.PI) * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)));
|
const lat = (180 / Math.PI) * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)));
|
||||||
|
@ -133,7 +123,7 @@ export const fetchImages = (
|
||||||
): Promise<{ x: number; y: number; image: HTMLImageElement }[]> => {
|
): Promise<{ x: number; y: number; image: HTMLImageElement }[]> => {
|
||||||
const { minX, maxX, minY, maxY, zoom } = geometry;
|
const { minX, maxX, minY, maxY, zoom } = geometry;
|
||||||
|
|
||||||
const images = [];
|
const images: { x: number; y: number; source: string }[] = [];
|
||||||
for (let x = minX; x <= maxX; x += 1) {
|
for (let x = minX; x <= maxX; x += 1) {
|
||||||
for (let y = minY; y <= maxY; y += 1) {
|
for (let y = minY; y <= maxY; y += 1) {
|
||||||
images.push({ x, y, source: getImageSource({ x, y, zoom }, provider) });
|
images.push({ x, y, source: getImageSource({ x, y, zoom }, provider) });
|
||||||
|
@ -173,7 +163,7 @@ export const composePoly = ({
|
||||||
ctx,
|
ctx,
|
||||||
color = 'gradient',
|
color = 'gradient',
|
||||||
weight = CLIENT.STROKE_WIDTH,
|
weight = CLIENT.STROKE_WIDTH,
|
||||||
dash = null,
|
dash = [],
|
||||||
}: {
|
}: {
|
||||||
points: Point[];
|
points: Point[];
|
||||||
ctx: CanvasRenderingContext2D;
|
ctx: CanvasRenderingContext2D;
|
||||||
|
@ -217,7 +207,7 @@ export const composePoly = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dash) {
|
if (dash) {
|
||||||
ctx.setLineDash([12, 12]);
|
ctx.setLineDash(dash);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
@ -472,14 +462,14 @@ export const composeStickers = async ({
|
||||||
if (!stickers || stickers.length < 0) return;
|
if (!stickers || stickers.length < 0) return;
|
||||||
|
|
||||||
stickers.map(({ x, y, angle, text }) => {
|
stickers.map(({ x, y, angle, text }) => {
|
||||||
composeStickerArrow(ctx, x, y, angle, zoom);
|
composeStickerArrow(ctx, x, y, angle || 0, zoom);
|
||||||
|
|
||||||
if (text) composeStickerText(ctx, x, y, angle, text, zoom);
|
if (text) composeStickerText(ctx, x, y, angle || 0, text, zoom);
|
||||||
});
|
});
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
stickers.map(({ x, y, angle, set, sticker }) =>
|
stickers.map(({ x, y, angle, set, sticker }) =>
|
||||||
composeStickerImage(ctx, x, y, angle, set, sticker, zoom)
|
composeStickerImage(ctx, x, y, angle || 0, set, sticker, zoom)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Map, LineUtil, LatLng } from 'leaflet';
|
import { LatLng, LineUtil } from 'leaflet';
|
||||||
import { MainMap } from '~/constants/map';
|
import { MainMap } from '~/constants/map';
|
||||||
|
|
||||||
export const simplify = (latlngs: LatLng[]): LatLng[] => {
|
export const simplify = (latlngs: LatLng[]): LatLng[] => {
|
||||||
|
|
|
@ -3,10 +3,10 @@ import { LatLngLiteral } from 'leaflet';
|
||||||
|
|
||||||
export const isMobile = (): boolean => window.innerWidth <= MOBILE_BREAKPOINT;
|
export const isMobile = (): boolean => window.innerWidth <= MOBILE_BREAKPOINT;
|
||||||
|
|
||||||
export const getLocation = (callback: (pos: LatLngLiteral) => void) => {
|
export const getLocation = (callback: (pos: LatLngLiteral | undefined) => void) => {
|
||||||
window.navigator.geolocation.getCurrentPosition(position => {
|
window.navigator.geolocation.getCurrentPosition(position => {
|
||||||
if (!position || !position.coords || !position.coords.latitude || !position.coords.longitude)
|
if (!position || !position.coords || !position.coords.latitude || !position.coords.longitude)
|
||||||
return callback(null);
|
return callback(undefined);
|
||||||
|
|
||||||
const { latitude: lat, longitude: lng } = position.coords;
|
const { latitude: lat, longitude: lng } = position.coords;
|
||||||
|
|
||||||
|
@ -15,18 +15,18 @@ export const getLocation = (callback: (pos: LatLngLiteral) => void) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const watchLocation = (callback: (pos: LatLngLiteral) => void): number => {
|
export const watchLocation = (callback: (pos: LatLngLiteral | undefined) => void): number => {
|
||||||
return window.navigator.geolocation.watchPosition(
|
return window.navigator.geolocation.watchPosition(
|
||||||
position => {
|
position => {
|
||||||
if (!position || !position.coords || !position.coords.latitude || !position.coords.longitude)
|
if (!position || !position.coords || !position.coords.latitude || !position.coords.longitude)
|
||||||
return callback(null);
|
return callback(undefined);
|
||||||
|
|
||||||
const { latitude: lat, longitude: lng } = position.coords;
|
const { latitude: lat, longitude: lng } = position.coords;
|
||||||
|
|
||||||
callback({ lat, lng });
|
callback({ lat, lng });
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
() => callback(null),
|
() => callback(undefined),
|
||||||
{
|
{
|
||||||
timeout: 30,
|
timeout: 30,
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"strict": false,
|
"strict": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
|
|
12
yarn.lock
12
yarn.lock
|
@ -7197,13 +7197,6 @@ leaflet-editable@^1.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/leaflet-editable/-/leaflet-editable-1.2.0.tgz#a3a01001764ba58ea923381ee6a1c814708a0b84"
|
resolved "https://registry.yarnpkg.com/leaflet-editable/-/leaflet-editable-1.2.0.tgz#a3a01001764ba58ea923381ee6a1c814708a0b84"
|
||||||
integrity sha512-wG11JwpL8zqIbypTop6xCRGagMuWw68ihYu4uqrqc5Ep0wnEJeyob7NB2Rt5t74Oih4rwJ3OfwaGbzdowOGfYQ==
|
integrity sha512-wG11JwpL8zqIbypTop6xCRGagMuWw68ihYu4uqrqc5Ep0wnEJeyob7NB2Rt5t74Oih4rwJ3OfwaGbzdowOGfYQ==
|
||||||
|
|
||||||
leaflet-geometryutil@^0.9.0:
|
|
||||||
version "0.9.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/leaflet-geometryutil/-/leaflet-geometryutil-0.9.3.tgz#e10fa302d99d4b1d3c6365a1f39298635a2704cd"
|
|
||||||
integrity sha512-Wi6YvfNx/Xu9q35AEfXpsUXmIFLen/MO+C2qimxHRnjyeyOxBhdcZa6kSiReaOX0cGK7yQInqrzz0dkIqZ8Dpg==
|
|
||||||
dependencies:
|
|
||||||
leaflet ">=0.7.0"
|
|
||||||
|
|
||||||
leaflet-routing-machine@^3.2.12:
|
leaflet-routing-machine@^3.2.12:
|
||||||
version "3.2.12"
|
version "3.2.12"
|
||||||
resolved "https://registry.yarnpkg.com/leaflet-routing-machine/-/leaflet-routing-machine-3.2.12.tgz#9e4aef008321b0227cf894d829c3b4c1f13e4e13"
|
resolved "https://registry.yarnpkg.com/leaflet-routing-machine/-/leaflet-routing-machine-3.2.12.tgz#9e4aef008321b0227cf894d829c3b4c1f13e4e13"
|
||||||
|
@ -7223,11 +7216,6 @@ leaflet@1.6.0:
|
||||||
resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.6.0.tgz#aecbb044b949ec29469eeb31c77a88e2f448f308"
|
resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.6.0.tgz#aecbb044b949ec29469eeb31c77a88e2f448f308"
|
||||||
integrity sha512-CPkhyqWUKZKFJ6K8umN5/D2wrJ2+/8UIpXppY7QDnUZW5bZL5+SEI2J7GBpwh4LIupOKqbNSQXgqmrEJopHVNQ==
|
integrity sha512-CPkhyqWUKZKFJ6K8umN5/D2wrJ2+/8UIpXppY7QDnUZW5bZL5+SEI2J7GBpwh4LIupOKqbNSQXgqmrEJopHVNQ==
|
||||||
|
|
||||||
leaflet@>=0.7.0:
|
|
||||||
version "1.7.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.7.1.tgz#10d684916edfe1bf41d688a3b97127c0322a2a19"
|
|
||||||
integrity sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw==
|
|
||||||
|
|
||||||
leven@^3.1.0:
|
leven@^3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
|
resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue