222 lines
5.5 KiB
TypeScript
222 lines
5.5 KiB
TypeScript
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 (
|
|
<div style={{ padding: 2 }}>
|
|
<Button
|
|
onClick={onClick}
|
|
buttonType="link"
|
|
version="v2"
|
|
icon="v2-Assets"
|
|
iconAlignment="left"
|
|
size="small"
|
|
id="imagevaultpicker"
|
|
>
|
|
Choose a file
|
|
</Button>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
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 (
|
|
<div key={Url} style={{ fontFamily: "Inter" }}>
|
|
<AssetCardVertical
|
|
assetType="image"
|
|
assetUrl={assetUrl}
|
|
title={title}
|
|
version="v2"
|
|
width={width}
|
|
height={height}
|
|
onDeleteClick={onDelete}
|
|
onEditClick={onEdit}
|
|
onFullScreenClick={() => {
|
|
const cbModalProps: CbModalProps = {
|
|
// @ts-expect-error: Component is badly typed
|
|
component: (props) => {
|
|
return (
|
|
<FullSizeImage
|
|
// eslint-disable-next-line react/prop-types
|
|
onClose={props.closeModal}
|
|
imageUrl={Url}
|
|
alt={alt}
|
|
title={Name}
|
|
aspectRatio={AspectRatio}
|
|
/>
|
|
)
|
|
},
|
|
modalProps: {
|
|
size: "max",
|
|
style: {
|
|
content: {
|
|
display: "grid",
|
|
width: "90dvw",
|
|
height: "90dvh",
|
|
maxHeight: "100%",
|
|
gridTemplateRows: "auto 1fr",
|
|
},
|
|
overlay: {},
|
|
},
|
|
},
|
|
}
|
|
cbModal(cbModalProps)
|
|
}}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default function ImageVaultDAM({
|
|
sdk,
|
|
config,
|
|
initialData,
|
|
}: ImageVaultDAMProps) {
|
|
const [media, setMedia] = useState<InsertResponse | null>(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 && result) {
|
|
flushSync(() => {
|
|
setMedia(result)
|
|
field.setData(result)
|
|
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) => (
|
|
<ImageEditModal
|
|
fieldData={fieldData}
|
|
setData={handleMedia}
|
|
{...compProps}
|
|
/>
|
|
),
|
|
modalProps: {
|
|
size: "max",
|
|
},
|
|
})
|
|
}
|
|
}, [field, handleMedia])
|
|
|
|
if (!field || !frame || !entry || !stack) {
|
|
return <p>Initializing custom field...</p>
|
|
}
|
|
|
|
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 (
|
|
<div>
|
|
<div>
|
|
<FieldLabel htmlFor="imagevaultpicker" version="v2">
|
|
ImageVault Asset
|
|
</FieldLabel>
|
|
</div>
|
|
|
|
{media ? (
|
|
<Media
|
|
media={media}
|
|
onDelete={() => {
|
|
setMedia(null)
|
|
handleMedia()
|
|
}}
|
|
onEdit={handleEdit}
|
|
/>
|
|
) : (
|
|
<DAMButton
|
|
onClick={() => {
|
|
openImageVault({
|
|
config,
|
|
entryData,
|
|
onSuccess: handleMedia,
|
|
})
|
|
}}
|
|
/>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|