diff --git a/src/components/FeaturePanel/Climbing/ClimbingView.tsx b/src/components/FeaturePanel/Climbing/ClimbingView.tsx index 52361e0b..0cd31ae6 100644 --- a/src/components/FeaturePanel/Climbing/ClimbingView.tsx +++ b/src/components/FeaturePanel/Climbing/ClimbingView.tsx @@ -32,6 +32,7 @@ import MapIcon from '@mui/icons-material/Map'; import EditIcon from '@mui/icons-material/Edit'; import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; import { useGetCragViewLayout } from './utils/useCragViewLayout'; +import { RouteFloatingMenu } from './Editor/RouteFloatingMenu'; export const DEFAULT_CRAG_VIEW_LAYOUT = 'horizontal'; @@ -483,6 +484,7 @@ export const ClimbingView = ({ photo }: { photo?: string }) => { )} + { + {isEditMode && } diff --git a/src/components/FeaturePanel/Climbing/Editor/Points/Point.tsx b/src/components/FeaturePanel/Climbing/Editor/Points/Point.tsx index 52c4fc33..7969021f 100644 --- a/src/components/FeaturePanel/Climbing/Editor/Points/Point.tsx +++ b/src/components/FeaturePanel/Climbing/Editor/Points/Point.tsx @@ -62,6 +62,7 @@ export const Point = ({ setRouteIndexHovered, photoZoom, getCurrentPath, + setIsPanningDisabled, } = useClimbingContext(); const isMobileMode = useMobileMode(); const isPointSelected = @@ -84,15 +85,17 @@ export const Point = ({ }; const onMouseDown = (e) => { + setIsPanningDisabled(true); setPointSelectedIndex(index); setIsPointClicked(true); - e.preventDefault(); + e.stopPropagation(); }; const onMouseUp = (e) => { // @TODO unify with RouteMarks.tsx if (!isPointMoving) { setPointSelectedIndex(null); + setIsPanningDisabled(false); onPointInSelectedRouteClick(e); setPointElement(pointElement !== null ? null : e.currentTarget); setPointSelectedIndex(index); @@ -133,7 +136,7 @@ export const Point = ({ ` - transform: scale(${({ $scale }) => 1 / $scale}); +const Container = styled.div` + position: absolute; + z-index: 1; + bottom: 16px; + right: 16px; `; export const RouteFloatingMenu = () => { @@ -31,7 +34,6 @@ export const RouteFloatingMenu = () => { routes, routeSelectedIndex, getCurrentPath, - photoZoom, setRouteIndexHovered, } = useClimbingContext(); @@ -122,8 +124,8 @@ export const RouteFloatingMenu = () => { - {showTypeMenu ? ( - + + {showTypeMenu ? ( - - ) : ( - + ) : ( {machine.currentStateName === 'pointMenu' && routes[routeSelectedIndex] && @@ -216,8 +216,8 @@ export const RouteFloatingMenu = () => { )} - - )} + )} + ); }; diff --git a/src/components/FeaturePanel/Climbing/Editor/RoutePath.tsx b/src/components/FeaturePanel/Climbing/Editor/RoutePath.tsx index 22f26644..0d8a4abd 100644 --- a/src/components/FeaturePanel/Climbing/Editor/RoutePath.tsx +++ b/src/components/FeaturePanel/Climbing/Editor/RoutePath.tsx @@ -32,7 +32,7 @@ export const RoutePath = ({ route, routeNumber }) => { routeIndexHovered, setRouteIndexHovered, getPathForRoute, - photoRef, + svgRef, photoZoom, } = useClimbingContext(); const isSelected = isRouteSelected(routeNumber); @@ -62,7 +62,7 @@ export const RoutePath = ({ route, routeNumber }) => { units: 'px', }; const positionInImage = getPositionInImageFromMouse( - photoRef, + svgRef, mousePosition, photoZoom, ); @@ -171,7 +171,8 @@ export const RoutePath = ({ route, routeNumber }) => { cy={tempPointPosition.y} fill="white" stroke="rgba(0,0,0,0.3)" - r={5} + r={7 / photoZoom.scale} + strokeWidth={1 / photoZoom.scale} /> )} {machine.currentStateName === 'extendRoute' && diff --git a/src/components/FeaturePanel/Climbing/Editor/RoutesEditor.tsx b/src/components/FeaturePanel/Climbing/Editor/RoutesEditor.tsx index 6b4d6506..158a22e6 100644 --- a/src/components/FeaturePanel/Climbing/Editor/RoutesEditor.tsx +++ b/src/components/FeaturePanel/Climbing/Editor/RoutesEditor.tsx @@ -4,9 +4,13 @@ import { RoutesLayer } from './RoutesLayer'; import { useClimbingContext } from '../contexts/ClimbingContext'; import { updateElementOnIndex } from '../utils/array'; import { PositionPx } from '../types'; -import { getPositionInImageFromMouse } from '../utils/mousePositionUtils'; +import { + getMouseFromPositionInImage, + getPositionInImageFromMouse, +} from '../utils/mousePositionUtils'; import { getCommonsImageUrl } from '../../../../services/images/getCommonsImageUrl'; import { isMobileDevice } from '../../../helpers'; +import { RouteFloatingMenu } from './RouteFloatingMenu'; const EditorContainer = styled.div<{ imageHeight: number }>` display: flex; @@ -33,7 +37,7 @@ const ImageContainer = styled.div` `; const ImageElement = styled.img<{ zoom?: number }>` - object-fit: contain; + object-fit: contain; // @TODO try to delete this max-width: 100%; transition: all 0.1s ease-in; height: 100%; @@ -61,6 +65,7 @@ export const RoutesEditor = ({ loadPhotoRelatedData, loadedPhotos, photoRef, + svgRef, photoPath, setLoadedPhotos, photoZoom, @@ -117,7 +122,7 @@ export const RoutesEditor = ({ units: 'px', }; const positionInImage = getPositionInImageFromMouse( - photoRef, + svgRef, mousePosition, photoZoom, ); diff --git a/src/components/FeaturePanel/Climbing/Editor/RoutesLayer.tsx b/src/components/FeaturePanel/Climbing/Editor/RoutesLayer.tsx index f0b0cc0c..b60ca053 100644 --- a/src/components/FeaturePanel/Climbing/Editor/RoutesLayer.tsx +++ b/src/components/FeaturePanel/Climbing/Editor/RoutesLayer.tsx @@ -36,12 +36,6 @@ const Svg = styled.svg<{ `} `; -const RouteFloatingMenuContainer = styled.div<{ $x: number; $y: number }>` - position: absolute; - left: ${({ $x }) => $x}px; - top: ${({ $y }) => $y}px; - z-index: 10000; -`; type Props = { onClick: (e: any) => void; onEditorMouseMove?: (e: any) => void; @@ -71,8 +65,8 @@ export const RoutesLayer = ({ setPointSelectedIndex, getCurrentPath, routes, - photoRef, photoZoom, + svgRef, } = useClimbingContext(); const machine = getMachine(); @@ -167,45 +161,28 @@ export const RoutesLayer = ({ ? selectedPointOfSelectedRoute : lastPointOfSelectedRoute; - // TODO this position doesnt work well when zoomed - const absolutePositionFromScreen = getMouseFromPositionInImage( - photoRef, - routeFloatingMenuPosition, - photoZoom, - ); - return ( - <> - { - onClick(e); - }} - onMouseUp={handleMovingPointDrop} - onMouseMove={onEditorMouseMove} - onTouchMove={onEditorTouchMove} - onPointerMove={onEditorTouchMove} - $imageSize={imageSize} - $isVisible={isVisible} - $transformOrigin={transformOrigin} - xmlns="http://www.w3.org/2000/svg" - > - {sortedRoutes.rest.map((item) => item.route)} - {sortedRoutes.rest.map((item) => item.marks)} - {sortedRoutes.selected.map((item) => item.route)} - {sortedRoutes.selected.map((item) => item.marks)} - {sortedRoutes.hovered.map((item) => item.route)} - {sortedRoutes.hovered.map((item) => item.marks)} - - - {absolutePositionFromScreen && ( - - - - )} - + { + onClick(e); + }} + onMouseUp={handleMovingPointDrop} + onMouseMove={onEditorMouseMove} + onTouchMove={onEditorTouchMove} + onPointerMove={onEditorTouchMove} + $imageSize={imageSize} + $isVisible={isVisible} + $transformOrigin={transformOrigin} + xmlns="http://www.w3.org/2000/svg" + ref={svgRef} + > + {sortedRoutes.rest.map((item) => item.route)} + {sortedRoutes.rest.map((item) => item.marks)} + {sortedRoutes.selected.map((item) => item.route)} + {sortedRoutes.selected.map((item) => item.marks)} + {sortedRoutes.hovered.map((item) => item.route)} + {sortedRoutes.hovered.map((item) => item.marks)} + ); }; diff --git a/src/components/FeaturePanel/Climbing/RouteList/ClimbingRouteTableRow.tsx b/src/components/FeaturePanel/Climbing/RouteList/ClimbingRouteTableRow.tsx index 75105e72..a435bf3d 100644 --- a/src/components/FeaturePanel/Climbing/RouteList/ClimbingRouteTableRow.tsx +++ b/src/components/FeaturePanel/Climbing/RouteList/ClimbingRouteTableRow.tsx @@ -50,12 +50,14 @@ const RouteName = styled.div<{ opacity: number }>` gap: 4px; justify-content: space-between; position: relative; + user-select: text; `; const RouteDescription = styled.div<{ opacity: number }>` font-size: 10px; opacity: ${({ opacity }) => opacity}; color: ${({ theme }) => theme.palette.text.secondary}; + user-select: text; `; const RouteGrade = styled.div``; diff --git a/src/components/FeaturePanel/Climbing/TransformWrapper.tsx b/src/components/FeaturePanel/Climbing/TransformWrapper.tsx index decaef63..d2bf2d2a 100644 --- a/src/components/FeaturePanel/Climbing/TransformWrapper.tsx +++ b/src/components/FeaturePanel/Climbing/TransformWrapper.tsx @@ -4,8 +4,12 @@ import { useClimbingContext } from './contexts/ClimbingContext'; import { ZoomState } from './types'; export const TransformWrapper = ({ children }) => { - const { setArePointerEventsDisabled, setPhotoZoom, isEditMode } = - useClimbingContext(); + const { + setArePointerEventsDisabled, + setPhotoZoom, + isEditMode, + isPanningDisabled, + } = useClimbingContext(); const startPointerEvents = () => { setArePointerEventsDisabled(false); @@ -31,6 +35,7 @@ export const TransformWrapper = ({ children }) => { onPanningStart={startPointerEvents} onPanningStop={startPointerEvents} disablePadding + panning={{ disabled: isPanningDisabled }} wheel={{ step: 100 }} centerOnInit onTransformed={(_ref, state: ZoomState) => { diff --git a/src/components/FeaturePanel/Climbing/contexts/ClimbingContext.tsx b/src/components/FeaturePanel/Climbing/contexts/ClimbingContext.tsx index ce5e16e0..c1948737 100644 --- a/src/components/FeaturePanel/Climbing/contexts/ClimbingContext.tsx +++ b/src/components/FeaturePanel/Climbing/contexts/ClimbingContext.tsx @@ -46,6 +46,8 @@ type ClimbingContextType = { imageSize: ImageSize; imageContainerSize: ImageSize; isPointMoving: boolean; + isPanningDisabled: boolean; + setIsPanningDisabled: (isPanningDisabled: boolean) => void; isRouteSelected: (routeNumber: number) => boolean; isOtherRouteSelected: (routeNumber: number) => boolean; isRouteHovered: (routeNumber: number) => boolean; @@ -111,6 +113,7 @@ type ClimbingContextType = { filterDifficulty: Array; setFilterDifficulty: (filterDifficulty: Array) => void; photoRef: React.MutableRefObject; + svgRef: React.MutableRefObject; getAllRoutesPhotos: (cragPhotos: Array) => void; showDebugMenu: boolean; setShowDebugMenu: (showDebugMenu: boolean) => void; @@ -142,6 +145,7 @@ export const ClimbingContextProvider = ({ children, feature }: Props) => { const initialRoutes = osmToClimbingRoutes(feature); publishDbgObject('climbingRoutes', initialRoutes); const photoRef = useRef(null); + const svgRef = useRef(null); const [photoPaths, setPhotoPaths] = useState>(null); const [photoPath, setPhotoPath] = useState(null); // photo, should be null const [showDebugMenu, setShowDebugMenu] = useState(false); @@ -155,6 +159,7 @@ export const ClimbingContextProvider = ({ children, feature }: Props) => { const [routes, setRoutes] = useState>(initialRoutes); const [splitPaneSize, setSplitPaneSize] = useState(null); const [isPointMoving, setIsPointMoving] = useState(false); + const [isPanningDisabled, setIsPanningDisabled] = useState(false); const [isPointClicked, setIsPointClicked] = useState(false); const [areRoutesLoading, setAreRoutesLoading] = useState(true); const [arePointerEventsDisabled, setArePointerEventsDisabled] = @@ -270,7 +275,7 @@ export const ClimbingContextProvider = ({ children, feature }: Props) => { updateRouteOnIndex, getPercentagePosition, findCloserPoint, - photoRef, + svgRef, photoZoom, }); @@ -323,6 +328,8 @@ export const ClimbingContextProvider = ({ children, feature }: Props) => { imageSize, isPointClicked, isPointMoving, + isPanningDisabled, + setIsPanningDisabled, isRouteSelected, isOtherRouteSelected, isRouteHovered, @@ -366,7 +373,8 @@ export const ClimbingContextProvider = ({ children, feature }: Props) => { loadPhotoRelatedData, filterDifficulty, setFilterDifficulty, - photoRef, + photoRef, // @TODO rename: technically it's not photoRef but photoContainerRef, because photo is scaled by object-fit: contain + svgRef, areRoutesLoading, setAreRoutesLoading, photoZoom, diff --git a/src/components/FeaturePanel/Climbing/utils/mousePositionUtils.ts b/src/components/FeaturePanel/Climbing/utils/mousePositionUtils.ts index 29b4fbbc..70edb222 100644 --- a/src/components/FeaturePanel/Climbing/utils/mousePositionUtils.ts +++ b/src/components/FeaturePanel/Climbing/utils/mousePositionUtils.ts @@ -2,15 +2,15 @@ import React from 'react'; import { PositionPx, ZoomState } from '../types'; export const getPositionInImageFromMouse = ( - photoRef: React.MutableRefObject, + svgRef: React.MutableRefObject, mousePosition: PositionPx, photoZoom: ZoomState, ) => { - if (photoRef.current === null || !mousePosition) { + if (svgRef.current === null || !mousePosition) { return null; } - const imageRect = photoRef.current.getBoundingClientRect(); + const imageRect = svgRef.current.getBoundingClientRect(); const posInImage: PositionPx = { x: (mousePosition.x - imageRect.x) / photoZoom.scale, @@ -21,15 +21,15 @@ export const getPositionInImageFromMouse = ( }; export const getMouseFromPositionInImage = ( - photoRef: React.MutableRefObject, + svgRef: React.MutableRefObject, position: PositionPx, photoZoom: ZoomState, ) => { - if (photoRef.current === null || !position) { + if (svgRef.current === null || !position) { return null; } - const imageRect = photoRef.current.getBoundingClientRect(); + const imageRect = svgRef.current.getBoundingClientRect(); return { x: position.x * photoZoom.scale + imageRect.x, diff --git a/src/components/FeaturePanel/Climbing/utils/useGetMachineFactory.ts b/src/components/FeaturePanel/Climbing/utils/useGetMachineFactory.ts index dae96db7..4fd5fc4e 100644 --- a/src/components/FeaturePanel/Climbing/utils/useGetMachineFactory.ts +++ b/src/components/FeaturePanel/Climbing/utils/useGetMachineFactory.ts @@ -49,7 +49,7 @@ export const useGetMachineFactory = ({ updateRouteOnIndex, getPercentagePosition, findCloserPoint, - photoRef, + svgRef, photoZoom, }) => { const [currentState, setCurrentState] = useState('init'); @@ -132,7 +132,7 @@ export const useGetMachineFactory = ({ const addPointToEnd = (props: { position: PositionPx }) => { if (!props) return; const positionInImage = getPositionInImageFromMouse( - photoRef, + svgRef, props.position, photoZoom, );