feat(SW-2541): Changed asset types to only add the data needed

This commit is contained in:
Erik Tiekstra
2025-09-05 11:25:16 +02:00
parent 64556d4b9c
commit 1337e8293f
7 changed files with 243 additions and 310 deletions

View File

@@ -11,11 +11,11 @@ import {
import { ChangeEvent, useEffect, useState } from "react" import { ChangeEvent, useEffect, useState } from "react"
import FocalPointPicker from "~/shared-components/FocalPointPicker" import FocalPointPicker from "~/shared-components/FocalPointPicker"
import type { FocalPoint, InsertResponse } from "~/types/imagevault" import type { FocalPoint, ImageVaultAsset } from "~/types/imagevault"
type ImageEditModalProps = { type ImageEditModalProps = {
fieldData: InsertResponse fieldData: ImageVaultAsset
setData: (data: InsertResponse) => void setData: (data: ImageVaultAsset) => void
closeModal: () => void closeModal: () => void
} }
@@ -28,47 +28,33 @@ export default function ImageEditModal({
const [caption, setCaption] = useState("") const [caption, setCaption] = useState("")
const [focalPoint, setFocalPoint] = useState<FocalPoint>({ x: 50, y: 50 }) const [focalPoint, setFocalPoint] = useState<FocalPoint>({ x: 50, y: 50 })
const assetUrl = fieldData.MediaConversions[0].Url const assetUrl = fieldData.url
useEffect(() => { useEffect(() => {
if (fieldData.Metadata && fieldData.Metadata.length) { if (fieldData.meta.alt) {
const altText = fieldData.Metadata.find((meta) => setAltText(fieldData.meta.alt)
meta.Name.includes("AltText_")
)?.Value
const caption = fieldData.Metadata.find((meta) =>
meta.Name.includes("Title_")
)?.Value
setAltText(altText ?? "")
setCaption(caption ?? "")
} }
}, [fieldData.Metadata]) if (fieldData.meta.caption) {
setCaption(fieldData.meta.caption)
}
}, [fieldData.meta])
useEffect(() => { useEffect(() => {
if (fieldData.FocalPoint) { if (fieldData.focalPoint) {
setFocalPoint(fieldData.FocalPoint) setFocalPoint(fieldData.focalPoint)
} }
}, [fieldData.FocalPoint]) }, [fieldData.focalPoint])
function handleSave() { function handleSave() {
const metaData = fieldData.Metadata ?? [] const newData = {
const newMetadata = metaData.map((meta) => {
if (meta.Name.includes("AltText_")) {
return { ...meta, Value: altText }
}
if (meta.Name.includes("Title_")) {
return { ...meta, Value: caption }
}
return meta
})
setData({
...fieldData, ...fieldData,
Metadata: newMetadata, meta: {
FocalPoint: focalPoint, alt: altText,
}) caption,
},
focalPoint,
}
setData(newData)
closeModal() closeModal()
} }
@@ -132,7 +118,11 @@ export default function ImageEditModal({
<FieldComponent> <FieldComponent>
<FieldLabel htmlFor="imageVaultId">Imagevault Id</FieldLabel> <FieldLabel htmlFor="imageVaultId">Imagevault Id</FieldLabel>
<TextInput value={fieldData.Id} name="imageVaultId" disabled /> <TextInput
value={fieldData.imageVaultId}
name="imageVaultId"
disabled
/>
</FieldComponent> </FieldComponent>
</div> </div>
</ModalBody> </ModalBody>

View File

@@ -7,12 +7,16 @@ import {
FieldLabel, FieldLabel,
cbModal, cbModal,
} from "@contentstack/venus-components" } from "@contentstack/venus-components"
import { isInsertResponse, openImageVault } from "~/utils/imagevault" import {
getImageVaultAssetFromData,
isInsertResponse,
openImageVault,
} from "~/utils/imagevault"
import ImageEditModal from "./ImageEditModal" import ImageEditModal from "./ImageEditModal"
import type UiLocation from "@contentstack/app-sdk/dist/src/uiLocation" import type UiLocation from "@contentstack/app-sdk/dist/src/uiLocation"
import type { CbModalProps } from "@contentstack/venus-components/build/components/Modal/Modal" import type { CbModalProps } from "@contentstack/venus-components/build/components/Modal/Modal"
import type { InsertResponse } from "~/types/imagevault" import type { ImageVaultAsset, InsertResponse } from "~/types/imagevault"
import type { Lang } from "~/types/lang" import type { Lang } from "~/types/lang"
import type { import type {
EntryDataPublishDetails, EntryDataPublishDetails,
@@ -23,7 +27,7 @@ import FullSizeImage from "./FullSizeImage"
export type ImageVaultDAMProps = { export type ImageVaultDAMProps = {
sdk: UiLocation sdk: UiLocation
config: ImageVaultDAMConfig config: ImageVaultDAMConfig
initialData: InsertResponse | null initialData: ImageVaultAsset | InsertResponse | null
} }
type DAMButtonProps = { onClick: () => void } type DAMButtonProps = { onClick: () => void }
@@ -47,30 +51,26 @@ function DAMButton({ onClick }: DAMButtonProps) {
} }
type MediaProps = { type MediaProps = {
media: InsertResponse media: ImageVaultAsset
onDelete: () => void onDelete: () => void
onEdit: () => void onEdit: () => void
} }
function Media({ media, onDelete, onEdit }: MediaProps) { function Media({ media, onDelete, onEdit }: MediaProps) {
// MediaConversions is an array but will only contain one object const { url, meta, dimensions } = media
const { Url, Height, FormatHeight, Width, FormatWidth, Name, AspectRatio } = const { width, height, aspectRatio } = dimensions
media.MediaConversions[0] const assetUrl = media.url
const assetTitle = `Id: ${media.imageVaultId} - ${media.fileName}`
const assetUrl = Url const alt = meta.alt || meta.caption || ""
const title = `Id: ${media.Id} - ${media.Name}` const title = meta.caption || meta.alt || ""
const width = FormatWidth || Width
const height = FormatHeight || Height
const alt =
media.Metadata?.find((meta) => meta.Name.includes("AltText_"))?.Value ||
Name
return ( return (
<div key={Url} style={{ fontFamily: "Inter" }}> <div key={url} style={{ fontFamily: "Inter" }}>
<AssetCardVertical <AssetCardVertical
assetType="image" assetType="image"
assetUrl={assetUrl} assetUrl={assetUrl}
title={title} title={assetTitle}
version="v2" version="v2"
width={width} width={width}
height={height} height={height}
@@ -84,10 +84,10 @@ function Media({ media, onDelete, onEdit }: MediaProps) {
<FullSizeImage <FullSizeImage
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
onClose={props.closeModal} onClose={props.closeModal}
imageUrl={Url} imageUrl={url}
alt={alt} alt={alt}
title={Name} title={title}
aspectRatio={AspectRatio} aspectRatio={aspectRatio}
/> />
) )
}, },
@@ -117,7 +117,8 @@ export default function ImageVaultDAM({
config, config,
initialData, initialData,
}: ImageVaultDAMProps) { }: ImageVaultDAMProps) {
const [media, setMedia] = useState<InsertResponse | null>(initialData) const imageVaultAsset = getImageVaultAssetFromData(initialData)
const [media, setMedia] = useState(imageVaultAsset)
const field = sdk.location.CustomField?.field const field = sdk.location.CustomField?.field
const frame = sdk.location.CustomField?.frame const frame = sdk.location.CustomField?.frame
@@ -133,12 +134,10 @@ export default function ImageVaultDAM({
}, [frame]) }, [frame])
const handleMedia = useCallback( const handleMedia = useCallback(
(result?: InsertResponse) => { (asset?: ImageVaultAsset) => {
if (field) { if (field) {
flushSync(() => { flushSync(() => {
const data = result const data = asset || null
? { ...result, FocalPoint: result.FocalPoint || { x: 50, y: 50 } }
: null
setMedia(data) setMedia(data)
field.setData(data) field.setData(data)
document.body.style.overflow = "hidden" document.body.style.overflow = "hidden"
@@ -150,13 +149,9 @@ export default function ImageVaultDAM({
[field, updateFrameHeight] [field, updateFrameHeight]
) )
useEffect(() => {
updateFrameHeight()
}, [updateFrameHeight])
const handleEdit = useCallback(() => { const handleEdit = useCallback(() => {
const fieldData = field?.getData() as InsertResponse const fieldData = field?.getData()
if (isInsertResponse(fieldData)) { if (fieldData) {
cbModal({ cbModal({
// @ts-expect-error: Component is badly typed // @ts-expect-error: Component is badly typed
component: (compProps) => ( component: (compProps) => (
@@ -177,6 +172,18 @@ export default function ImageVaultDAM({
} }
}, [field, handleMedia]) }, [field, handleMedia])
useEffect(() => {
updateFrameHeight()
}, [updateFrameHeight])
// The existing data might still be in InsertResponse format if the user has not edited it yet.
// We'll convert it to ImageVaultAsset when the user edits the the emtry.
useEffect(() => {
if (isInsertResponse(initialData) && imageVaultAsset) {
handleMedia(imageVaultAsset)
}
}, [initialData, imageVaultAsset, handleMedia])
if (!field || !frame || !entry || !stack) { if (!field || !frame || !entry || !stack) {
return <p>Initializing custom field...</p> return <p>Initializing custom field...</p>
} }

View File

@@ -1,154 +1,65 @@
import { import {
Button, Button,
ButtonGroup, ButtonGroup,
Field, Field as FieldComponent,
FieldLabel, FieldLabel,
ModalBody, ModalBody,
ModalFooter, ModalFooter,
ModalHeader, ModalHeader,
Select,
TextInput, TextInput,
} from "@contentstack/venus-components" } from "@contentstack/venus-components"
import React, { ChangeEvent, useEffect, useState } from "react" import React, { ChangeEvent, useEffect, useState } from "react"
import { Path } from "slate"
import type { import type { IRteElementType } from "@contentstack/app-sdk/dist/src/RTE/types"
IRteElementType,
IRteParam,
} from "@contentstack/app-sdk/dist/src/RTE/types"
import FocalPointPicker from "~/shared-components/FocalPointPicker" import FocalPointPicker from "~/shared-components/FocalPointPicker"
import type { FocalPoint, InsertResponse } from "~/types/imagevault" import type { FocalPoint, ImageVaultAsset } from "~/types/imagevault"
enum DropdownValues {
center = "center",
left = "left",
right = "right",
none = "none",
}
type DropDownItem = {
label: string
value: DropdownValues
type: string
}
const dropdownList: DropDownItem[] = [
{
label: "None",
value: DropdownValues.none,
type: "select",
},
{
label: "Center",
value: DropdownValues.center,
type: "select",
},
{
label: "Left",
value: DropdownValues.left,
type: "select",
},
{
label: "Right",
value: DropdownValues.right,
type: "select",
},
]
type ImageEditModalProps = { type ImageEditModalProps = {
element: IRteElementType & { element: IRteElementType & {
attrs: InsertResponse attrs: ImageVaultAsset
} }
rte: IRteParam setData: (data: ImageVaultAsset) => void
closeModal: () => void closeModal: () => void
path: Path
} }
export default function ImageEditModal({ export default function ImageEditModal({
element, element,
setData,
closeModal, closeModal,
path,
rte,
}: ImageEditModalProps) { }: ImageEditModalProps) {
const [alignment, setAlignment] = useState<DropDownItem>({ const imageVaultAsset: ImageVaultAsset = element.attrs
label: "None",
value: DropdownValues.none,
type: "select",
})
const [altText, setAltText] = useState("") const [altText, setAltText] = useState("")
const [caption, setCaption] = useState("") const [caption, setCaption] = useState("")
const [focalPoint, setFocalPoint] = useState<FocalPoint>({ x: 50, y: 50 }) const [focalPoint, setFocalPoint] = useState<FocalPoint>({ x: 50, y: 50 })
const assetUrl = element.attrs.MediaConversions[0].Url const assetUrl = imageVaultAsset.url
useEffect(() => { useEffect(() => {
if (element.attrs.Metadata && element.attrs.Metadata.length) { if (imageVaultAsset.meta.alt) {
const altText = element.attrs.Metadata.find((meta) => setAltText(imageVaultAsset.meta.alt)
meta.Name.includes("AltText_")
)?.Value
const caption = element.attrs.Metadata.find((meta) =>
meta.Name.includes("Title_")
)?.Value
setAltText(altText ?? "")
setCaption(caption ?? "")
} }
}, [element.attrs.Metadata]) if (imageVaultAsset.meta.caption) {
setCaption(imageVaultAsset.meta.caption)
}
}, [imageVaultAsset.meta])
useEffect(() => { useEffect(() => {
if (element.attrs.FocalPoint) { if (imageVaultAsset.focalPoint) {
setFocalPoint(element.attrs.FocalPoint) setFocalPoint(imageVaultAsset.focalPoint)
} }
}, [element.attrs.FocalPoint]) }, [imageVaultAsset.focalPoint])
function handleSave() { function handleSave() {
let newStyle const newData = {
...imageVaultAsset,
switch (alignment.value) { meta: {
case DropdownValues.center: alt: altText,
case DropdownValues.left: caption,
case DropdownValues.right:
newStyle = {
textAlign: alignment.value,
maxWidth: element.attrs.width
? `${element.attrs.width}px`
: undefined,
}
break
case DropdownValues.none:
default:
newStyle = {}
break
}
const metaData = element.attrs.Metadata ?? []
const newMetadata = metaData.map((meta) => {
if (meta.Name.includes("AltText_")) {
return { ...meta, Value: altText }
}
if (meta.Name.includes("Title_")) {
return { ...meta, Value: caption }
}
return meta
})
rte._adv.Transforms?.setNodes<IRteElementType>(
rte._adv.editor,
{
attrs: {
...element.attrs,
Metadata: newMetadata,
position: alignment.value,
style: { ...element.attrs.style, ...newStyle },
FocalPoint: focalPoint,
}, },
}, focalPoint,
{ at: path } }
) setData(newData)
closeModal() closeModal()
} }
@@ -177,18 +88,7 @@ export default function ImageEditModal({
onChange={changeFocalPoint} onChange={changeFocalPoint}
/> />
<div> <div>
<Field> <FieldComponent>
{/* @ts-expect-error: Type incompatibility with Venus components */}
<Select
selectLabel="Alignment"
value={alignment}
onChange={(e: DropDownItem) => {
setAlignment(e)
}}
options={dropdownList}
/>
</Field>
<Field>
<FieldLabel htmlFor="alt">Alt text</FieldLabel> <FieldLabel htmlFor="alt">Alt text</FieldLabel>
<TextInput <TextInput
value={altText} value={altText}
@@ -198,9 +98,9 @@ export default function ImageEditModal({
setAltText(e.target.value) setAltText(e.target.value)
} }
/> />
</Field> </FieldComponent>
<Field> <FieldComponent>
<FieldLabel htmlFor="caption">Caption</FieldLabel> <FieldLabel htmlFor="caption">Caption</FieldLabel>
<TextInput <TextInput
value={caption} value={caption}
@@ -210,16 +110,25 @@ export default function ImageEditModal({
setCaption(e.target.value) setCaption(e.target.value)
} }
/> />
</Field> </FieldComponent>
<Field> <FieldComponent>
<FieldLabel htmlFor="focalPoint">Focal Point</FieldLabel> <FieldLabel htmlFor="focalPoint">Focal Point</FieldLabel>
<TextInput <TextInput
value={`X: ${focalPoint.x}, Y: ${focalPoint.y}`} value={`X: ${focalPoint.x}, Y: ${focalPoint.y}`}
name="focalPoint" name="focalPoint"
disabled disabled
/> />
</Field> </FieldComponent>
<FieldComponent>
<FieldLabel htmlFor="imageVaultId">Imagevault Id</FieldLabel>
<TextInput
value={imageVaultAsset.imageVaultId}
name="imageVaultId"
disabled
/>
</FieldComponent>
</div> </div>
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>

View File

@@ -1,5 +1,5 @@
import { Icon, Tooltip, cbModal } from "@contentstack/venus-components" import { Icon, Tooltip, cbModal } from "@contentstack/venus-components"
import React, { PropsWithChildren, useCallback } from "react" import React, { PropsWithChildren, useCallback, useEffect } from "react"
import EmbedBtn from "./EmbedBtn" import EmbedBtn from "./EmbedBtn"
import ImageEditModal from "./ImageEditModal" import ImageEditModal from "./ImageEditModal"
@@ -8,28 +8,46 @@ import type {
IRteElementType, IRteElementType,
IRteParam, IRteParam,
} from "@contentstack/app-sdk/dist/src/RTE/types" } from "@contentstack/app-sdk/dist/src/RTE/types"
import type { InsertResponse } from "~/types/imagevault" import type { ImageVaultAsset, InsertResponse } from "~/types/imagevault"
import {
getImageVaultAssetFromData,
isInsertResponse,
} from "~/utils/imagevault"
type ImageElementProps = PropsWithChildren & { type ImageElementProps = PropsWithChildren & {
element: IRteElementType & { attrs: InsertResponse } element: IRteElementType & { attrs: ImageVaultAsset | InsertResponse }
rte: IRteParam rte: IRteParam
} }
export function ImageElement({ children, element, rte }: ImageElementProps) { export function ImageElement({ children, element, rte }: ImageElementProps) {
const assetUrl = element.attrs.MediaConversions[0].Url const imageVaultAsset = getImageVaultAssetFromData(element.attrs)
const isSelected = rte?.selection?.isSelected() const isSelected = rte.selection.isSelected()
const isFocused = rte?.selection?.isFocused() const isFocused = rte.selection.isFocused()
const path = rte.getPath(element)
const isHighlight = isFocused && isSelected const isHighlight = isFocused && isSelected
const handleMedia = useCallback(
(asset: ImageVaultAsset) => {
rte._adv.Transforms.setNodes<IRteElementType>(
rte._adv.editor,
{
attrs: { ...asset },
},
{ at: path }
)
},
[rte, path]
)
const handleEdit = useCallback(() => { const handleEdit = useCallback(() => {
cbModal({ cbModal({
// @ts-expect-error: Component is badly typed // @ts-expect-error: Component is badly typed
component: (compProps) => component: (compProps) => (
ImageEditModal({ <ImageEditModal
element, element={element}
rte, setData={handleMedia}
path: rte.getPath(element), {...compProps}
...compProps, />
}), ),
modalProps: { modalProps: {
size: "max", size: "max",
style: { style: {
@@ -42,7 +60,15 @@ export function ImageElement({ children, element, rte }: ImageElementProps) {
}, },
}, },
}) })
}, [element, rte]) }, [element, handleMedia])
// The existing data might still be in InsertResponse format if the user has not edited it yet.
// We'll convert it to ImageVaultAsset when the user edits the RTE.
useEffect(() => {
if (isInsertResponse(element.attrs) && imageVaultAsset) {
handleMedia(imageVaultAsset)
}
}, [element.attrs, imageVaultAsset, handleMedia])
const ToolTipButtons = () => { const ToolTipButtons = () => {
return ( return (
@@ -54,7 +80,7 @@ export function ImageElement({ children, element, rte }: ImageElementProps) {
<EmbedBtn <EmbedBtn
title="remove" title="remove"
content={"Remove"} content={"Remove"}
onClick={() => rte?.removeNode(element)} onClick={() => rte.removeNode(element)}
> >
<Icon icon="Trash" /> <Icon icon="Trash" />
</EmbedBtn> </EmbedBtn>
@@ -62,24 +88,11 @@ export function ImageElement({ children, element, rte }: ImageElementProps) {
) )
} }
let alignmentStyles = {} if (!imageVaultAsset) {
const marginAlignment: Record<string, { [key: string]: string }> = { return <>{children}</>
center: { margin: "auto" },
left: { marginRight: "auto" },
right: { marginLeft: "auto" },
}
if (typeof element.attrs.position === "string") {
alignmentStyles = marginAlignment[element.attrs.position]
} }
return ( return (
<div
style={{
...alignmentStyles,
...element.attrs.style,
}}
>
<Tooltip <Tooltip
zIndex={909} zIndex={909}
className="p-0" className="p-0"
@@ -89,11 +102,7 @@ export function ImageElement({ children, element, rte }: ImageElementProps) {
offset={[0, -15]} offset={[0, -15]}
content={<ToolTipButtons />} content={<ToolTipButtons />}
> >
<span <span data-type="asset" contentEditable={false}>
data-type="asset"
contentEditable={false}
style={element.attrs?.style}
>
<div <div
contentEditable={false} contentEditable={false}
style={{ style={{
@@ -103,37 +112,22 @@ export function ImageElement({ children, element, rte }: ImageElementProps) {
}} }}
> >
<img <img
src={assetUrl} src={imageVaultAsset.url}
onError={(event) => { onError={(event) => {
event.currentTarget.src = "https://placehold.co/600x400" event.currentTarget.src = "https://placehold.co/600x400"
}} }}
style={{ style={{
width: "100%", width: "100%",
height: "auto", height: "auto",
aspectRatio: element.attrs.MediaConversions[0].AspectRatio, aspectRatio: imageVaultAsset.dimensions.aspectRatio,
borderRadius: "8px", borderRadius: "8px",
}} }}
alt={element.attrs.altText} alt={imageVaultAsset.meta.alt}
title={element.attrs?.Name} title={`Id: ${imageVaultAsset.imageVaultId} - ${imageVaultAsset.fileName}`}
/> />
<div
style={{
position: "absolute",
bottom: 0,
right: 0,
background: "#fff",
color: "#000",
height: "25px",
padding: "3px",
borderRadius: "4px",
}}
>
<Icon icon="Embed" />
</div>
</div> </div>
{children} {children}
</span> </span>
</Tooltip> </Tooltip>
</div>
) )
} }

View File

@@ -1,11 +1,10 @@
import ContentstackSDK from "@contentstack/app-sdk" import ContentstackSDK from "@contentstack/app-sdk"
import React from "react" import React from "react"
import { ImageElement } from "~/components/ImageElement"
import { Icon } from "@contentstack/venus-components" import { Icon } from "@contentstack/venus-components"
import { openImageVault } from "~/utils/imagevault" import { openImageVault } from "~/utils/imagevault"
import { ImageElement } from "~/components/ImageElement"
import type { import type {
ContentstackEmbeddedData, ContentstackEmbeddedData,
ContentstackPluginDefinition, ContentstackPluginDefinition,
@@ -121,10 +120,10 @@ function loadIV(plugin: ContentstackPluginDefinition) {
} }
} }
export default ContentstackSDK.init().then(async (sdk) => { export default ContentstackSDK.init().then((sdk) => {
try { try {
const extensionObj = await sdk["location"] const extensionObj = sdk["location"]
const RTE = await extensionObj["RTEPlugin"] const RTE = extensionObj["RTEPlugin"]
if (!RTE) { if (!RTE) {
return return
@@ -178,7 +177,7 @@ export default ContentstackSDK.init().then(async (sdk) => {
ImageVault.on("exec", async (rte) => { ImageVault.on("exec", async (rte) => {
if (rte) { if (rte) {
const savedSelection = rte?.selection?.get() ?? undefined const savedSelection = rte.selection.get() ?? undefined
const appConfig = await rte.getConfig() const appConfig = await rte.getConfig()
@@ -214,7 +213,6 @@ export default ContentstackSDK.init().then(async (sdk) => {
type: "ImageVault", type: "ImageVault",
attrs: { attrs: {
...result, ...result,
FocalPoint: result.FocalPoint || { x: 50, y: 50 },
}, },
uid: crypto.randomUUID(), uid: crypto.randomUUID(),
children: [{ text: "" }], children: [{ text: "" }],

View File

@@ -55,8 +55,8 @@ export type MetaData = {
} }
export type ImageVaultAsset = { export type ImageVaultAsset = {
id: number imageVaultId: number
title: string fileName: string
url: string url: string
dimensions: { dimensions: {
width: number width: number
@@ -64,7 +64,7 @@ export type ImageVaultAsset = {
aspectRatio: number aspectRatio: number
} }
focalPoint: FocalPoint focalPoint: FocalPoint
meta: { alt: string | undefined; caption: string | undefined } meta: { alt: string; caption: string }
} }
/** /**

View File

@@ -1,7 +1,6 @@
import { langEnum } from "../types/lang" import { langEnum } from "../types/lang"
import type { GenericObjectType } from "@contentstack/app-sdk/dist/src/types/common.types" import type { GenericObjectType } from "@contentstack/app-sdk/dist/src/types/common.types"
import type { Lang } from "../types/lang"
import type { import type {
Config, Config,
FocalPoint, FocalPoint,
@@ -9,6 +8,7 @@ import type {
InsertResponse, InsertResponse,
PublishDetails, PublishDetails,
} from "../types/imagevault" } from "../types/imagevault"
import type { Lang } from "../types/lang"
const metaIds = { const metaIds = {
[langEnum.de]: { altText: 68, title: 77 }, [langEnum.de]: { altText: 68, title: 77 },
@@ -50,9 +50,15 @@ export function getPublishDetails(
} }
export function isInsertResponse( export function isInsertResponse(
res: InsertResponse | GenericObjectType res: InsertResponse | GenericObjectType | null | undefined
): res is InsertResponse { ): res is InsertResponse {
return (res as InsertResponse).MediaConversions !== undefined return (res as InsertResponse)?.MediaConversions !== undefined
}
export function isImageVaultAsset(
res: ImageVaultAsset | InsertResponse | GenericObjectType | null | undefined
): res is ImageVaultAsset {
return (res as ImageVaultAsset)?.url !== undefined
} }
export type ImageVaultDAMConfig = { export type ImageVaultDAMConfig = {
@@ -67,42 +73,67 @@ export function isImageVaultDAMConfig(
return !!(config.baseUrl && config.formatId && config.imageVaultUrl) return !!(config.baseUrl && config.formatId && config.imageVaultUrl)
} }
// Utility function to convert InsertResponse to ImageVaultAsset, used mainly for custom field images // Utility function to get ImageVaultAsset from either InsertResponse or ImageVaultAsset
// For RTE this function is not enough since rte:s also need attrs, like position and the size thats // Because we currently have both types saved in Contentstack, we need to account for both when retrieving the data of the asset.
// chosen in the editor export function getImageVaultAssetFromData(
export function insertResponseToImageVaultAsset( data: InsertResponse | ImageVaultAsset | GenericObjectType | null | undefined
): ImageVaultAsset | null {
if (isImageVaultAsset(data)) {
return data
}
if (isInsertResponse(data)) {
return mapInsertResponseToImageVaultAsset(data, { x: 50, y: 50 })
}
return null
}
// Utility function to convert InsertResponse to ImageVaultAsset
function mapInsertResponseToImageVaultAsset(
response: InsertResponse, response: InsertResponse,
focalPoint: FocalPoint focalPoint: FocalPoint
): ImageVaultAsset { ): ImageVaultAsset {
const alt = response.Metadata?.find((meta) => let image = response.MediaConversions.find(
meta.Name.includes("AltText_") (conversion) =>
)?.Value conversion.MediaFormatName === "Original" &&
conversion.ContentType === "image/jpeg"
)
const caption = response.Metadata?.find((meta) => // We only receive one alt and title is in the response, depending on the language of the entry
meta.Name.includes("Title_") // This is why we're getting the first one found
)?.Value const alt =
response.Metadata?.find((meta) => meta.Name.includes("AltText_"))?.Value ||
""
const caption =
response.Metadata?.find((meta) => meta.Name.includes("Title_"))?.Value || ""
if (!image) {
const imageAsJpeg = response.MediaConversions.find(
(conversion) => conversion.ContentType === "image/jpeg"
)
image = imageAsJpeg || response.MediaConversions[0]
}
return { return {
url: response.MediaConversions[0].Url, imageVaultId: response.Id,
id: response.Id, url: image.Url,
meta: { meta: {
alt, alt,
caption, caption,
}, },
title: response.Name, fileName: response.Name,
dimensions: { dimensions: {
width: response.MediaConversions[0].Width, width: image.Width,
height: response.MediaConversions[0].Height, height: image.Height,
aspectRatio: response.MediaConversions[0].FormatAspectRatio, aspectRatio: image.AspectRatio,
}, },
focalPoint, focalPoint: response.FocalPoint || focalPoint,
} }
} }
export type openImageVaultParams = { export type openImageVaultParams = {
config: ImageVaultDAMConfig config: ImageVaultDAMConfig
entryData: EntryDataPublishDetails entryData: EntryDataPublishDetails
onSuccess: (result: InsertResponse) => void onSuccess: (result: ImageVaultAsset) => void
onClose?: () => void onClose?: () => void
} }
@@ -126,7 +157,11 @@ export function openImageVault({
publishDetails, publishDetails,
insertMultiple: false, insertMultiple: false,
success: (result) => { success: (result) => {
onSuccess(result.response) const imageVaultAsset = mapInsertResponseToImageVaultAsset(
result.response,
{ x: 50, y: 50 }
)
onSuccess(imageVaultAsset)
}, },
close: () => { close: () => {
if (typeof onClose === "function") { if (typeof onClose === "function") {