feat(know-your-world): show magnifier on mobile drag gesture
Detect touch drag gestures on the map to show the magnifier on mobile: - Track touch start position and detect drag when moved past threshold - Show magnifier when user drags on the map (not just on the magnifier) - Position magnifier in opposite corner from touch point - Use adaptive zoom based on region detection - Hide magnifier when touch ends This makes the magnifier discoverable on mobile by appearing automatically when the user starts dragging on the map to search for regions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
7dab07b3a7
commit
a02a7108e9
|
|
@ -503,6 +503,11 @@ export function MapRenderer({
|
||||||
const magnifierRef = useRef<HTMLDivElement>(null) // Ref to magnifier element for tap position calculation
|
const magnifierRef = useRef<HTMLDivElement>(null) // Ref to magnifier element for tap position calculation
|
||||||
const magnifierTapPositionRef = useRef<{ x: number; y: number } | null>(null) // Where user tapped on magnifier
|
const magnifierTapPositionRef = useRef<{ x: number; y: number } | null>(null) // Where user tapped on magnifier
|
||||||
|
|
||||||
|
// Mobile map drag state - detect touch drags on the map to show magnifier
|
||||||
|
const [isMobileMapDragging, setIsMobileMapDragging] = useState(false)
|
||||||
|
const mapTouchStartRef = useRef<{ x: number; y: number } | null>(null)
|
||||||
|
const MOBILE_DRAG_THRESHOLD = 10 // pixels before we consider it a drag
|
||||||
|
|
||||||
// Give up reveal animation state
|
// Give up reveal animation state
|
||||||
const [giveUpFlashProgress, setGiveUpFlashProgress] = useState(0) // 0-1 pulsing value
|
const [giveUpFlashProgress, setGiveUpFlashProgress] = useState(0) // 0-1 pulsing value
|
||||||
const [isGiveUpAnimating, setIsGiveUpAnimating] = useState(false) // Track if animation in progress
|
const [isGiveUpAnimating, setIsGiveUpAnimating] = useState(false) // Track if animation in progress
|
||||||
|
|
@ -1960,9 +1965,11 @@ export function MapRenderer({
|
||||||
} = detectionResult
|
} = detectionResult
|
||||||
|
|
||||||
// Show magnifier when:
|
// Show magnifier when:
|
||||||
// 1. Shift key is held down (manual override)
|
// 1. Shift key is held down (manual override on desktop)
|
||||||
// 2. Current target region needs magnification AND there's a small region nearby
|
// 2. Current target region needs magnification AND there's a small region nearby
|
||||||
const shouldShow = shiftPressed || (targetNeedsMagnification && hasSmallRegion)
|
// 3. User is dragging on the map on mobile (always show magnifier for mobile drag)
|
||||||
|
const shouldShow =
|
||||||
|
shiftPressed || isMobileMapDragging || (targetNeedsMagnification && hasSmallRegion)
|
||||||
|
|
||||||
// Update smallest region size for adaptive cursor dampening
|
// Update smallest region size for adaptive cursor dampening
|
||||||
// Use hysteresis to prevent rapid flickering at boundaries
|
// Use hysteresis to prevent rapid flickering at boundaries
|
||||||
|
|
@ -2191,6 +2198,83 @@ export function MapRenderer({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mobile map touch handlers - detect drag gestures to show magnifier
|
||||||
|
const handleMapTouchStart = useCallback((e: React.TouchEvent<HTMLDivElement>) => {
|
||||||
|
// Only handle single-finger touch
|
||||||
|
if (e.touches.length !== 1) return
|
||||||
|
|
||||||
|
const touch = e.touches[0]
|
||||||
|
mapTouchStartRef.current = { x: touch.clientX, y: touch.clientY }
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleMapTouchMove = useCallback(
|
||||||
|
(e: React.TouchEvent<HTMLDivElement>) => {
|
||||||
|
if (!mapTouchStartRef.current || !svgRef.current || !containerRef.current) return
|
||||||
|
if (e.touches.length !== 1) return
|
||||||
|
|
||||||
|
const touch = e.touches[0]
|
||||||
|
const dx = touch.clientX - mapTouchStartRef.current.x
|
||||||
|
const dy = touch.clientY - mapTouchStartRef.current.y
|
||||||
|
const distance = Math.sqrt(dx * dx + dy * dy)
|
||||||
|
|
||||||
|
// Once we detect a drag (moved past threshold), show magnifier and update cursor
|
||||||
|
if (distance >= MOBILE_DRAG_THRESHOLD) {
|
||||||
|
if (!isMobileMapDragging) {
|
||||||
|
setIsMobileMapDragging(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update cursor position based on touch location
|
||||||
|
const containerRect = containerRef.current.getBoundingClientRect()
|
||||||
|
const cursorX = touch.clientX - containerRect.left
|
||||||
|
const cursorY = touch.clientY - containerRect.top
|
||||||
|
|
||||||
|
cursorPositionRef.current = { x: cursorX, y: cursorY }
|
||||||
|
setCursorPosition({ x: cursorX, y: cursorY })
|
||||||
|
|
||||||
|
// Show magnifier and set it up for mobile drag
|
||||||
|
setShowMagnifier(true)
|
||||||
|
setTargetOpacity(1)
|
||||||
|
|
||||||
|
// Use adaptive zoom from region detection if available
|
||||||
|
const detectionResult = detectRegions(cursorX, cursorY)
|
||||||
|
const { detectedSmallestSize, hasSmallRegion } = detectionResult
|
||||||
|
|
||||||
|
// For mobile, use a moderate fixed zoom or adapt based on regions
|
||||||
|
const mobileZoom = hasSmallRegion ? Math.min(4, MAX_ZOOM) : 2.5
|
||||||
|
setTargetZoom(mobileZoom)
|
||||||
|
|
||||||
|
// Position magnifier away from touch point (opposite corner)
|
||||||
|
const isLeftHalf = cursorX < containerRect.width / 2
|
||||||
|
const isTopHalf = cursorY < containerRect.height / 2
|
||||||
|
|
||||||
|
const { width: magnifierWidth, height: magnifierHeight } = getMagnifierDimensions(
|
||||||
|
containerRect.width,
|
||||||
|
containerRect.height
|
||||||
|
)
|
||||||
|
|
||||||
|
// Place magnifier in opposite corner from where user is touching
|
||||||
|
const newTop = isTopHalf ? containerRect.height - magnifierHeight - 20 : 20
|
||||||
|
const newLeft = isLeftHalf ? containerRect.width - magnifierWidth - 20 : 20
|
||||||
|
|
||||||
|
setTargetTop(newTop)
|
||||||
|
setTargetLeft(newLeft)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[isMobileMapDragging, MOBILE_DRAG_THRESHOLD, detectRegions, MAX_ZOOM, getMagnifierDimensions]
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleMapTouchEnd = useCallback(() => {
|
||||||
|
mapTouchStartRef.current = null
|
||||||
|
if (isMobileMapDragging) {
|
||||||
|
setIsMobileMapDragging(false)
|
||||||
|
// Hide magnifier and clear cursor when drag ends
|
||||||
|
setShowMagnifier(false)
|
||||||
|
setTargetOpacity(0)
|
||||||
|
setCursorPosition(null)
|
||||||
|
cursorPositionRef.current = null
|
||||||
|
}
|
||||||
|
}, [isMobileMapDragging])
|
||||||
|
|
||||||
// Mobile magnifier touch handlers - allow panning by dragging on the magnifier
|
// Mobile magnifier touch handlers - allow panning by dragging on the magnifier
|
||||||
const handleMagnifierTouchStart = useCallback((e: React.TouchEvent<HTMLDivElement>) => {
|
const handleMagnifierTouchStart = useCallback((e: React.TouchEvent<HTMLDivElement>) => {
|
||||||
if (e.touches.length !== 1) return // Only handle single-finger touch
|
if (e.touches.length !== 1) return // Only handle single-finger touch
|
||||||
|
|
@ -2390,6 +2474,9 @@ export function MapRenderer({
|
||||||
onMouseMove={handleMouseMove}
|
onMouseMove={handleMouseMove}
|
||||||
onMouseLeave={handleMouseLeave}
|
onMouseLeave={handleMouseLeave}
|
||||||
onClick={handleContainerClick}
|
onClick={handleContainerClick}
|
||||||
|
onTouchStart={handleMapTouchStart}
|
||||||
|
onTouchMove={handleMapTouchMove}
|
||||||
|
onTouchEnd={handleMapTouchEnd}
|
||||||
className={css({
|
className={css({
|
||||||
position: fillContainer ? 'absolute' : 'relative',
|
position: fillContainer ? 'absolute' : 'relative',
|
||||||
top: fillContainer ? 0 : undefined,
|
top: fillContainer ? 0 : undefined,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue