From 9c3c8cf46dcd31610d914682f45ca6ea89aa4e49 Mon Sep 17 00:00:00 2001 From: Fedor Katurov Date: Mon, 30 Dec 2019 16:41:37 +0700 Subject: [PATCH] passing editing status to map --- src/components/StickerDesc.tsx | 4 +- src/components/dialogs/AppInfoDialog.tsx | 3 +- src/containers/map/Map/index.tsx | 38 ++++++-- src/containers/map/Sticker/index.tsx | 109 ++++++++++++++++++----- src/containers/map/Stickers/index.tsx | 52 ++++++----- src/redux/map/actions.ts | 12 +++ src/redux/map/constants.ts | 3 + src/redux/map/handlers.ts | 22 ++++- src/redux/user/sagas.ts | 32 +++++-- src/redux/user/selectors.ts | 3 + 10 files changed, 213 insertions(+), 65 deletions(-) create mode 100644 src/redux/user/selectors.ts diff --git a/src/components/StickerDesc.tsx b/src/components/StickerDesc.tsx index b4da352..835ea52 100644 --- a/src/components/StickerDesc.tsx +++ b/src/components/StickerDesc.tsx @@ -16,12 +16,10 @@ export class StickerDesc extends React.PureComponent { }; setText = e => { - this.setState({ text: e.target.value }); this.props.onChange(e.target.value); }; blockMouse = e => { - console.log('BM'); e.stopPropagation(); this.input.focus(); }; @@ -30,7 +28,7 @@ export class StickerDesc extends React.PureComponent { // todo: pass here locker for moving markers from Sticker.js render() { - const { text } = this.state; + const { value: text } = this.props; return (
(
Исходный код:
github.com/muerwre/orchid-front - github.com/muerwre/orchid-backed +
+ github.com/muerwre/orchid-backend
Frontend:
diff --git a/src/containers/map/Map/index.tsx b/src/containers/map/Map/index.tsx index 65b8a3e..2c39b00 100644 --- a/src/containers/map/Map/index.tsx +++ b/src/containers/map/Map/index.tsx @@ -1,30 +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 { selectMapProvider, selectMapRoute, selectMapStickers } from "$redux/map/selectors"; +import { + selectMapProvider, + selectMapRoute, + selectMapStickers +} from "$redux/map/selectors"; import { connect } from "react-redux"; import * as MAP_ACTIONS from "$redux/map/actions"; import { Route } from "$containers/map/Route"; import { TileLayer } from "$containers/map/TileLayer"; import { Stickers } from "$containers/map/Stickers"; +import { selectUserEditing } from '$redux/user/selectors' const mapStateToProps = state => ({ provider: selectMapProvider(state), route: selectMapRoute(state), stickers: selectMapStickers(state), + editing: selectUserEditing(state), }); const mapDispatchToProps = { - mapSetRoute: MAP_ACTIONS.mapSetRoute + mapSetRoute: MAP_ACTIONS.mapSetRoute, + mapDropSticker: MAP_ACTIONS.mapDropSticker, + mapSetSticker: MAP_ACTIONS.mapSetSticker }; type IProps = React.HTMLAttributes & ReturnType & typeof mapDispatchToProps & {}; -const MapUnconnected: React.FC = ({ provider, route, mapSetRoute, stickers }) => { +const MapUnconnected: React.FC = ({ + provider, + route, + stickers, + editing, + + mapSetRoute, + mapSetSticker, + mapDropSticker +}) => { const ref = React.useRef(null); const [maps, setMaps] = React.useState(null); @@ -34,13 +50,17 @@ const MapUnconnected: React.FC = ({ provider, route, mapSetRoute, sticke setMaps(map(ref.current).setView([55.0153275, 82.9071235], 13)); }, [ref]); - // console.log('RERENDER!'); - return createPortal(
- - - + + +
, document.getElementById("canvas") ); diff --git a/src/containers/map/Sticker/index.tsx b/src/containers/map/Sticker/index.tsx index c487bd3..c2abae4 100644 --- a/src/containers/map/Sticker/index.tsx +++ b/src/containers/map/Sticker/index.tsx @@ -10,27 +10,35 @@ import { createPortal } from "react-dom"; interface IProps { map: Map; sticker: IStickerDump; + index: number; + is_editing: boolean; + + mapSetSticker: (index: number, sticker: IStickerDump) => void; + mapDropSticker: (index: number) => void; } -const preventPropagation = (e): void => { - if (!e || !e.stopPropagation) return; - - e.stopPropagation(); - e.preventDefault(); -}; +export const getLabelDirection = (angle: number): "left" | "right" => + angle % Math.PI >= -(Math.PI / 2) && angle % Math.PI <= Math.PI / 2 + ? "left" + : "right"; const getX = e => e.touches && e.touches.length > 0 ? { pageX: e.touches[0].pageX, pageY: e.touches[0].pageY } : { pageX: e.pageX, pageY: e.pageY }; -const Sticker: React.FC = ({ map, sticker }) => { +const Sticker: React.FC = ({ map, sticker, index, mapSetSticker, mapDropSticker, is_editing }) => { const [layer, setLayer] = React.useState(null); 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 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(() => { layer.dragging.disable(); map.dragging.disable(); @@ -43,29 +51,76 @@ const Sticker: React.FC = ({ map, sticker }) => { map.dragging.enable(); setDragging(false); - }, [setDragging, layer, map]); + onChange({ + ...sticker, + angle + }); + }, [setDragging, layer, map, sticker, angle]); - const onDrag = React.useCallback(event => { - // event.stopPrapagation(); - // console.log("drag") - }, []); + const onMoveFinished = React.useCallback(event => { + const target = event.target as Marker; - const onDelete = console.log; - const setText = console.log; + onChange({ + ...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(() => { if (dragging) { 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]); + // Initial leaflet marker creation, when element (dom element div) is ready React.useEffect(() => { if (!map) return; @@ -75,12 +130,24 @@ const Sticker: React.FC = ({ map, sticker }) => { }); const item = marker(sticker.latlng, { icon, draggable: true }).addTo(map); - + setLayer(item); - return () => item.removeFrom(map); + return () => { + item.removeFrom(map); + item.remove(); + } }, [element, map, sticker]); + React.useEffect(() => { + if (is_editing) { + element.className = 'sticker-container'; + + } else { + element.className = 'sticker-container inactive'; + + } + }, [element, is_editing]) return createPortal(
@@ -88,7 +155,7 @@ const Sticker: React.FC = ({ map, sticker }) => { className={classNames(`sticker-label ${direction}`, {})} ref={stickerImage} > - +
= React.memo(({ stickers, is_editing, map }) => { - const [layer, setLayer] = React.useState(null); +const Stickers: React.FC = React.memo( + ({ stickers, is_editing, map, mapSetSticker, mapDropSticker }) => { + const [layer, setLayer] = React.useState(null); - React.useEffect(() => { - if (!map) return; + React.useEffect(() => { + if (!map) return; - setLayer(new FeatureGroup().addTo(map)); - }, [map]); + setLayer(new FeatureGroup().addTo(map)); + }, [map]); - return ( -
- {layer && - stickers.map((sticker, index) => ( - - ))} -
- ); - // return null; -}); + return ( +
+ {layer && + stickers.map((sticker, index) => ( + + ))} +
+ ); + // return null; + } +); export { Stickers }; diff --git a/src/redux/map/actions.ts b/src/redux/map/actions.ts index ec8a222..49aad53 100644 --- a/src/redux/map/actions.ts +++ b/src/redux/map/actions.ts @@ -1,5 +1,6 @@ import { MAP_ACTIONS } from "./constants"; import { IMapReducer } from "./"; +import { IStickerDump } from "$modules/Sticker"; export const mapSet = (map: Partial) => ({ type: MAP_ACTIONS.SET_MAP, @@ -15,3 +16,14 @@ export const mapSetRoute = (route: IMapReducer['route']) => ({ type: MAP_ACTIONS.SET_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, +}); diff --git a/src/redux/map/constants.ts b/src/redux/map/constants.ts index 4e363d9..630c2d1 100644 --- a/src/redux/map/constants.ts +++ b/src/redux/map/constants.ts @@ -4,4 +4,7 @@ export const MAP_ACTIONS = { SET_MAP: `${P}-SET_MAP`, SET_PROVIDER: `${P}-SET_PROVIDER`, SET_ROUTE: `${P}-SET_ROUTE`, + + SET_STICKER: `${P}-SET_STICKER`, + DROP_STICKER: `${P}-DROP_STICKER`, } \ No newline at end of file diff --git a/src/redux/map/handlers.ts b/src/redux/map/handlers.ts index 912bd2a..af98fa9 100644 --- a/src/redux/map/handlers.ts +++ b/src/redux/map/handlers.ts @@ -1,6 +1,6 @@ import { MAP_ACTIONS } from "./constants"; import { IMapReducer } from "."; -import { mapSet, mapSetProvider, mapSetRoute } from "./actions"; +import { mapSet, mapSetProvider, mapSetRoute, mapSetSticker } from "./actions"; const setMap = ( state: IMapReducer, @@ -26,8 +26,26 @@ const setRoute = ( route }); +const setSticker = ( + state: IMapReducer, + { sticker, index }: ReturnType +): IMapReducer => ({ + ...state, + stickers: state.stickers.map((item, i) => (i === index ? sticker : item)) +}); + +const dropSticker = ( + state: IMapReducer, + { index }: ReturnType +): IMapReducer => ({ + ...state, + stickers: state.stickers.filter((_, i) => i !== index) +}); + export const MAP_HANDLERS = { [MAP_ACTIONS.SET_MAP]: setMap, [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, }; diff --git a/src/redux/user/sagas.ts b/src/redux/user/sagas.ts index d148e84..f5e2683 100644 --- a/src/redux/user/sagas.ts +++ b/src/redux/user/sagas.ts @@ -167,11 +167,13 @@ function* loadMapSaga(path) { // TODO: REACTIVE BRANCH: // yield put(mapSetProvider(route.provider)); - yield put(mapSet({ - provider: route.provider, - route: route.route, - stickers: route.stickers, - })) + yield put( + mapSet({ + provider: route.provider, + route: route.route, + stickers: route.stickers + }) + ); return { route, random_url }; } @@ -263,7 +265,7 @@ function* mapInitSaga() { } function* authCheckSaga({ key }: RehydrateAction) { - if (key !== 'user') return; + if (key !== "user") return; pushLoaderState(70); @@ -360,15 +362,31 @@ function* clearSaga({ type }) { case USER_ACTIONS.CLEAR_POLY: yield editor.poly.clearAll(); yield editor.router.clearAll(); + yield put( + mapSet({ + route: [] + }) + ); break; case USER_ACTIONS.CLEAR_STICKERS: yield editor.stickers.clearAll(); + yield put( + mapSet({ + stickers: [] + }) + ); break; case USER_ACTIONS.CLEAR_ALL: yield editor.clearAll(); yield put(setChanged(false)); + yield put( + mapSet({ + route: [], + stickers: [] + }) + ); break; default: @@ -539,7 +557,7 @@ function* changeProviderSaga({ yield put(setProvider(provider)); // TODO: REACTIVE BRANCH - yield put(mapSetProvider(provider)) + yield put(mapSetProvider(provider)); if (current_provider === provider) return; diff --git a/src/redux/user/selectors.ts b/src/redux/user/selectors.ts new file mode 100644 index 0000000..529aad2 --- /dev/null +++ b/src/redux/user/selectors.ts @@ -0,0 +1,3 @@ +import { IState } from '$redux/store' + +export const selectUserEditing = (state: IState) => state.user.editing; \ No newline at end of file