mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2025-12-13 07:46:10 +00:00
241 lines
8.2 KiB
TypeScript
241 lines
8.2 KiB
TypeScript
/* eslint-disable camelcase */ // Directus database fields use snake_case
|
|
/* eslint-disable react/prop-types */
|
|
/* eslint-disable @typescript-eslint/prefer-optional-chain */
|
|
/* eslint-disable @typescript-eslint/no-floating-promises */
|
|
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
|
|
/* eslint-disable @typescript-eslint/restrict-plus-operands */
|
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
/* eslint-disable @typescript-eslint/non-nullable-type-assertion-style */
|
|
/* eslint-disable no-catch-all/no-catch-all */
|
|
|
|
import { createContext, useContext, useEffect, useState, useCallback } from 'react'
|
|
import { toast } from 'react-toastify'
|
|
|
|
import { useUpdateItem } from './useItems'
|
|
import { useLayers } from './useLayers'
|
|
import { useHasUserPermission } from './usePermissions'
|
|
|
|
import type { Item } from '#types/Item'
|
|
import type { LayerProps } from '#types/LayerProps'
|
|
import type { PopupFormState } from '#types/PopupFormState'
|
|
import type { Point } from 'geojson'
|
|
import type { LatLng } from 'leaflet'
|
|
|
|
interface PolygonClickedProps {
|
|
position: LatLng
|
|
setItemFormPopup: React.Dispatch<React.SetStateAction<PopupFormState | null>>
|
|
}
|
|
|
|
type UseSelectPositionManagerResult = ReturnType<typeof useSelectPositionManager>
|
|
|
|
const SelectPositionContext = createContext<UseSelectPositionManagerResult>({
|
|
selectPosition: null,
|
|
setSelectPosition: () => {},
|
|
setMarkerClicked: () => {},
|
|
setMapClicked: () => {},
|
|
})
|
|
|
|
function useSelectPositionManager(): {
|
|
selectPosition: Item | LayerProps | null
|
|
setSelectPosition: React.Dispatch<React.SetStateAction<Item | LayerProps | null>>
|
|
setMarkerClicked: React.Dispatch<React.SetStateAction<Item>>
|
|
setMapClicked: React.Dispatch<React.SetStateAction<PolygonClickedProps | undefined>>
|
|
} {
|
|
const [selectPosition, setSelectPosition] = useState<LayerProps | null | Item>(null)
|
|
const [markerClicked, setMarkerClicked] = useState<Item | null>()
|
|
const [mapClicked, setMapClicked] = useState<PolygonClickedProps>()
|
|
const updateItem = useUpdateItem()
|
|
const hasUserPermission = useHasUserPermission()
|
|
const layers = useLayers()
|
|
|
|
// Handle API operations with consistent error handling and return response data
|
|
const handleApiOperation = useCallback(
|
|
async (
|
|
operation: () => Promise<Item>,
|
|
toastId: string | number,
|
|
successMessage: string,
|
|
): Promise<{ success: boolean; data?: Item }> => {
|
|
try {
|
|
const data = await operation()
|
|
toast.update(toastId, {
|
|
render: successMessage,
|
|
type: 'success',
|
|
isLoading: false,
|
|
autoClose: 5000,
|
|
})
|
|
return { success: true, data }
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
toast.update(toastId, {
|
|
render: errorMessage,
|
|
type: 'error',
|
|
isLoading: false,
|
|
autoClose: 5000,
|
|
closeButton: true,
|
|
})
|
|
return { success: false }
|
|
}
|
|
},
|
|
[],
|
|
)
|
|
|
|
useEffect(() => {
|
|
if (
|
|
selectPosition &&
|
|
markerClicked &&
|
|
'text' in selectPosition &&
|
|
markerClicked.id !== selectPosition.id
|
|
) {
|
|
itemUpdateParent({ ...selectPosition, parent: markerClicked.id })
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [markerClicked])
|
|
|
|
useEffect(() => {
|
|
if (selectPosition != null) {
|
|
// selectPosition can be null, Layer or Item
|
|
if ('markerIcon' in selectPosition) {
|
|
// if selectPosition is a Layer
|
|
mapClicked &&
|
|
mapClicked.setItemFormPopup({
|
|
layer: selectPosition as LayerProps,
|
|
position: mapClicked.position,
|
|
})
|
|
setSelectPosition(null)
|
|
}
|
|
if ('layer' in selectPosition) {
|
|
// if selectPosition is an Item
|
|
const position =
|
|
mapClicked?.position.lng &&
|
|
({
|
|
type: 'Point',
|
|
coordinates: [mapClicked.position.lng, mapClicked.position.lat],
|
|
} as Point)
|
|
position && itemUpdatePosition({ ...selectPosition, position })
|
|
setSelectPosition(null)
|
|
}
|
|
}
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [mapClicked])
|
|
|
|
const itemUpdateParent = async (updatedItem: Item) => {
|
|
const toastId = toast.loading('Adding item to ' + markerClicked?.name)
|
|
if (
|
|
markerClicked?.layer?.api?.collectionName &&
|
|
hasUserPermission(markerClicked.layer.api.collectionName, 'update', markerClicked)
|
|
) {
|
|
const result = await handleApiOperation(
|
|
async () => {
|
|
const updateResult = await updatedItem.layer?.api?.updateItem!({
|
|
id: updatedItem.id,
|
|
parent: updatedItem.parent,
|
|
position: null,
|
|
})
|
|
return updateResult as Item
|
|
},
|
|
toastId,
|
|
'Item position updated',
|
|
)
|
|
|
|
if (result.success && result.data) {
|
|
// Find the layer object by ID from server response
|
|
const layer = layers.find((l) => l.id === (result.data!.layer as unknown as string))
|
|
const itemWithLayer = { ...result.data, layer, user_created: updatedItem.user_created }
|
|
updateItem(itemWithLayer)
|
|
await linkItem(updatedItem.id)
|
|
setSelectPosition(null)
|
|
setMarkerClicked(null)
|
|
}
|
|
} else {
|
|
setSelectPosition(null)
|
|
toast.update(toastId, {
|
|
render: "you don't have permission to add items to " + markerClicked?.name,
|
|
type: 'error',
|
|
isLoading: false,
|
|
autoClose: 5000,
|
|
closeButton: true,
|
|
})
|
|
}
|
|
}
|
|
|
|
const itemUpdatePosition = async (updatedItem: Item) => {
|
|
const toastId = toast.loading('Updating item position')
|
|
|
|
const result = await handleApiOperation(
|
|
async () => {
|
|
const updateResult = await updatedItem.layer?.api?.updateItem!({
|
|
id: updatedItem.id,
|
|
position: updatedItem.position,
|
|
})
|
|
return updateResult as Item
|
|
},
|
|
toastId,
|
|
'Item position updated',
|
|
)
|
|
|
|
if (result.success && result.data) {
|
|
const layer = layers.find((l) => l.id === (result.data!.layer as unknown as string))
|
|
const itemWithLayer = { ...result.data, layer, user_created: updatedItem.user_created }
|
|
updateItem(itemWithLayer)
|
|
}
|
|
}
|
|
|
|
const linkItem = async (id: string) => {
|
|
if (markerClicked) {
|
|
const newRelations = markerClicked.relations || []
|
|
|
|
if (!newRelations.some((r) => r.related_items_id === id)) {
|
|
newRelations.push({ items_id: markerClicked.id, related_items_id: id })
|
|
const updatedItem = { id: markerClicked.id, relations: newRelations }
|
|
|
|
const toastId = toast.loading('Linking item')
|
|
const result = await handleApiOperation(
|
|
async () => {
|
|
const updateResult = await markerClicked.layer?.api?.updateItem!(updatedItem)
|
|
return updateResult as Item
|
|
},
|
|
toastId,
|
|
'Item linked',
|
|
)
|
|
|
|
if (result.success && result.data) {
|
|
// Find the layer object by ID from server response
|
|
const layer = layers.find((l) => l.id === (result.data!.layer as unknown as string))
|
|
const itemWithLayer = { ...result.data, layer, user_created: markerClicked.user_created }
|
|
updateItem(itemWithLayer)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return { selectPosition, setSelectPosition, setMarkerClicked, setMapClicked }
|
|
}
|
|
|
|
export const SelectPositionProvider: React.FunctionComponent<{
|
|
children?: React.ReactNode
|
|
}> = ({ children }) => (
|
|
<SelectPositionContext.Provider value={useSelectPositionManager()}>
|
|
{children}
|
|
</SelectPositionContext.Provider>
|
|
)
|
|
|
|
export const useSelectPosition = (): Item | LayerProps | null => {
|
|
const { selectPosition } = useContext(SelectPositionContext)
|
|
return selectPosition
|
|
}
|
|
|
|
export const useSetSelectPosition = (): UseSelectPositionManagerResult['setSelectPosition'] => {
|
|
const { setSelectPosition } = useContext(SelectPositionContext)
|
|
return setSelectPosition
|
|
}
|
|
|
|
export const useSetMarkerClicked = (): UseSelectPositionManagerResult['setMarkerClicked'] => {
|
|
const { setMarkerClicked } = useContext(SelectPositionContext)
|
|
return setMarkerClicked
|
|
}
|
|
|
|
export const useSetMapClicked = (): UseSelectPositionManagerResult['setMapClicked'] => {
|
|
const { setMapClicked } = useContext(SelectPositionContext)
|
|
return setMapClicked
|
|
}
|