import { useCallback, useEffect, useState } from "react" import { flushSync } from "react-dom" import { AssetCardVertical, Button, FieldLabel, cbModal, } from "@contentstack/venus-components" import { getImageVaultAssetFromData, isInsertResponse, openImageVault, } from "~/utils/imagevault" import ImageEditModal from "./ImageEditModal" import type UiLocation from "@contentstack/app-sdk/dist/src/uiLocation" import type { CbModalProps } from "@contentstack/venus-components/build/components/Modal/Modal" import type { ImageVaultAsset, InsertResponse } from "~/types/imagevault" import type { Lang } from "~/types/lang" import type { EntryDataPublishDetails, ImageVaultDAMConfig, } from "~/utils/imagevault" import FullSizeImage from "./FullSizeImage" export type ImageVaultDAMProps = { sdk: UiLocation config: ImageVaultDAMConfig initialData: ImageVaultAsset | InsertResponse | null } type DAMButtonProps = { onClick: () => void } function DAMButton({ onClick }: DAMButtonProps) { return (
) } type MediaProps = { media: ImageVaultAsset onDelete: () => void onEdit: () => void } function Media({ media, onDelete, onEdit }: MediaProps) { const { url, meta, dimensions } = media const { width, height, aspectRatio } = dimensions const assetUrl = media.url const assetTitle = `Id: ${media.imageVaultId} - ${media.fileName}` const alt = meta.alt || meta.caption || "" const title = meta.caption || meta.alt || "" 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 imageVaultAsset = getImageVaultAssetFromData(initialData) const [media, setMedia] = useState(imageVaultAsset) 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(), 0) } }, [frame]) const handleMedia = useCallback( (asset?: ImageVaultAsset) => { if (field) { flushSync(() => { const data = asset || null setMedia(data) field.setData(data) document.body.style.overflow = "hidden" }) } updateFrameHeight() }, [field, updateFrameHeight] ) const handleEdit = useCallback(() => { const fieldData = field?.getData() if (fieldData) { cbModal({ // @ts-expect-error: Component is badly typed component: (compProps) => ( ), modalProps: { size: "max", style: { content: { maxHeight: "90dvh", maxWidth: "90dvw", width: "auto" }, overlay: {}, }, }, }) } }, [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) { 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 }) }} /> )}
) }