mirror of
https://github.com/muerwre/orchidmap-front.git
synced 2025-05-01 05:56:40 +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}
|
||||
- docker-compose build
|
||||
- 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_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_PROFILE = bike
|
||||
|
|
|
@ -32,7 +32,6 @@
|
|||
"gpx-parser-builder": "^1.0.2",
|
||||
"leaflet": "1.6.0",
|
||||
"leaflet-editable": "^1.1.0",
|
||||
"leaflet-geometryutil": "^0.9.0",
|
||||
"leaflet-routing-machine": "^3.2.12",
|
||||
"leaflet.markercluster": "^1.4.1",
|
||||
"node-sass": "^5.0.0",
|
||||
|
|
|
@ -23,10 +23,15 @@ class StickerDesc extends React.PureComponent<Props, State> {
|
|||
blockMouse = e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (!this.input) {
|
||||
return
|
||||
}
|
||||
|
||||
this.input.focus();
|
||||
};
|
||||
|
||||
input: HTMLTextAreaElement;
|
||||
input: HTMLTextAreaElement | null = null;
|
||||
|
||||
render() {
|
||||
const { value: text } = this.props;
|
||||
|
|
|
@ -64,9 +64,8 @@ export interface State {
|
|||
|
||||
class MapListDialogUnconnected extends PureComponent<Props, State> {
|
||||
state = {
|
||||
menu_target: null,
|
||||
editor_target: null,
|
||||
|
||||
menu_target: '',
|
||||
editor_target: '',
|
||||
is_editing: false,
|
||||
is_dropping: false,
|
||||
};
|
||||
|
@ -74,7 +73,7 @@ class MapListDialogUnconnected extends PureComponent<Props, State> {
|
|||
startEditing = (editor_target: IRouteListItem['address']): void =>
|
||||
this.setState({
|
||||
editor_target,
|
||||
menu_target: null,
|
||||
menu_target: '',
|
||||
is_editing: true,
|
||||
is_dropping: false,
|
||||
});
|
||||
|
@ -86,19 +85,19 @@ class MapListDialogUnconnected extends PureComponent<Props, State> {
|
|||
|
||||
hideMenu = (): void =>
|
||||
this.setState({
|
||||
menu_target: null,
|
||||
menu_target: '',
|
||||
});
|
||||
|
||||
showDropCard = (editor_target: IRouteListItem['address']): void =>
|
||||
this.setState({
|
||||
editor_target,
|
||||
menu_target: null,
|
||||
menu_target: '',
|
||||
is_editing: false,
|
||||
is_dropping: true,
|
||||
});
|
||||
|
||||
stopEditing = (): void => {
|
||||
this.setState({ editor_target: null });
|
||||
this.setState({ editor_target: '' });
|
||||
};
|
||||
|
||||
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 })})`,
|
||||
}}
|
||||
onMouseDown={() => mapSetProvider(item)}
|
||||
key={PROVIDERS[item].name}
|
||||
key={PROVIDERS[item]?.name}
|
||||
>
|
||||
{
|
||||
provider === item &&
|
||||
|
@ -44,4 +44,4 @@ const ProviderDialogUnconnected = ({ provider, mapSetProvider }: Props) => (
|
|||
|
||||
const ProviderDialog = connect(mapStateToProps, mapDispatchToProps)(ProviderDialogUnconnected)
|
||||
|
||||
export { ProviderDialog }
|
||||
export { ProviderDialog }
|
||||
|
|
|
@ -42,7 +42,7 @@ export class TitleDialogUnconnected extends React.PureComponent<Props, State> {
|
|||
this.setMaxHeight();
|
||||
}
|
||||
|
||||
setMaxHeight = (): number => {
|
||||
setMaxHeight = () => {
|
||||
if (!this.ref_sizer || !this.ref_title || !this.ref_text) return 0;
|
||||
|
||||
const { height: sizer_height } = this.ref_sizer.getBoundingClientRect();
|
||||
|
|
|
@ -40,7 +40,7 @@ export const RouteRowView = ({
|
|||
is_admin,
|
||||
is_published,
|
||||
toggleStarred
|
||||
}: Props): ReactElement<Props, null> => (
|
||||
}: Props): ReactElement<Props> => (
|
||||
<div className={classnames("route-row-view", { has_menu: tab === "my" })}>
|
||||
{(tab === TABS.PENDING || tab === TABS.STARRED) && is_admin && (
|
||||
<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 classnames from 'classnames';
|
||||
|
||||
|
@ -7,12 +7,12 @@ import { EditorDialog } from '~/components/panels/EditorDialog';
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
editorChangeMode,
|
||||
editorKeyPressed,
|
||||
editorRedo,
|
||||
editorStartEditing,
|
||||
editorStopEditing,
|
||||
editorTakeAShot,
|
||||
editorKeyPressed,
|
||||
editorUndo,
|
||||
editorRedo,
|
||||
} from '~/redux/editor/actions';
|
||||
import { Tooltip } from '~/components/panels/Tooltip';
|
||||
import { IState } from '~/redux/store';
|
||||
|
@ -47,6 +47,10 @@ type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {}
|
|||
|
||||
class EditorPanelUnconnected extends PureComponent<Props, void> {
|
||||
componentDidMount() {
|
||||
if (!this.panel) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', this.onKeyPress as any);
|
||||
|
||||
const obj = document.getElementById('control-dialog');
|
||||
|
@ -57,7 +61,7 @@ class EditorPanelUnconnected extends PureComponent<Props, void> {
|
|||
obj.style.width = String(width);
|
||||
}
|
||||
|
||||
panel: HTMLElement = null;
|
||||
panel: HTMLDivElement | null = null;
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('keydown', this.onKeyPress as any);
|
||||
|
|
|
@ -31,6 +31,10 @@ class Component extends React.Component<Props, State> {
|
|||
};
|
||||
|
||||
onImageLoaded = () => {
|
||||
if (!this.image) {
|
||||
return
|
||||
}
|
||||
|
||||
this.croppr = new Croppr(this.image, {
|
||||
onInitialize: this.onCropInit,
|
||||
});
|
||||
|
@ -57,12 +61,12 @@ class Component extends React.Component<Props, State> {
|
|||
regionEl.append(this.logo);
|
||||
};
|
||||
|
||||
croppr: Croppr;
|
||||
logo: HTMLDivElement;
|
||||
image: HTMLImageElement;
|
||||
logoImg: HTMLImageElement;
|
||||
croppr?: Croppr;
|
||||
logo: HTMLDivElement | null = null;
|
||||
image: HTMLImageElement | null = null;
|
||||
logoImg: HTMLImageElement | null = null;
|
||||
|
||||
getImage = () => this.props.editorCropAShot(this.croppr.getValue());
|
||||
getImage = () => this.props.editorCropAShot(this.croppr?.getValue());
|
||||
|
||||
render() {
|
||||
const { data } = this.props.editor.renderer;
|
||||
|
|
|
@ -7,7 +7,7 @@ interface Props {
|
|||
max: number;
|
||||
search: string;
|
||||
distance: [number, number];
|
||||
onDistanceChange: (val: [number, number]) => void;
|
||||
onDistanceChange: (val: number[]) => void;
|
||||
onSearchChange: ChangeEventHandler<HTMLInputElement>;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
import { CLIENT } from '~/config/frontend';
|
||||
|
||||
export const API = {
|
||||
GET_GUEST: `${CLIENT.API_ADDR}/api/auth/`,
|
||||
CHECK_TOKEN: `${CLIENT.API_ADDR}/api/auth/`,
|
||||
IFRAME_LOGIN_VK: `${CLIENT.API_ADDR}/api/auth/vk`,
|
||||
GET_MAP: `${CLIENT.API_ADDR}/api/route/`,
|
||||
POST_MAP: `${CLIENT.API_ADDR}/api/route/`,
|
||||
GET_ROUTE_LIST: tab => `${CLIENT.API_ADDR}/api/route/list/${tab}`,
|
||||
GET_GUEST: `/api/auth/`,
|
||||
CHECK_TOKEN: `/api/auth/`,
|
||||
IFRAME_LOGIN_VK: `/api/auth/vk`,
|
||||
GET_MAP: `/api/route/`,
|
||||
POST_MAP: `/api/route/`,
|
||||
GET_ROUTE_LIST: tab => `/api/route/list/${tab}`,
|
||||
|
||||
DROP_ROUTE: `${CLIENT.API_ADDR}/api/route/`,
|
||||
MODIFY_ROUTE: `${CLIENT.API_ADDR}/api/route/`,
|
||||
SET_STARRED: `${CLIENT.API_ADDR}/api/route/publish`,
|
||||
DROP_ROUTE: `/api/route/`,
|
||||
MODIFY_ROUTE: `/api/route/`,
|
||||
SET_STARRED: `/api/route/publish`,
|
||||
};
|
||||
|
||||
export const API_RETRY_INTERVAL = 10;
|
||||
|
|
|
@ -11,7 +11,7 @@ export interface IUser {
|
|||
role: IRoles[keyof IRoles];
|
||||
routes: {};
|
||||
success: boolean;
|
||||
id?: string;
|
||||
id: string;
|
||||
uid: string;
|
||||
token?: string;
|
||||
photo: string;
|
||||
|
@ -31,9 +31,9 @@ export const DEFAULT_USER: IUser = {
|
|||
role: ROLES.guest,
|
||||
routes: {},
|
||||
success: false,
|
||||
id: null,
|
||||
token: null,
|
||||
photo: null,
|
||||
name: null,
|
||||
uid: null,
|
||||
id: '',
|
||||
token: undefined,
|
||||
photo: '',
|
||||
name: '',
|
||||
uid: '',
|
||||
};
|
||||
|
|
|
@ -5,21 +5,6 @@ export interface 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 = {
|
||||
|
@ -53,7 +38,7 @@ const TILEMAPS: ITileMaps = {
|
|||
const ENABLED: Array<keyof ITileMaps> = ['BLANK', 'DEFAULT', 'DGIS', 'HOT', 'ESAT'];
|
||||
|
||||
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,
|
||||
[provider]: TILEMAPS[provider],
|
||||
}), {});
|
||||
|
|
|
@ -14,17 +14,17 @@ const mapDispatchToProps = {};
|
|||
type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
|
||||
|
||||
const ArrowsUnconnected: FC<Props> = memo(({ route }) => {
|
||||
const [layer, setLayer] = useState(null);
|
||||
const [layer, setLayer] = useState<ArrowsLayer | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const item = new ArrowsLayer({}).addTo(MainMap);
|
||||
setLayer(item);
|
||||
setLayer(item);
|
||||
return () => MainMap.removeLayer(item);
|
||||
}, [MainMap]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!layer) return
|
||||
|
||||
|
||||
layer.setLatLngs(route);
|
||||
}, [layer, route])
|
||||
return null;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import React, { FC, useState, useEffect, useCallback } from 'react';
|
||||
import { LatLngLiteral, marker, Marker, DivIcon } from 'leaflet';
|
||||
import React, { FC, useCallback, useEffect } from 'react';
|
||||
import { DivIcon, LatLngLiteral, Marker } from 'leaflet';
|
||||
import { MainMap } from '~/constants/map';
|
||||
|
||||
interface IProps {
|
||||
location: LatLngLiteral;
|
||||
location?: LatLngLiteral;
|
||||
}
|
||||
|
||||
const CurrentLocation: FC<IProps> = ({ location }) => {
|
||||
|
|
|
@ -8,14 +8,14 @@ interface IProps {
|
|||
}
|
||||
|
||||
const GpxPolyline: FC<IProps> = ({ latlngs, color }) => {
|
||||
const [layer, setLayer] = useState<Polyline>(null);
|
||||
const [layer, setLayer] = useState<Polyline | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const item = new Polyline([], {
|
||||
color,
|
||||
stroke: true,
|
||||
opacity: 1,
|
||||
weight: 7,
|
||||
weight: 7,
|
||||
// dashArray: [12,12],
|
||||
}).addTo(MainMap);
|
||||
setLayer(item);
|
||||
|
|
|
@ -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 { MainMap } from '~/constants/map';
|
||||
import { selectMap } from '~/redux/map/selectors';
|
||||
|
@ -14,14 +14,14 @@ const mapDispatchToProps = {};
|
|||
type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
|
||||
|
||||
const KmMarksUnconnected: FC<Props> = memo(({ map: { route } }) => {
|
||||
const [layer, setLayer] = useState(null);
|
||||
const [layer, setLayer] = useState<KmMarksLayer | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const layer = new KmMarksLayer([]);
|
||||
layer.addTo(MainMap);
|
||||
setLayer(layer);
|
||||
return () => MainMap.removeLayer(layer);
|
||||
}, [MainMap]);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!layer) return;
|
||||
|
|
|
@ -93,7 +93,7 @@ const MapUnconnected: React.FC<IProps> = memo(
|
|||
enabled && <GpxPolyline latlngs={latlngs} color={color} key={index} />
|
||||
)}
|
||||
</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 { isMobile } from '~/utils/window';
|
||||
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 { connect } from 'react-redux';
|
||||
import { selectMapRoute } from '~/redux/map/selectors';
|
||||
|
@ -28,7 +28,7 @@ type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {}
|
|||
|
||||
const RouteUnconnected: FC<Props> = memo(
|
||||
({ 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), [
|
||||
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 { connect } from 'react-redux';
|
||||
import { selectMapRoute } from '~/redux/map/selectors';
|
||||
import {
|
||||
selectEditorRouter,
|
||||
selectEditorMode,
|
||||
selectEditorDistance,
|
||||
} from '~/redux/editor/selectors';
|
||||
import { selectEditorDistance, selectEditorMode, selectEditorRouter } from '~/redux/editor/selectors';
|
||||
import { MainMap } from '~/constants/map';
|
||||
import * as EDITOR_ACTIONS from '~/redux/editor/actions';
|
||||
import { MODES } from '~/constants/modes';
|
||||
import { LatLngLiteral, marker, divIcon } from 'leaflet';
|
||||
import { divIcon, LatLngLiteral, marker } from 'leaflet';
|
||||
import classNames from 'classnames';
|
||||
import { angleBetweenPoints } from '~/utils/geom';
|
||||
|
||||
|
@ -30,7 +26,7 @@ type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {}
|
|||
const RouterUnconnected: FC<Props> = memo(
|
||||
({ route, mode, router: { waypoints }, editorSetRouter, distance }) => {
|
||||
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 updateWaypoints = useCallback(
|
||||
|
|
|
@ -20,8 +20,8 @@ interface IProps {
|
|||
mapDropSticker: (index: number) => void;
|
||||
}
|
||||
|
||||
export const getLabelDirection = (angle: number): 'left' | 'right' =>
|
||||
angle % Math.PI >= -(Math.PI / 2) && angle % Math.PI <= Math.PI / 2 ? 'left' : 'right';
|
||||
export const getLabelDirection = (angle?: number): 'left' | 'right' =>
|
||||
!!angle && angle % Math.PI >= -(Math.PI / 2) && angle % Math.PI <= Math.PI / 2 ? 'left' : 'right';
|
||||
|
||||
const getX = e =>
|
||||
e.touches && e.touches.length > 0
|
||||
|
@ -36,50 +36,58 @@ const Sticker: React.FC<IProps> = ({
|
|||
mapSetSticker,
|
||||
mapDropSticker,
|
||||
}) => {
|
||||
const [text, setText] = useState(sticker.text);
|
||||
const [layer, setLayer] = React.useState<Marker>(null);
|
||||
const [text, setText] = useState(sticker.text || '');
|
||||
const [layer, setLayer] = React.useState<Marker | null>(null);
|
||||
const [dragging, setDragging] = React.useState(false);
|
||||
const wrapper = useRef(null);
|
||||
const wrapper = useRef<HTMLDivElement>(null);
|
||||
|
||||
let angle = useRef(sticker.angle);
|
||||
|
||||
const element = React.useMemo(() => document.createElement('div'), []);
|
||||
|
||||
const stickerArrow = React.useRef(null);
|
||||
const stickerImage = React.useRef(null);
|
||||
const stickerArrow = React.useRef<HTMLDivElement>(null);
|
||||
const stickerImage = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
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(
|
||||
ang => {
|
||||
if (!stickerImage.current || !stickerArrow.current) return;
|
||||
|
||||
const x = Math.cos(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.top = String(6 + y);
|
||||
|
||||
stickerArrow.current.style.transform = `rotate(${ang + Math.PI}rad)`;
|
||||
},
|
||||
[stickerArrow, stickerImage, angle]
|
||||
[stickerArrow, stickerImage],
|
||||
);
|
||||
|
||||
const onDragStart = React.useCallback(() => {
|
||||
if (!layer?.dragging) {
|
||||
return;
|
||||
}
|
||||
|
||||
layer.dragging.disable();
|
||||
MainMap.dragging.disable();
|
||||
MainMap.disableClicks();
|
||||
|
||||
setDragging(true);
|
||||
}, [setDragging, layer, MainMap]);
|
||||
}, [setDragging, layer]);
|
||||
|
||||
const onDragStop = React.useCallback(
|
||||
event => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
if (!layer) return;
|
||||
if (!layer?.dragging) {
|
||||
return;
|
||||
}
|
||||
|
||||
setDragging(false);
|
||||
onChange({
|
||||
|
@ -92,7 +100,7 @@ const Sticker: React.FC<IProps> = ({
|
|||
|
||||
setTimeout(MainMap.enableClicks, 100);
|
||||
},
|
||||
[setDragging, layer, MainMap, sticker, angle]
|
||||
[setDragging, layer, MainMap, sticker, angle],
|
||||
);
|
||||
|
||||
const onMoveStarted = React.useCallback(() => {
|
||||
|
@ -110,7 +118,7 @@ const Sticker: React.FC<IProps> = ({
|
|||
|
||||
MainMap.enableClicks();
|
||||
},
|
||||
[onChange, sticker]
|
||||
[onChange, sticker],
|
||||
);
|
||||
|
||||
const onDrag = React.useCallback(
|
||||
|
@ -122,7 +130,7 @@ const Sticker: React.FC<IProps> = ({
|
|||
angle.current = parseFloat(Math.atan2(y - pageY, x - pageX).toFixed(2));
|
||||
updateAngle(angle.current);
|
||||
},
|
||||
[element, updateAngle, angle]
|
||||
[element, updateAngle, angle],
|
||||
);
|
||||
|
||||
const onTextChange = React.useCallback(text => setText(text), [sticker, onChange]);
|
||||
|
@ -134,7 +142,9 @@ const Sticker: React.FC<IProps> = ({
|
|||
});
|
||||
}, [text, onChange, sticker]);
|
||||
|
||||
const direction = React.useMemo(() => getLabelDirection(sticker.angle), [sticker.angle]);
|
||||
const direction = React.useMemo(() => {
|
||||
getLabelDirection(sticker?.angle);
|
||||
}, [sticker.angle]);
|
||||
|
||||
useEffect(() => {
|
||||
updateAngle(sticker.angle);
|
||||
|
@ -148,15 +158,16 @@ const Sticker: React.FC<IProps> = ({
|
|||
|
||||
useEffect(() => {
|
||||
if (!layer) return;
|
||||
setText(sticker.text);
|
||||
|
||||
setText(sticker.text || '');
|
||||
}, [layer, sticker.text]);
|
||||
|
||||
useEffect(() => {
|
||||
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]);
|
||||
|
||||
// Attaches onMoveFinished event to item
|
||||
|
@ -235,7 +246,7 @@ const Sticker: React.FC<IProps> = ({
|
|||
<div className="sticker-delete" onMouseDown={onDelete} onTouchStart={onDelete} />
|
||||
</div>
|
||||
</div>,
|
||||
element
|
||||
element,
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ interface IProps {
|
|||
}
|
||||
|
||||
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 onZoomChange = useCallback(
|
||||
|
|
|
@ -10,7 +10,7 @@ type IProps = React.HTMLAttributes<HTMLDivElement> & {
|
|||
};
|
||||
|
||||
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(() => {
|
||||
if (!map) return;
|
||||
|
@ -34,7 +34,11 @@ const TileLayer: React.FC<IProps> = React.memo(({ children, provider, map }) =>
|
|||
layer.setUrl(url);
|
||||
}, [layer, provider]);
|
||||
|
||||
return <TileContext.Provider value={layer}>{children}</TileContext.Provider>;
|
||||
return (
|
||||
<TileContext.Provider value={layer}>
|
||||
{children}
|
||||
</TileContext.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
export { TileLayer };
|
||||
|
|
|
@ -41,7 +41,7 @@ export interface IEditorState {
|
|||
distance: number;
|
||||
estimated: number;
|
||||
speed: number;
|
||||
activeSticker: { set?: string; sticker?: string };
|
||||
activeSticker: { set: string; sticker: string };
|
||||
is_empty: boolean;
|
||||
is_published: boolean;
|
||||
is_routing: boolean;
|
||||
|
@ -134,7 +134,7 @@ export const EDITOR_INITIAL_STATE = {
|
|||
},
|
||||
|
||||
save: {
|
||||
error: null,
|
||||
error: '',
|
||||
finished: false,
|
||||
overwriting: false,
|
||||
processing: false,
|
||||
|
|
|
@ -69,8 +69,14 @@ import uuid from 'uuid';
|
|||
import { getRandomColor, getAdaptiveScale } from '~/utils/dom';
|
||||
|
||||
const hideLoader = () => {
|
||||
document.getElementById('loader').style.opacity = String(0);
|
||||
document.getElementById('loader').style.pointerEvents = 'none';
|
||||
const el = document.getElementById('loader');
|
||||
|
||||
if (!el) {
|
||||
return true;
|
||||
}
|
||||
|
||||
el.style.opacity = String(0);
|
||||
el.style.pointerEvents = 'none';
|
||||
|
||||
return true;
|
||||
};
|
||||
|
@ -125,6 +131,10 @@ function* getRenderData() {
|
|||
canvas.height = window.innerHeight;
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
if (!ctx) {
|
||||
return
|
||||
}
|
||||
|
||||
const geometry = getTilePlacement();
|
||||
const points = getPolyPlacement(route);
|
||||
const sticker_points = getStickersPlacement(stickers);
|
||||
|
@ -184,6 +194,11 @@ function* getCropData({ x, y, width, height }) {
|
|||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
if (!ctx) {
|
||||
return
|
||||
}
|
||||
|
||||
const image = yield imageFetcher(data);
|
||||
|
||||
ctx.drawImage(image, -x, -y);
|
||||
|
@ -281,7 +296,7 @@ function* mapClick({ latlng }: ReturnType<typeof mapClicked>) {
|
|||
|
||||
function* routerSubmit() {
|
||||
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);
|
||||
|
||||
|
|
|
@ -28,9 +28,9 @@ export const MAP_INITIAL_STATE: IMapReducer = {
|
|||
address: '',
|
||||
address_origin: '',
|
||||
description: '',
|
||||
owner: { id: null },
|
||||
owner: { id: '' },
|
||||
is_public: false,
|
||||
zoom: 13,
|
||||
}
|
||||
|
||||
export const map = createReducer(MAP_INITIAL_STATE, MAP_HANDLERS)
|
||||
export const map = createReducer(MAP_INITIAL_STATE, MAP_HANDLERS)
|
||||
|
|
|
@ -1,40 +1,30 @@
|
|||
import {
|
||||
takeEvery,
|
||||
select,
|
||||
put,
|
||||
call,
|
||||
TakeEffect,
|
||||
race,
|
||||
take,
|
||||
takeLatest,
|
||||
delay,
|
||||
} from 'redux-saga/effects';
|
||||
import { call, delay, put, race, select, take, TakeEffect, takeEvery, takeLatest } from 'redux-saga/effects';
|
||||
import { MAP_ACTIONS } from './constants';
|
||||
import {
|
||||
mapClicked,
|
||||
mapAddSticker,
|
||||
mapSetProvider,
|
||||
mapClicked,
|
||||
mapSet,
|
||||
mapSetTitle,
|
||||
mapSetAddressOrigin,
|
||||
mapSetProvider,
|
||||
mapSetRoute,
|
||||
mapSetStickers,
|
||||
mapSetTitle,
|
||||
} from './actions';
|
||||
import { selectUser, selectUserUser } from '~/redux/user/selectors';
|
||||
import { selectUser } from '~/redux/user/selectors';
|
||||
import { MODES } from '~/constants/modes';
|
||||
import {
|
||||
editorCaptureHistory,
|
||||
editorChangeMode,
|
||||
editorClearAll,
|
||||
editorSendSaveRequest,
|
||||
editorSetActiveSticker,
|
||||
editorSetChanged,
|
||||
editorSetEditing,
|
||||
editorSetReady,
|
||||
editorSetActiveSticker,
|
||||
editorSendSaveRequest,
|
||||
editorSetSave,
|
||||
editorClearAll,
|
||||
editorSetHistory,
|
||||
editorCaptureHistory,
|
||||
editorSetReady,
|
||||
editorSetSave,
|
||||
} 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 { Unwrap } from '~/utils/middleware';
|
||||
import { selectMap, selectMapProvider, selectMapRoute, selectMapStickers } from './selectors';
|
||||
|
@ -70,29 +60,36 @@ export function* replaceAddressIfItsBusy(destination, original) {
|
|||
}
|
||||
|
||||
export function* loadMapSaga(path) {
|
||||
const {
|
||||
data: { route, error, random_url },
|
||||
}: Unwrap<typeof getStoredMap> = yield call(getStoredMap, { name: path });
|
||||
try {
|
||||
const {
|
||||
data: {
|
||||
route, error, random_url,
|
||||
},
|
||||
}: Unwrap<typeof getStoredMap> = yield call(getStoredMap, { name: path });
|
||||
|
||||
if (route && !error) {
|
||||
yield put(
|
||||
mapSet({
|
||||
provider: route.provider,
|
||||
route: route.route,
|
||||
stickers: route.stickers,
|
||||
title: route.title,
|
||||
address: route.address,
|
||||
description: route.description,
|
||||
is_public: route.is_public,
|
||||
logo: route.logo,
|
||||
})
|
||||
);
|
||||
if (route && !error) {
|
||||
yield put(
|
||||
mapSet({
|
||||
provider: route.provider,
|
||||
route: route.route,
|
||||
stickers: route.stickers,
|
||||
title: route.title,
|
||||
address: route.address,
|
||||
description: route.description,
|
||||
is_public: route.is_public,
|
||||
logo: route.logo,
|
||||
}),
|
||||
);
|
||||
|
||||
yield put(editorSetHistory({ records: [{ route: route.route, stickers: route.stickers }] }));
|
||||
return { route, random_url };
|
||||
yield put(editorSetHistory({ records: [{ route: route.route, stickers: route.stickers }] }));
|
||||
return { route, random_url };
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
yield call(startEmptyEditorSaga);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function* startEmptyEditorSaga() {
|
||||
|
@ -142,10 +139,10 @@ export function* mapInitSaga() {
|
|||
yield put(mapSetProvider(provider));
|
||||
|
||||
if (hash && /^#map/.test(hash)) {
|
||||
const [, newUrl] = hash.match(/^#map[:/?!](.*)$/);
|
||||
const matches = hash.match(/^#map[:/?!](.*)$/);
|
||||
|
||||
if (newUrl) {
|
||||
yield pushPath(`/${newUrl}`);
|
||||
if (matches && matches[1]) {
|
||||
yield pushPath(`/${matches[1]}`);
|
||||
yield call(setReadySaga);
|
||||
return;
|
||||
}
|
||||
|
@ -161,7 +158,7 @@ function* setActiveStickerSaga() {
|
|||
yield put(editorChangeMode(MODES.STICKERS));
|
||||
}
|
||||
|
||||
function* setTitleSaga({ title }: ReturnType<typeof mapSetTitle>) {
|
||||
function setTitleSaga({ title }: ReturnType<typeof mapSetTitle>) {
|
||||
if (title) {
|
||||
document.title = `${title} | Редактор маршрутов`;
|
||||
}
|
||||
|
@ -216,7 +213,7 @@ function* clearSaga({ type }) {
|
|||
const { mode, activeSticker }: ReturnType<typeof selectEditor> = yield select(selectEditor);
|
||||
|
||||
if (activeSticker && activeSticker.set && activeSticker.sticker) {
|
||||
yield put(editorSetActiveSticker(null));
|
||||
yield put(editorSetActiveSticker({ set: '', sticker: '' }));
|
||||
}
|
||||
|
||||
if (mode !== MODES.NONE) {
|
||||
|
@ -231,94 +228,96 @@ function* sendSaveRequestSaga({
|
|||
is_public,
|
||||
description,
|
||||
}: ReturnType<typeof editorSendSaveRequest>) {
|
||||
const { route, stickers, provider }: ReturnType<typeof selectMap> = yield select(selectMap);
|
||||
try {
|
||||
const { route, stickers, provider }: ReturnType<typeof selectMap> = yield select(selectMap);
|
||||
|
||||
if (!route.length && !stickers.length) {
|
||||
return yield put(
|
||||
editorSetSave({ error: TIPS.SAVE_EMPTY, loading: false, overwriting: false, finished: false })
|
||||
if (!route.length && !stickers.length) {
|
||||
return yield put(
|
||||
editorSetSave({ error: TIPS.SAVE_EMPTY, loading: false, overwriting: false, finished: false }),
|
||||
);
|
||||
}
|
||||
|
||||
const { logo }: ReturnType<typeof selectMap> = yield select(selectMap);
|
||||
const { distance }: ReturnType<typeof selectEditor> = yield select(selectEditor);
|
||||
|
||||
yield put(editorSetSave({ loading: true, overwriting: false, finished: false, error: '' }));
|
||||
|
||||
const {
|
||||
result,
|
||||
timeout,
|
||||
cancel,
|
||||
}: {
|
||||
result: Unwrap<typeof postMap>;
|
||||
timeout: boolean;
|
||||
cancel: TakeEffect;
|
||||
} = yield race({
|
||||
result: postMap({
|
||||
route,
|
||||
stickers,
|
||||
title,
|
||||
force,
|
||||
address,
|
||||
logo,
|
||||
distance,
|
||||
provider,
|
||||
is_public,
|
||||
description,
|
||||
}),
|
||||
timeout: delay(10000),
|
||||
cancel: take(EDITOR_ACTIONS.RESET_SAVE_DIALOG),
|
||||
});
|
||||
|
||||
yield put(editorSetSave({ loading: false }));
|
||||
|
||||
if (cancel) return yield put(editorChangeMode(MODES.NONE));
|
||||
|
||||
if (result && result.data.code === 'already_exist')
|
||||
return yield put(editorSetSave({ overwriting: true }));
|
||||
|
||||
if (result && result.data.code === 'conflict')
|
||||
return yield put(
|
||||
editorSetSave({
|
||||
error: TIPS.SAVE_EXISTS,
|
||||
loading: false,
|
||||
overwriting: false,
|
||||
finished: false,
|
||||
}),
|
||||
);
|
||||
|
||||
if (timeout || !result || !result.data.route || !result.data.route.address)
|
||||
return yield put(
|
||||
editorSetSave({
|
||||
error: TIPS.SAVE_TIMED_OUT,
|
||||
loading: false,
|
||||
overwriting: false,
|
||||
finished: false,
|
||||
}),
|
||||
);
|
||||
|
||||
yield put(
|
||||
mapSet({
|
||||
address: result.data.route.address,
|
||||
title: result.data.route.title,
|
||||
is_public: result.data.route.is_public,
|
||||
description: result.data.route.description,
|
||||
}),
|
||||
);
|
||||
|
||||
yield put(editorSetReady(false));
|
||||
pushPath(`/${address}/edit`);
|
||||
yield put(editorSetReady(true));
|
||||
|
||||
yield put(
|
||||
editorSetSave({
|
||||
error: TIPS.SAVE_SUCCESS,
|
||||
loading: false,
|
||||
overwriting: false,
|
||||
finished: true,
|
||||
}),
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
const { logo }: ReturnType<typeof selectMap> = yield select(selectMap);
|
||||
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 }));
|
||||
|
||||
const {
|
||||
result,
|
||||
timeout,
|
||||
cancel,
|
||||
}: {
|
||||
result: Unwrap<typeof postMap>;
|
||||
timeout: boolean;
|
||||
cancel: TakeEffect;
|
||||
} = yield race({
|
||||
result: postMap({
|
||||
token,
|
||||
route,
|
||||
stickers,
|
||||
title,
|
||||
force,
|
||||
address,
|
||||
logo,
|
||||
distance,
|
||||
provider,
|
||||
is_public,
|
||||
description,
|
||||
}),
|
||||
timeout: delay(10000),
|
||||
cancel: take(EDITOR_ACTIONS.RESET_SAVE_DIALOG),
|
||||
});
|
||||
|
||||
yield put(editorSetSave({ loading: false }));
|
||||
|
||||
if (cancel) return yield put(editorChangeMode(MODES.NONE));
|
||||
|
||||
if (result && result.data.code === 'already_exist')
|
||||
return yield put(editorSetSave({ overwriting: true }));
|
||||
|
||||
if (result && result.data.code === 'conflict')
|
||||
return yield put(
|
||||
editorSetSave({
|
||||
error: TIPS.SAVE_EXISTS,
|
||||
loading: false,
|
||||
overwriting: false,
|
||||
finished: false,
|
||||
})
|
||||
);
|
||||
|
||||
if (timeout || !result || !result.data.route || !result.data.route.address)
|
||||
return yield put(
|
||||
editorSetSave({
|
||||
error: TIPS.SAVE_TIMED_OUT,
|
||||
loading: false,
|
||||
overwriting: false,
|
||||
finished: false,
|
||||
})
|
||||
);
|
||||
|
||||
yield put(
|
||||
mapSet({
|
||||
address: result.data.route.address,
|
||||
title: result.data.route.title,
|
||||
is_public: result.data.route.is_public,
|
||||
description: result.data.route.description,
|
||||
})
|
||||
);
|
||||
|
||||
yield put(editorSetReady(false));
|
||||
pushPath(`/${address}/edit`);
|
||||
yield put(editorSetReady(true));
|
||||
|
||||
yield put(
|
||||
editorSetSave({
|
||||
error: TIPS.SAVE_SUCCESS,
|
||||
loading: false,
|
||||
overwriting: false,
|
||||
finished: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function* setChanged() {
|
||||
|
@ -328,14 +327,10 @@ function* setChanged() {
|
|||
yield put(editorSetChanged(true));
|
||||
}
|
||||
|
||||
function* onZoomChange() {
|
||||
|
||||
}
|
||||
|
||||
export function* mapSaga() {
|
||||
yield takeEvery(
|
||||
[MAP_ACTIONS.SET_ROUTE, MAP_ACTIONS.SET_STICKER, MAP_ACTIONS.SET_STICKERS],
|
||||
setChanged
|
||||
[MAP_ACTIONS.SET_ROUTE, MAP_ACTIONS.SET_STICKER, MAP_ACTIONS.SET_STICKERS, MAP_ACTIONS.ADD_STICKER],
|
||||
setChanged,
|
||||
);
|
||||
|
||||
yield takeEvery(EDITOR_ACTIONS.START_EDITING, startEditingSaga);
|
||||
|
@ -351,6 +346,6 @@ export function* mapSaga() {
|
|||
EDITOR_ACTIONS.CLEAR_ALL,
|
||||
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 createSagaMiddleware from 'redux-saga';
|
||||
|
||||
|
@ -8,19 +8,28 @@ import { createBrowserHistory } from 'history';
|
|||
import { editorLocationChanged } from '~/redux/editor/actions';
|
||||
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 { editor, IEditorState } from '~/redux/editor';
|
||||
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 { watchLocation, getLocation } from '~/utils/window';
|
||||
import { watchLocation } from '~/utils/window';
|
||||
import { LatLngLiteral } from 'leaflet';
|
||||
import { setUserLocation } from './user/actions';
|
||||
import { setUserLocation, userLogout } from './user/actions';
|
||||
import { MainMap } from '~/constants/map';
|
||||
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 = {
|
||||
key: 'user',
|
||||
|
@ -52,7 +61,7 @@ export const store = createStore(
|
|||
combineReducers({
|
||||
user: persistReducer(userPersistConfig, userReducer),
|
||||
editor: persistReducer(editorPersistConfig, editor),
|
||||
map,
|
||||
map: persistReducer(mapPersistConfig, map),
|
||||
}),
|
||||
composeEnhancers(applyMiddleware(sagaMiddleware))
|
||||
);
|
||||
|
@ -64,6 +73,28 @@ export function configureStore(): { store: Store<any>; persistor: Persistor } {
|
|||
|
||||
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 };
|
||||
}
|
||||
|
||||
|
@ -74,5 +105,5 @@ history.listen((location, action) => {
|
|||
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)))
|
||||
|
|
|
@ -15,7 +15,7 @@ export interface IRouteListItem {
|
|||
export interface IRootReducer {
|
||||
// ready: boolean,
|
||||
user: IUser;
|
||||
location: LatLngLiteral;
|
||||
location?: LatLngLiteral;
|
||||
routes: {
|
||||
limit: 0;
|
||||
loading: boolean;
|
||||
|
@ -38,7 +38,7 @@ export type IRootState = Readonly<IRootReducer>;
|
|||
|
||||
export const INITIAL_STATE: IRootReducer = {
|
||||
user: { ...DEFAULT_USER },
|
||||
location: null,
|
||||
location: undefined,
|
||||
routes: {
|
||||
limit: 0,
|
||||
loading: false, // <-- maybe delete this
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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 {
|
||||
checkIframeToken,
|
||||
checkUserToken,
|
||||
|
@ -9,15 +9,16 @@ import {
|
|||
modifyRoute,
|
||||
sendRouteStarred,
|
||||
} from '~/utils/api';
|
||||
import * as ActionCreators from '~/redux/user/actions';
|
||||
import {
|
||||
searchSetTab,
|
||||
setUser,
|
||||
mapsSetShift,
|
||||
searchChangeDistance,
|
||||
searchPutRoutes,
|
||||
searchSetLoading,
|
||||
searchSetTab,
|
||||
searchSetTitle,
|
||||
setRouteStarred,
|
||||
setUser,
|
||||
userLogin,
|
||||
} from '~/redux/user/actions';
|
||||
|
||||
|
@ -26,8 +27,6 @@ import { USER_ACTIONS } from '~/redux/user/constants';
|
|||
import { DEFAULT_USER } from '~/constants/auth';
|
||||
|
||||
import { DIALOGS, TABS } from '~/constants/dialogs';
|
||||
|
||||
import * as ActionCreators from '~/redux/user/actions';
|
||||
import { Unwrap } from '~/utils/middleware';
|
||||
import { selectUser, selectUserUser } from './selectors';
|
||||
import { mapInitSaga } from '~/redux/map/sagas';
|
||||
|
@ -35,64 +34,72 @@ import { editorSetDialog, editorSetDialogActive } from '../editor/actions';
|
|||
import { selectEditor } from '../editor/selectors';
|
||||
|
||||
function* generateGuestSaga() {
|
||||
const {
|
||||
data: { user, random_url },
|
||||
}: Unwrap<typeof getGuestToken> = yield call(getGuestToken);
|
||||
try {
|
||||
const {
|
||||
data: { user, random_url },
|
||||
}: Unwrap<typeof getGuestToken> = yield call(getGuestToken);
|
||||
|
||||
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) {
|
||||
if (key !== 'user') return;
|
||||
try {
|
||||
if (key !== 'user') return;
|
||||
|
||||
pushLoaderState(70);
|
||||
pushLoaderState(70);
|
||||
|
||||
const { id, token }: ReturnType<typeof selectUserUser> = yield select(selectUserUser);
|
||||
const { ready }: ReturnType<typeof selectEditor> = yield select(selectEditor);
|
||||
const { id, token }: ReturnType<typeof selectUserUser> = yield select(selectUserUser);
|
||||
const { ready }: ReturnType<typeof selectEditor> = yield select(selectEditor);
|
||||
|
||||
if (window.location.search || true) {
|
||||
const { viewer_id, auth_key } = yield parseQuery(window.location.search);
|
||||
if (window.location.search || true) {
|
||||
const { viewer_id, auth_key } = yield parseQuery(window.location.search);
|
||||
|
||||
if (viewer_id && auth_key && id !== `vk:${viewer_id}`) {
|
||||
const user = yield call(checkIframeToken, { viewer_id, auth_key });
|
||||
if (viewer_id && auth_key && id !== `vk:${viewer_id}`) {
|
||||
const user = yield call(checkIframeToken, { viewer_id, auth_key });
|
||||
|
||||
if (user) {
|
||||
yield put(setUser(user));
|
||||
|
||||
pushLoaderState(99);
|
||||
|
||||
return yield call(mapInitSaga);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (id && token) {
|
||||
const {
|
||||
data: { user, random_url },
|
||||
}: Unwrap<typeof checkUserToken> = yield call(checkUserToken, {
|
||||
id,
|
||||
token,
|
||||
});
|
||||
|
||||
if (user) {
|
||||
yield put(setUser(user));
|
||||
yield put(setUser({ ...user, random_url }));
|
||||
|
||||
pushLoaderState(99);
|
||||
|
||||
return yield call(mapInitSaga);
|
||||
} else if (!ready) {
|
||||
pushNetworkInitError();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
yield call(generateGuestSaga);
|
||||
|
||||
pushLoaderState(80);
|
||||
|
||||
return yield call(mapInitSaga);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
if (id && token) {
|
||||
const {
|
||||
data: { user, random_url },
|
||||
}: Unwrap<typeof checkUserToken> = yield call(checkUserToken, {
|
||||
id,
|
||||
token,
|
||||
});
|
||||
|
||||
if (user) {
|
||||
yield put(setUser({ ...user, random_url }));
|
||||
|
||||
pushLoaderState(99);
|
||||
|
||||
return yield call(mapInitSaga);
|
||||
} else if (!ready) {
|
||||
pushNetworkInitError();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
yield call(generateGuestSaga);
|
||||
|
||||
pushLoaderState(80);
|
||||
|
||||
return yield call(mapInitSaga);
|
||||
}
|
||||
|
||||
function* gotVkUserSaga({ user: u }: ReturnType<typeof ActionCreators.gotVkUser>) {
|
||||
|
@ -105,58 +112,63 @@ function* gotVkUserSaga({ user: u }: ReturnType<typeof ActionCreators.gotVkUser>
|
|||
}
|
||||
|
||||
function* searchGetRoutes() {
|
||||
const { token }: ReturnType<typeof selectUserUser> = yield select(selectUserUser);
|
||||
try {
|
||||
const {
|
||||
routes: {
|
||||
step,
|
||||
shift,
|
||||
filter: { title, distance, tab },
|
||||
},
|
||||
}: ReturnType<typeof selectUser> = yield select(selectUser);
|
||||
|
||||
const {
|
||||
routes: {
|
||||
const result: Unwrap<typeof getRouteList> = yield getRouteList({
|
||||
search: title,
|
||||
min: distance[0],
|
||||
max: distance[1],
|
||||
step,
|
||||
shift,
|
||||
filter: { title, distance, tab },
|
||||
},
|
||||
}: ReturnType<typeof selectUser> = yield select(selectUser);
|
||||
tab,
|
||||
});
|
||||
|
||||
const result: Unwrap<typeof getRouteList> = yield getRouteList({
|
||||
token,
|
||||
search: title,
|
||||
min: distance[0],
|
||||
max: distance[1],
|
||||
step,
|
||||
shift,
|
||||
tab,
|
||||
});
|
||||
|
||||
return result;
|
||||
return result;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
export function* searchSetSagaWorker() {
|
||||
const {
|
||||
routes: { filter },
|
||||
}: ReturnType<typeof selectUser> = yield select(selectUser);
|
||||
try {
|
||||
const {
|
||||
routes: { filter },
|
||||
}: ReturnType<typeof selectUser> = yield select(selectUser);
|
||||
|
||||
const {
|
||||
data: {
|
||||
routes,
|
||||
limits: { min, max, count: limit },
|
||||
filter: { shift, step },
|
||||
},
|
||||
}: Unwrap<typeof getRouteList> = yield call(searchGetRoutes);
|
||||
const {
|
||||
data: {
|
||||
routes,
|
||||
limits: { min, max, count: limit },
|
||||
filter: { shift, step },
|
||||
},
|
||||
}: Unwrap<typeof getRouteList> = yield call(searchGetRoutes);
|
||||
|
||||
yield put(searchPutRoutes({ list: routes, min, max, limit, shift, step }));
|
||||
yield put(searchPutRoutes({ list: routes, min, max, limit, shift, step }));
|
||||
|
||||
// change distange range if needed and load additional data
|
||||
if (
|
||||
(filter.min > min && filter.distance[0] <= filter.min) ||
|
||||
(filter.max < max && filter.distance[1] >= filter.max)
|
||||
) {
|
||||
yield put(
|
||||
searchChangeDistance([
|
||||
filter.min > min && filter.distance[0] <= filter.min ? min : filter.distance[0],
|
||||
filter.max < max && filter.distance[1] >= filter.max ? max : filter.distance[1],
|
||||
])
|
||||
);
|
||||
// change distange range if needed and load additional data
|
||||
if (
|
||||
(filter.min > min && filter.distance[0] <= filter.min) ||
|
||||
(filter.max < max && filter.distance[1] >= filter.max)
|
||||
) {
|
||||
yield put(
|
||||
searchChangeDistance([
|
||||
filter.min > min && filter.distance[0] <= filter.min ? min : filter.distance[0],
|
||||
filter.max < max && filter.distance[1] >= filter.max ? max : filter.distance[1],
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
return yield put(searchSetLoading(false));
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
return yield put(searchSetLoading(false));
|
||||
}
|
||||
|
||||
function* searchSetSaga() {
|
||||
|
@ -167,26 +179,30 @@ function* searchSetSaga() {
|
|||
}
|
||||
|
||||
function* openMapDialogSaga({ tab }: ReturnType<typeof ActionCreators.openMapDialog>) {
|
||||
const {
|
||||
routes: {
|
||||
filter: { tab: current },
|
||||
},
|
||||
}: ReturnType<typeof selectUser> = yield select(selectUser);
|
||||
try {
|
||||
const {
|
||||
routes: {
|
||||
filter: { tab: current },
|
||||
},
|
||||
}: ReturnType<typeof selectUser> = yield select(selectUser);
|
||||
|
||||
const { dialog_active }: ReturnType<typeof selectEditor> = yield select(selectEditor);
|
||||
const { dialog_active }: ReturnType<typeof selectEditor> = yield select(selectEditor);
|
||||
|
||||
if (dialog_active && tab === current) {
|
||||
return yield put(editorSetDialogActive(false));
|
||||
if (dialog_active && tab === current) {
|
||||
return yield put(editorSetDialogActive(false));
|
||||
}
|
||||
|
||||
if (tab !== current) {
|
||||
yield put(searchSetTab(tab));
|
||||
}
|
||||
|
||||
yield put(editorSetDialog(DIALOGS.MAP_LIST));
|
||||
yield put(editorSetDialogActive(true));
|
||||
|
||||
return tab;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
if (tab !== current) {
|
||||
yield put(searchSetTab(tab));
|
||||
}
|
||||
|
||||
yield put(editorSetDialog(DIALOGS.MAP_LIST));
|
||||
yield put(editorSetDialogActive(true));
|
||||
|
||||
return tab;
|
||||
}
|
||||
|
||||
function* searchSetTabSaga() {
|
||||
|
@ -210,78 +226,85 @@ function* setUserSaga() {
|
|||
}
|
||||
|
||||
function* mapsLoadMoreSaga() {
|
||||
const {
|
||||
routes: { limit, list, shift, step, loading, filter },
|
||||
}: ReturnType<typeof selectUser> = yield select(selectUser);
|
||||
try {
|
||||
const {
|
||||
routes: { limit, list, shift, step, loading, filter },
|
||||
}: ReturnType<typeof selectUser> = yield select(selectUser);
|
||||
|
||||
if (loading || list.length >= limit || limit === 0) return;
|
||||
if (loading || list.length >= limit || limit === 0) return;
|
||||
|
||||
yield delay(50);
|
||||
yield delay(50);
|
||||
|
||||
yield put(searchSetLoading(true));
|
||||
yield put(mapsSetShift(shift + step));
|
||||
yield put(searchSetLoading(true));
|
||||
yield put(mapsSetShift(shift + step));
|
||||
|
||||
const {
|
||||
data: {
|
||||
limits: { min, max, count },
|
||||
filter: { shift: resp_shift, step: resp_step },
|
||||
routes,
|
||||
},
|
||||
}: Unwrap<typeof getRouteList> = yield call(searchGetRoutes);
|
||||
const {
|
||||
data: {
|
||||
limits: { min, max, count },
|
||||
filter: { shift: resp_shift, step: resp_step },
|
||||
routes,
|
||||
},
|
||||
}: Unwrap<typeof getRouteList> = yield call(searchGetRoutes);
|
||||
|
||||
if (
|
||||
(filter.min > min && filter.distance[0] <= filter.min) ||
|
||||
(filter.max < max && filter.distance[1] >= filter.max)
|
||||
) {
|
||||
yield put(
|
||||
searchChangeDistance([
|
||||
filter.min > min && filter.distance[0] <= filter.min ? min : filter.distance[0],
|
||||
filter.max < max && filter.distance[1] >= filter.max ? max : filter.distance[1],
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
(filter.min > min && filter.distance[0] <= filter.min) ||
|
||||
(filter.max < max && filter.distance[1] >= filter.max)
|
||||
) {
|
||||
yield put(
|
||||
searchChangeDistance([
|
||||
filter.min > min && filter.distance[0] <= filter.min ? min : filter.distance[0],
|
||||
filter.max < max && filter.distance[1] >= filter.max ? max : filter.distance[1],
|
||||
])
|
||||
searchPutRoutes({
|
||||
min,
|
||||
max,
|
||||
limit: count,
|
||||
shift: resp_shift,
|
||||
step: resp_step,
|
||||
list: [...list, ...routes],
|
||||
}),
|
||||
);
|
||||
yield put(searchSetLoading(false));
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
yield put(
|
||||
searchPutRoutes({
|
||||
min,
|
||||
max,
|
||||
limit: count,
|
||||
shift: resp_shift,
|
||||
step: resp_step,
|
||||
list: [...list, ...routes],
|
||||
})
|
||||
);
|
||||
yield put(searchSetLoading(false));
|
||||
}
|
||||
|
||||
function* dropRouteSaga({ address }: ReturnType<typeof ActionCreators.dropRoute>) {
|
||||
const { token }: ReturnType<typeof selectUserUser> = yield select(selectUserUser);
|
||||
const {
|
||||
routes: {
|
||||
list,
|
||||
step,
|
||||
shift,
|
||||
limit,
|
||||
filter: { min, max },
|
||||
},
|
||||
}: ReturnType<typeof selectUser> = yield select(selectUser);
|
||||
|
||||
const index = list.findIndex(el => el.address === address);
|
||||
|
||||
if (index >= 0) {
|
||||
yield put(
|
||||
searchPutRoutes({
|
||||
list: list.filter(el => el.address !== address),
|
||||
min,
|
||||
max,
|
||||
try {
|
||||
const {
|
||||
routes: {
|
||||
list,
|
||||
step,
|
||||
shift: shift > 0 ? shift - 1 : 0,
|
||||
limit: limit > 0 ? limit - 1 : limit,
|
||||
})
|
||||
);
|
||||
}
|
||||
shift,
|
||||
limit,
|
||||
filter: { min, max },
|
||||
},
|
||||
}: ReturnType<typeof selectUser> = yield select(selectUser);
|
||||
|
||||
return yield call(dropRoute, { address, token });
|
||||
const index = list.findIndex(el => el.address === address);
|
||||
|
||||
if (index >= 0) {
|
||||
yield put(
|
||||
searchPutRoutes({
|
||||
list: list.filter(el => el.address !== address),
|
||||
min,
|
||||
max,
|
||||
step,
|
||||
shift: shift > 0 ? shift - 1 : 0,
|
||||
limit: limit > 0 ? limit - 1 : limit,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return yield call(dropRoute, { address });
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
function* modifyRouteSaga({
|
||||
|
@ -289,53 +312,59 @@ function* modifyRouteSaga({
|
|||
title,
|
||||
is_public,
|
||||
}: ReturnType<typeof ActionCreators.modifyRoute>) {
|
||||
const { token }: ReturnType<typeof selectUserUser> = yield select(selectUserUser);
|
||||
const {
|
||||
routes: {
|
||||
list,
|
||||
step,
|
||||
shift,
|
||||
limit,
|
||||
filter: { min, max },
|
||||
},
|
||||
}: ReturnType<typeof selectUser> = yield select(selectUser);
|
||||
|
||||
const index = list.findIndex(el => el.address === address);
|
||||
|
||||
if (index >= 0) {
|
||||
yield put(
|
||||
searchPutRoutes({
|
||||
list: list.map(el => (el.address !== address ? el : { ...el, title, is_public })),
|
||||
min,
|
||||
max,
|
||||
try {
|
||||
const {
|
||||
routes: {
|
||||
list,
|
||||
step,
|
||||
shift: shift > 0 ? shift - 1 : 0,
|
||||
limit: limit > 0 ? limit - 1 : limit,
|
||||
})
|
||||
);
|
||||
}
|
||||
shift,
|
||||
limit,
|
||||
filter: { min, max },
|
||||
},
|
||||
}: ReturnType<typeof selectUser> = yield select(selectUser);
|
||||
|
||||
return yield call(modifyRoute, { address, token, title, is_public });
|
||||
const index = list.findIndex(el => el.address === address);
|
||||
|
||||
if (index >= 0) {
|
||||
yield put(
|
||||
searchPutRoutes({
|
||||
list: list.map(el => (el.address !== address ? el : { ...el, title, is_public })),
|
||||
min,
|
||||
max,
|
||||
step,
|
||||
shift: shift > 0 ? shift - 1 : 0,
|
||||
limit: limit > 0 ? limit - 1 : limit,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return yield call(modifyRoute, { address, title, is_public });
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
function* toggleRouteStarredSaga({
|
||||
address,
|
||||
}: ReturnType<typeof ActionCreators.toggleRouteStarred>) {
|
||||
const { token }: ReturnType<typeof selectUserUser> = yield select(selectUserUser);
|
||||
const {
|
||||
routes: { list },
|
||||
}: ReturnType<typeof selectUser> = yield select(selectUser);
|
||||
try {
|
||||
const {
|
||||
routes: { list },
|
||||
}: 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));
|
||||
const result = yield sendRouteStarred({
|
||||
token,
|
||||
address,
|
||||
is_published: !route.is_published,
|
||||
});
|
||||
yield put(setRouteStarred(address, !route?.is_published));
|
||||
|
||||
if (!result) return yield put(setRouteStarred(address, route.is_published));
|
||||
const result = yield sendRouteStarred({
|
||||
address,
|
||||
is_published: !route?.is_published,
|
||||
});
|
||||
|
||||
if (!result) return yield put(setRouteStarred(address, !!route?.is_published));
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
export function* updateUserRoutes() {
|
||||
|
@ -350,7 +379,7 @@ export function* userSaga() {
|
|||
|
||||
yield takeLatest(
|
||||
[USER_ACTIONS.SEARCH_SET_TITLE, USER_ACTIONS.SEARCH_SET_DISTANCE],
|
||||
searchSetSaga
|
||||
searchSetSaga,
|
||||
);
|
||||
|
||||
yield takeLatest(USER_ACTIONS.OPEN_MAP_DIALOG, openMapDialogSaga);
|
||||
|
|
|
@ -32,4 +32,4 @@ $tooltip_background: #123740;
|
|||
$loading_shade: darken($blue_secondary, 20%);
|
||||
$cluster_small: #0069a7;
|
||||
|
||||
$title_dialog_color: fade(#111111, 85%);
|
||||
$title_dialog_color: darken(#111111, 85%);
|
||||
|
|
|
@ -178,7 +178,7 @@
|
|||
left: 0;
|
||||
width: 100%;
|
||||
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;
|
||||
pointer-events: none;
|
||||
transition: opacity 100ms;
|
||||
|
@ -240,7 +240,7 @@
|
|||
&.has_edit {
|
||||
//transform: translateY(-2px);
|
||||
.route-row {
|
||||
background: fade($green_secondary, 30%);
|
||||
background: darken($green_secondary, 30%);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -280,11 +280,11 @@
|
|||
}
|
||||
|
||||
.route-row-edit {
|
||||
background: fade($green_secondary, 30%);
|
||||
background: darken($green_secondary, 30%);
|
||||
}
|
||||
|
||||
.route-row-drop {
|
||||
background: fade($red_secondary, 20%);
|
||||
background: darken($red_secondary, 20%);
|
||||
|
||||
.route-row {
|
||||
align-items: center;
|
||||
|
@ -341,13 +341,13 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
fill: fade(white, 30%);
|
||||
background: fade(white, 8%);
|
||||
fill: darken(white, 30%);
|
||||
background: transparentize(white, 0.9);
|
||||
cursor: pointer;
|
||||
transition: background 250ms, transform 500ms;
|
||||
|
||||
&:hover {
|
||||
background: fade(white, 10%);
|
||||
background: transparentize(white, 0.95);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -361,7 +361,7 @@
|
|||
overflow: hidden;
|
||||
transition: all 500ms;
|
||||
display: flex;
|
||||
fill: fade(white, 30%);
|
||||
fill: darken(white, 30%);
|
||||
|
||||
div {
|
||||
width: 60px;
|
||||
|
@ -371,16 +371,16 @@
|
|||
align-items: center;
|
||||
|
||||
&:first-child {
|
||||
box-shadow: fade(black, 30%) 1px 0;
|
||||
box-shadow: darken(black, 30%) 1px 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: fade($red_secondary, 30%);
|
||||
background: darken($red_secondary, 30%);
|
||||
}
|
||||
|
||||
&.modify-button {
|
||||
&:hover {
|
||||
background: fade($green_secondary, 30%);
|
||||
background: darken($green_secondary, 30%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -399,7 +399,7 @@
|
|||
|
||||
.route-row-corner {
|
||||
svg {
|
||||
fill: fade(white, 50%);
|
||||
fill: darken(white, 50%);
|
||||
margin-right: 2px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
|
|
@ -12,7 +12,28 @@
|
|||
}
|
||||
|
||||
.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 {
|
||||
|
@ -308,7 +329,7 @@
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
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-size: 13px;
|
||||
transform: translate(-12px, -12px);
|
||||
|
@ -317,7 +338,7 @@
|
|||
outline: none;
|
||||
|
||||
&:hover {
|
||||
box-shadow: fade($cluster_small, 70%) 0 0 0 7px;
|
||||
box-shadow: darken($cluster_small, 70%) 0 0 0 7px;
|
||||
}
|
||||
|
||||
span {
|
||||
|
|
|
@ -729,7 +729,7 @@
|
|||
margin-bottom: 10px;
|
||||
padding: 10px;
|
||||
background: $title_dialog_color;
|
||||
color: fade(white, 50%);
|
||||
color: darken(white, 50%);
|
||||
font-size: 13px;
|
||||
box-sizing: border-box;
|
||||
border-radius: $panel_radius;
|
||||
|
@ -761,7 +761,7 @@
|
|||
content: ' ';
|
||||
width: 100%;
|
||||
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;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
pointer-events: none;
|
||||
text-transform: uppercase;
|
||||
font-size: 1.2em;
|
||||
color: fade(white, 70%);
|
||||
color: darken(white, 70%);
|
||||
|
||||
svg {
|
||||
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 { Map, TileLayer } from 'leaflet';
|
||||
|
||||
export const MapContext = React.createContext<Map>(null);
|
||||
export const TileContext = React.createContext<TileLayer>(null)
|
||||
export const MapContext = React.createContext<Map | undefined>(undefined);
|
||||
export const TileContext = React.createContext<TileLayer | undefined>(undefined)
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
import { LatLng, LatLngLiteral, point, Point, PointExpression, latLng } from 'leaflet';
|
||||
|
||||
// interface LatLng {
|
||||
// lat: number;
|
||||
// lng: number;
|
||||
// }
|
||||
import { LatLng, latLng, LatLngLiteral, Point, point } from 'leaflet';
|
||||
|
||||
export const middleCoord = (l1: LatLng, l2: LatLng): LatLng => latLng({
|
||||
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 => {
|
||||
const R = 6371; // km
|
||||
const dLat = ((t2 - t1) * Math.PI) / 180;
|
||||
var dLon = ((n2 - n1) * Math.PI) / 180;
|
||||
var a =
|
||||
const dLon = ((n2 - n1) * Math.PI) / 180;
|
||||
const a =
|
||||
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
||||
Math.cos((t1 * Math.PI) / 180) *
|
||||
Math.cos((t2 * Math.PI) / 180) *
|
||||
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;
|
||||
};
|
||||
|
||||
|
@ -88,7 +83,7 @@ export const dist2 = (A: LatLngLiteral, B: LatLngLiteral): number =>
|
|||
|
||||
const distToSegmentSquared = (A: LatLng, B: LatLng, C: LatLng): number => {
|
||||
const l2 = dist2(A, B);
|
||||
if (l2 == 0) return dist2(C, A);
|
||||
if (l2 === 0) return dist2(C, A);
|
||||
|
||||
const t = Math.max(
|
||||
0,
|
||||
|
|
|
@ -39,7 +39,7 @@ export const getGPXString = ({
|
|||
<metadata>
|
||||
<name>${title || 'GPX Track'}</name>
|
||||
</metadata>
|
||||
${stickers.reduce(
|
||||
${(stickers || []).reduce(
|
||||
(cat, { latlng: { lat, lng }, text }) =>
|
||||
`${cat}
|
||||
<wpt lat="${lat}" lon="${lng}">
|
||||
|
@ -93,12 +93,12 @@ export const importGpxTrack = async (file: File) => {
|
|||
return trkseg.trkpt
|
||||
? [
|
||||
...trkseg_res,
|
||||
...trkseg.trkpt.map(pnt => ({ lat: pnt['$'].lat, lng: pnt['$'].lon })),
|
||||
...trkseg.trkpt.map(pnt => new LatLng(pnt['$'].lat, pnt['$'].lon)),
|
||||
]
|
||||
: trkseg_res;
|
||||
}, trk_res)
|
||||
: trk_res;
|
||||
}, []);
|
||||
}, [] as LatLng[]);
|
||||
|
||||
return [
|
||||
{
|
||||
|
|
|
@ -1,13 +1,5 @@
|
|||
import { history } from '~/redux/store';
|
||||
import {API_RETRY_INTERVAL} from "~/constants/api";
|
||||
|
||||
interface IUrlData {
|
||||
path: string,
|
||||
mode: 'edit' | '',
|
||||
host: string,
|
||||
hash: string,
|
||||
protocol: 'http' | 'https',
|
||||
}
|
||||
import { API_RETRY_INTERVAL } from '~/constants/api';
|
||||
|
||||
export const getPath = (): string => (window.location && window.location.pathname);
|
||||
export const pushPath = (url: string): string => history.push(url);
|
||||
|
@ -36,20 +28,20 @@ export const parseQuery = (queryString: string) => {
|
|||
};
|
||||
|
||||
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 => {
|
||||
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);
|
||||
};
|
||||
|
||||
export const pushNetworkInitError = () => {
|
||||
document.getElementById('loader-bar').classList.add('is_failed');
|
||||
document.getElementById('loader-bar').style.width = '100%';
|
||||
document.getElementById('loader-error').style.opacity = String(1);
|
||||
document.getElementById('loader-bar')!.classList.add('is_failed');
|
||||
document.getElementById('loader-bar')!.style.width = '100%';
|
||||
document.getElementById('loader-error')!.style.opacity = String(1);
|
||||
|
||||
countDownToRefresh();
|
||||
};
|
||||
|
|
|
@ -9,6 +9,7 @@ import { LatLng, LatLngLiteral, LayerGroup, Map, Marker } from 'leaflet';
|
|||
import { arrowClusterIcon, createArrow } from '~/utils/arrow';
|
||||
import { MarkerClusterGroup } from 'leaflet.markercluster/dist/leaflet.markercluster-src.js';
|
||||
import { angleBetweenPoints, dist2, middleCoord } from '~/utils/geom';
|
||||
import { MainMap } from '~/constants/map';
|
||||
|
||||
class ArrowsLayer extends LayerGroup {
|
||||
constructor(props) {
|
||||
|
@ -46,13 +47,13 @@ class ArrowsLayer extends LayerGroup {
|
|||
),
|
||||
]
|
||||
: res,
|
||||
[]
|
||||
[] as Marker[]
|
||||
);
|
||||
|
||||
this.arrowLayer.addLayers(midpoints);
|
||||
};
|
||||
|
||||
map: Map;
|
||||
map: Map = MainMap;
|
||||
arrowLayer = new MarkerClusterGroup({
|
||||
spiderfyOnMaxZoom: false,
|
||||
showCoverageOnHover: false,
|
||||
|
@ -62,10 +63,10 @@ class ArrowsLayer extends LayerGroup {
|
|||
iconCreateFunction: arrowClusterIcon,
|
||||
});
|
||||
|
||||
layers: Marker<any>[] = [];
|
||||
layers: Marker[] = [];
|
||||
}
|
||||
|
||||
ArrowsLayer.addInitHook(function() {
|
||||
ArrowsLayer.addInitHook(function(this: ArrowsLayer) {
|
||||
this.once('add', event => {
|
||||
if (event.target instanceof ArrowsLayer) {
|
||||
this.map = event.target._map;
|
||||
|
|
|
@ -36,9 +36,9 @@ class InteractivePoly extends Polyline {
|
|||
|
||||
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);
|
||||
|
||||
|
@ -162,10 +162,12 @@ class InteractivePoly extends Polyline {
|
|||
? { ...obj, hidden: [...obj.hidden, 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();
|
||||
|
||||
|
@ -337,11 +339,11 @@ class InteractivePoly extends Polyline {
|
|||
|
||||
onMarkerDrag = ({ target }: { target: Marker }) => {
|
||||
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(
|
||||
(this.vertex_index < this.markers.length - 1 &&
|
||||
this.markers[this.vertex_index + 1].getLatLng()) ||
|
||||
(this.vertex_index! < this.markers.length - 1 &&
|
||||
this.markers[this.vertex_index! + 1].getLatLng()) ||
|
||||
[]
|
||||
);
|
||||
|
||||
|
@ -369,17 +371,17 @@ class InteractivePoly extends Polyline {
|
|||
onMarkerDragEnd = ({ target }: { target: Marker }): void => {
|
||||
const latlngs = this.getLatLngs() as LatLngLiteral[];
|
||||
this.markerDragChangeDistance(
|
||||
this.vertex_index,
|
||||
latlngs[this.vertex_index],
|
||||
this.vertex_index!,
|
||||
latlngs[this.vertex_index!],
|
||||
target.getLatLng()
|
||||
);
|
||||
|
||||
this.replaceLatlng(target.getLatLng(), this.vertex_index);
|
||||
this.replaceLatlng(target.getLatLng(), this.vertex_index!);
|
||||
|
||||
this.is_dragging = false;
|
||||
this.constrLine.removeFrom(this._map);
|
||||
|
||||
this.vertex_index = null;
|
||||
this.vertex_index = 0;
|
||||
|
||||
if (this.is_drawing) this.startDrawing();
|
||||
|
||||
|
@ -496,7 +498,7 @@ class InteractivePoly extends Polyline {
|
|||
this.constrLine.setLatLngs(coords);
|
||||
};
|
||||
|
||||
setDirection = (direction: 'forward' | 'backward') => {
|
||||
setDirection = (direction: 'forward' | 'backward') => {
|
||||
this.drawing_direction = direction;
|
||||
this.updateConstraintsToLatLngs(this.getLatLngs() as LatLngExpression[]);
|
||||
}
|
||||
|
@ -505,7 +507,7 @@ class InteractivePoly extends Polyline {
|
|||
const index = this.markers.indexOf(target);
|
||||
const latlngs = this.getLatLngs();
|
||||
|
||||
if (typeof index === 'undefined' || latlngs.length == 0) return;
|
||||
if (typeof index === 'undefined' || latlngs.length === 0) return;
|
||||
|
||||
this.dropMarkerDistanceChange(index);
|
||||
this._map.removeLayer(this.markers[index]);
|
||||
|
@ -568,27 +570,27 @@ class InteractivePoly extends Polyline {
|
|||
is_drawing: boolean = false;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
InteractivePoly.addInitHook(function() {
|
||||
InteractivePoly.addInitHook(function(this: InteractivePoly) {
|
||||
this.once('add', event => {
|
||||
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.hintMarker.addTo(event.target._map);
|
||||
this.constrLine.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) {
|
||||
try {
|
||||
|
@ -605,7 +607,7 @@ InteractivePoly.addInitHook(function() {
|
|||
this.constrLine.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,
|
||||
plan: Routing.plan([], {
|
||||
createMarker: (_, wp) => {
|
||||
const marker = new Marker(wp.latLng, {
|
||||
return new Marker(wp.latLng, {
|
||||
draggable: true,
|
||||
icon: createWaypointMarker(),
|
||||
})
|
||||
|
@ -45,12 +45,10 @@ export const OsrmRouter = Routing.control({
|
|||
OsrmRouter.setWaypoints(
|
||||
OsrmRouter.getWaypoints().filter(
|
||||
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,
|
||||
}),
|
||||
|
|
|
@ -3,6 +3,7 @@ import { arrowClusterIcon } from '~/utils/arrow';
|
|||
import { MarkerClusterGroup } from 'leaflet.markercluster/dist/leaflet.markercluster-src.js';
|
||||
import { allwaysPositiveAngleDeg, angleBetweenPoints, distKmHaversine } from '~/utils/geom';
|
||||
import classNames from 'classnames';
|
||||
import { MainMap } from '~/constants/map';
|
||||
|
||||
const arrow_image = '/images/arrow.svg';
|
||||
|
||||
|
@ -43,8 +44,7 @@ class KmMarksLayer extends LayerGroup {
|
|||
};
|
||||
|
||||
drawMiddleMarkers = (latlngs: LatLngLiteral[]) => {
|
||||
const marks = [];
|
||||
const arrows = [];
|
||||
const marks: Marker[] = [];
|
||||
let last_km_mark = 0;
|
||||
|
||||
this.distance = latlngs.reduce((dist, current, index) => {
|
||||
|
@ -160,7 +160,7 @@ class KmMarksLayer extends LayerGroup {
|
|||
};
|
||||
|
||||
options: KmMarksOptions;
|
||||
map: Map;
|
||||
map: Map = MainMap;
|
||||
marksLayer: MarkerClusterGroup = new MarkerClusterGroup({
|
||||
spiderfyOnMaxZoom: false,
|
||||
showCoverageOnHover: false,
|
||||
|
@ -173,7 +173,7 @@ class KmMarksLayer extends LayerGroup {
|
|||
distance: number = 0;
|
||||
}
|
||||
|
||||
KmMarksLayer.addInitHook(function() {
|
||||
KmMarksLayer.addInitHook(function(this: KmMarksLayer) {
|
||||
this.once('add', event => {
|
||||
if (event.target instanceof KmMarksLayer) {
|
||||
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;
|
||||
|
||||
|
@ -23,28 +23,3 @@ export const HTTP_RESPONSES = {
|
|||
NOT_FOUND: 404,
|
||||
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 = (
|
||||
initialState,
|
||||
handlers,
|
||||
|
|
|
@ -1,18 +1,10 @@
|
|||
// import { editor } from '~/modules/Editor';
|
||||
import { COLORS, CLIENT } from '~/config/frontend';
|
||||
import { CLIENT, COLORS } from '~/config/frontend';
|
||||
import saveAs from 'file-saver';
|
||||
import { replaceProviderUrl } from '~/constants/providers';
|
||||
import { STICKERS } from '~/constants/stickers';
|
||||
import { IRoute } from '~/redux/map/types';
|
||||
import { IStickerDump } from '~/redux/map/types';
|
||||
import { IRootState } from '~/redux/user';
|
||||
import {
|
||||
angleBetweenPoints,
|
||||
angleBetweenPointsRad,
|
||||
findDistancePx,
|
||||
middleCoordPx,
|
||||
} from '~/utils/geom';
|
||||
import { Point, LatLng, latLng } from 'leaflet';
|
||||
import { IRoute, IStickerDump } from '~/redux/map/types';
|
||||
import { angleBetweenPoints, angleBetweenPointsRad, findDistancePx, middleCoordPx } from '~/utils/geom';
|
||||
import { LatLng, latLng, Point } from 'leaflet';
|
||||
import { MainMap } from '~/constants/map';
|
||||
|
||||
export interface ITilePlacement {
|
||||
|
@ -37,8 +29,7 @@ const latLngToTile = (latlng: {
|
|||
lat: number;
|
||||
lng: number;
|
||||
}): { x: number; y: number; z: number } => {
|
||||
const map = MainMap;
|
||||
const zoom = map.getZoom();
|
||||
const zoom = MainMap.getZoom();
|
||||
const xtile = Number(Math.floor(((latlng.lng + 180) / 360) * (1 << zoom)));
|
||||
const ytile = Number(
|
||||
Math.floor(
|
||||
|
@ -56,8 +47,7 @@ const latLngToTile = (latlng: {
|
|||
};
|
||||
|
||||
const tileToLatLng = (point: { x: number; y: number }): LatLng => {
|
||||
const map = MainMap;
|
||||
const z = map.getZoom();
|
||||
const z = MainMap.getZoom();
|
||||
const lng = (point.x / Math.pow(2, z)) * 360 - 180;
|
||||
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)));
|
||||
|
@ -133,7 +123,7 @@ export const fetchImages = (
|
|||
): Promise<{ x: number; y: number; image: HTMLImageElement }[]> => {
|
||||
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 y = minY; y <= maxY; y += 1) {
|
||||
images.push({ x, y, source: getImageSource({ x, y, zoom }, provider) });
|
||||
|
@ -173,7 +163,7 @@ export const composePoly = ({
|
|||
ctx,
|
||||
color = 'gradient',
|
||||
weight = CLIENT.STROKE_WIDTH,
|
||||
dash = null,
|
||||
dash = [],
|
||||
}: {
|
||||
points: Point[];
|
||||
ctx: CanvasRenderingContext2D;
|
||||
|
@ -217,7 +207,7 @@ export const composePoly = ({
|
|||
}
|
||||
|
||||
if (dash) {
|
||||
ctx.setLineDash([12, 12]);
|
||||
ctx.setLineDash(dash);
|
||||
}
|
||||
|
||||
ctx.stroke();
|
||||
|
@ -472,14 +462,14 @@ export const composeStickers = async ({
|
|||
if (!stickers || stickers.length < 0) return;
|
||||
|
||||
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(
|
||||
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';
|
||||
|
||||
export const simplify = (latlngs: LatLng[]): LatLng[] => {
|
||||
|
|
|
@ -3,10 +3,10 @@ import { LatLngLiteral } from 'leaflet';
|
|||
|
||||
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 => {
|
||||
if (!position || !position.coords || !position.coords.latitude || !position.coords.longitude)
|
||||
return callback(null);
|
||||
return callback(undefined);
|
||||
|
||||
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(
|
||||
position => {
|
||||
if (!position || !position.coords || !position.coords.latitude || !position.coords.longitude)
|
||||
return callback(null);
|
||||
return callback(undefined);
|
||||
|
||||
const { latitude: lat, longitude: lng } = position.coords;
|
||||
|
||||
callback({ lat, lng });
|
||||
return;
|
||||
},
|
||||
() => callback(null),
|
||||
() => callback(undefined),
|
||||
{
|
||||
timeout: 30,
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": false,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"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"
|
||||
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:
|
||||
version "3.2.12"
|
||||
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"
|
||||
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:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue