2025-10-12 12:45:16 +02:00

181 lines
5.9 KiB
TypeScript

/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { DomEvent } from 'leaflet'
import { useEffect, useRef, useState } from 'react'
import SVG from 'react-inlinesvg'
import PlusSVG from '#assets/plus.svg'
import { useAppState } from '#components/AppShell/hooks/useAppState'
import { useLayers } from '#components/Map/hooks/useLayers'
import { useHasUserPermission } from '#components/Map/hooks/usePermissions'
import useWindowDimensions from '#components/Map/hooks/useWindowDimension'
import type {
MouseEvent as ReactMouseEvent,
TouchEvent as ReactTouchEvent,
PointerEvent as ReactPointerEvent,
} from 'react'
export default function AddButton({
triggerAction,
}: {
triggerAction: React.Dispatch<React.SetStateAction<any>>
}) {
const layers = useLayers()
const hasUserPermission = useHasUserPermission()
const appState = useAppState()
const { width } = useWindowDimensions()
const isMobile = width < 768
const [isOpen, setIsOpen] = useState(false)
const containerRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const container = containerRef.current
if (!container) return
DomEvent.disableClickPropagation(container)
DomEvent.disableScrollPropagation(container)
const stopPointerPropagation = (event: PointerEvent) => {
event.stopPropagation()
}
DomEvent.on(container, 'pointerdown pointerup pointermove', stopPointerPropagation)
return () => {
DomEvent.off(container, 'pointerdown pointerup pointermove', stopPointerPropagation)
}
}, [])
const canAddItems = () => {
let canAdd = false
layers.map((layer) => {
if (
layer.api?.createItem &&
hasUserPermission(layer.api.collectionName!, 'create', undefined, layer) &&
layer.listed
)
canAdd = true
return null
})
return canAdd
}
const stopPropagation = (
event:
| ReactMouseEvent<HTMLElement>
| ReactTouchEvent<HTMLElement>
| ReactPointerEvent<HTMLElement>,
): void => {
event.preventDefault()
event.stopPropagation()
if (
'nativeEvent' in event &&
typeof event.nativeEvent.stopImmediatePropagation === 'function'
) {
event.nativeEvent.stopImmediatePropagation()
}
}
const handleLayerSelect = (
event: ReactMouseEvent<HTMLButtonElement> | ReactTouchEvent<HTMLButtonElement>,
layer: (typeof layers)[number],
) => {
stopPropagation(event)
triggerAction(layer)
setIsOpen(false)
}
return (
<>
{canAddItems() ? (
<div
ref={containerRef}
className={`tw:dropdown tw:dropdown-top tw:dropdown-end tw:z-500 tw:absolute tw:right-4 tw:bottom-4 ${isOpen ? 'tw:dropdown-open' : ''}`}
>
<button
type='button'
className='tw:z-500 tw:btn tw:btn-circle tw:btn-lg tw:shadow tw:bg-base-100'
onPointerDown={(event) => {
stopPropagation(event)
}}
onPointerUp={(event) => {
stopPropagation(event)
}}
onMouseDown={(event) => {
stopPropagation(event)
}}
onMouseUp={(event) => {
stopPropagation(event)
}}
onClick={(event) => {
stopPropagation(event)
setIsOpen((prev) => !prev)
}}
onTouchStart={(event) => {
stopPropagation(event)
}}
onTouchEnd={(event) => {
stopPropagation(event)
setIsOpen((prev) => !prev)
}}
>
<SVG src={PlusSVG} className='tw:h-5 tw:w-5' />
</button>
<ul
tabIndex={0}
className='tw:dropdown-content tw:pr-1 tw:list-none tw:space-y-3 tw:pb-3'
>
{layers.map(
(layer) =>
layer.api?.createItem &&
hasUserPermission(layer.api.collectionName!, 'create', undefined, layer) &&
layer.listed && (
<li key={layer.name}>
<a>
<div
className={`tw:tooltip tw:tooltip-left ${isMobile ? 'tw:tooltip-open' : ''}`}
data-tip={layer.menuText}
>
<button
tabIndex={0}
className='tw:z-500 tw:border-0 tw:p-0 tw:w-10 tw:h-10 tw:cursor-pointer tw:rounded-full tw:mouse tw:drop-shadow-md tw:transition tw:ease-in tw:duration-200 tw:focus:outline-hidden tw:flex tw:items-center tw:justify-center'
style={{ backgroundColor: layer.menuColor || '#777' }}
onMouseDown={(event) => {
stopPropagation(event)
}}
onMouseUp={(event) => {
stopPropagation(event)
}}
onClick={(event) => {
handleLayerSelect(event, layer)
}}
onTouchStart={(event) => {
stopPropagation(event)
}}
onTouchEnd={(event) => {
handleLayerSelect(event, layer)
}}
>
<img
src={appState.assetsApi.url + layer.markerIcon.image}
style={{
filter: 'invert(100%) brightness(200%)',
width: `${(layer.markerIcon.size ?? 18) * 1.3}px`,
}}
/>
</button>
</div>
</a>
</li>
),
)}
</ul>
</div>
) : (
''
)}
</>
)
}