Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add image advanced data object data type #890

Merged
merged 2 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions assets/js/src/core/app/config/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ import { DynamicTypeObjectDataTime } from '@Pimcore/modules/element/dynamic-type
import { DynamicTypeObjectDataExternalImage } from '@Pimcore/modules/element/dynamic-types/defintinitions/objects/data-related/types/dynamic-type-object-data-external-image'
import { DynamicTypeObjectDataImage } from '@Pimcore/modules/element/dynamic-types/defintinitions/objects/data-related/types/dynamic-type-object-data-image'
import { DynamicTypeObjectDataVideo } from '@Pimcore/modules/element/dynamic-types/defintinitions/objects/data-related/types/dynamic-type-object-data-video'
import { DynamicTypeObjectDataHotspotImage } from '@Pimcore/modules/element/dynamic-types/defintinitions/objects/data-related/types/dynamic-type-object-data-hotspotimage'
import { DynamicTypeObjectDataImageGallery } from '@Pimcore/modules/element/dynamic-types/defintinitions/objects/data-related/types/dynamic-type-object-data-image-gallery'
import { DynamicTypeObjectDataGeoPoint } from '@Pimcore/modules/element/dynamic-types/defintinitions/objects/data-related/types/dynamic-type-object-data-geopoint'
import { DynamicTypeObjectDataGeoBounds } from '@Pimcore/modules/element/dynamic-types/defintinitions/objects/data-related/types/dynamic-type-object-data-geobounds'
Expand Down Expand Up @@ -262,6 +263,7 @@ container.bind(serviceIds['DynamicTypes/ObjectData/Time']).to(DynamicTypeObjectD
container.bind(serviceIds['DynamicTypes/ObjectData/ExternalImage']).to(DynamicTypeObjectDataExternalImage).inSingletonScope()
container.bind(serviceIds['DynamicTypes/ObjectData/Image']).to(DynamicTypeObjectDataImage).inSingletonScope()
container.bind(serviceIds['DynamicTypes/ObjectData/Video']).to(DynamicTypeObjectDataVideo).inSingletonScope()
container.bind(serviceIds['DynamicTypes/ObjectData/HotspotImage']).to(DynamicTypeObjectDataHotspotImage).inSingletonScope()
container.bind(serviceIds['DynamicTypes/ObjectData/ImageGallery']).to(DynamicTypeObjectDataImageGallery).inSingletonScope()
container.bind(serviceIds['DynamicTypes/ObjectData/GeoPoint']).to(DynamicTypeObjectDataGeoPoint).inSingletonScope()
container.bind(serviceIds['DynamicTypes/ObjectData/GeoBounds']).to(DynamicTypeObjectDataGeoBounds).inSingletonScope()
Expand Down
1 change: 1 addition & 0 deletions assets/js/src/core/app/config/services/service-ids.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export const serviceIds = {
'DynamicTypes/ObjectData/ExternalImage': 'DynamicTypes/ObjectData/ExternalImage',
'DynamicTypes/ObjectData/Image': 'DynamicTypes/ObjectData/Image',
'DynamicTypes/ObjectData/Video': 'DynamicTypes/ObjectData/Video',
'DynamicTypes/ObjectData/HotspotImage': 'DynamicTypes/ObjectData/HotspotImage',
'DynamicTypes/ObjectData/ImageGallery': 'DynamicTypes/ObjectData/ImageGallery',
'DynamicTypes/ObjectData/GeoPoint': 'DynamicTypes/ObjectData/GeoPoint',
'DynamicTypes/ObjectData/GeoBounds': 'DynamicTypes/ObjectData/GeoBounds',
Expand Down
1 change: 0 additions & 1 deletion assets/js/src/core/components/hotspot-image/utils/drag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ export const dragItem = (
marginLeft: number,
marginTop: number
): IHotspot[] => {
console.log('margins', marginLeft, marginTop)
const hotspot = convertHotspotToPixel(hotspots[hotspotIndex], containerBounds)
const newX = Math.min(containerBounds.width - hotspot.width, Math.max(0, evt.clientX - containerBounds.left - dragStart.x)) - marginLeft
const newY = Math.min(containerBounds.height - hotspot.height, Math.max(0, evt.clientY - containerBounds.top - dragStart.y)) - marginTop
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**
* 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 React from 'react'
import { IconButton } from '@Pimcore/components/icon-button/icon-button'
import { type HotspotImageValue } from './hotspot-image'
import _ from 'lodash'
import { Tooltip } from 'antd'
import { useTranslation } from 'react-i18next'
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'

interface HotspotImageFooterProps {
emptyValue?: () => void
disabled?: boolean
value?: HotspotImageValue | null
setValue: (value: HotspotImageValue | null) => void
setCropModalOpen: (open: boolean) => void
setMarkerModalOpen: (open: boolean) => void
}

export const HotspotImageFooter = (props: HotspotImageFooterProps): React.JSX.Element => {
const { t } = useTranslation()
const { openAsset } = useAssetHelper()

const clearValueData = (): 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)
}

return (
<ButtonGroup
items={ [
<Tooltip
key="empty"
title={ t('empty') }
>
<IconButton
disabled={ _.isEmpty(props.value) || props.disabled }
icon={ { value: 'trash' } }
onClick={ props.emptyValue }
/>
</Tooltip>,
<Tooltip
key="open"
title={ t('open') }
>
<IconButton
disabled={ _.isEmpty(props.value) }
icon={ { value: 'open-folder' } }
onClick={ () => {
if (typeof props.value?.image?.id === 'number') {
openAsset({ config: { id: props.value.image.id } })
}
} }
/>
</Tooltip>,
<Dropdown
key="more"
menu={ {
items: [
{
label: t('crop'),
key: 'crop',
icon: <Icon value={ 'crop' } />,
onClick: async () => {
props.setCropModalOpen(true)
}
},
{
label: t('hotspots.edit'),
key: 'hotspots-edit',
icon: <Icon value={ 'new-marker' } />,
onClick: async () => {
props.setMarkerModalOpen(true)
}
},
{
disabled: !hasValueData(),
label: t('hotspots.clear-data'),
key: 'hotspots-edit',
icon: <Icon value={ 'remove-marker' } />,
onClick: clearValueData
}
]
} }
placement='topLeft'
trigger={ ['click'] }
>
<IconButton
icon={ { value: 'more' } }
onClick={ (e) => { e.stopPropagation() } }
size="small"
/>
</Dropdown>
] }
noSpacing
/>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/**
* 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 React, { useEffect, useState } from 'react'
import { Card } from '@Pimcore/components/card/card'
import {
HotspotImageFooter
} from './footer'
import { AssetTarget } from '@Pimcore/components/asset-target/asset-target'
import { useTranslation } from 'react-i18next'
import { Droppable } from '@Pimcore/components/drag-and-drop/droppable'
import type { DragAndDropInfo } from '@Pimcore/components/drag-and-drop/context-provider'
import type {
ImageValue
} from '@Pimcore/modules/element/dynamic-types/defintinitions/objects/data-related/components/image/image'
import type {
Hotspot,
Marker
} from '@Pimcore/modules/element/dynamic-types/defintinitions/objects/data-related/helpers/hotspot-image/types/hotspot-types'
import type {
CropSettings
} from '@Pimcore/modules/element/dynamic-types/defintinitions/objects/data-related/helpers/hotspot-image/types/crop-types'
import {
HotspotImagePreview
} from '@Pimcore/modules/element/dynamic-types/defintinitions/objects/data-related/components/hotspot-image/image-preview'

export interface HotspotImageValue {
image: ImageValue | null
hotspots?: Hotspot[] | null
marker?: Marker[] | null
crop?: CropSettings | null
}

export interface HotspotImageProps {
width: string | number | null
height: string | number | null
disabled?: boolean
value?: HotspotImageValue | null
onChange?: (value: HotspotImageValue | null) => void
}

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 { t } = useTranslation()
const emptyValue = (): void => {
setValue(null)
}

useEffect(() => {
props.onChange?.(value)
}, [value])

const width = props.width === null || props.width === '' ? 300 : props.width
const height = props.height === null || props.width === '' ? 150 : props.height

return (
<Card
className="max-w-full"
fitContent
footer={ <HotspotImageFooter
disabled={ props.disabled }
emptyValue={ emptyValue }
key="image-footer"
setCropModalOpen={ setCropModalOpen }
setMarkerModalOpen={ setMarkerModalOpen }
setValue={ setValue }
value={ value }
/> }
>
<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 } }) } }
variant="outline"
>
{ // eslint-disable-next-line @typescript-eslint/prefer-optional-chain
value !== null && value?.image !== null
? (
<HotspotImagePreview
assetId={ value.image.id }
cropModalOpen={ cropModalOpen }
height={ height }
markerModalOpen={ markerModalOpen }
onChange={ props.onChange }
setCropModalOpen={ setCropModalOpen }
setMarkerModalOpen={ setMarkerModalOpen }
value={ value }
width={ width }
/>
)
: (
<AssetTarget
dndIcon={ props.disabled !== true }
height={ height }
title={ t(props.disabled !== true ? 'image.dnd-target' : 'empty') }
uploadIcon={ props.disabled !== true }
width={ width }
/>
) }
</Droppable>
</Card>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/**
* 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 React, { useState, forwardRef, type MutableRefObject, useEffect } 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'
import type { IHotspot } from '@Pimcore/components/hotspot-image/hotspot-image'
import type { HotspotImageValue } from './hotspot-image'
import _ from 'lodash'
import {
CropModal
} from '@Pimcore/modules/element/dynamic-types/defintinitions/objects/data-related/helpers/hotspot-image/crop-modal'
import type {
CropSettings
} from '@Pimcore/modules/element/dynamic-types/defintinitions/objects/data-related/helpers/hotspot-image/types/crop-types'

interface HotspotImagePreviewProps {
assetId: number
height: number | string
width: number | string
value: HotspotImageValue
onChange?: (value: HotspotImageValue) => void
cropModalOpen: boolean
markerModalOpen: boolean
setCropModalOpen: (open: boolean) => void
setMarkerModalOpen: (open: boolean) => void
}

export const HotspotImagePreview = forwardRef(function HotspotImagePreview (
{ assetId, height, width, value: initialValue, onChange, cropModalOpen, setCropModalOpen, markerModalOpen, setMarkerModalOpen }: 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)
}

const hasHotspotData = (): boolean => {
return !_.isEmpty(value.hotspots) || !_.isEmpty(value.marker)
}

const hideMarkerModal = (): void => {
setMarkerModalOpen(false)
}

const hideCropModal = (): void => {
setCropModalOpen(false)
}

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

return (
<div ref={ ref }>
<ImagePreview
assetId={ assetId }
height={ height }
onHotspotsDataButtonClick={ hasHotspotData() ? () => { setMarkerModalOpen(true) } : undefined }
width={ width }
/>

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

{ markerModalOpen && (
<HotspotMarkersModal
hotspots={ toIHotspots(value.hotspots ?? [], value.marker ?? []) }
imageId={ assetId }
onChange={ handleHotspotsChange }
onClose={ hideMarkerModal }
open={ markerModalOpen }
/>
) }
</div>
)
})
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ export const CropModal = (props: CropModalProps): React.JSX.Element => {
}

const onUpdate = (item: IHotspot): void => {
console.log('onupdate', item, hotspotToCrop(item))
setCrop(hotspotToCrop(item))
}

Expand All @@ -72,8 +71,8 @@ export const CropModal = (props: CropModalProps): React.JSX.Element => {
props.onClose?.()
}

const thumbnailSrc = `${getPrefix()}/assets/${props.imageId}/image/stream/custom?width=${width}&height=${height}&mimeType=JPEG&resizeMode=none&contain=true`
console.log('currentcrop', cropToHotspot(crop))
const thumbnailSrc = `${getPrefix()}/assets/${props.imageId}/image/stream/custom?width=${width}&height=${height}&mimeType=PNG&resizeMode=none&contain=true`

return (
<Modal
afterOpenChange={ afterOpenChange }
Expand Down
Loading
Loading