mirror of
https://github.com/muerwre/orchidmap-front.git
synced 2025-04-30 21:46:41 +07:00
commit
bc5892120f
56 changed files with 909 additions and 891 deletions
.drone.yml.envpackage.jsontsconfig.jsonyarn.lock
src
components
constants
map
Arrows
CurrentLocation
GpxPolyline
KmMarks
Map
Route
Router
Sticker
Stickers
TileLayer
redux
styles
utils
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