All files / src/context MapContext.tsx

0% Statements 0/83
0% Branches 0/1
0% Functions 0/1
0% Lines 0/83

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109                                                                                                                                                                                                                         
import { EmptyRequest } from "@shared/proto/cline/common"
import type { MapLayer } from "@shared/proto/cline/map"
import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from "react"
import { MapServiceClient } from "../services/grpc-client"
 
interface MapContextType {
	layers: MapLayer[]
	addLayer: (layer: MapLayer) => void
	removeLayer: (layerId: string) => void
	clearLayers: () => void
	getLayer: (layerId: string) => MapLayer | undefined
}
 
const MapContext = createContext<MapContextType | undefined>(undefined)
const MAP_OPERATION_KEY = "__operation"
 
export const MapContextProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
	const [layers, setLayers] = useState<MapLayer[]>([])
	const subscriptionRef = useRef<(() => void) | null>(null)
 
	const applyIncomingLayer = useCallback((prevLayers: MapLayer[], incomingLayer: MapLayer): MapLayer[] => {
		const operation = incomingLayer.metadata?.[MAP_OPERATION_KEY]
 
		if (operation === "clear") {
			return []
		}
 
		if (operation === "remove") {
			return prevLayers.filter((layer) => layer.id !== incomingLayer.id)
		}
 
		const existingIndex = prevLayers.findIndex((layer) => layer.id === incomingLayer.id)
		if (existingIndex >= 0) {
			const nextLayers = [...prevLayers]
			nextLayers[existingIndex] = incomingLayer
			return nextLayers
		}
		return [...prevLayers, incomingLayer]
	}, [])
 
	// Subscribe to map layer updates from backend
	useEffect(() => {
		MapServiceClient.getMapState(EmptyRequest.create({}))
			.then((response) => {
				setLayers(response.layers || [])
			})
			.catch((error) => {
				console.error("[MapContext] Failed to fetch initial map state:", error)
			})
 
		subscriptionRef.current = MapServiceClient.subscribeToMapLayers(EmptyRequest.create({}), {
			onResponse: (layer: MapLayer) => {
				console.log("[MapContext] Received layer update:", layer.id)
				setLayers((prevLayers) => applyIncomingLayer(prevLayers, layer))
			},
			onError: (error) => {
				console.error("[MapContext] Error in layer subscription:", error)
			},
			onComplete: () => {
				console.log("[MapContext] Layer subscription completed")
			},
		})
 
		return () => {
			if (subscriptionRef.current) {
				subscriptionRef.current()
				subscriptionRef.current = null
			}
		}
	}, [applyIncomingLayer])
 
	const addLayer = useCallback((layer: MapLayer) => {
		setLayers((prevLayers) => {
			const existingIndex = prevLayers.findIndex((l) => l.id === layer.id)
			if (existingIndex >= 0) {
				const newLayers = [...prevLayers]
				newLayers[existingIndex] = layer
				return newLayers
			}
			return [...prevLayers, layer]
		})
	}, [])
 
	const removeLayer = useCallback((layerId: string) => {
		setLayers((prevLayers) => prevLayers.filter((l) => l.id !== layerId))
	}, [])
 
	const clearLayers = useCallback(() => {
		setLayers([])
	}, [])
 
	const getLayer = useCallback(
		(layerId: string) => {
			return layers.find((l) => l.id === layerId)
		},
		[layers],
	)
 
	return <MapContext.Provider value={{ layers, addLayer, removeLayer, clearLayers, getLayer }}>{children}</MapContext.Provider>
}
 
export const useMapContext = () => {
	const context = useContext(MapContext)
	if (!context) {
		throw new Error("useMapContext must be used within MapContextProvider")
	}
	return context
}