369 lines
12 KiB
TypeScript

/* eslint-disable @typescript-eslint/restrict-plus-operands */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/prefer-optional-chain */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { Children, isValidElement, useEffect, useState } from 'react'
import { Marker, Tooltip } from 'react-leaflet'
import { encodeTag } from '#utils/FormatTags'
import { getValue } from '#utils/GetValue'
import { hashTagRegex } from '#utils/HashTagRegex'
import MarkerIconFactory from '#utils/MarkerIconFactory'
import { randomColor } from '#utils/RandomColor'
import {
useFilterTags,
useIsGroupTypeVisible,
useIsLayerVisible,
useVisibleGroupType,
} from './hooks/useFilter'
import { useAllItemsLoaded, useItems, useSetItemsApi, useSetItemsData } from './hooks/useItems'
import { useAddMarker, useAddPopup, useLeafletRefs } from './hooks/useLeafletRefs'
import { useSelectPosition, useSetMarkerClicked } from './hooks/useSelectPosition'
import { useAddTag, useAllTagsLoaded, useGetItemTags, useTags } from './hooks/useTags'
import { ItemFormPopup } from './Subcomponents/ItemFormPopup'
import { ItemViewPopup } from './Subcomponents/ItemViewPopup'
import type { Item } from '#types/Item'
import type { LayerProps } from '#types/LayerProps'
import type { Tag } from '#types/Tag'
import type { Popup } from 'leaflet'
import type { ReactElement, ReactNode } from 'react'
export const Layer = ({
data,
children,
name = 'places',
menuIcon = 'MapPinIcon',
menuText = 'add new place',
menuColor = '#2E7D32',
markerIcon = 'point',
markerShape = 'circle',
markerDefaultColor = '#777',
markerDefaultColor2 = 'RGBA(35, 31, 32, 0.2)',
api,
itemType,
itemNameField = 'name',
itemSubnameField,
itemTextField = 'text',
itemAvatarField,
itemColorField,
itemOwnerField,
itemLatitudeField = 'position.coordinates.1',
itemLongitudeField = 'position.coordinates.0',
itemTagsField,
itemOffersField,
itemNeedsField,
onlyOnePerOwner = false,
customEditLink,
customEditParameter,
// eslint-disable-next-line camelcase
public_edit_items,
listed = true,
setItemFormPopup,
itemFormPopup,
clusterRef,
}: LayerProps) => {
const filterTags = useFilterTags()
const items = useItems()
const setItemsApi = useSetItemsApi()
const setItemsData = useSetItemsData()
const getItemTags = useGetItemTags()
const addMarker = useAddMarker()
const addPopup = useAddPopup()
const leafletRefs = useLeafletRefs()
const allTagsLoaded = useAllTagsLoaded()
const allItemsLoaded = useAllItemsLoaded()
const setMarkerClicked = useSetMarkerClicked()
const selectPosition = useSelectPosition()
const tags = useTags()
const addTag = useAddTag()
const [newTagsToAdd, setNewTagsToAdd] = useState<Tag[]>([])
const [tagsReady, setTagsReady] = useState<boolean>(false)
const isLayerVisible = useIsLayerVisible()
const isGroupTypeVisible = useIsGroupTypeVisible()
const visibleGroupTypes = useVisibleGroupType()
useEffect(() => {
data &&
setItemsData({
data,
children,
name,
menuIcon,
menuText,
menuColor,
markerIcon,
markerShape,
markerDefaultColor,
markerDefaultColor2,
api,
itemType,
itemNameField,
itemSubnameField,
itemTextField,
itemAvatarField,
itemColorField,
itemOwnerField,
itemTagsField,
itemOffersField,
itemNeedsField,
onlyOnePerOwner,
customEditLink,
customEditParameter,
// eslint-disable-next-line camelcase
public_edit_items,
listed,
setItemFormPopup,
itemFormPopup,
clusterRef,
})
api &&
setItemsApi({
data,
children,
name,
menuIcon,
menuText,
menuColor,
markerIcon,
markerShape,
markerDefaultColor,
markerDefaultColor2,
api,
itemType,
itemNameField,
itemSubnameField,
itemTextField,
itemAvatarField,
itemColorField,
itemOwnerField,
itemTagsField,
itemOffersField,
itemNeedsField,
onlyOnePerOwner,
customEditLink,
customEditParameter,
// eslint-disable-next-line camelcase
public_edit_items,
listed,
setItemFormPopup,
itemFormPopup,
clusterRef,
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [data, api])
useEffect(() => {
if (tagsReady) {
const processedTags = {}
newTagsToAdd.map((newtag) => {
if (!processedTags[newtag.name]) {
processedTags[newtag.name] = true
addTag(newtag)
}
return null
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [tagsReady])
return (
<>
{items &&
items
.filter((item) => item.layer?.name === name)
.filter((item) =>
filterTags.length === 0
? item
: filterTags.some((tag) =>
getItemTags(item).some(
(filterTag) =>
filterTag.name.toLocaleLowerCase() === tag.name.toLocaleLowerCase(),
),
),
)
.filter((item) => item.layer && isLayerVisible(item.layer))
.filter(
(item) =>
(item.group_type && isGroupTypeVisible(item.group_type)) ||
visibleGroupTypes.length === 0,
)
.map((item: Item) => {
if (getValue(item, itemLongitudeField) && getValue(item, itemLatitudeField)) {
// eslint-disable-next-line security/detect-object-injection
if (getValue(item, itemTextField)) item[itemTextField] = getValue(item, itemTextField)
// eslint-disable-next-line security/detect-object-injection
else item[itemTextField] = ''
if (item.tags) {
// eslint-disable-next-line security/detect-object-injection
item[itemTextField] = item[itemTextField] + '\n\n'
item.tags.map((tag) => {
// eslint-disable-next-line security/detect-object-injection
if (!item[itemTextField].includes(`#${encodeTag(tag)}`)) {
// eslint-disable-next-line security/detect-object-injection
return (item[itemTextField] = item[itemTextField] + `#${encodeTag(tag)} `)
}
// eslint-disable-next-line security/detect-object-injection
return item[itemTextField]
})
}
if (allTagsLoaded && allItemsLoaded) {
// eslint-disable-next-line security/detect-object-injection
item[itemTextField].match(hashTagRegex)?.map((tag) => {
if (
!tags.find(
(t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase(),
) &&
!newTagsToAdd.find(
(t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase(),
)
) {
const newTag = {
id: crypto.randomUUID(),
name: tag.slice(1),
color: randomColor(),
}
setNewTagsToAdd((current) => [...current, newTag])
}
return null
})
!tagsReady && setTagsReady(true)
}
const itemTags = getItemTags(item)
const latitude =
itemLatitudeField && item ? getValue(item, itemLatitudeField) : undefined
const longitude =
itemLongitudeField && item ? getValue(item, itemLongitudeField) : undefined
let color1 = markerDefaultColor
let color2 = markerDefaultColor2
if (itemColorField && getValue(item, itemColorField) != null)
color1 = getValue(item, itemColorField)
else if (itemTags && itemTags[0]) {
color1 = itemTags[0].color
}
if (itemTags && itemTags[0] && itemColorField) color2 = itemTags[0].color
else if (itemTags && itemTags[1]) {
color2 = itemTags[1].color
}
return (
<Marker
ref={(r) => {
if (!(item.id in leafletRefs && leafletRefs[item.id].marker === r)) {
r && addMarker(item, r)
}
}}
eventHandlers={{
click: () => {
selectPosition && setMarkerClicked(item)
},
}}
icon={MarkerIconFactory(
markerShape,
color1,
color2,
item.markerIcon ? item.markerIcon : markerIcon,
)}
key={item.id}
position={[latitude, longitude]}
>
{children &&
Children.toArray(children).some(
(child) => isComponentWithType(child) && child.type.__TYPE === 'ItemView',
) ? (
Children.toArray(children).map((child) =>
isComponentWithType(child) && child.type.__TYPE === 'ItemView' ? (
<ItemViewPopup
ref={(r) => {
if (!(item.id in leafletRefs && leafletRefs[item.id].popup === r)) {
r && addPopup(item, r as Popup)
}
}}
key={item.id + item.name}
item={item}
setItemFormPopup={setItemFormPopup}
>
{child}
</ItemViewPopup>
) : null,
)
) : (
<>
<ItemViewPopup
key={item.id + item.name}
ref={(r) => {
if (!(item.id in leafletRefs && leafletRefs[item.id].popup === r)) {
r && addPopup(item, r as Popup)
}
}}
item={item}
setItemFormPopup={setItemFormPopup}
/>
</>
)}
<Tooltip offset={[0, -38]} direction='top'>
{item.name ? item.name : getValue(item, itemNameField)}
</Tooltip>
</Marker>
)
} else return null
})}
{
// {children}}
}
{itemFormPopup &&
itemFormPopup.layer.name === name &&
(children &&
Children.toArray(children).some(
(child) => isComponentWithType(child) && child.type.__TYPE === 'ItemForm',
) ? (
Children.toArray(children).map((child) =>
isComponentWithType(child) && child.type.__TYPE === 'ItemForm' ? (
<ItemFormPopup
key={setItemFormPopup?.name}
position={itemFormPopup.position}
layer={itemFormPopup.layer}
setItemFormPopup={setItemFormPopup}
item={itemFormPopup.item}
>
{child}
</ItemFormPopup>
) : (
''
),
)
) : (
<>
<ItemFormPopup
position={itemFormPopup.position}
layer={itemFormPopup.layer}
setItemFormPopup={setItemFormPopup}
item={itemFormPopup.item}
/>
</>
))}
</>
)
}
function isComponentWithType(node: ReactNode): node is ReactElement & { type: { __TYPE: string } } {
return isValidElement(node) && typeof node.type !== 'string' && '__TYPE' in node.type
}