feat(know-your-world): wire interaction state machine to MapRenderer
Add state machine integration with sync effects for incremental migration: - Call useInteractionStateMachine hook in MapRenderer - Add sync effects to drive state machine from existing boolean state: - Precision mode sync (pointerLocked, isReleasingPointerLock) - Magnifier visibility sync (showMagnifier) - Magnifier expanded sync (isMagnifierExpanded) - Add debug logging to compare machine state vs boolean state - Clean up unused imports (CompassCrosshair, FeedbackType) - Organize imports per Biome rules The state machine now runs in parallel with existing state. Next steps: 1. Test sync effects work correctly in dev console 2. Migrate handlers to dispatch events directly to state machine 3. Replace rendering conditionals with state machine checks 4. Remove old boolean state once migration is complete 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
e4d6748d70
commit
7e55953eee
File diff suppressed because it is too large
Load Diff
|
|
@ -6,11 +6,11 @@
|
|||
*/
|
||||
|
||||
export type {
|
||||
InteractionState,
|
||||
InteractionEvent,
|
||||
InteractionContext,
|
||||
TouchPoint,
|
||||
InteractionEvent,
|
||||
InteractionState,
|
||||
MachineState,
|
||||
TouchPoint,
|
||||
UseInteractionStateMachineReturn,
|
||||
} from './useInteractionStateMachine'
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
'use client'
|
||||
|
||||
import { useReducer, useCallback, useRef, useMemo } from 'react'
|
||||
import { useCallback, useMemo, useReducer, useRef } from 'react'
|
||||
|
||||
// ============================================================================
|
||||
// State Types
|
||||
|
|
@ -680,10 +680,7 @@ export function useInteractionStateMachine(): UseInteractionStateMachineReturn {
|
|||
]
|
||||
)
|
||||
|
||||
const showCursor = useMemo(
|
||||
() => isHovering || showMagnifier,
|
||||
[isHovering, showMagnifier]
|
||||
)
|
||||
const showCursor = useMemo(() => isHovering || showMagnifier, [isHovering, showMagnifier])
|
||||
|
||||
const isAnyPanning = useMemo(
|
||||
() => isMagnifierPanning || isMapPanningMobile || isMapPanningDesktop,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
*/
|
||||
|
||||
import { forceCollide, forceSimulation, forceX, forceY, type SimulationNodeDatum } from 'd3-force'
|
||||
import { RefObject, useEffect, useState } from 'react'
|
||||
import { type RefObject, useEffect, useState } from 'react'
|
||||
|
||||
import type { MapData, MapRegion } from '../../types'
|
||||
import { getArrowStartPoint, getRenderedViewport } from './labelUtils'
|
||||
|
|
|
|||
|
|
@ -90,15 +90,7 @@ export const LetterDisplay = memo(function LetterDisplay({
|
|||
const isSpace = char === ' '
|
||||
|
||||
if (isSpace) {
|
||||
return (
|
||||
<StyledLetter
|
||||
key={index}
|
||||
char={char}
|
||||
status="space"
|
||||
isDark={isDark}
|
||||
index={index}
|
||||
/>
|
||||
)
|
||||
return <StyledLetter key={index} char={char} status="space" isDark={isDark} index={index} />
|
||||
}
|
||||
|
||||
// Get current index before incrementing
|
||||
|
|
@ -113,15 +105,7 @@ export const LetterDisplay = memo(function LetterDisplay({
|
|||
isComplete
|
||||
)
|
||||
|
||||
return (
|
||||
<StyledLetter
|
||||
key={index}
|
||||
char={char}
|
||||
status={status}
|
||||
isDark={isDark}
|
||||
index={index}
|
||||
/>
|
||||
)
|
||||
return <StyledLetter key={index} char={char} status={status} isDark={isDark} index={index} />
|
||||
})
|
||||
}, [regionName, confirmedCount, requiredLetters, isComplete, isDark])
|
||||
|
||||
|
|
|
|||
|
|
@ -89,10 +89,7 @@ export function getLetterStatus(
|
|||
* @param isDark - Whether dark mode is active
|
||||
* @returns CSS properties for the letter
|
||||
*/
|
||||
export function getLetterStyles(
|
||||
status: LetterStatus,
|
||||
isDark: boolean
|
||||
): React.CSSProperties {
|
||||
export function getLetterStyles(status: LetterStatus, isDark: boolean): React.CSSProperties {
|
||||
const baseStyles: React.CSSProperties = {
|
||||
transition: 'all 0.15s ease-out',
|
||||
}
|
||||
|
|
@ -129,10 +126,7 @@ export function getLetterStyles(
|
|||
* @param requiredLetters - Number of letters required
|
||||
* @returns Progress value (0 = none, 1 = complete)
|
||||
*/
|
||||
export function calculateProgress(
|
||||
confirmedCount: number,
|
||||
requiredLetters: number
|
||||
): number {
|
||||
export function calculateProgress(confirmedCount: number, requiredLetters: number): number {
|
||||
if (requiredLetters === 0) return 1
|
||||
return Math.min(1, confirmedCount / requiredLetters)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,12 +91,7 @@ export function useLetterConfirmation({
|
|||
// Get letter status for display
|
||||
const getLetterStatus = useCallback(
|
||||
(nonSpaceIndex: number): LetterStatus => {
|
||||
return getLetterStatusUtil(
|
||||
nonSpaceIndex,
|
||||
confirmedCount,
|
||||
requiredLetters,
|
||||
isComplete
|
||||
)
|
||||
return getLetterStatusUtil(nonSpaceIndex, confirmedCount, requiredLetters, isComplete)
|
||||
},
|
||||
[confirmedCount, requiredLetters, isComplete]
|
||||
)
|
||||
|
|
@ -109,10 +104,7 @@ export function useLetterConfirmation({
|
|||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
// Ignore if typing in an input or textarea
|
||||
if (
|
||||
e.target instanceof HTMLInputElement ||
|
||||
e.target instanceof HTMLTextAreaElement
|
||||
) {
|
||||
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -211,11 +211,7 @@ export const MagnifierControls = memo(function MagnifierControls({
|
|||
<>
|
||||
{/* Expand button - top-right corner (when not expanded and not pointer locked) */}
|
||||
{!isExpanded && !pointerLocked && (
|
||||
<ControlButton
|
||||
position="top-right"
|
||||
style="icon"
|
||||
onClick={onExpand}
|
||||
>
|
||||
<ControlButton position="top-right" style="icon" onClick={onExpand}>
|
||||
<ExpandIcon isDark={isDark} />
|
||||
</ControlButton>
|
||||
)}
|
||||
|
|
@ -234,11 +230,7 @@ export const MagnifierControls = memo(function MagnifierControls({
|
|||
|
||||
{/* Full Map button - bottom-left corner (when expanded) */}
|
||||
{isExpanded && showSelectButton && (
|
||||
<ControlButton
|
||||
position="bottom-left"
|
||||
style="secondary"
|
||||
onClick={onExitExpanded}
|
||||
>
|
||||
<ControlButton position="bottom-left" style="secondary" onClick={onExitExpanded}>
|
||||
Full Map
|
||||
</ControlButton>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,12 @@ export interface MagnifierRegionsProps {
|
|||
/** Get player ID who found a region */
|
||||
getPlayerWhoFoundRegion: (regionId: string) => string | null
|
||||
/** Get region fill color */
|
||||
getRegionColor: (regionId: string, isFound: boolean, isHovered: boolean, isDark: boolean) => string
|
||||
getRegionColor: (
|
||||
regionId: string,
|
||||
isFound: boolean,
|
||||
isHovered: boolean,
|
||||
isDark: boolean
|
||||
) => string
|
||||
/** Get region stroke color */
|
||||
getRegionStroke: (isFound: boolean, isDark: boolean) => string
|
||||
/** Whether to show region outline */
|
||||
|
|
@ -80,13 +85,8 @@ export const MagnifierRegions = memo(function MagnifierRegions({
|
|||
getRegionStroke,
|
||||
showOutline,
|
||||
}: MagnifierRegionsProps) {
|
||||
const {
|
||||
regionsFound,
|
||||
hoveredRegion,
|
||||
celebrationRegionId,
|
||||
giveUpRegionId,
|
||||
isGiveUpAnimating,
|
||||
} = regionState
|
||||
const { regionsFound, hoveredRegion, celebrationRegionId, giveUpRegionId, isGiveUpAnimating } =
|
||||
regionState
|
||||
const { celebrationFlash, giveUpFlash } = flashProgress
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@
|
|||
|
||||
import { memo, useMemo } from 'react'
|
||||
import { getRenderedViewport } from '../labels'
|
||||
import { getAdjustedMagnifiedDimensions, getMagnifierDimensions } from '../../utils/magnifierDimensions'
|
||||
import {
|
||||
getAdjustedMagnifiedDimensions,
|
||||
getMagnifierDimensions,
|
||||
} from '../../utils/magnifierDimensions'
|
||||
|
||||
// ============================================================================
|
||||
// Types
|
||||
|
|
@ -124,12 +127,7 @@ export const ZoomLines = memo(function ZoomLines({
|
|||
isDark,
|
||||
}: ZoomLinesProps) {
|
||||
// Memoize all the calculated values
|
||||
const {
|
||||
paths,
|
||||
visibleCorners,
|
||||
lineColor,
|
||||
glowColor,
|
||||
} = useMemo(() => {
|
||||
const { paths, visibleCorners, lineColor, glowColor } = useMemo(() => {
|
||||
// Calculate leftover rectangle dimensions (area not covered by UI elements)
|
||||
const leftoverWidth = containerRect.width - safeZoneMargins.left - safeZoneMargins.right
|
||||
const leftoverHeight = containerRect.height - safeZoneMargins.top - safeZoneMargins.bottom
|
||||
|
|
@ -210,14 +208,7 @@ export const ZoomLines = memo(function ZoomLines({
|
|||
magTop + magnifierHeight
|
||||
)
|
||||
// Check if line passes through indicator
|
||||
const passesThroughInd = linePassesThroughRect(
|
||||
from,
|
||||
to,
|
||||
indTL.x,
|
||||
indTL.y,
|
||||
indBR.x,
|
||||
indBR.y
|
||||
)
|
||||
const passesThroughInd = linePassesThroughRect(from, to, indTL.x, indTL.y, indBR.x, indBR.y)
|
||||
return !passesThroughMag && !passesThroughInd
|
||||
})
|
||||
|
||||
|
|
@ -233,9 +224,7 @@ export const ZoomLines = memo(function ZoomLines({
|
|||
: isDark
|
||||
? '#60a5fa'
|
||||
: '#3b82f6' // blue
|
||||
const calculatedGlowColor = isHighZoom
|
||||
? 'rgba(251, 191, 36, 0.6)'
|
||||
: 'rgba(96, 165, 250, 0.6)'
|
||||
const calculatedGlowColor = isHighZoom ? 'rgba(251, 191, 36, 0.6)' : 'rgba(96, 165, 250, 0.6)'
|
||||
|
||||
return {
|
||||
paths: calculatedPaths,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,12 @@
|
|||
|
||||
'use client'
|
||||
|
||||
import React, { useCallback, useRef, type RefObject, type TouchEvent as ReactTouchEvent } from 'react'
|
||||
import React, {
|
||||
useCallback,
|
||||
useRef,
|
||||
type RefObject,
|
||||
type TouchEvent as ReactTouchEvent,
|
||||
} from 'react'
|
||||
|
||||
import type { UseMagnifierStateReturn } from './useMagnifierState'
|
||||
|
||||
|
|
@ -223,8 +228,10 @@ export function useMagnifierTouch(options: UseMagnifierTouchOptions): UseMagnifi
|
|||
totalDeltaRef.current.y += deltaY
|
||||
|
||||
// Track if user has moved significantly
|
||||
if (Math.abs(totalDeltaRef.current.x) > moveThreshold ||
|
||||
Math.abs(totalDeltaRef.current.y) > moveThreshold) {
|
||||
if (
|
||||
Math.abs(totalDeltaRef.current.x) > moveThreshold ||
|
||||
Math.abs(totalDeltaRef.current.y) > moveThreshold
|
||||
) {
|
||||
magnifierState.didMoveRef.current = true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -103,9 +103,7 @@ export function MapRendererProvider({ children, value }: MapRendererProviderProp
|
|||
]
|
||||
)
|
||||
|
||||
return (
|
||||
<MapRendererContext.Provider value={memoizedValue}>{children}</MapRendererContext.Provider>
|
||||
)
|
||||
return <MapRendererContext.Provider value={memoizedValue}>{children}</MapRendererContext.Provider>
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
|
|
|||
|
|
@ -177,14 +177,7 @@ export interface FlashProgress {
|
|||
/**
|
||||
* Hot/cold feedback type for visual indicators
|
||||
*/
|
||||
export type HotColdFeedbackType =
|
||||
| 'freezing'
|
||||
| 'cold'
|
||||
| 'cool'
|
||||
| 'warm'
|
||||
| 'hot'
|
||||
| 'burning'
|
||||
| null
|
||||
export type HotColdFeedbackType = 'freezing' | 'cold' | 'cool' | 'warm' | 'hot' | 'burning' | null
|
||||
|
||||
/**
|
||||
* Heat-based styling for borders and glows
|
||||
|
|
|
|||
|
|
@ -96,13 +96,7 @@ export function screenToSVG(
|
|||
svgRect: DOMRect,
|
||||
viewBox: ViewBoxComponents
|
||||
): SVGPosition {
|
||||
const viewport = getRenderedViewport(
|
||||
svgRect,
|
||||
viewBox.x,
|
||||
viewBox.y,
|
||||
viewBox.width,
|
||||
viewBox.height
|
||||
)
|
||||
const viewport = getRenderedViewport(svgRect, viewBox.x, viewBox.y, viewBox.width, viewBox.height)
|
||||
|
||||
// Calculate offset from container origin to SVG rendered content
|
||||
const svgOffsetX = svgRect.left - containerRect.left + viewport.letterboxX
|
||||
|
|
@ -130,13 +124,7 @@ export function svgToScreen(
|
|||
svgRect: DOMRect,
|
||||
viewBox: ViewBoxComponents
|
||||
): CursorPosition {
|
||||
const viewport = getRenderedViewport(
|
||||
svgRect,
|
||||
viewBox.x,
|
||||
viewBox.y,
|
||||
viewBox.width,
|
||||
viewBox.height
|
||||
)
|
||||
const viewport = getRenderedViewport(svgRect, viewBox.x, viewBox.y, viewBox.width, viewBox.height)
|
||||
|
||||
// Calculate offset from container origin to SVG rendered content
|
||||
const svgOffsetX = svgRect.left - containerRect.left + viewport.letterboxX
|
||||
|
|
|
|||
Loading…
Reference in New Issue