import { useCallback, useEffect, useState } from "react" import { flushSync } from "react-dom" import { AssetCardVertical, Button, FieldLabel, cbModal, } from "@contentstack/venus-components" import ImageEditModal from "./ImageEditModal" import FullSizeImage from "./FullSizeImage" import { isInsertResponse, openImageVault } from "~/utils/imagevault" import type { CbModalProps } from "@contentstack/venus-components/build/components/Modal/Modal" import type UiLocation from "@contentstack/app-sdk/dist/src/uiLocation" import type { InsertResponse } from "~/types/imagevault" import type { EntryDataPublishDetails, ImageVaultDAMConfig, } from "~/utils/imagevault" import type { Lang } from "~/types/lang" export type ImageVaultDAMProps = { sdk: UiLocation config: ImageVaultDAMConfig initialData: InsertResponse | null } type DAMButtonProps = { onClick: () => void } function DAMButton({ onClick }: DAMButtonProps) { return (
) } type MediaProps = { media: InsertResponse onDelete: () => void onEdit: () => void } function Media({ media, onDelete, onEdit }: MediaProps) { // MediaConversions is an array but will only contain one object const { Url, Height, FormatHeight, Width, FormatWidth, Name, AspectRatio } = media.MediaConversions[0] const assetUrl = Url const title = media.Name const width = FormatWidth || Width const height = FormatHeight || Height const alt = media.Metadata?.find((meta) => meta.Name.includes("AltText_"))?.Value || Name return (
{ const cbModalProps: CbModalProps = { // @ts-expect-error: Component is badly typed component: (props) => { return ( ) }, modalProps: { size: "max", style: { content: { display: "grid", width: "90dvw", height: "90dvh", maxHeight: "100%", gridTemplateRows: "auto 1fr", }, overlay: {}, }, }, } cbModal(cbModalProps) }} />
) } export default function ImageVaultDAM({ sdk, config, initialData, }: ImageVaultDAMProps) { const [media, setMedia] = useState(initialData) const field = sdk.location.CustomField?.field const frame = sdk.location.CustomField?.frame const entry = sdk.location.CustomField?.entry const stack = sdk.location.CustomField?.stack const updateFrameHeight = useCallback(() => { if (frame?.updateHeight) { // We need to recalculate the height of the iframe when an image is added. // Cannot use flushSync inside useEffect. setTimeout(() => frame.updateHeight(document.body.scrollHeight), 0) } }, [frame]) const handleMedia = useCallback( (result?: InsertResponse) => { if (field) { flushSync(() => { const data = result ? { ...result, FocalPoint: result.FocalPoint || { x: 50, y: 50 }, } : null setMedia(data) // Data inside the field is supposed to be an empty object if nothing is selected field.setData(data || {}) document.body.style.overflow = "hidden" }) } updateFrameHeight() }, [field, updateFrameHeight] ) useEffect(() => { updateFrameHeight() }, [updateFrameHeight]) const handleEdit = useCallback(() => { const fieldData = field?.getData() as InsertResponse if (isInsertResponse(fieldData)) { cbModal({ // @ts-expect-error: Component is badly typed component: (compProps) => ( ), modalProps: { size: "max", style: { content: { maxHeight: "90dvh", width: "auto", }, overlay: {}, }, }, }) } }, [field, handleMedia]) if (!field || !frame || !entry || !stack) { return

Initializing custom field...

} const entryData: EntryDataPublishDetails = { //TODO: Add support for branches branch: "main", contentTypeUid: entry.content_type.uid, locale: entry.locale as Lang, stackApiKey: stack._data.api_key, title: entry.getField("title").getData().toString() || `Untitled (${entry.content_type.uid})`, uid: entry._data.uid, } return (
ImageVault Asset
{media ? ( ) : ( { openImageVault({ config, entryData, onSuccess: handleMedia, }) }} /> )}
) }