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 => {
|
||||
if (!e.clientX || !e.clientY) return;
|
||||
if (!e.clientX || !e.clientY || !this.cursor || !this.cursor.style) return;
|
||||
|
||||
const { clientX, clientY } = e;
|
||||
|
||||
|
|
|
@ -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} />
|
||||
}
|
||||
|
|
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>
|
||||
<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>
|
||||
|
|
|
@ -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
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 @@
|
|||
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
|
||||
};
|
||||
|
|
|
@ -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)
|
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 { 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))
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
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