mirror of
https://github.com/muerwre/orchidmap-front.git
synced 2025-04-25 02:56:41 +07:00
added editable route
This commit is contained in:
parent
5664291c92
commit
fca52df9f5
17 changed files with 282 additions and 11 deletions
|
@ -16,7 +16,7 @@ export class Cursor extends React.PureComponent<Props, {}> {
|
||||||
}
|
}
|
||||||
|
|
||||||
moveCursor = e => {
|
moveCursor = e => {
|
||||||
if (!e.clientX || !e.clientY) return;
|
if (!e.clientX || !e.clientY || !this.cursor || !this.cursor.style) return;
|
||||||
|
|
||||||
const { clientX, clientY } = e;
|
const { clientX, clientY } = e;
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,9 @@ import { IStickerPack } from "$constants/stickers";
|
||||||
import { IDialogs } from "$constants/dialogs";
|
import { IDialogs } from "$constants/dialogs";
|
||||||
import { IModes } from "$constants/modes";
|
import { IModes } from "$constants/modes";
|
||||||
|
|
||||||
|
import { Map } from "$containers/map/Map"
|
||||||
|
import { TileLayer } from '$containers/map/TileLayer';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
sticker: string,
|
sticker: string,
|
||||||
renderer_active: boolean,
|
renderer_active: boolean,
|
||||||
|
@ -49,6 +52,8 @@ const Component = (props: Props) => (
|
||||||
setDialogActive={props.setDialogActive}
|
setDialogActive={props.setDialogActive}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Map />
|
||||||
|
|
||||||
{ props.renderer_active &&
|
{ props.renderer_active &&
|
||||||
<Renderer onClick={props.hideRenderer} />
|
<Renderer onClick={props.hideRenderer} />
|
||||||
}
|
}
|
||||||
|
|
46
src/containers/map/Map/index.tsx
Normal file
46
src/containers/map/Map/index.tsx
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import { Map as MapInterface, map } from "leaflet";
|
||||||
|
import * as React from "react";
|
||||||
|
import { createPortal } from "react-dom";
|
||||||
|
import { MapContext } from "$utils/context.ts";
|
||||||
|
import { TileLayer } from "$containers/map/TileLayer";
|
||||||
|
import { Route } from "$containers/map/Route";
|
||||||
|
import { selectMapProvider, selectMapRoute } from "$redux/map/selectors";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import * as MAP_ACTIONS from "$redux/map/actions";
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
provider: selectMapProvider(state),
|
||||||
|
route: selectMapRoute(state)
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
mapSetRoute: MAP_ACTIONS.mapSetRoute
|
||||||
|
};
|
||||||
|
|
||||||
|
type IProps = React.HTMLAttributes<HTMLDivElement> &
|
||||||
|
ReturnType<typeof mapStateToProps> &
|
||||||
|
typeof mapDispatchToProps & {};
|
||||||
|
|
||||||
|
const MapUnconnected: React.FC<IProps> = ({ provider, route, mapSetRoute }) => {
|
||||||
|
const ref = React.useRef(null);
|
||||||
|
const [maps, setMaps] = React.useState<MapInterface>(null);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!ref.current) return;
|
||||||
|
|
||||||
|
setMaps(map(ref.current).setView([55.0153275, 82.9071235], 13));
|
||||||
|
}, [ref]);
|
||||||
|
|
||||||
|
return createPortal(
|
||||||
|
<div ref={ref}>
|
||||||
|
<MapContext.Provider value={maps}>
|
||||||
|
<TileLayer provider={provider} />
|
||||||
|
<Route route={route} mapSetRoute={mapSetRoute} is_editing />
|
||||||
|
</MapContext.Provider>
|
||||||
|
</div>,
|
||||||
|
document.getElementById("canvas")
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Map = connect(mapStateToProps, mapDispatchToProps)(MapUnconnected);
|
||||||
|
export { Map };
|
80
src/containers/map/Route/index.tsx
Normal file
80
src/containers/map/Route/index.tsx
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
import React, {
|
||||||
|
FC,
|
||||||
|
useEffect,
|
||||||
|
memo,
|
||||||
|
useContext,
|
||||||
|
useState,
|
||||||
|
useCallback
|
||||||
|
} from "react";
|
||||||
|
import { IMapRoute, ILatLng } from "../../../redux/map/types";
|
||||||
|
import { MapContext } from "$utils/context";
|
||||||
|
import { InteractivePoly } from "$modules/InteractivePoly";
|
||||||
|
import { isMobile } from "$utils/window";
|
||||||
|
import { LatLng } from "leaflet";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
route: IMapRoute;
|
||||||
|
is_editing: boolean;
|
||||||
|
mapSetRoute: (latlngs: ILatLng[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Route: FC<IProps> = memo(({ route, is_editing, mapSetRoute }) => {
|
||||||
|
const [layer, setLayer] = useState<InteractivePoly>(null);
|
||||||
|
const map = useContext(MapContext);
|
||||||
|
|
||||||
|
const onRouteChanged = useCallback(
|
||||||
|
({ latlngs }) => {
|
||||||
|
// mapSetRoute(latlngs)
|
||||||
|
},
|
||||||
|
[mapSetRoute]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!layer) return;
|
||||||
|
|
||||||
|
layer.on("latlngschange", onRouteChanged);
|
||||||
|
|
||||||
|
return () => layer.off("latlngschange", onRouteChanged);
|
||||||
|
}, [layer, onRouteChanged]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!map) return;
|
||||||
|
|
||||||
|
setLayer(
|
||||||
|
new InteractivePoly([], {
|
||||||
|
color: "url(#activePathGradient)",
|
||||||
|
weight: 6,
|
||||||
|
maxMarkers: isMobile() ? 20 : 100,
|
||||||
|
smoothFactor: 3,
|
||||||
|
updateself: false,
|
||||||
|
})
|
||||||
|
.addTo(map)
|
||||||
|
.on("distancechange", console.log)
|
||||||
|
.on("allvertexhide", console.log)
|
||||||
|
.on("allvertexshow", console.log)
|
||||||
|
// .on("latlngschange", console.log)
|
||||||
|
);
|
||||||
|
}, [map]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!layer) return;
|
||||||
|
|
||||||
|
const points = (route && route.length > 0 && route) || [];
|
||||||
|
|
||||||
|
layer.setPoints(points as LatLng[]); // TODO: refactor this
|
||||||
|
}, [route, layer]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!layer) return;
|
||||||
|
|
||||||
|
if (is_editing) {
|
||||||
|
layer.editor.enable();
|
||||||
|
} else {
|
||||||
|
layer.editor.disable();
|
||||||
|
}
|
||||||
|
}, [is_editing, layer]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
export { Route };
|
40
src/containers/map/TileLayer/index.tsx
Normal file
40
src/containers/map/TileLayer/index.tsx
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import { MapContext, TileContext } from "../../../utils/context";
|
||||||
|
import { TileLayer as TileLayerInterface, tileLayer } from "leaflet";
|
||||||
|
import { DEFAULT_PROVIDER, PROVIDERS } from "$constants/providers";
|
||||||
|
import { IMapReducer } from "$redux/map";
|
||||||
|
|
||||||
|
type IProps = React.HTMLAttributes<HTMLDivElement> & {
|
||||||
|
provider: IMapReducer['provider'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const TileLayer: React.FC<IProps> = ({ children, provider }) => {
|
||||||
|
const [layer, setLayer] = React.useState<TileLayerInterface>(null);
|
||||||
|
const map = React.useContext(MapContext);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!map) return;
|
||||||
|
|
||||||
|
setLayer(
|
||||||
|
tileLayer(PROVIDERS[DEFAULT_PROVIDER].url, {
|
||||||
|
attribution: "Независимое Велосообщество",
|
||||||
|
maxNativeZoom: 18,
|
||||||
|
maxZoom: 18
|
||||||
|
}).addTo(map)
|
||||||
|
);
|
||||||
|
}, [map]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!layer || !provider) return;
|
||||||
|
|
||||||
|
const { url } =
|
||||||
|
(provider && PROVIDERS[provider] && PROVIDERS[provider]) ||
|
||||||
|
PROVIDERS[DEFAULT_PROVIDER];
|
||||||
|
|
||||||
|
layer.setUrl(url);
|
||||||
|
}, [layer, provider]);
|
||||||
|
|
||||||
|
return <TileContext.Provider value={layer}>{children}</TileContext.Provider>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { TileLayer };
|
|
@ -130,7 +130,10 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<canvas id="renderer"></canvas>
|
<canvas id="renderer"></canvas>
|
||||||
|
|
||||||
<section id="map" style="position: absolute; width: 100%; height: 100%;"></section>
|
<section id="map" style="position: absolute; width: 100%; height: 100%;"></section>
|
||||||
|
<section id="canvas" style="position: absolute; width: 50%; height: 100%;right: 0;"></section>
|
||||||
|
|
||||||
<section id="loader">
|
<section id="loader">
|
||||||
<div id="loader-container">
|
<div id="loader-container">
|
||||||
<div id="loader-current">ЗАГРУЗКА</div>
|
<div id="loader-current">ЗАГРУЗКА</div>
|
||||||
|
|
|
@ -27,7 +27,7 @@ interface InteractivePolylineOptions extends PolylineOptions {
|
||||||
kmMarksStep?: number,
|
kmMarksStep?: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Component extends Polyline {
|
export class InteractivePoly extends Polyline {
|
||||||
constructor(latlngs: LatLngExpression[] | LatLngExpression[][], options?: InteractivePolylineOptions) {
|
constructor(latlngs: LatLngExpression[] | LatLngExpression[][], options?: InteractivePolylineOptions) {
|
||||||
super(latlngs, options);
|
super(latlngs, options);
|
||||||
|
|
||||||
|
@ -47,7 +47,6 @@ export class Component extends Polyline {
|
||||||
this.setLatLngs(latlngs);
|
this.setLatLngs(latlngs);
|
||||||
this.recreateMarkers();
|
this.recreateMarkers();
|
||||||
this.recalcDistance();
|
this.recalcDistance();
|
||||||
// this.recalcKmMarks();
|
|
||||||
this.touchHinter.setLatLngs(latlngs);
|
this.touchHinter.setLatLngs(latlngs);
|
||||||
this.fire('latlngschange', { latlngs });
|
this.fire('latlngschange', { latlngs });
|
||||||
};
|
};
|
||||||
|
@ -101,7 +100,6 @@ export class Component extends Polyline {
|
||||||
|
|
||||||
this._map.addLayer(this.markerLayer);
|
this._map.addLayer(this.markerLayer);
|
||||||
this.fire('allvertexshow');
|
this.fire('allvertexshow');
|
||||||
console.log();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
hideAllMarkers = (): void => {
|
hideAllMarkers = (): void => {
|
||||||
|
@ -516,7 +514,7 @@ export class Component extends Polyline {
|
||||||
distance: number = 0;
|
distance: number = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.addInitHook(function () {
|
InteractivePoly.addInitHook(function () {
|
||||||
this.once('add', (event) => {
|
this.once('add', (event) => {
|
||||||
if (event.target instanceof InteractivePoly) {
|
if (event.target instanceof InteractivePoly) {
|
||||||
this.map = event.target._map;
|
this.map = event.target._map;
|
||||||
|
@ -550,7 +548,7 @@ Component.addInitHook(function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export const InteractivePoly = Component;
|
// export const InteractivePoly = Component;
|
||||||
/*
|
/*
|
||||||
events:
|
events:
|
||||||
vertexdragstart,
|
vertexdragstart,
|
||||||
|
|
17
src/redux/map/actions.ts
Normal file
17
src/redux/map/actions.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { MAP_ACTIONS } from "./constants";
|
||||||
|
import { IMapReducer } from "./";
|
||||||
|
|
||||||
|
export const mapSet = (map: Partial<IMapReducer>) => ({
|
||||||
|
type: MAP_ACTIONS.SET_MAP,
|
||||||
|
map
|
||||||
|
});
|
||||||
|
|
||||||
|
export const mapSetProvider = (provider: IMapReducer['provider']) => ({
|
||||||
|
type: MAP_ACTIONS.SET_PROVIDER,
|
||||||
|
provider
|
||||||
|
});
|
||||||
|
|
||||||
|
export const mapSetRoute = (route: IMapReducer['route']) => ({
|
||||||
|
type: MAP_ACTIONS.SET_ROUTE,
|
||||||
|
route,
|
||||||
|
});
|
7
src/redux/map/constants.ts
Normal file
7
src/redux/map/constants.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
const P = 'MAP'
|
||||||
|
|
||||||
|
export const MAP_ACTIONS = {
|
||||||
|
SET_MAP: `${P}-SET_MAP`,
|
||||||
|
SET_PROVIDER: `${P}-SET_PROVIDER`,
|
||||||
|
SET_ROUTE: `${P}-SET_ROUTE`,
|
||||||
|
}
|
|
@ -1,3 +1,33 @@
|
||||||
|
import { MAP_ACTIONS } from "./constants";
|
||||||
|
import { IMapReducer } from ".";
|
||||||
|
import { mapSet, mapSetProvider, mapSetRoute } from "./actions";
|
||||||
|
|
||||||
|
const setMap = (
|
||||||
|
state: IMapReducer,
|
||||||
|
{ map }: ReturnType<typeof mapSet>
|
||||||
|
): IMapReducer => ({
|
||||||
|
...state,
|
||||||
|
...map
|
||||||
|
});
|
||||||
|
|
||||||
|
const setProvider = (
|
||||||
|
state: IMapReducer,
|
||||||
|
{ provider }: ReturnType<typeof mapSetProvider>
|
||||||
|
): IMapReducer => ({
|
||||||
|
...state,
|
||||||
|
provider
|
||||||
|
});
|
||||||
|
|
||||||
|
const setRoute = (
|
||||||
|
state: IMapReducer,
|
||||||
|
{ route }: ReturnType<typeof mapSetRoute>
|
||||||
|
): IMapReducer => ({
|
||||||
|
...state,
|
||||||
|
route
|
||||||
|
});
|
||||||
|
|
||||||
export const MAP_HANDLERS = {
|
export const MAP_HANDLERS = {
|
||||||
|
[MAP_ACTIONS.SET_MAP]: setMap,
|
||||||
}
|
[MAP_ACTIONS.SET_PROVIDER]: setProvider,
|
||||||
|
[MAP_ACTIONS.SET_ROUTE]: setRoute
|
||||||
|
};
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
import { createReducer } from 'reduxsauce';
|
import { createReducer } from 'reduxsauce';
|
||||||
import { MAP_HANDLERS } from './handlers';
|
import { MAP_HANDLERS } from './handlers';
|
||||||
|
import { DEFAULT_PROVIDER } from '$constants/providers';
|
||||||
|
import { IMapRoute } from './types';
|
||||||
|
|
||||||
export interface IMapReducer {
|
export interface IMapReducer {
|
||||||
|
provider: string;
|
||||||
|
route: IMapRoute;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MAP_INITIAL_STATE = {
|
export const MAP_INITIAL_STATE = {
|
||||||
|
provider: DEFAULT_PROVIDER,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const map = createReducer(MAP_INITIAL_STATE, MAP_HANDLERS)
|
export const map = createReducer(MAP_INITIAL_STATE, MAP_HANDLERS)
|
2
src/redux/map/selectors.ts
Normal file
2
src/redux/map/selectors.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export const selectMapProvider = state => state.map.provider;
|
||||||
|
export const selectMapRoute= state => state.map.route;
|
8
src/redux/map/types.ts
Normal file
8
src/redux/map/types.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { LatLng } from "leaflet";
|
||||||
|
|
||||||
|
export type ILatLng = {
|
||||||
|
lat: number,
|
||||||
|
lng: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IMapRoute = ILatLng[];
|
|
@ -9,6 +9,7 @@ import { userSaga } from '$redux/user/sagas';
|
||||||
import { createBrowserHistory } from 'history';
|
import { createBrowserHistory } from 'history';
|
||||||
import { locationChanged } from '$redux/user/actions';
|
import { locationChanged } from '$redux/user/actions';
|
||||||
import { PersistConfig, Persistor } from "redux-persist/es/types";
|
import { PersistConfig, Persistor } from "redux-persist/es/types";
|
||||||
|
import { map, IMapReducer } from '$redux/map';
|
||||||
|
|
||||||
const userPersistConfig: PersistConfig = {
|
const userPersistConfig: PersistConfig = {
|
||||||
key: 'user',
|
key: 'user',
|
||||||
|
@ -18,6 +19,7 @@ const userPersistConfig: PersistConfig = {
|
||||||
|
|
||||||
export interface IState {
|
export interface IState {
|
||||||
user: IRootReducer
|
user: IRootReducer
|
||||||
|
map: IMapReducer,
|
||||||
}
|
}
|
||||||
// create the saga middleware
|
// create the saga middleware
|
||||||
export const sagaMiddleware = createSagaMiddleware();
|
export const sagaMiddleware = createSagaMiddleware();
|
||||||
|
@ -32,6 +34,7 @@ const composeEnhancers =
|
||||||
export const store = createStore(
|
export const store = createStore(
|
||||||
combineReducers({
|
combineReducers({
|
||||||
user: persistReducer(userPersistConfig, userReducer),
|
user: persistReducer(userPersistConfig, userReducer),
|
||||||
|
map,
|
||||||
// routing: routerReducer
|
// routing: routerReducer
|
||||||
}),
|
}),
|
||||||
composeEnhancers(applyMiddleware(/* routerMiddleware(history), */ sagaMiddleware))
|
composeEnhancers(applyMiddleware(/* routerMiddleware(history), */ sagaMiddleware))
|
||||||
|
|
|
@ -89,6 +89,7 @@ import { IRootState } from "$redux/user";
|
||||||
import { downloadGPXTrack, getGPXString } from "$utils/gpx";
|
import { downloadGPXTrack, getGPXString } from "$utils/gpx";
|
||||||
import { Unwrap } from "$utils/middleware";
|
import { Unwrap } from "$utils/middleware";
|
||||||
import { IState } from "$redux/store";
|
import { IState } from "$redux/store";
|
||||||
|
import { mapSetProvider, mapSet } from "$redux/map/actions";
|
||||||
|
|
||||||
const getUser = (state: IState) => state.user.user;
|
const getUser = (state: IState) => state.user.user;
|
||||||
const getState = (state: IState) => state.user;
|
const getState = (state: IState) => state.user;
|
||||||
|
@ -156,6 +157,8 @@ function* loadMapSaga(path) {
|
||||||
data: { route, error, random_url }
|
data: { route, error, random_url }
|
||||||
}: Unwrap<typeof getStoredMap> = yield call(getStoredMap, { name: path });
|
}: Unwrap<typeof getStoredMap> = yield call(getStoredMap, { name: path });
|
||||||
|
|
||||||
|
console.log({ route });
|
||||||
|
|
||||||
if (route && !error) {
|
if (route && !error) {
|
||||||
yield editor.clearAll();
|
yield editor.clearAll();
|
||||||
yield editor.setData(route);
|
yield editor.setData(route);
|
||||||
|
@ -164,6 +167,13 @@ function* loadMapSaga(path) {
|
||||||
|
|
||||||
yield put(setChanged(false));
|
yield put(setChanged(false));
|
||||||
|
|
||||||
|
// TODO: REACTIVE BRANCH:
|
||||||
|
// yield put(mapSetProvider(route.provider));
|
||||||
|
yield put(mapSet({
|
||||||
|
provider: route.provider,
|
||||||
|
route: route.route,
|
||||||
|
}))
|
||||||
|
|
||||||
return { route, random_url };
|
return { route, random_url };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -529,6 +539,9 @@ function* changeProviderSaga({
|
||||||
|
|
||||||
yield put(setProvider(provider));
|
yield put(setProvider(provider));
|
||||||
|
|
||||||
|
// TODO: REACTIVE BRANCH
|
||||||
|
yield put(mapSetProvider(provider))
|
||||||
|
|
||||||
if (current_provider === provider) return;
|
if (current_provider === provider) return;
|
||||||
|
|
||||||
yield put(setChanged(true));
|
yield put(setChanged(true));
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#map {
|
#map {
|
||||||
width: 100%;
|
width: 50% !important;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
@ -283,3 +283,13 @@
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#canvas {
|
||||||
|
background: #ff3344;
|
||||||
|
z-index: 0;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
5
src/utils/context.ts
Normal file
5
src/utils/context.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Map, TileLayer } from 'leaflet';
|
||||||
|
|
||||||
|
export const MapContext = React.createContext<Map>(null);
|
||||||
|
export const TileContext = React.createContext<TileLayer>(null)
|
Loading…
Add table
Add a link
Reference in a new issue