feat(SW-2541): Changed asset types to only add the data needed
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{ at: path }
|
focalPoint,
|
||||||
)
|
}
|
||||||
|
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>
|
||||||
|
|||||||
@@ -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,78 +88,46 @@ 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
|
<Tooltip
|
||||||
style={{
|
zIndex={909}
|
||||||
...alignmentStyles,
|
className="p-0"
|
||||||
...element.attrs.style,
|
style={{ marginBottom: "10px" }}
|
||||||
}}
|
position="top"
|
||||||
|
variantType="light"
|
||||||
|
offset={[0, -15]}
|
||||||
|
content={<ToolTipButtons />}
|
||||||
>
|
>
|
||||||
<Tooltip
|
<span data-type="asset" contentEditable={false}>
|
||||||
zIndex={909}
|
<div
|
||||||
className="p-0"
|
|
||||||
style={{ marginBottom: "10px" }}
|
|
||||||
position="top"
|
|
||||||
variantType="light"
|
|
||||||
offset={[0, -15]}
|
|
||||||
content={<ToolTipButtons />}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
data-type="asset"
|
|
||||||
contentEditable={false}
|
contentEditable={false}
|
||||||
style={element.attrs?.style}
|
style={{
|
||||||
|
width: "280px",
|
||||||
|
height: "auto",
|
||||||
|
...(isHighlight ? { border: "1px solid #6c5ce7" } : {}),
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<img
|
||||||
contentEditable={false}
|
src={imageVaultAsset.url}
|
||||||
style={{
|
onError={(event) => {
|
||||||
width: "280px",
|
event.currentTarget.src = "https://placehold.co/600x400"
|
||||||
height: "auto",
|
|
||||||
...(isHighlight ? { border: "1px solid #6c5ce7" } : {}),
|
|
||||||
}}
|
}}
|
||||||
>
|
style={{
|
||||||
<img
|
width: "100%",
|
||||||
src={assetUrl}
|
height: "auto",
|
||||||
onError={(event) => {
|
aspectRatio: imageVaultAsset.dimensions.aspectRatio,
|
||||||
event.currentTarget.src = "https://placehold.co/600x400"
|
borderRadius: "8px",
|
||||||
}}
|
}}
|
||||||
style={{
|
alt={imageVaultAsset.meta.alt}
|
||||||
width: "100%",
|
title={`Id: ${imageVaultAsset.imageVaultId} - ${imageVaultAsset.fileName}`}
|
||||||
height: "auto",
|
/>
|
||||||
aspectRatio: element.attrs.MediaConversions[0].AspectRatio,
|
</div>
|
||||||
borderRadius: "8px",
|
{children}
|
||||||
}}
|
</span>
|
||||||
alt={element.attrs.altText}
|
</Tooltip>
|
||||||
title={element.attrs?.Name}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
bottom: 0,
|
|
||||||
right: 0,
|
|
||||||
background: "#fff",
|
|
||||||
color: "#000",
|
|
||||||
height: "25px",
|
|
||||||
padding: "3px",
|
|
||||||
borderRadius: "4px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon icon="Embed" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{children}
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
12
rte/main.tsx
12
rte/main.tsx
@@ -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: "" }],
|
||||||
|
|||||||
@@ -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 }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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") {
|
||||||
|
|||||||
Reference in New Issue
Block a user