mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2026-03-01 12:44:17 +00:00
optimized add item process
This commit is contained in:
parent
108a9a0ba4
commit
2b638dffe5
@ -10,6 +10,8 @@ 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 } from 'react'
|
||||
|
||||
export default function AddButton({
|
||||
triggerAction,
|
||||
}: {
|
||||
@ -24,9 +26,20 @@ export default function AddButton({
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (containerRef.current) {
|
||||
DomEvent.disableClickPropagation(containerRef.current)
|
||||
DomEvent.disableScrollPropagation(containerRef.current)
|
||||
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)
|
||||
}
|
||||
}, [])
|
||||
|
||||
@ -44,6 +57,28 @@ export default function AddButton({
|
||||
return canAdd
|
||||
}
|
||||
|
||||
const stopPropagation = (
|
||||
event: ReactMouseEvent<HTMLElement> | ReactTouchEvent<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() ? (
|
||||
@ -54,7 +89,23 @@ export default function AddButton({
|
||||
<label
|
||||
tabIndex={0}
|
||||
className='tw:z-500 tw:btn tw:btn-circle tw:btn-lg tw:shadow tw:bg-base-100'
|
||||
onClick={() => {
|
||||
onMouseDown={(event) => {
|
||||
stopPropagation(event)
|
||||
}}
|
||||
onMouseUp={(event) => {
|
||||
stopPropagation(event)
|
||||
}}
|
||||
onClick={(event) => {
|
||||
stopPropagation(event)
|
||||
if (isMobile) {
|
||||
setIsOpen(!isOpen)
|
||||
}
|
||||
}}
|
||||
onTouchStart={(event) => {
|
||||
stopPropagation(event)
|
||||
}}
|
||||
onTouchEnd={(event) => {
|
||||
stopPropagation(event)
|
||||
if (isMobile) {
|
||||
setIsOpen(!isOpen)
|
||||
}
|
||||
@ -62,7 +113,10 @@ export default function AddButton({
|
||||
>
|
||||
<SVG src={PlusSVG} className='tw:h-5 tw:w-5' />
|
||||
</label>
|
||||
<ul tabIndex={0} className='tw:dropdown-content tw:pr-1 tw:list-none'>
|
||||
<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 &&
|
||||
@ -76,16 +130,22 @@ export default function AddButton({
|
||||
>
|
||||
<button
|
||||
tabIndex={0}
|
||||
className='tw:z-500 tw:border-0 tw:p-0 tw:mb-3 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'
|
||||
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' }}
|
||||
onClick={() => {
|
||||
triggerAction(layer)
|
||||
setIsOpen(false)
|
||||
onMouseDown={(event) => {
|
||||
stopPropagation(event)
|
||||
}}
|
||||
onTouchEnd={(e) => {
|
||||
triggerAction(layer)
|
||||
setIsOpen(false)
|
||||
e.preventDefault()
|
||||
onMouseUp={(event) => {
|
||||
stopPropagation(event)
|
||||
}}
|
||||
onClick={(event) => {
|
||||
handleLayerSelect(event, layer)
|
||||
}}
|
||||
onTouchStart={(event) => {
|
||||
stopPropagation(event)
|
||||
}}
|
||||
onTouchEnd={(event) => {
|
||||
handleLayerSelect(event, layer)
|
||||
}}
|
||||
>
|
||||
<img
|
||||
|
||||
@ -1,12 +1,18 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useEffect, useMemo, useRef } from 'react'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
import { SVG } from '#components/AppShell'
|
||||
import { useAppState } from '#components/AppShell/hooks/useAppState'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
import type { LayerProps } from '#types/LayerProps'
|
||||
import type { Dispatch, SetStateAction } from 'react'
|
||||
|
||||
const isItemSelection = (value: Item | LayerProps): value is Item => 'layer' in value
|
||||
|
||||
interface SelectPositionToastProps {
|
||||
selectNewItemPosition: Item | LayerProps | null
|
||||
setSelectNewItemPosition: React.Dispatch<React.SetStateAction<Item | LayerProps | null>>
|
||||
setSelectNewItemPosition: Dispatch<SetStateAction<Item | LayerProps | null>>
|
||||
}
|
||||
|
||||
export const SelectPositionToast = ({
|
||||
@ -14,12 +20,14 @@ export const SelectPositionToast = ({
|
||||
setSelectNewItemPosition,
|
||||
}: SelectPositionToastProps) => {
|
||||
const toastIdRef = useRef<string | number | null>(null)
|
||||
const toastId = 'select-position-toast'
|
||||
const appState = useAppState()
|
||||
|
||||
// Escape-Key Listener
|
||||
useEffect(() => {
|
||||
const handleEscape = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape' && selectNewItemPosition) {
|
||||
toast.dismiss('select-position-toast')
|
||||
toast.dismiss(toastId)
|
||||
toastIdRef.current = null
|
||||
setSelectNewItemPosition(null)
|
||||
}
|
||||
@ -29,48 +37,106 @@ export const SelectPositionToast = ({
|
||||
return () => window.removeEventListener('keydown', handleEscape)
|
||||
}, [selectNewItemPosition, setSelectNewItemPosition])
|
||||
|
||||
useEffect(() => {
|
||||
if (selectNewItemPosition && !toastIdRef.current) {
|
||||
let message = ''
|
||||
if ('layer' in selectNewItemPosition) {
|
||||
message = `Select the new position of ${selectNewItemPosition.name} on the map!`
|
||||
} else if ('markerIcon' in selectNewItemPosition) {
|
||||
message = 'Select the position on the map!'
|
||||
}
|
||||
const toastContent = useMemo(() => {
|
||||
if (!selectNewItemPosition) return null
|
||||
|
||||
const CloseButton = () => (
|
||||
const itemSelection = isItemSelection(selectNewItemPosition)
|
||||
const layer: LayerProps | null = itemSelection
|
||||
? (selectNewItemPosition.layer ?? null)
|
||||
: selectNewItemPosition
|
||||
const markerIcon = itemSelection
|
||||
? (selectNewItemPosition.layer?.markerIcon ?? selectNewItemPosition.markerIcon)
|
||||
: selectNewItemPosition.markerIcon
|
||||
const message = itemSelection
|
||||
? `Select the new position of ${selectNewItemPosition.name} on the map!`
|
||||
: 'Select the position on the map!'
|
||||
|
||||
const dismissToast = () => {
|
||||
toast.dismiss(toastId)
|
||||
toastIdRef.current = null
|
||||
setSelectNewItemPosition(null)
|
||||
}
|
||||
|
||||
const assetsApiUrl = appState.assetsApi.url
|
||||
const assetsBaseUrl: string | undefined = assetsApiUrl.length > 0 ? assetsApiUrl : undefined
|
||||
|
||||
const resolveColor = (...candidates: (string | null | undefined)[]): string => {
|
||||
for (const candidate of candidates) {
|
||||
if (typeof candidate === 'string' && candidate.length > 0) {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
return '#777'
|
||||
}
|
||||
|
||||
const itemColor =
|
||||
itemSelection && typeof selectNewItemPosition.color === 'string'
|
||||
? selectNewItemPosition.color
|
||||
: undefined
|
||||
const itemLayerColor =
|
||||
itemSelection && typeof selectNewItemPosition.layer?.menuColor === 'string'
|
||||
? selectNewItemPosition.layer.menuColor
|
||||
: undefined
|
||||
const layerMenuColor = layer?.menuColor
|
||||
const baseLayerColor = typeof layerMenuColor === 'string' ? layerMenuColor : undefined
|
||||
|
||||
const backgroundColor = resolveColor(itemColor, itemLayerColor, baseLayerColor)
|
||||
const iconSrc: string | undefined =
|
||||
markerIcon?.image != null
|
||||
? assetsBaseUrl
|
||||
? `${assetsBaseUrl}${markerIcon.image}`
|
||||
: markerIcon.image
|
||||
: undefined
|
||||
|
||||
return (
|
||||
<div className='tw:relative'>
|
||||
<div className='tw:flex tw:flex-row tw:items-center'>
|
||||
<div
|
||||
className='tw:flex tw:items-center tw:gap-3 tw:p-2 tw:rounded-selector tw:text-white tw:mr-2'
|
||||
style={{ backgroundColor }}
|
||||
>
|
||||
{iconSrc && <SVG src={iconSrc} className='tw:h-4 tw:w-4 tw:object-contain' />}
|
||||
</div>
|
||||
<div className='tw:flex tw:flex-col tw:gap-0.5'>
|
||||
<span className='tw:text-sm'>{message}</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
toast.dismiss('select-position-toast')
|
||||
toastIdRef.current = null
|
||||
setSelectNewItemPosition(null)
|
||||
}}
|
||||
onClick={dismissToast}
|
||||
className='tw:btn tw:btn-sm tw:btn-ghost tw:btn-circle tw:absolute tw:top-0 tw:right-0'
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
)
|
||||
</div>
|
||||
)
|
||||
}, [appState.assetsApi.url, selectNewItemPosition, setSelectNewItemPosition, toastId])
|
||||
|
||||
toastIdRef.current = toast(
|
||||
<div>
|
||||
{message}
|
||||
<CloseButton />
|
||||
</div>,
|
||||
{
|
||||
toastId: 'select-position-toast',
|
||||
useEffect(() => {
|
||||
if (selectNewItemPosition && toastContent) {
|
||||
if (!toastIdRef.current) {
|
||||
toastIdRef.current = toast(toastContent, {
|
||||
toastId,
|
||||
autoClose: false,
|
||||
closeButton: false,
|
||||
closeOnClick: false,
|
||||
draggable: false,
|
||||
},
|
||||
)
|
||||
})
|
||||
} else {
|
||||
toast.update(toastId, {
|
||||
render: toastContent,
|
||||
autoClose: false,
|
||||
closeButton: false,
|
||||
closeOnClick: false,
|
||||
draggable: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (!selectNewItemPosition && toastIdRef.current) {
|
||||
toast.dismiss(toastIdRef.current)
|
||||
toastIdRef.current = null
|
||||
}
|
||||
}, [selectNewItemPosition, setSelectNewItemPosition])
|
||||
}, [selectNewItemPosition, toastContent, toastId])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user