From eb054f4d1f04a534f7afde9ba7800e07121d7865 Mon Sep 17 00:00:00 2001 From: Maximilian Harz Date: Tue, 18 Mar 2025 11:17:16 +0100 Subject: [PATCH] Refactor PopupForm and PopupView --- src/Components/Item/PopupForm.tsx | 5 --- src/Components/Item/PopupView.tsx | 14 +------ src/Components/Map/Layer.tsx | 13 ------- src/Components/Map/LayerContext.ts | 6 --- .../Map/Subcomponents/ItemFormPopup.tsx | 27 +++++++++----- .../Map/Subcomponents/ItemViewPopup.tsx | 37 +++++++------------ src/Components/Map/UtopiaMapInner.tsx | 20 +++------- src/Components/Map/hooks/usePopupForm.tsx | 32 ++++++++++++++++ src/types/ItemFormPopupProps.d.ts | 1 - src/types/LayerProps.d.ts | 5 --- vite.config.ts | 2 +- 11 files changed, 72 insertions(+), 90 deletions(-) create mode 100644 src/Components/Map/hooks/usePopupForm.tsx diff --git a/src/Components/Item/PopupForm.tsx b/src/Components/Item/PopupForm.tsx index 2e9045ce..abe93f3a 100644 --- a/src/Components/Item/PopupForm.tsx +++ b/src/Components/Item/PopupForm.tsx @@ -1,6 +1,3 @@ -import { useContext } from 'react' - -import LayerContext from '#components/Map/LayerContext' import { ItemFormPopup } from '#components/Map/Subcomponents/ItemFormPopup' import TemplateItemContext from './TemplateItemContext' @@ -9,10 +6,8 @@ import TemplateItemContext from './TemplateItemContext' * @category Item */ export const PopupForm = ({ children }: { children?: React.ReactNode }) => { - const { itemFormPopup, setItemFormPopup } = useContext(LayerContext) return ( - itemFormPopup && ( { const cardViewContext = useContext(LayerContext) - const { - name, - markerDefaultColor, - markerDefaultColor2, - markerShape, - markerIcon, - setItemFormPopup, - } = cardViewContext + const { name, markerDefaultColor, markerDefaultColor2, markerShape, markerIcon } = cardViewContext const filterTags = useFilterTags() @@ -94,10 +87,6 @@ export const PopupView = ({ children }: { children?: React.ReactNode }) => { ], ) - 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 @@ -170,7 +159,6 @@ export const PopupView = ({ children }: { children?: React.ReactNode }) => { } }} item={item} - setItemFormPopup={setItemFormPopup} > {children} diff --git a/src/Components/Map/Layer.tsx b/src/Components/Map/Layer.tsx index 6ca86600..22f1ce00 100644 --- a/src/Components/Map/Layer.tsx +++ b/src/Components/Map/Layer.tsx @@ -35,9 +35,6 @@ export const Layer = ({ // eslint-disable-next-line camelcase public_edit_items, listed = true, - setItemFormPopup, - itemFormPopup, - clusterRef, }: LayerProps) => { const setItemsApi = useSetItemsApi() const setItemsData = useSetItemsData() @@ -68,10 +65,6 @@ export const Layer = ({ // eslint-disable-next-line camelcase public_edit_items, listed, - setItemFormPopup, - itemFormPopup, - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - clusterRef, }) api && setItemsApi({ @@ -93,10 +86,6 @@ export const Layer = ({ // eslint-disable-next-line camelcase public_edit_items, listed, - setItemFormPopup, - itemFormPopup, - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - clusterRef, }) // eslint-disable-next-line react-hooks/exhaustive-deps }, [data, api]) @@ -123,8 +112,6 @@ export const Layer = ({ markerDefaultColor2, markerShape, markerIcon, - itemFormPopup, - setItemFormPopup, }} > {children} diff --git a/src/Components/Map/LayerContext.ts b/src/Components/Map/LayerContext.ts index 06727baa..8095e14d 100644 --- a/src/Components/Map/LayerContext.ts +++ b/src/Components/Map/LayerContext.ts @@ -1,15 +1,11 @@ import { createContext } from 'react' -import type { ItemFormPopupProps } from '#types/ItemFormPopupProps' - interface LayerContextType { name: string markerDefaultColor: string markerDefaultColor2: string markerShape: string markerIcon: string - itemFormPopup: ItemFormPopupProps | null | undefined - setItemFormPopup: React.Dispatch> | undefined } const LayerContext = createContext({ @@ -18,8 +14,6 @@ const LayerContext = createContext({ markerDefaultColor2: '', markerShape: '', markerIcon: '', - itemFormPopup: undefined, - setItemFormPopup: undefined, }) export default LayerContext diff --git a/src/Components/Map/Subcomponents/ItemFormPopup.tsx b/src/Components/Map/Subcomponents/ItemFormPopup.tsx index 0f346b25..b3cf41a4 100644 --- a/src/Components/Map/Subcomponents/ItemFormPopup.tsx +++ b/src/Components/Map/Subcomponents/ItemFormPopup.tsx @@ -2,7 +2,6 @@ /* eslint-disable @typescript-eslint/no-misused-promises */ /* eslint-disable @typescript-eslint/prefer-optional-chain */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ @@ -14,18 +13,22 @@ import { useAuth } from '#components/Auth/useAuth' import { TextAreaInput } from '#components/Input/TextAreaInput' import { TextInput } from '#components/Input/TextInput' import { useResetFilterTags } from '#components/Map/hooks/useFilter' -import { useAddItem, useItems, useRemoveItem, useUpdateItem } from '#components/Map/hooks/useItems' +import { useAddItem, useItems, useUpdateItem } from '#components/Map/hooks/useItems' +import { usePopupForm } from '#components/Map/hooks/usePopupForm' import { useAddTag, useTags } from '#components/Map/hooks/useTags' import { hashTagRegex } from '#utils/HashTagRegex' import { randomColor } from '#utils/RandomColor' import type { Item } from '#types/Item' -import type { ItemFormPopupProps } from '#types/ItemFormPopupProps' -export function ItemFormPopup(props: ItemFormPopupProps) { +interface Props { + children?: React.ReactNode +} + +export function ItemFormPopup(props: Props) { const [spinner, setSpinner] = useState(false) - const [popupTitle, setPopupTitle] = useState('') + const [, setPopupTitle] = useState('') const formRef = useRef(null) @@ -35,8 +38,6 @@ export function ItemFormPopup(props: ItemFormPopupProps) { const updateItem = useUpdateItem() const items = useItems() - const removeItem = useRemoveItem() - const tags = useTags() const addTag = useAddTag() @@ -44,14 +45,22 @@ export function ItemFormPopup(props: ItemFormPopupProps) { const { user } = useAuth() + const { popupForm, setPopupForm } = usePopupForm() + const handleSubmit = async (evt: any) => { + if (!popupForm) { + throw new Error('Popup form is not defined') + } const formItem: Item = {} as Item Array.from(evt.target).forEach((input: HTMLInputElement) => { if (input.name) { formItem[input.name] = input.value } }) - formItem.position = { type: 'Point', coordinates: [props.position.lng, props.position.lat] } + formItem.position = { + type: 'Point', + coordinates: [popupForm.position.lng, popupForm.position.lat], + } evt.preventDefault() const name = formItem.name ? formItem.name : user?.first_name @@ -126,7 +135,7 @@ export function ItemFormPopup(props: ItemFormPopupProps) { setSpinner(false) map.closePopup() } - props.setItemFormPopup!(null) + setPopupForm(null) } const resetPopup = () => { diff --git a/src/Components/Map/Subcomponents/ItemViewPopup.tsx b/src/Components/Map/Subcomponents/ItemViewPopup.tsx index 402f09bc..a621c1bf 100644 --- a/src/Components/Map/Subcomponents/ItemViewPopup.tsx +++ b/src/Components/Map/Subcomponents/ItemViewPopup.tsx @@ -8,12 +8,13 @@ /* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ import { LatLng } from 'leaflet' -import { Children, cloneElement, forwardRef, isValidElement, useState } from 'react' +import { forwardRef, useState } from 'react' import { Popup as LeafletPopup, useMap } from 'react-leaflet' import { useNavigate } from 'react-router-dom' import { toast } from 'react-toastify' import { useRemoveItem, useUpdateItem } from '#components/Map/hooks/useItems' +import { usePopupForm } from '#components/Map/hooks/usePopupForm' import { useSetSelectPosition } from '#components/Map/hooks/useSelectPosition' import { timeAgo } from '#utils/TimeAgo' @@ -21,12 +22,10 @@ import { HeaderView } from './ItemPopupComponents/HeaderView' import { TextView } from './ItemPopupComponents/TextView' import type { Item } from '#types/Item' -import type { ItemFormPopupProps } from '#types/ItemFormPopupProps' export interface ItemViewPopupProps { item: Item children?: React.ReactNode - setItemFormPopup?: React.Dispatch> } // eslint-disable-next-line react/display-name @@ -37,22 +36,22 @@ export const ItemViewPopup = forwardRef((props: ItemViewPopupProps, ref: any) => const updadateItem = useUpdateItem() const navigate = useNavigate() const setSelectPosition = useSetSelectPosition() + const { setPopupForm } = usePopupForm() const [infoExpanded, setInfoExpanded] = useState(false) const handleEdit = (event: React.MouseEvent) => { event.stopPropagation() map.closePopup() - props.setItemFormPopup && - props.setItemFormPopup({ - position: new LatLng( - props.item.position?.coordinates[1]!, - props.item.position?.coordinates[0]!, - ), - layer: props.item.layer!, - item: props.item, - setItemFormPopup: props.setItemFormPopup, - }) + + setPopupForm({ + position: new LatLng( + props.item.position?.coordinates[1]!, + props.item.position?.coordinates[0]!, + ), + layer: props.item.layer!, + item: props.item, + }) } const handleDelete = async (event: React.MouseEvent) => { @@ -97,16 +96,8 @@ export const ItemViewPopup = forwardRef((props: ItemViewPopupProps, ref: any) => }} loading={loading} /> -
- {props.children ? ( - Children.toArray(props.children).map((child) => - isValidElement<{ item: Item; test: string }>(child) - ? cloneElement(child, { item: props.item }) - : '', - ) - ) : ( - - )} +
+ {props.children ?? }
{infoExpanded ? ( diff --git a/src/Components/Map/UtopiaMapInner.tsx b/src/Components/Map/UtopiaMapInner.tsx index ca5056fa..97a0302a 100644 --- a/src/Components/Map/UtopiaMapInner.tsx +++ b/src/Components/Map/UtopiaMapInner.tsx @@ -6,7 +6,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -import { Children, cloneElement, isValidElement, useEffect, useRef, useState } from 'react' +import { useEffect, useRef } from 'react' import { TileLayer, useMapEvents, GeoJSON, useMap } from 'react-leaflet' import MarkerClusterGroup from 'react-leaflet-cluster' import { Outlet, useLocation } from 'react-router-dom' @@ -20,6 +20,7 @@ import { useClusterRef, useSetClusterRef } from './hooks/useClusterRef' import { useAddVisibleLayer } from './hooks/useFilter' import { useLayers } from './hooks/useLayers' import { useLeafletRefs } from './hooks/useLeafletRefs' +import { usePopupForm } from './hooks/usePopupForm' import { useSelectPosition, useSetMapClicked, @@ -35,7 +36,6 @@ import { TagsControl } from './Subcomponents/Controls/TagsControl' import { TextView } from './Subcomponents/ItemPopupComponents/TextView' import { SelectPosition } from './Subcomponents/SelectPosition' -import type { ItemFormPopupProps } from '#types/ItemFormPopupProps' import type { Feature, Geometry as GeoJSONGeometry, GeoJsonObject } from 'geojson' export function UtopiaMapInner({ @@ -62,7 +62,7 @@ export function UtopiaMapInner({ const setClusterRef = useSetClusterRef() const clusterRef = useClusterRef() const setMapClicked = useSetMapClicked() - const [itemFormPopup, setItemFormPopup] = useState(null) + const { setPopupForm } = usePopupForm() useTheme(defaultTheme) @@ -119,7 +119,7 @@ export function UtopiaMapInner({ // eslint-disable-next-line no-console console.log(e.latlng.lat + ',' + e.latlng.lng) if (selectNewItemPosition) { - setMapClicked({ position: e.latlng, setItemFormPopup }) + setMapClicked({ position: e.latlng, setItemFormPopup: setPopupForm }) } }, moveend: () => {}, @@ -224,15 +224,7 @@ export function UtopiaMapInner({ maxClusterRadius={50} removeOutsideVisibleBounds={false} > - {Children.toArray(children).map((child) => - isValidElement<{ - setItemFormPopup: React.Dispatch> - itemFormPopup: ItemFormPopupProps | null - clusterRef: React.MutableRefObject - }>(child) - ? cloneElement(child, { setItemFormPopup, itemFormPopup, clusterRef }) - : child, - )} + {children} {geo && ( { if (selectNewItemPosition) { e.layer.closePopup() - setMapClicked({ position: e.latlng, setItemFormPopup }) + setMapClicked({ position: e.latlng, setItemFormPopup: setPopupForm }) } }, }} diff --git a/src/Components/Map/hooks/usePopupForm.tsx b/src/Components/Map/hooks/usePopupForm.tsx new file mode 100644 index 00000000..7e2f5f20 --- /dev/null +++ b/src/Components/Map/hooks/usePopupForm.tsx @@ -0,0 +1,32 @@ +import { createContext, useContext, useState } from 'react' + +import type { ItemFormPopupProps } from '#types/ItemFormPopupProps' + +type UsePopupFormManagerResult = ReturnType + +const PoupFormContext = createContext({ + popupForm: {} as ItemFormPopupProps | null, + setPopupForm: () => {}, +}) + +function usePopupFormManager(): { + popupForm: ItemFormPopupProps | null + setPopupForm: React.Dispatch> +} { + const [popupForm, setPopupForm] = useState(null) + + return { popupForm, setPopupForm } +} + +interface Props { + children?: React.ReactNode +} + +export const ClusterRefProvider: React.FunctionComponent = ({ children }: Props) => ( + {children} +) + +export const usePopupForm = (): UsePopupFormManagerResult => { + const { popupForm, setPopupForm } = useContext(PoupFormContext) + return { popupForm, setPopupForm } +} diff --git a/src/types/ItemFormPopupProps.d.ts b/src/types/ItemFormPopupProps.d.ts index 64e880bb..8d715be2 100644 --- a/src/types/ItemFormPopupProps.d.ts +++ b/src/types/ItemFormPopupProps.d.ts @@ -7,5 +7,4 @@ export interface ItemFormPopupProps { layer: LayerProps item?: Item children?: React.ReactNode - setItemFormPopup?: React.Dispatch> } diff --git a/src/types/LayerProps.d.ts b/src/types/LayerProps.d.ts index 9c720bc4..ef0ade1f 100644 --- a/src/types/LayerProps.d.ts +++ b/src/types/LayerProps.d.ts @@ -1,5 +1,4 @@ import type { Item } from './Item' -import type { ItemFormPopupProps } from './ItemFormPopupProps' import type { ItemsApi } from './ItemsApi' import type { ItemType } from './ItemType' @@ -26,8 +25,4 @@ export interface LayerProps { public_edit_items?: boolean listed?: boolean item_presets?: Record - setItemFormPopup?: React.Dispatch> - itemFormPopup?: ItemFormPopupProps | null - // eslint-disable-next-line @typescript-eslint/no-explicit-any - clusterRef?: any } diff --git a/vite.config.ts b/vite.config.ts index 98517ad9..1b50fcb5 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -15,7 +15,7 @@ export default defineConfig({ exclude: [...configDefaults.exclude, 'src/**/*.cy.tsx'], reporter: ['html', 'json-summary'], thresholds: { - lines: 1, + lines: 2, functions: 54, branches: 56, statements: 1,