Skip to content

Commit

Permalink
Hotspot images adjustments
Browse files Browse the repository at this point in the history
  • Loading branch information
markus-moser committed Jan 17, 2025
1 parent 0abe6e2 commit dd2866c
Show file tree
Hide file tree
Showing 10 changed files with 107 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ export const useStyle = createStyles(({ token, css }) => {
justify-content: center;
align-items: center;
}
.hotspot-image__item--disabled {
cursor: default;
&:before {
cursor: default;
}
}
.hotspot-image__popover {
}
Expand Down
9 changes: 5 additions & 4 deletions assets/js/src/core/components/hotspot-image/hotspot-image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,10 @@ interface IHotspotImage {
onClone?: (id: number) => void
onUpdate?: (item: IHotspot) => void
disableContextMenu?: boolean
disabled?: boolean
}

export const HotspotImage = ({ src, data, styleOptions = defaultStyleOptions, onRemove, onEdit, onClone, onUpdate, disableContextMenu }: IHotspotImage): JSX.Element => {
export const HotspotImage = ({ src, data, styleOptions = defaultStyleOptions, onRemove, onEdit, onClone, onUpdate, disableContextMenu, disabled }: IHotspotImage): JSX.Element => {
const { styles } = useStyle()
const [imageLoaded, setImageLoaded] = useState<boolean>(false)
const imageRef = useRef<HTMLImageElement | null>(null)
Expand Down Expand Up @@ -136,7 +137,7 @@ export const HotspotImage = ({ src, data, styleOptions = defaultStyleOptions, on
}

const handleMouseMove = (evt: MouseEvent): void => {
if (selectedId === null || containerRef.current === null) return
if (selectedId === null || containerRef.current === null || disabled === true) return
const containerBounds = containerRef.current.getBoundingClientRect()
const hotspotIndex = items.findIndex(h => h.id === selectedId)
const dx = evt.clientX - resizeStart.x
Expand Down Expand Up @@ -221,10 +222,10 @@ export const HotspotImage = ({ src, data, styleOptions = defaultStyleOptions, on
onOpenChange={ (open) => { setPopoverOpen(open) } }
open={ popoverOpen && selectedId === hotspot.id }
overlayClassName={ [styles.Popover].join(' ') }
trigger={ disableContextMenu === true ? [] : ['contextMenu'] }
trigger={ disableContextMenu === true || disabled === true ? [] : ['contextMenu'] }
>
<button
className={ `hotspot-image__item ${hotspot.type === 'marker' ? 'hotspot-image__item--marker' : ''}` }
className={ `hotspot-image__item ${hotspot.type === 'marker' ? 'hotspot-image__item--marker' : ''} ${disabled === true ? 'hotspot-image__item--disabled' : ''}` }
key={ hotspot.id }
onMouseDown={ evt => { handleMouseDown(evt, hotspot) } }
style={ {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ import { ButtonGroup } from '@Pimcore/components/button-group/button-group'
import { useAssetHelper } from '@Pimcore/modules/asset/hooks/use-asset-helper'
import { Dropdown } from '@Pimcore/components/dropdown/dropdown'
import { Icon } from '@Pimcore/components/icon/icon'
import {
hasValueData
} from '@Pimcore/modules/element/dynamic-types/defintinitions/objects/data-related/components/hotspot-image/utils/value-data'
import { useMessage } from '@Pimcore/components/message/useMessage'

interface HotspotImageFooterProps {
emptyValue?: () => void
Expand All @@ -34,17 +38,17 @@ interface HotspotImageFooterProps {
export const HotspotImageFooter = (props: HotspotImageFooterProps): React.JSX.Element => {
const { t } = useTranslation()
const { openAsset } = useAssetHelper()
const messageApi = useMessage()

const clearValueData = (): void => {
const clearValueData = async (): Promise<void> => {
props.setValue({
...props.value!,
hotspots: [],
marker: [],
crop: null
})
}
const hasValueData = (): boolean => {
return !_.isEmpty(props.value?.hotspots) || !_.isEmpty(props.value?.marker) || !_.isEmpty(props.value?.crop)

await messageApi.success(t('hotspots.data-cleared'))
}

return (
Expand Down Expand Up @@ -79,6 +83,7 @@ export const HotspotImageFooter = (props: HotspotImageFooterProps): React.JSX.El
menu={ {
items: [
{
disabled: !_.isNumber(props.value?.image?.id),
label: t('crop'),
key: 'crop',
icon: <Icon value={ 'crop' } />,
Expand All @@ -87,6 +92,7 @@ export const HotspotImageFooter = (props: HotspotImageFooterProps): React.JSX.El
}
},
{
disabled: !_.isNumber(props.value?.image?.id),
label: t('hotspots.edit'),
key: 'hotspots-edit',
icon: <Icon value={ 'new-marker' } />,
Expand All @@ -95,9 +101,9 @@ export const HotspotImageFooter = (props: HotspotImageFooterProps): React.JSX.El
}
},
{
disabled: !hasValueData(),
disabled: !hasValueData(props.value) || props.disabled === true,
label: t('hotspots.clear-data'),
key: 'hotspots-edit',
key: 'clear-data',
icon: <Icon value={ 'remove-marker' } />,
onClick: clearValueData
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ import type {
import {
HotspotImagePreview
} from '@Pimcore/modules/element/dynamic-types/defintinitions/objects/data-related/components/hotspot-image/image-preview'
import { useFormModal } from '@Pimcore/components/modal/form-modal/hooks/use-form-modal'
import {
hasValueData
} from '@Pimcore/modules/element/dynamic-types/defintinitions/objects/data-related/components/hotspot-image/utils/value-data'

export interface HotspotImageValue {
image: ImageValue | null
Expand All @@ -53,6 +57,7 @@ export const HotspotImage = (props: HotspotImageProps): React.JSX.Element => {
const [value, setValue] = React.useState<HotspotImageValue | null>(props.value ?? null)
const [markerModalOpen, setMarkerModalOpen] = useState(false)
const [cropModalOpen, setCropModalOpen] = useState(false)
const { confirm } = useFormModal()

const { t } = useTranslation()
const emptyValue = (): void => {
Expand All @@ -66,6 +71,18 @@ export const HotspotImage = (props: HotspotImageProps): React.JSX.Element => {
const width = props.width === null || props.width === '' ? 300 : props.width
const height = props.height === null || props.width === '' ? 150 : props.height

const setImage = (image: ImageValue, replaceValueData: boolean): void => {
let newValue: HotspotImageValue = value === null ? { image: null } : { ...value }

if (replaceValueData) {
newValue = { image }
} else {
newValue = { ...newValue, image }
}

setValue(newValue)
}

return (
<Card
className="max-w-full"
Expand All @@ -83,7 +100,25 @@ export const HotspotImage = (props: HotspotImageProps): React.JSX.Element => {
<Droppable
isValidContext={ (info: DragAndDropInfo) => props.disabled !== true }
isValidData={ (info: DragAndDropInfo) => info.type === 'asset' && info.data.type === 'image' }
onDrop={ (info: DragAndDropInfo) => { setValue({ image: { type: 'asset', id: info.data.id as number } }) } }
onDrop={ (info: DragAndDropInfo) => {
const newImage: ImageValue = { type: 'asset', id: info.data.id as number }
if (hasValueData(value)) {
confirm({
title: t('hotspots.clear-data'),
content: t('hotspots.clear-data.dnd-message'),
okText: t('yes'),
cancelText: t('no'),
onOk: () => {
setImage(newImage, true)
},
onCancel: () => {
setImage(newImage, false)
}
})
} else {
setImage(newImage, true)
}
} }
variant="outline"
>
{ // eslint-disable-next-line @typescript-eslint/prefer-optional-chain
Expand All @@ -92,9 +127,10 @@ export const HotspotImage = (props: HotspotImageProps): React.JSX.Element => {
<HotspotImagePreview
assetId={ value.image.id }
cropModalOpen={ cropModalOpen }
disabled={ props.disabled }
height={ height }
markerModalOpen={ markerModalOpen }
onChange={ props.onChange }
onChange={ setValue }
setCropModalOpen={ setCropModalOpen }
setMarkerModalOpen={ setMarkerModalOpen }
value={ value }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* @license https://github.com/pimcore/studio-ui-bundle/blob/1.x/LICENSE.md POCL and PCL
*/

import React, { useState, forwardRef, type MutableRefObject, useEffect } from 'react'
import React, { forwardRef, type MutableRefObject } from 'react'
import { ImagePreview } from '@Pimcore/components/image-preview/image-preview'
import { HotspotMarkersModal } from '@Pimcore/modules/element/dynamic-types/defintinitions/objects/data-related/helpers/hotspot-image/hotspot-markers-modal'
import { fromIHotspots, toIHotspots } from '@Pimcore/modules/element/dynamic-types/defintinitions/objects/data-related/helpers/hotspot-image/utils/hotspot-converter'
Expand All @@ -35,22 +35,16 @@ interface HotspotImagePreviewProps {
markerModalOpen: boolean
setCropModalOpen: (open: boolean) => void
setMarkerModalOpen: (open: boolean) => void
disabled?: boolean
}

export const HotspotImagePreview = forwardRef(function HotspotImagePreview (
{ assetId, height, width, value: initialValue, onChange, cropModalOpen, setCropModalOpen, markerModalOpen, setMarkerModalOpen }: HotspotImagePreviewProps,
{ assetId, height, width, value, onChange, cropModalOpen, setCropModalOpen, markerModalOpen, setMarkerModalOpen, disabled }: HotspotImagePreviewProps,
ref: MutableRefObject<HTMLDivElement>
): React.JSX.Element {
const [value, setValue] = useState<HotspotImageValue>(initialValue)

useEffect(() => {
setValue(initialValue)
}, [initialValue])

const handleHotspotsChange = (iHotspots: IHotspot[]): void => {
const { hotspots, marker } = fromIHotspots(iHotspots)
const newValue: HotspotImageValue = { ...value, hotspots, marker }
setValue(newValue)
onChange?.(newValue)
}

Expand All @@ -68,7 +62,6 @@ export const HotspotImagePreview = forwardRef(function HotspotImagePreview (

const onCropChange = (crop: CropSettings | null): void => {
const newValue = { ...value, crop }
setValue(newValue)
onChange?.(newValue)
}

Expand All @@ -82,17 +75,19 @@ export const HotspotImagePreview = forwardRef(function HotspotImagePreview (
/>

{ cropModalOpen && (
<CropModal
crop={ value.crop }
imageId={ value.image!.id }
onChange={ onCropChange }
onClose={ hideCropModal }
open={ cropModalOpen }
/>
<CropModal
crop={ value.crop }
disabled={ disabled }
imageId={ value.image!.id }
onChange={ onCropChange }
onClose={ hideCropModal }
open={ cropModalOpen }
/>
) }

{ markerModalOpen && (
<HotspotMarkersModal
disabled={ disabled }
hotspots={ toIHotspots(value.hotspots ?? [], value.marker ?? []) }
imageId={ assetId }
onChange={ handleHotspotsChange }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Pimcore
*
* This source file is available under two different licenses:
* - Pimcore Open Core License (POCL)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license https://github.com/pimcore/studio-ui-bundle/blob/1.x/LICENSE.md POCL and PCL
*/

import _ from 'lodash'
import {
type HotspotImageValue
} from '@Pimcore/modules/element/dynamic-types/defintinitions/objects/data-related/components/hotspot-image/hotspot-image'

export const hasValueData = (value?: HotspotImageValue | null): boolean => {
return !_.isEmpty(value?.hotspots) || !_.isEmpty(value?.marker) || !_.isEmpty(value?.crop)
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export const ImageGalleryImagePreview = ({ item, index, value, setValue, disable

if (hotspotMarkersModalContainer.current !== null) {
const hotspotMarkersModalProps = {
disabled,
hotspots,
imageId: item.image!.id,
open: markerModalOpen,
Expand Down Expand Up @@ -207,9 +208,9 @@ export const ImageGalleryImagePreview = ({ item, index, value, setValue, disable
}
},
{
disabled: !hasValueData(index),
disabled: !hasValueData(index) || disabled === true,
label: t('hotspots.clear-data'),
key: 'hotspots-edit',
key: 'clear-data',
icon: <Icon value={ 'remove-marker' } />,
onClick: clearValueData
},
Expand All @@ -226,8 +227,9 @@ export const ImageGalleryImagePreview = ({ item, index, value, setValue, disable
}
},
{
disabled,
label: t('empty'),
key: 'open',
key: 'empty',
icon: <Icon value={ 'trash' } />,
onClick: async () => {
setValue(value.map((v, i) => i === index ? { image: null, hotspots: null, marker: null, crop: null } : v))
Expand All @@ -243,6 +245,7 @@ export const ImageGalleryImagePreview = ({ item, index, value, setValue, disable
{ cropModalOpen && (
<CropModal
crop={ item.crop }
disabled={ disabled }
imageId={ item.image!.id }
onChange={ onCropChange }
onClose={ hideCropModal }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
type CropSettings
} from '@Pimcore/modules/element/dynamic-types/defintinitions/objects/data-related/helpers/hotspot-image/types/crop-types'
import {
cropToHotspot, hotspotToCrop
cropToHotspot, defaultCrop, hotspotToCrop
} from '@Pimcore/modules/element/dynamic-types/defintinitions/objects/data-related/helpers/hotspot-image/utils/crop-converter'

export interface CropModalProps {
Expand All @@ -48,7 +48,7 @@ export const CropModal = (props: CropModalProps): React.JSX.Element => {
}, [props.crop])

const handleOk = (): void => {
props.onChange?.(crop)
props.onChange?.(crop ?? defaultCrop())
props.onClose?.()
}

Expand Down Expand Up @@ -115,6 +115,7 @@ export const CropModal = (props: CropModalProps): React.JSX.Element => {
<HotspotImage
data={ modalOpened ? [cropToHotspot(crop)] : [] }
disableContextMenu
disabled={ props.disabled }
onUpdate={ onUpdate }
src={ thumbnailSrc }
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ export const HotspotMarkersModal = (props: HotspotMarkersModalProps): React.JSX.
>
<HotspotImage
data={ modalOpened ? hotspots : [] }
disabled={ props.disabled }
onClone={ onClone }
onRemove={ onRemove }
onUpdate={ onUpdate }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ export const hotspotToCrop = (hotspot: IHotspot): CropSettings => {
}
}

export const defaultCrop = (): CropSettings => hotspotToCrop(defaultHotspot)

const defaultHotspot: IHotspot = {
id: 1,
x: 10,
Expand Down

0 comments on commit dd2866c

Please sign in to comment.