diff --git a/src/Components/Map/ItemForm.tsx b/src/Components/Map/ItemForm.tsx deleted file mode 100644 index b0fc58a1..00000000 --- a/src/Components/Map/ItemForm.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Children, cloneElement, isValidElement, useEffect } from 'react' - -import type { Item } from '#types/Item' - -/** - * @category Map - */ -export const ItemForm = ({ - children, - item, - title, - setPopupTitle, -}: { - children?: React.ReactNode - item?: Item - title?: string - setPopupTitle?: React.Dispatch> -}) => { - useEffect(() => { - setPopupTitle && title && setPopupTitle(title) - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [title]) - - return ( -
- {children - ? Children.toArray(children).map((child) => - isValidElement<{ item: Item; test: string }>(child) - ? cloneElement(child, { item, test: 'test' }) - : '', - ) - : ''} -
- ) -} - -ItemForm.__TYPE = 'ItemForm' diff --git a/src/Components/Map/ItemView.tsx b/src/Components/Map/ItemView.tsx deleted file mode 100644 index bdad06b3..00000000 --- a/src/Components/Map/ItemView.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Children, cloneElement, isValidElement } from 'react' - -import type { Item } from '#types/Item' - -/** - * @category Map - */ -export const ItemView = ({ children, item }: { children?: React.ReactNode; item?: Item }) => { - return ( -
- {children - ? Children.toArray(children).map((child) => - isValidElement<{ item: Item }>(child) ? cloneElement(child, { item }) : null, - ) - : null} -
- ) -} - -ItemView.__TYPE = 'ItemView' diff --git a/src/Components/Map/Layer.tsx b/src/Components/Map/Layer.tsx index 9e4cc6cd..ad3f3556 100644 --- a/src/Components/Map/Layer.tsx +++ b/src/Components/Map/Layer.tsx @@ -1,31 +1,12 @@ -/* eslint-disable @typescript-eslint/no-unnecessary-condition */ -/* eslint-disable @typescript-eslint/prefer-optional-chain */ -import { Children, isValidElement, useEffect, useState } from 'react' -import { Marker, Tooltip } from 'react-leaflet' +import { useEffect, useState } from 'react' -import { encodeTag } from '#utils/FormatTags' -import { hashTagRegex } from '#utils/HashTagRegex' -import MarkerIconFactory from '#utils/MarkerIconFactory' -import { randomColor } from '#utils/RandomColor' +import LayerContext from '#components/Profile/templateComponents/LayerContext' -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 { useSetItemsApi, useSetItemsData } from './hooks/useItems' +import { useAddTag } from './hooks/useTags' -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 type { Point } from 'geojson' export type { Item } from '#types/Item' @@ -59,32 +40,12 @@ export const Layer = ({ 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([]) - const [tagsReady, setTagsReady] = useState(false) - - const isLayerVisible = useIsLayerVisible() - - const isGroupTypeVisible = useIsGroupTypeVisible() - - const visibleGroupTypes = useVisibleGroupType() + const [newTagsToAdd] = useState([]) + const [tagsReady] = useState(false) useEffect(() => { data && @@ -156,178 +117,18 @@ export const Layer = ({ }, [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 (item.position?.coordinates[0] && item.position?.coordinates[1]) { - if (item.tags) { - item.text += '\n\n' - item.tags.map((tag) => { - if (!item.text?.includes(`#${encodeTag(tag)}`)) { - item.text += `#${encodeTag(tag)}` - } - return item.text - }) - } - - if (allTagsLoaded && allItemsLoaded) { - item.text?.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 = item.position.coordinates[1] - const longitude = item.position.coordinates[0] - - let color1 = markerDefaultColor - let color2 = markerDefaultColor2 - if (item.color) { - color1 = item.color - } else if (itemTags[0]) { - color1 = itemTags[0].color - } - if (itemTags[0] && item.color) { - color2 = itemTags[0].color - } else if (itemTags[1]) { - color2 = itemTags[1].color - } - return ( - { - 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' ? ( - { - 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} - - ) : null, - ) - ) : ( - <> - { - if (!(item.id in leafletRefs && leafletRefs[item.id].popup === r)) { - r && addPopup(item, r as Popup) - } - }} - item={item} - setItemFormPopup={setItemFormPopup} - /> - - )} - - - {item.name} - - - ) - } 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' ? ( - - {child} - - ) : ( - '' - ), - ) - ) : ( - <> - - - ))} - + + {children} + ) } - -function isComponentWithType(node: ReactNode): node is ReactElement & { type: { __TYPE: string } } { - return isValidElement(node) && typeof node.type !== 'string' && '__TYPE' in node.type -} diff --git a/src/Components/Map/ProfileView.tsx b/src/Components/Map/ProfileView.tsx new file mode 100644 index 00000000..e69de29b diff --git a/src/Components/Map/index.tsx b/src/Components/Map/index.tsx index 6ef08d6c..b1630142 100644 --- a/src/Components/Map/index.tsx +++ b/src/Components/Map/index.tsx @@ -2,8 +2,6 @@ export { UtopiaMap } from './UtopiaMap' export * from './Layer' export { Tags } from './Tags' export * from './Permissions' -export { ItemForm } from './ItemForm' -export { ItemView } from './ItemView' export { PopupTextAreaInput } from './Subcomponents/ItemPopupComponents/PopupTextAreaInput' export { PopupStartEndInput } from './Subcomponents/ItemPopupComponents/PopupStartEndInput' export { PopupTextInput } from './Subcomponents/ItemPopupComponents/PopupTextInput' diff --git a/src/Components/Profile/index.tsx b/src/Components/Profile/index.tsx index 595a9c7f..9178efd7 100644 --- a/src/Components/Profile/index.tsx +++ b/src/Components/Profile/index.tsx @@ -2,3 +2,5 @@ export { UserSettings } from './UserSettings' // export { PlusButton } from './Subcomponents/PlusButton' export { ProfileView } from './ProfileView' export { ProfileForm } from './ProfileForm' +export { CardForm } from './templateComponents/CardForm' +export { CardView } from './templateComponents/CardView' diff --git a/src/Components/Profile/templateComponents/CardForm.tsx b/src/Components/Profile/templateComponents/CardForm.tsx new file mode 100644 index 00000000..7c3039b6 --- /dev/null +++ b/src/Components/Profile/templateComponents/CardForm.tsx @@ -0,0 +1,29 @@ +import { useContext } from 'react' + +import { ItemFormPopup } from '#components/Map/Subcomponents/ItemFormPopup' + +import LayerContext from './LayerContext' +import TemplateItemContext from './TemplateItemContext' + +/** + * @category Map + */ +export const CardForm = ({ children }: { children?: React.ReactNode }) => { + const { itemFormPopup, setItemFormPopup } = useContext(LayerContext) + + return ( + itemFormPopup && ( + + + {children} + + + ) + ) +} diff --git a/src/Components/Profile/templateComponents/CardFormContext.ts b/src/Components/Profile/templateComponents/CardFormContext.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/Components/Profile/templateComponents/CardView.tsx b/src/Components/Profile/templateComponents/CardView.tsx new file mode 100644 index 00000000..9db44706 --- /dev/null +++ b/src/Components/Profile/templateComponents/CardView.tsx @@ -0,0 +1,186 @@ +import { useContext, useMemo, useState } from 'react' +import { Marker, Tooltip } from 'react-leaflet' + +import { + useFilterTags, + useIsLayerVisible, + useIsGroupTypeVisible, + useVisibleGroupType, +} from '#components/Map/hooks/useFilter' +import { useItems, useAllItemsLoaded } from '#components/Map/hooks/useItems' +import { useAddMarker, useAddPopup, useLeafletRefs } from '#components/Map/hooks/useLeafletRefs' +import { useSetMarkerClicked, useSelectPosition } from '#components/Map/hooks/useSelectPosition' +import { useGetItemTags, useAllTagsLoaded, useTags } from '#components/Map/hooks/useTags' +import { ItemViewPopup } from '#components/Map/Subcomponents/ItemViewPopup' +import { encodeTag } from '#utils/FormatTags' +import { hashTagRegex } from '#utils/HashTagRegex' +import MarkerIconFactory from '#utils/MarkerIconFactory' +import { randomColor } from '#utils/RandomColor' + +import LayerContext from './LayerContext' +import TemplateItemContext from './TemplateItemContext' + +import type { Item } from '#types/Item' +import type { Tag } from '#types/Tag' +import type { Popup } from 'leaflet' + +// TODO Think about folder structure. This is not for profile, but for card / popup. Both can use the same template components. + +/** + * @category Profile + */ +export const CardView = ({ children }: { children?: React.ReactNode }) => { + const cardViewContext = useContext(LayerContext) + const { + name, + markerDefaultColor, + markerDefaultColor2, + markerShape, + markerIcon, + setItemFormPopup, + } = cardViewContext + + const filterTags = useFilterTags() + + const items = useItems() + 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 [newTagsToAdd, setNewTagsToAdd] = useState([]) + const [tagsReady, setTagsReady] = useState(false) + + const isLayerVisible = useIsLayerVisible() + + const isGroupTypeVisible = useIsGroupTypeVisible() + + const visibleGroupTypes = useVisibleGroupType() + + const visibleItems = useMemo( + () => + 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, + ), + [ + filterTags, + getItemTags, + isGroupTypeVisible, + isLayerVisible, + items, + name, + visibleGroupTypes.length, + ], + ) + + if (!setItemFormPopup) { + throw new Error('setItemFormPopup is not defined') + } + + return visibleItems.map((item: Item) => { + if (!(item.position?.coordinates[0] && item.position.coordinates[1])) return null + + if (item.tags) { + item.text += '\n\n' + item.tags.map((tag) => { + if (!item.text?.includes(`#${encodeTag(tag)}`)) { + item.text += `#${encodeTag(tag)}` + } + return item.text + }) + } + + if (allTagsLoaded && allItemsLoaded) { + item.text?.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 = item.position.coordinates[1] + const longitude = item.position.coordinates[0] + + let color1 = markerDefaultColor + let color2 = markerDefaultColor2 + if (item.color) { + color1 = item.color + } else if (itemTags[0]) { + color1 = itemTags[0].color + } + if (itemTags[0] && item.color) { + color2 = itemTags[0].color + } else if (itemTags[1]) { + color2 = itemTags[1].color + } + + return ( + + { + 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 ?? markerIcon)} + position={[latitude, longitude]} + > + { + if (!(item.id in leafletRefs && leafletRefs[item.id].popup === r)) { + r && addPopup(item, r) + } + }} + item={item} + setItemFormPopup={setItemFormPopup} + > + {children} + + + + {item.name} + + + + ) + }) +} diff --git a/src/Components/Profile/templateComponents/LayerContext.ts b/src/Components/Profile/templateComponents/LayerContext.ts new file mode 100644 index 00000000..3564e298 --- /dev/null +++ b/src/Components/Profile/templateComponents/LayerContext.ts @@ -0,0 +1,27 @@ +import { createContext } from 'react' + +import type { ItemFormPopupProps } from '#types/ItemFormPopupProps' + +// Where should we define defaults, here or in Layer.tsx? + +interface LayerContextType { + name: string + markerDefaultColor: string + markerDefaultColor2: string + markerShape: string + markerIcon: string + itemFormPopup: ItemFormPopupProps | null | undefined + setItemFormPopup: React.Dispatch> | undefined +} + +const LayerContext = createContext({ + name: '', + markerDefaultColor: '#777', + markerDefaultColor2: 'RGBA(35, 31, 32, 0.2)', + markerShape: 'circle', + markerIcon: '', + itemFormPopup: undefined, + setItemFormPopup: undefined, +}) + +export default LayerContext diff --git a/src/Components/Profile/templateComponents/TemplateItemContext.ts b/src/Components/Profile/templateComponents/TemplateItemContext.ts new file mode 100644 index 00000000..f9da3309 --- /dev/null +++ b/src/Components/Profile/templateComponents/TemplateItemContext.ts @@ -0,0 +1,7 @@ +import { createContext } from 'react' + +import type { Item } from '#types/Item' + +const ItemContext = createContext(undefined) + +export default ItemContext