passing editing status to map

This commit is contained in:
Fedor Katurov 2019-12-30 16:41:37 +07:00
parent 58eefd5670
commit 9c3c8cf46d
10 changed files with 213 additions and 65 deletions

View file

@ -16,12 +16,10 @@ export class StickerDesc extends React.PureComponent<Props, State> {
}; };
setText = e => { setText = e => {
this.setState({ text: e.target.value });
this.props.onChange(e.target.value); this.props.onChange(e.target.value);
}; };
blockMouse = e => { blockMouse = e => {
console.log('BM');
e.stopPropagation(); e.stopPropagation();
this.input.focus(); this.input.focus();
}; };
@ -30,7 +28,7 @@ export class StickerDesc extends React.PureComponent<Props, State> {
// todo: pass here locker for moving markers from Sticker.js // todo: pass here locker for moving markers from Sticker.js
render() { render() {
const { text } = this.state; const { value: text } = this.props;
return ( return (
<div <div

View file

@ -20,7 +20,8 @@ export const AppInfoDialog = () => (
<div> <div>
<div>Исходный код:</div> <div>Исходный код:</div>
<a href="//github.com/muerwre/orchid-front" target="_blank">github.com/muerwre/orchid-front</a> <a href="//github.com/muerwre/orchid-front" target="_blank">github.com/muerwre/orchid-front</a>
<a href="//github.com/muerwre/orchid-backend" target="_blank">github.com/muerwre/orchid-backed</a> <br />
<a href="//github.com/muerwre/orchid-backend" target="_blank">github.com/muerwre/orchid-backend</a>
</div> </div>
<div> <div>
<div>Frontend:</div> <div>Frontend:</div>

View file

@ -1,30 +1,46 @@
import { Map as MapInterface, map } from "leaflet"; import { Map as MapInterface, map } from "leaflet";
import * as React from "react"; import * as React from "react";
import { createPortal } from "react-dom"; import { createPortal } from "react-dom";
import { MapContext } from "$utils/context.ts"; import {
import { selectMapProvider, selectMapRoute, selectMapStickers } from "$redux/map/selectors"; selectMapProvider,
selectMapRoute,
selectMapStickers
} from "$redux/map/selectors";
import { connect } from "react-redux"; import { connect } from "react-redux";
import * as MAP_ACTIONS from "$redux/map/actions"; import * as MAP_ACTIONS from "$redux/map/actions";
import { Route } from "$containers/map/Route"; import { Route } from "$containers/map/Route";
import { TileLayer } from "$containers/map/TileLayer"; import { TileLayer } from "$containers/map/TileLayer";
import { Stickers } from "$containers/map/Stickers"; import { Stickers } from "$containers/map/Stickers";
import { selectUserEditing } from '$redux/user/selectors'
const mapStateToProps = state => ({ const mapStateToProps = state => ({
provider: selectMapProvider(state), provider: selectMapProvider(state),
route: selectMapRoute(state), route: selectMapRoute(state),
stickers: selectMapStickers(state), stickers: selectMapStickers(state),
editing: selectUserEditing(state),
}); });
const mapDispatchToProps = { const mapDispatchToProps = {
mapSetRoute: MAP_ACTIONS.mapSetRoute mapSetRoute: MAP_ACTIONS.mapSetRoute,
mapDropSticker: MAP_ACTIONS.mapDropSticker,
mapSetSticker: MAP_ACTIONS.mapSetSticker
}; };
type IProps = React.HTMLAttributes<HTMLDivElement> & type IProps = React.HTMLAttributes<HTMLDivElement> &
ReturnType<typeof mapStateToProps> & ReturnType<typeof mapStateToProps> &
typeof mapDispatchToProps & {}; typeof mapDispatchToProps & {};
const MapUnconnected: React.FC<IProps> = ({ provider, route, mapSetRoute, stickers }) => { const MapUnconnected: React.FC<IProps> = ({
provider,
route,
stickers,
editing,
mapSetRoute,
mapSetSticker,
mapDropSticker
}) => {
const ref = React.useRef(null); const ref = React.useRef(null);
const [maps, setMaps] = React.useState<MapInterface>(null); const [maps, setMaps] = React.useState<MapInterface>(null);
@ -34,13 +50,17 @@ const MapUnconnected: React.FC<IProps> = ({ provider, route, mapSetRoute, sticke
setMaps(map(ref.current).setView([55.0153275, 82.9071235], 13)); setMaps(map(ref.current).setView([55.0153275, 82.9071235], 13));
}, [ref]); }, [ref]);
// console.log('RERENDER!');
return createPortal( return createPortal(
<div ref={ref}> <div ref={ref}>
<TileLayer provider={provider} map={maps} /> <TileLayer provider={provider} map={maps} />
<Route route={route} mapSetRoute={mapSetRoute} map={maps} is_editing /> <Route route={route} mapSetRoute={mapSetRoute} map={maps} is_editing={editing} />
<Stickers stickers={stickers} map={maps} is_editing /> <Stickers
stickers={stickers}
map={maps}
mapSetSticker={mapSetSticker}
mapDropSticker={mapDropSticker}
is_editing={editing}
/>
</div>, </div>,
document.getElementById("canvas") document.getElementById("canvas")
); );

View file

@ -10,27 +10,35 @@ import { createPortal } from "react-dom";
interface IProps { interface IProps {
map: Map; map: Map;
sticker: IStickerDump; sticker: IStickerDump;
index: number;
is_editing: boolean;
mapSetSticker: (index: number, sticker: IStickerDump) => void;
mapDropSticker: (index: number) => void;
} }
const preventPropagation = (e): void => { export const getLabelDirection = (angle: number): "left" | "right" =>
if (!e || !e.stopPropagation) return; angle % Math.PI >= -(Math.PI / 2) && angle % Math.PI <= Math.PI / 2
? "left"
e.stopPropagation(); : "right";
e.preventDefault();
};
const getX = e => const getX = e =>
e.touches && e.touches.length > 0 e.touches && e.touches.length > 0
? { pageX: e.touches[0].pageX, pageY: e.touches[0].pageY } ? { pageX: e.touches[0].pageX, pageY: e.touches[0].pageY }
: { pageX: e.pageX, pageY: e.pageY }; : { pageX: e.pageX, pageY: e.pageY };
const Sticker: React.FC<IProps> = ({ map, sticker }) => { const Sticker: React.FC<IProps> = ({ map, sticker, index, mapSetSticker, mapDropSticker, is_editing }) => {
const [layer, setLayer] = React.useState<Marker>(null); const [layer, setLayer] = React.useState<Marker>(null);
const [dragging, setDragging] = React.useState(false); const [dragging, setDragging] = React.useState(false);
const [angle, setAngle] = React.useState(sticker.angle);
const element = React.useMemo(() => document.createElement("div"), []);
const stickerArrow = React.useRef(null); const stickerArrow = React.useRef(null);
const stickerImage = React.useRef(null); const stickerImage = React.useRef(null);
const onChange = React.useCallback(state => mapSetSticker(index, state), [mapSetSticker, index]);
const onDelete = React.useCallback(state => mapDropSticker(index), [mapSetSticker, index]);
const onDragStart = React.useCallback(() => { const onDragStart = React.useCallback(() => {
layer.dragging.disable(); layer.dragging.disable();
map.dragging.disable(); map.dragging.disable();
@ -43,29 +51,76 @@ const Sticker: React.FC<IProps> = ({ map, sticker }) => {
map.dragging.enable(); map.dragging.enable();
setDragging(false); setDragging(false);
}, [setDragging, layer, map]); onChange({
...sticker,
angle
});
}, [setDragging, layer, map, sticker, angle]);
const onDrag = React.useCallback(event => { const onMoveFinished = React.useCallback(event => {
// event.stopPrapagation(); const target = event.target as Marker;
// console.log("drag")
}, []);
const onDelete = console.log; onChange({
const setText = console.log; ...sticker,
latlng: target.getLatLng(),
});
}, [onChange, sticker]);
const direction = React.useMemo(() => "left", [sticker.angle]); const onDrag = React.useCallback(
event => {
if (!element) return;
const element = React.useMemo(() => document.createElement("div"), []); const { x, y } = element.getBoundingClientRect() as DOMRect;
const { pageX, pageY } = getX(event);
setAngle(parseFloat(Math.atan2(y - pageY, x - pageX).toFixed(2)));
},
[element]
);
const onTextChange = React.useCallback(text => onChange({
...sticker,
text,
}), [sticker, onChange])
const direction = React.useMemo(() => getLabelDirection(angle), [angle]);
// Updates html element accroding to current angle
React.useEffect(() => {
if (!stickerImage.current || !stickerArrow.current) return;
const x = Math.cos(angle + Math.PI) * 56 - 30;
const y = Math.sin(angle + Math.PI) * 56 - 30;
stickerImage.current.style.left = String(6 + x);
stickerImage.current.style.top = String(6 + y);
stickerArrow.current.style.transform = `rotate(${angle + Math.PI}rad)`;
}, [stickerArrow, stickerImage, angle]);
// Attaches onMoveFinished event to item
React.useEffect(() => {
if (!layer) return;
layer.addEventListener("dragend", onMoveFinished);
return () => layer.removeEventListener("dragend", onMoveFinished);
}, [layer, onMoveFinished]);
// Attaches and detaches handlers when user starts dragging
React.useEffect(() => { React.useEffect(() => {
if (dragging) { if (dragging) {
document.addEventListener("mousemove", onDrag); document.addEventListener("mousemove", onDrag);
document.addEventListener("mouseUp", onDragStop); document.addEventListener("mouseup", onDragStop);
} }
return () => document.removeEventListener("mousemove", onDrag); return () => {
document.removeEventListener("mousemove", onDrag);
document.removeEventListener("mouseup", onDragStop);
};
}, [dragging, onDrag]); }, [dragging, onDrag]);
// Initial leaflet marker creation, when element (dom element div) is ready
React.useEffect(() => { React.useEffect(() => {
if (!map) return; if (!map) return;
@ -75,12 +130,24 @@ const Sticker: React.FC<IProps> = ({ map, sticker }) => {
}); });
const item = marker(sticker.latlng, { icon, draggable: true }).addTo(map); const item = marker(sticker.latlng, { icon, draggable: true }).addTo(map);
setLayer(item); setLayer(item);
return () => item.removeFrom(map); return () => {
item.removeFrom(map);
item.remove();
}
}, [element, map, sticker]); }, [element, map, sticker]);
React.useEffect(() => {
if (is_editing) {
element.className = 'sticker-container';
} else {
element.className = 'sticker-container inactive';
}
}, [element, is_editing])
return createPortal( return createPortal(
<React.Fragment> <React.Fragment>
<div className="sticker-arrow" ref={stickerArrow} /> <div className="sticker-arrow" ref={stickerArrow} />
@ -88,7 +155,7 @@ const Sticker: React.FC<IProps> = ({ map, sticker }) => {
className={classNames(`sticker-label ${direction}`, {})} className={classNames(`sticker-label ${direction}`, {})}
ref={stickerImage} ref={stickerImage}
> >
<StickerDesc value={sticker.text} onChange={setText} /> <StickerDesc value={sticker.text} onChange={onTextChange} />
<div <div
className="sticker-image" className="sticker-image"

View file

@ -1,39 +1,47 @@
import * as React from "react"; import * as React from "react";
import { IStickerDump } from "$modules/Sticker"; import { IStickerDump } from "$modules/Sticker";
import { Layer, FeatureGroup, Map } from "leaflet"; import { FeatureGroup, Map } from "leaflet";
import { MapContext } from "$utils/context";
import { Sticker } from "$containers/map/Sticker"; import { Sticker } from "$containers/map/Sticker";
import { mapSetSticker, mapDropSticker } from "$redux/map/actions";
interface IProps { interface IProps {
stickers: IStickerDump[]; stickers: IStickerDump[];
is_editing: boolean; is_editing: boolean;
map: Map; map: Map;
mapSetSticker: typeof mapSetSticker;
mapDropSticker: typeof mapDropSticker;
} }
// const { FC, useContext, useState, useEffect } = React; // const { FC, useContext, useState, useEffect } = React;
const Stickers: React.FC<IProps> = React.memo(({ stickers, is_editing, map }) => { const Stickers: React.FC<IProps> = React.memo(
const [layer, setLayer] = React.useState<FeatureGroup>(null); ({ stickers, is_editing, map, mapSetSticker, mapDropSticker }) => {
const [layer, setLayer] = React.useState<FeatureGroup>(null);
React.useEffect(() => { React.useEffect(() => {
if (!map) return; if (!map) return;
setLayer(new FeatureGroup().addTo(map)); setLayer(new FeatureGroup().addTo(map));
}, [map]); }, [map]);
return ( return (
<div> <div>
{layer && {layer &&
stickers.map((sticker, index) => ( stickers.map((sticker, index) => (
<Sticker <Sticker
map={map} map={map}
sticker={sticker} sticker={sticker}
key={`${sticker.set}.${sticker.sticker}.${index}`} key={`${sticker.set}.${sticker.sticker}.${index}`}
/> index={index}
))} is_editing={is_editing}
</div> mapSetSticker={mapSetSticker}
); mapDropSticker={mapDropSticker}
// return null; />
}); ))}
</div>
);
// return null;
}
);
export { Stickers }; export { Stickers };

View file

@ -1,5 +1,6 @@
import { MAP_ACTIONS } from "./constants"; import { MAP_ACTIONS } from "./constants";
import { IMapReducer } from "./"; import { IMapReducer } from "./";
import { IStickerDump } from "$modules/Sticker";
export const mapSet = (map: Partial<IMapReducer>) => ({ export const mapSet = (map: Partial<IMapReducer>) => ({
type: MAP_ACTIONS.SET_MAP, type: MAP_ACTIONS.SET_MAP,
@ -15,3 +16,14 @@ export const mapSetRoute = (route: IMapReducer['route']) => ({
type: MAP_ACTIONS.SET_ROUTE, type: MAP_ACTIONS.SET_ROUTE,
route, route,
}); });
export const mapSetSticker = (index: number, sticker: IStickerDump) => ({
type: MAP_ACTIONS.SET_STICKER,
index,
sticker,
});
export const mapDropSticker = (index: number) => ({
type: MAP_ACTIONS.DROP_STICKER,
index,
});

View file

@ -4,4 +4,7 @@ export const MAP_ACTIONS = {
SET_MAP: `${P}-SET_MAP`, SET_MAP: `${P}-SET_MAP`,
SET_PROVIDER: `${P}-SET_PROVIDER`, SET_PROVIDER: `${P}-SET_PROVIDER`,
SET_ROUTE: `${P}-SET_ROUTE`, SET_ROUTE: `${P}-SET_ROUTE`,
SET_STICKER: `${P}-SET_STICKER`,
DROP_STICKER: `${P}-DROP_STICKER`,
} }

View file

@ -1,6 +1,6 @@
import { MAP_ACTIONS } from "./constants"; import { MAP_ACTIONS } from "./constants";
import { IMapReducer } from "."; import { IMapReducer } from ".";
import { mapSet, mapSetProvider, mapSetRoute } from "./actions"; import { mapSet, mapSetProvider, mapSetRoute, mapSetSticker } from "./actions";
const setMap = ( const setMap = (
state: IMapReducer, state: IMapReducer,
@ -26,8 +26,26 @@ const setRoute = (
route route
}); });
const setSticker = (
state: IMapReducer,
{ sticker, index }: ReturnType<typeof mapSetSticker>
): IMapReducer => ({
...state,
stickers: state.stickers.map((item, i) => (i === index ? sticker : item))
});
const dropSticker = (
state: IMapReducer,
{ index }: ReturnType<typeof mapSetSticker>
): IMapReducer => ({
...state,
stickers: state.stickers.filter((_, i) => i !== index)
});
export const MAP_HANDLERS = { export const MAP_HANDLERS = {
[MAP_ACTIONS.SET_MAP]: setMap, [MAP_ACTIONS.SET_MAP]: setMap,
[MAP_ACTIONS.SET_PROVIDER]: setProvider, [MAP_ACTIONS.SET_PROVIDER]: setProvider,
[MAP_ACTIONS.SET_ROUTE]: setRoute [MAP_ACTIONS.SET_ROUTE]: setRoute,
[MAP_ACTIONS.SET_STICKER]: setSticker,
[MAP_ACTIONS.DROP_STICKER]: dropSticker,
}; };

View file

@ -167,11 +167,13 @@ function* loadMapSaga(path) {
// TODO: REACTIVE BRANCH: // TODO: REACTIVE BRANCH:
// yield put(mapSetProvider(route.provider)); // yield put(mapSetProvider(route.provider));
yield put(mapSet({ yield put(
provider: route.provider, mapSet({
route: route.route, provider: route.provider,
stickers: route.stickers, route: route.route,
})) stickers: route.stickers
})
);
return { route, random_url }; return { route, random_url };
} }
@ -263,7 +265,7 @@ function* mapInitSaga() {
} }
function* authCheckSaga({ key }: RehydrateAction) { function* authCheckSaga({ key }: RehydrateAction) {
if (key !== 'user') return; if (key !== "user") return;
pushLoaderState(70); pushLoaderState(70);
@ -360,15 +362,31 @@ function* clearSaga({ type }) {
case USER_ACTIONS.CLEAR_POLY: case USER_ACTIONS.CLEAR_POLY:
yield editor.poly.clearAll(); yield editor.poly.clearAll();
yield editor.router.clearAll(); yield editor.router.clearAll();
yield put(
mapSet({
route: []
})
);
break; break;
case USER_ACTIONS.CLEAR_STICKERS: case USER_ACTIONS.CLEAR_STICKERS:
yield editor.stickers.clearAll(); yield editor.stickers.clearAll();
yield put(
mapSet({
stickers: []
})
);
break; break;
case USER_ACTIONS.CLEAR_ALL: case USER_ACTIONS.CLEAR_ALL:
yield editor.clearAll(); yield editor.clearAll();
yield put(setChanged(false)); yield put(setChanged(false));
yield put(
mapSet({
route: [],
stickers: []
})
);
break; break;
default: default:
@ -539,7 +557,7 @@ function* changeProviderSaga({
yield put(setProvider(provider)); yield put(setProvider(provider));
// TODO: REACTIVE BRANCH // TODO: REACTIVE BRANCH
yield put(mapSetProvider(provider)) yield put(mapSetProvider(provider));
if (current_provider === provider) return; if (current_provider === provider) return;

View file

@ -0,0 +1,3 @@
import { IState } from '$redux/store'
export const selectUserEditing = (state: IState) => state.user.editing;