summaryrefslogtreecommitdiff
path: root/node_modules/pigeon-maps/src/overlays
diff options
context:
space:
mode:
authorAlexander Neonxp Kiryukhin <i@neonxp.ru>2024-08-18 13:29:54 +0300
committerAlexander Neonxp Kiryukhin <i@neonxp.ru>2024-08-18 13:29:54 +0300
commitfd70f95224374d23157ee7c0357733102cd0df53 (patch)
treee490c12e021cedaf211b292d5d623baa32a673fc /node_modules/pigeon-maps/src/overlays
initialHEADmaster
Diffstat (limited to 'node_modules/pigeon-maps/src/overlays')
-rw-r--r--node_modules/pigeon-maps/src/overlays/Draggable.tsx181
-rw-r--r--node_modules/pigeon-maps/src/overlays/GeoJson.tsx244
-rw-r--r--node_modules/pigeon-maps/src/overlays/Marker.tsx86
-rw-r--r--node_modules/pigeon-maps/src/overlays/Overlay.tsx23
4 files changed, 534 insertions, 0 deletions
diff --git a/node_modules/pigeon-maps/src/overlays/Draggable.tsx b/node_modules/pigeon-maps/src/overlays/Draggable.tsx
new file mode 100644
index 0000000..4cac3c6
--- /dev/null
+++ b/node_modules/pigeon-maps/src/overlays/Draggable.tsx
@@ -0,0 +1,181 @@
+import React, { useEffect, useRef, useState } from 'react'
+import { PigeonProps, Point } from '../types'
+
+function isDescendentOf(element, ancestor) {
+ while (element) {
+ if (element === ancestor) {
+ return true
+ }
+ element = element.parentElement
+ }
+
+ return false
+}
+
+interface DraggableProps extends PigeonProps {
+ className?: string
+ style?: React.CSSProperties
+
+ children?: React.ReactNode
+
+ onDragStart?: (anchor: Point) => void
+ onDragMove?: (anchor: Point) => void
+ onDragEnd?: (anchor: Point) => void
+}
+
+interface DraggableState {
+ isDragging: boolean
+ startX?: number
+ startY?: number
+ startLeft?: number
+ startTop?: number
+ deltaX: number
+ deltaY: number
+}
+
+const defaultState: DraggableState = {
+ isDragging: false,
+ startX: undefined,
+ startY: undefined,
+ startLeft: undefined,
+ startTop: undefined,
+ deltaX: 0,
+ deltaY: 0,
+}
+
+export function Draggable(props: DraggableProps): JSX.Element {
+ const dragRef = useRef<HTMLDivElement>()
+ const propsRef = useRef<DraggableProps>(props)
+ const stateRef = useRef({ ...defaultState })
+ const [_state, _setState] = useState(defaultState)
+
+ propsRef.current = props
+
+ const setState = (stateUpdate: Partial<DraggableState>): void => {
+ const newState = { ...stateRef.current, ...stateUpdate }
+ stateRef.current = newState
+ _setState(newState)
+ }
+
+ const { mouseEvents, touchEvents } = props.mapProps
+
+ useEffect(() => {
+ const handleDragStart = (event: MouseEvent | TouchEvent) => {
+ if (isDescendentOf(event.target, dragRef.current)) {
+ event.preventDefault()
+
+ setState({
+ isDragging: true,
+ startX: ('touches' in event ? event.touches[0] : event).clientX,
+ startY: ('touches' in event ? event.touches[0] : event).clientY,
+ startLeft: propsRef.current.left,
+ startTop: propsRef.current.top,
+ deltaX: 0,
+ deltaY: 0,
+ })
+
+ if (propsRef.current.onDragStart) {
+ const { left, top, offset, pixelToLatLng } = propsRef.current
+ propsRef.current.onDragMove(pixelToLatLng([left + (offset ? offset[0] : 0), top + (offset ? offset[1] : 0)]))
+ }
+ }
+ }
+
+ const handleDragMove = (event: MouseEvent | TouchEvent) => {
+ if (!stateRef.current.isDragging) {
+ return
+ }
+
+ event.preventDefault()
+
+ const x = ('touches' in event ? event.touches[0] : event).clientX
+ const y = ('touches' in event ? event.touches[0] : event).clientY
+
+ const deltaX = x - stateRef.current.startX
+ const deltaY = y - stateRef.current.startY
+
+ setState({ deltaX, deltaY })
+
+ if (propsRef.current.onDragMove) {
+ const { offset, pixelToLatLng } = propsRef.current
+ const { startLeft, startTop } = stateRef.current
+
+ propsRef.current.onDragMove(
+ pixelToLatLng([startLeft + deltaX + (offset ? offset[0] : 0), startTop + deltaY + (offset ? offset[1] : 0)])
+ )
+ }
+ }
+
+ const handleDragEnd = (event: MouseEvent | TouchEvent) => {
+ if (!stateRef.current.isDragging) {
+ return
+ }
+
+ event.preventDefault()
+
+ const { offset, pixelToLatLng } = propsRef.current
+ const { deltaX, deltaY, startLeft, startTop } = stateRef.current
+
+ propsRef.current.onDragEnd?.(
+ pixelToLatLng([startLeft + deltaX + (offset ? offset[0] : 0), startTop + deltaY + (offset ? offset[1] : 0)])
+ )
+
+ setState({
+ isDragging: false,
+ startX: undefined,
+ startY: undefined,
+ startLeft: undefined,
+ startTop: undefined,
+ deltaX: 0,
+ deltaY: 0,
+ })
+ }
+
+ const wa = (e: string, t: EventListener, o?: AddEventListenerOptions) => window.addEventListener(e, t, o)
+ const wr = (e: string, t: EventListener) => window.removeEventListener(e, t)
+
+ if (mouseEvents) {
+ wa('mousedown', handleDragStart)
+ wa('mousemove', handleDragMove)
+ wa('mouseup', handleDragEnd)
+ }
+
+ if (touchEvents) {
+ wa('touchstart', handleDragStart, { passive: false })
+ wa('touchmove', handleDragMove, { passive: false })
+ wa('touchend', handleDragEnd, { passive: false })
+ }
+
+ return () => {
+ if (mouseEvents) {
+ wr('mousedown', handleDragStart)
+ wr('mousemove', handleDragMove)
+ wr('mouseup', handleDragEnd)
+ }
+
+ if (touchEvents) {
+ wr('touchstart', handleDragStart)
+ wr('touchmove', handleDragMove)
+ wr('touchend', handleDragEnd)
+ }
+ }
+ }, [mouseEvents, touchEvents])
+
+ const { left, top, className, style } = props
+ const { deltaX, deltaY, startLeft, startTop, isDragging } = _state
+
+ return (
+ <div
+ style={{
+ cursor: isDragging ? 'grabbing' : 'grab',
+ ...(style || {}),
+ position: 'absolute',
+ transform: `translate(${isDragging ? startLeft + deltaX : left}px, ${isDragging ? startTop + deltaY : top}px)`,
+ }}
+ ref={dragRef}
+ className={`pigeon-drag-block${className ? ` ${className}` : ''}`}
+ >
+ {props.children}
+ </div>
+ )
+}
diff --git a/node_modules/pigeon-maps/src/overlays/GeoJson.tsx b/node_modules/pigeon-maps/src/overlays/GeoJson.tsx
new file mode 100644
index 0000000..d25cfe6
--- /dev/null
+++ b/node_modules/pigeon-maps/src/overlays/GeoJson.tsx
@@ -0,0 +1,244 @@
+import React, { CSSProperties, SVGProps, useMemo, useEffect, useState } from 'react'
+import { PigeonProps, Point } from '../types'
+
+interface GeoJsonProps extends PigeonProps {
+ className?: string
+ data?: any
+ svgAttributes?: any
+ styleCallback?: any
+ hover?: any
+ feature?: any
+ style?: CSSProperties
+ children?: React.ReactNode
+
+ // callbacks
+ onClick?: ({ event: HTMLMouseEvent, anchor: Point, payload: any }) => void
+ onContextMenu?: ({ event: HTMLMouseEvent, anchor: Point, payload: any }) => void
+ onMouseOver?: ({ event: HTMLMouseEvent, anchor: Point, payload: any }) => void
+ onMouseOut?: ({ event: HTMLMouseEvent, anchor: Point, payload: any }) => void
+}
+
+interface GeoJsonLoaderProps extends GeoJsonProps {
+ link?: string
+}
+
+interface GeoJsonGeometry {
+ type: string
+ coordinates?:
+ | [number, number]
+ | Array<[number, number]>
+ | Array<Array<[number, number]>>
+ | Array<Array<Array<[number, number]>>>
+ geometries?: Array<GeoJsonGeometry>
+}
+
+interface GeometryProps {
+ coordinates?:
+ | [number, number]
+ | Array<[number, number]>
+ | Array<Array<[number, number]>>
+ | Array<Array<Array<[number, number]>>>
+ latLngToPixel?: (latLng: Point, center?: Point, zoom?: number) => Point
+ svgAttributes?: SVGProps<SVGElement>
+ geometry?: GeoJsonGeometry
+}
+
+const defaultSvgAttributes = { fill: '#93c0d099', strokeWidth: '2', stroke: 'white', r: '30' }
+
+export function PointComponent(props: GeometryProps): JSX.Element {
+ const { latLngToPixel } = props
+ const [y, x] = props.coordinates as [number, number]
+ const [cx, cy] = latLngToPixel([x, y])
+ if (props.svgAttributes?.path) {
+ const path = `M${cx},${cy}c${props.svgAttributes.path.split(/[c|C|L|l|v|V|h|H](.*)/s)[1]}`
+ return <path d={path} {...(props.svgAttributes as SVGProps<SVGCircleElement>)} />
+ }
+ return <circle cx={cx} cy={cy} {...(props.svgAttributes as SVGProps<SVGCircleElement>)} />
+}
+
+export function MultiPoint(props: GeometryProps): JSX.Element {
+ return (
+ <>
+ {props.coordinates.map((point, i) => (
+ <PointComponent {...props} coordinates={point} key={i} />
+ ))}
+ </>
+ )
+}
+
+export function LineString(props: GeometryProps): JSX.Element {
+ const { latLngToPixel } = props
+ const p =
+ 'M' +
+ (props.coordinates as Array<[number, number]>).reduce((a, [y, x]) => {
+ const [v, w] = latLngToPixel([x, y])
+ return a + ' ' + v + ' ' + w
+ }, '')
+
+ return <path d={p} {...(props.svgAttributes as SVGProps<SVGPathElement>)} />
+}
+
+export function MultiLineString(props: GeometryProps): JSX.Element {
+ return (
+ <>
+ {props.coordinates.map((line, i) => (
+ <LineString {...props} coordinates={line} key={i} />
+ ))}
+ </>
+ )
+}
+
+export function Polygon(props: GeometryProps): JSX.Element {
+ const { latLngToPixel } = props
+ // GeoJson polygons is a collection of linear rings
+ const p = (props.coordinates as Array<Array<[number, number]>>).reduce(
+ (a, part) =>
+ a +
+ ' M' +
+ part.reduce((a, [y, x]) => {
+ const [v, w] = latLngToPixel([x, y])
+ return a + ' ' + v + ' ' + w
+ }, '') +
+ 'Z',
+ ''
+ )
+ return <path d={p} {...(props.svgAttributes as SVGProps<SVGPathElement>)} />
+}
+
+export function MultiPolygon(props: GeometryProps): JSX.Element {
+ return (
+ <>
+ {props.coordinates.map((polygon, i) => (
+ <Polygon {...props} coordinates={polygon} key={i} />
+ ))}
+ </>
+ )
+}
+
+export function GeometryCollection(props: GeometryProps): JSX.Element {
+ const renderer = {
+ Point: PointComponent,
+ MultiPoint,
+ LineString,
+ MultiLineString,
+ Polygon,
+ MultiPolygon,
+ }
+
+ const { type, coordinates, geometries } = props.geometry
+
+ if (type === 'GeometryCollection') {
+ return (
+ <>
+ {geometries.map((geometry, i) => (
+ <GeometryCollection key={i} {...props} geometry={geometry} />
+ ))}
+ </>
+ )
+ }
+
+ const Component = renderer[type]
+
+ if (Component === undefined) {
+ console.warn(`The GeoJson Type ${type} is not known`)
+ return null
+ }
+ return (
+ <Component
+ latLngToPixel={props.latLngToPixel}
+ geometry={props.geometry}
+ coordinates={coordinates}
+ svgAttributes={props.svgAttributes}
+ />
+ )
+}
+
+export function GeoJsonFeature(props: GeoJsonProps): JSX.Element {
+ const [internalHover, setInternalHover] = useState(props.hover || false)
+ const hover = props.hover !== undefined ? props.hover : internalHover
+ const callbackSvgAttributes = props.styleCallback && props.styleCallback(props.feature, hover)
+ const svgAttributes = callbackSvgAttributes
+ ? props.svgAttributes
+ ? { ...props.svgAttributes, ...callbackSvgAttributes }
+ : callbackSvgAttributes
+ : props.svgAttributes
+ ? props.svgAttributes
+ : defaultSvgAttributes
+
+ const eventParameters = (event: React.MouseEvent<SVGElement>) => ({
+ event,
+ anchor: props.anchor,
+ payload: props.feature,
+ })
+
+ return (
+ <g
+ clipRule="evenodd"
+ style={{ pointerEvents: 'auto' }}
+ onClick={props.onClick ? (event) => props.onClick(eventParameters(event)) : null}
+ onContextMenu={props.onContextMenu ? (event) => props.onContextMenu(eventParameters(event)) : null}
+ onMouseOver={(event) => {
+ props.onMouseOver && props.onMouseOver(eventParameters(event))
+ setInternalHover(true)
+ }}
+ onMouseOut={(event) => {
+ props.onMouseOut && props.onMouseOut(eventParameters(event))
+ setInternalHover(false)
+ }}
+ >
+ <GeometryCollection {...props} {...props.feature} svgAttributes={svgAttributes} />
+ </g>
+ )
+}
+
+export function GeoJson(props: GeoJsonProps): JSX.Element {
+ const { width, height } = props.mapState
+
+ return (
+ <div
+ style={{
+ position: 'absolute',
+ left: '0',
+ top: '0',
+ pointerEvents: 'none',
+ cursor: 'pointer',
+ ...(props.style || {}),
+ }}
+ className={props.className ? `${props.className} pigeon-click-block` : 'pigeon-click-block'}
+ >
+ <svg
+ width={width}
+ height={height}
+ viewBox={`0 0 ${width} ${height}`}
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ {props.data && props.data.features.map((feature, i) => <GeoJsonFeature key={i} {...props} feature={feature} />)}
+
+ {React.Children.map(props.children, (child) => {
+ if (!child) {
+ return null
+ }
+
+ if (!React.isValidElement(child)) {
+ return child
+ }
+
+ return React.cloneElement(child, props)
+ })}
+ </svg>
+ </div>
+ )
+}
+
+export function GeoJsonLoader(props: GeoJsonLoaderProps): JSX.Element {
+ const [data, setData] = useState(props.data ? props.data : null)
+
+ useEffect(() => {
+ fetch(props.link)
+ .then((response) => response.json())
+ .then((data) => setData(data))
+ }, [props.link])
+
+ return data ? <GeoJson data={data} {...props} /> : null
+}
diff --git a/node_modules/pigeon-maps/src/overlays/Marker.tsx b/node_modules/pigeon-maps/src/overlays/Marker.tsx
new file mode 100644
index 0000000..f1970c2
--- /dev/null
+++ b/node_modules/pigeon-maps/src/overlays/Marker.tsx
@@ -0,0 +1,86 @@
+import React, { useState } from 'react'
+import { PigeonProps } from '../types'
+
+interface MarkerProps extends PigeonProps {
+ color?: string
+ payload?: any
+
+ width?: number
+ height?: number
+
+ // optional modifiers
+ hover?: boolean
+ style?: React.CSSProperties
+ className?: string
+
+ children?: JSX.Element
+
+ // callbacks
+ onClick?: ({ event: HTMLMouseEvent, anchor: Point, payload: any }) => void
+ onContextMenu?: ({ event: HTMLMouseEvent, anchor: Point, payload: any }) => void
+ onMouseOver?: ({ event: HTMLMouseEvent, anchor: Point, payload: any }) => void
+ onMouseOut?: ({ event: HTMLMouseEvent, anchor: Point, payload: any }) => void
+}
+
+export function Marker(props: MarkerProps): JSX.Element {
+ const width =
+ typeof props.width !== 'undefined'
+ ? props.width
+ : typeof props.height !== 'undefined'
+ ? (props.height * 29) / 34
+ : 29
+ const height =
+ typeof props.height !== 'undefined'
+ ? props.height
+ : typeof props.width !== 'undefined'
+ ? (props.width * 34) / 29
+ : 34
+ const [internalHover, setInternalHover] = useState(props.hover || false)
+ const hover = typeof props.hover === 'undefined' ? internalHover : props.hover
+ const color = props.color || '#93C0D0'
+
+ // what do you expect to get back with the event
+ const eventParameters = (event: React.MouseEvent) => ({
+ event,
+ anchor: props.anchor,
+ payload: props.payload,
+ })
+
+ return (
+ <div
+ style={{
+ position: 'absolute',
+ transform: `translate(${props.left - width / 2}px, ${props.top - (height - 1)}px)`,
+ filter: hover ? 'drop-shadow(0 0 4px rgba(0, 0, 0, .3))' : '',
+ pointerEvents: 'none',
+ cursor: 'pointer',
+ ...(props.style || {}),
+ }}
+ className={props.className ? `${props.className} pigeon-click-block` : 'pigeon-click-block'}
+ onClick={props.onClick ? (event) => props.onClick(eventParameters(event)) : null}
+ onContextMenu={props.onContextMenu ? (event) => props.onContextMenu(eventParameters(event)) : null}
+ onMouseOver={(event) => {
+ props.onMouseOver && props.onMouseOver(eventParameters(event))
+ setInternalHover(true)
+ }}
+ onMouseOut={(event) => {
+ props.onMouseOut && props.onMouseOut(eventParameters(event))
+ setInternalHover(false)
+ }}
+ >
+ {props.children || (
+ <svg width={width} height={height} viewBox="0 0 61 71" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <g style={{ pointerEvents: 'auto' }}>
+ <path
+ d="M52 31.5C52 36.8395 49.18 42.314 45.0107 47.6094C40.8672 52.872 35.619 57.678 31.1763 61.6922C30.7916 62.0398 30.2084 62.0398 29.8237 61.6922C25.381 57.678 20.1328 52.872 15.9893 47.6094C11.82 42.314 9 36.8395 9 31.5C9 18.5709 18.6801 9 30.5 9C42.3199 9 52 18.5709 52 31.5Z"
+ fill={color}
+ stroke="white"
+ strokeWidth="4"
+ />
+ <circle cx="30.5" cy="30.5" r="8.5" fill="white" opacity={hover ? 0.98 : 0.6} />
+ </g>
+ </svg>
+ )}
+ </div>
+ )
+}
diff --git a/node_modules/pigeon-maps/src/overlays/Overlay.tsx b/node_modules/pigeon-maps/src/overlays/Overlay.tsx
new file mode 100644
index 0000000..0bc8ecd
--- /dev/null
+++ b/node_modules/pigeon-maps/src/overlays/Overlay.tsx
@@ -0,0 +1,23 @@
+import React from 'react'
+import { PigeonProps } from '../types'
+
+interface OverlayProps extends PigeonProps {
+ style?: React.CSSProperties
+ className?: string
+ children?: React.ReactNode
+}
+
+export function Overlay(props: OverlayProps) {
+ return (
+ <div
+ style={{
+ position: 'absolute',
+ transform: `translate(${props.left}px, ${props.top}px)`,
+ ...(props.style || {}),
+ }}
+ className={props.className ? `${props.className} pigeon-click-block` : 'pigeon-click-block'}
+ >
+ {props.children}
+ </div>
+ )
+}