added editable route

This commit is contained in:
Fedor Katurov 2019-12-30 13:16:35 +07:00
parent 5664291c92
commit fca52df9f5
17 changed files with 282 additions and 11 deletions

View file

@ -16,7 +16,7 @@ export class Cursor extends React.PureComponent<Props, {}> {
}
moveCursor = e => {
if (!e.clientX || !e.clientY) return;
if (!e.clientX || !e.clientY || !this.cursor || !this.cursor.style) return;
const { clientX, clientY } = e;

View file

@ -19,6 +19,9 @@ import { IStickerPack } from "$constants/stickers";
import { IDialogs } from "$constants/dialogs";
import { IModes } from "$constants/modes";
import { Map } from "$containers/map/Map"
import { TileLayer } from '$containers/map/TileLayer';
type Props = {
sticker: string,
renderer_active: boolean,
@ -49,6 +52,8 @@ const Component = (props: Props) => (
setDialogActive={props.setDialogActive}
/>
<Map />
{ props.renderer_active &&
<Renderer onClick={props.hideRenderer} />
}

View 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 };

View 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 };

View 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 };

View file

@ -130,7 +130,10 @@
</head>
<body>
<canvas id="renderer"></canvas>
<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">
<div id="loader-container">
<div id="loader-current">ЗАГРУЗКА</div>

View file

@ -27,7 +27,7 @@ interface InteractivePolylineOptions extends PolylineOptions {
kmMarksStep?: number,
}
export class Component extends Polyline {
export class InteractivePoly extends Polyline {
constructor(latlngs: LatLngExpression[] | LatLngExpression[][], options?: InteractivePolylineOptions) {
super(latlngs, options);
@ -47,7 +47,6 @@ export class Component extends Polyline {
this.setLatLngs(latlngs);
this.recreateMarkers();
this.recalcDistance();
// this.recalcKmMarks();
this.touchHinter.setLatLngs(latlngs);
this.fire('latlngschange', { latlngs });
};
@ -101,7 +100,6 @@ export class Component extends Polyline {
this._map.addLayer(this.markerLayer);
this.fire('allvertexshow');
console.log();
};
hideAllMarkers = (): void => {
@ -516,7 +514,7 @@ export class Component extends Polyline {
distance: number = 0;
}
Component.addInitHook(function () {
InteractivePoly.addInitHook(function () {
this.once('add', (event) => {
if (event.target instanceof InteractivePoly) {
this.map = event.target._map;
@ -550,7 +548,7 @@ Component.addInitHook(function () {
});
});
export const InteractivePoly = Component;
// export const InteractivePoly = Component;
/*
events:
vertexdragstart,

17
src/redux/map/actions.ts Normal file
View 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,
});

View 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`,
}

View file

@ -1,3 +1,33 @@
export const MAP_HANDLERS = {
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 = {
[MAP_ACTIONS.SET_MAP]: setMap,
[MAP_ACTIONS.SET_PROVIDER]: setProvider,
[MAP_ACTIONS.SET_ROUTE]: setRoute
};

View file

@ -1,11 +1,15 @@
import { createReducer } from 'reduxsauce';
import { MAP_HANDLERS } from './handlers';
import { DEFAULT_PROVIDER } from '$constants/providers';
import { IMapRoute } from './types';
export interface IMapReducer {
provider: string;
route: IMapRoute;
}
export const MAP_INITIAL_STATE = {
provider: DEFAULT_PROVIDER,
}
export const map = createReducer(MAP_INITIAL_STATE, MAP_HANDLERS)

View 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
View file

@ -0,0 +1,8 @@
import { LatLng } from "leaflet";
export type ILatLng = {
lat: number,
lng: number,
}
export type IMapRoute = ILatLng[];

View file

@ -9,6 +9,7 @@ import { userSaga } from '$redux/user/sagas';
import { createBrowserHistory } from 'history';
import { locationChanged } from '$redux/user/actions';
import { PersistConfig, Persistor } from "redux-persist/es/types";
import { map, IMapReducer } from '$redux/map';
const userPersistConfig: PersistConfig = {
key: 'user',
@ -18,6 +19,7 @@ const userPersistConfig: PersistConfig = {
export interface IState {
user: IRootReducer
map: IMapReducer,
}
// create the saga middleware
export const sagaMiddleware = createSagaMiddleware();
@ -32,6 +34,7 @@ const composeEnhancers =
export const store = createStore(
combineReducers({
user: persistReducer(userPersistConfig, userReducer),
map,
// routing: routerReducer
}),
composeEnhancers(applyMiddleware(/* routerMiddleware(history), */ sagaMiddleware))

View file

@ -89,6 +89,7 @@ import { IRootState } from "$redux/user";
import { downloadGPXTrack, getGPXString } from "$utils/gpx";
import { Unwrap } from "$utils/middleware";
import { IState } from "$redux/store";
import { mapSetProvider, mapSet } from "$redux/map/actions";
const getUser = (state: IState) => state.user.user;
const getState = (state: IState) => state.user;
@ -156,6 +157,8 @@ function* loadMapSaga(path) {
data: { route, error, random_url }
}: Unwrap<typeof getStoredMap> = yield call(getStoredMap, { name: path });
console.log({ route });
if (route && !error) {
yield editor.clearAll();
yield editor.setData(route);
@ -164,6 +167,13 @@ function* loadMapSaga(path) {
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 };
}
@ -529,6 +539,9 @@ function* changeProviderSaga({
yield put(setProvider(provider));
// TODO: REACTIVE BRANCH
yield put(mapSetProvider(provider))
if (current_provider === provider) return;
yield put(setChanged(true));

View file

@ -1,5 +1,5 @@
#map {
width: 100%;
width: 50% !important;
height: 100%;
position: absolute;
z-index: 1;
@ -283,3 +283,13 @@
font-size: 11px;
}
}
#canvas {
background: #ff3344;
z-index: 0;
> div {
width: 100%;
height: 100%;
}
}

5
src/utils/context.ts Normal file
View 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)