Files
contentstack-imagevault/rte/components/ImageElement.tsx
2024-03-26 13:03:24 +01:00

174 lines
4.7 KiB
TypeScript

import React, { useRef, useCallback, PropsWithChildren } from "react"
import { Tooltip, Icon, cbModal } from "@contentstack/venus-components"
import { Resizable } from "re-resizable"
import EmbedBtn from "./EmbedBtn"
import ImageEditModal from "./ImageEditModal"
import type {
IRteParam,
IRteElementType,
} from "@contentstack/app-sdk/dist/src/RTE/types"
import type { InsertResponse } from "~/types/imagevault"
type ImageElementProps = PropsWithChildren & {
element: IRteElementType & { attrs: InsertResponse }
rte: IRteParam
}
export function ImageElement({ children, element, rte }: ImageElementProps) {
const assetUrl = element.attrs.MediaConversions[0].Url
const isSelected = rte?.selection?.isSelected()
const isFocused = rte?.selection?.isFocused()
const isHighlight = isFocused && isSelected
const imgRef = useRef<HTMLDivElement | null>(null)
const handleEdit = useCallback(() => {
cbModal({
// @ts-expect-error: Component is badly typed
component: (compProps) =>
ImageEditModal({
element,
rte,
path: rte.getPath(element),
...compProps,
}),
modalProps: {
size: "max",
},
})
}, [element, rte])
const ToolTipButtons = () => {
return (
<div contentEditable={false} className="embed--btn-group">
<EmbedBtn title="edit" content="Edit" onClick={handleEdit}>
<Icon icon="Rename" />
</EmbedBtn>
<EmbedBtn
title="remove"
content={"Remove"}
onClick={() => rte?.removeNode(element)}
>
<Icon icon="Trash" />
</EmbedBtn>
</div>
)
}
const onResizeStop = () => {
const { attrs: elementAttrs } = element
const { offsetWidth: width, offsetHeight: height } = imgRef?.current ?? {}
const newAttrs: { [key: string]: unknown } = {
...elementAttrs,
style: {
...(elementAttrs?.style ?? {}),
"max-width": `${width}px`,
},
...(width && height
? { width: `${width.toString()}px`, height: `${height.toString()}px` }
: {}),
}
rte?._adv?.Transforms?.setNodes<IRteElementType>(
rte._adv.editor,
{ attrs: newAttrs },
{ at: rte.getPath(element) }
)
}
let alignmentStyles = {}
const marginAlignment: Record<string, { [key: string]: string }> = {
center: { margin: "auto" },
left: { marginRight: "auto" },
right: { marginLeft: "auto" },
}
if (typeof element.attrs.position === "string") {
alignmentStyles = marginAlignment[element.attrs.position]
}
return (
<div
style={{
...alignmentStyles,
...element.attrs.style,
}}
>
<Tooltip
zIndex={909}
className="p-0"
style={{ marginBottom: "10px" }}
position="top"
variantType="light"
offset={[0, -15]}
content={<ToolTipButtons />}
>
<span
data-type="asset"
contentEditable={false}
style={element.attrs?.style}
>
<Resizable
lockAspectRatio
size={{
width: element.attrs.width ?? "180px",
height: element.attrs.heigth ?? "auto",
}}
onResizeStop={onResizeStop}
handleStyles={{
right: { right: 0, width: "15px" },
left: { left: 0, width: "15px" },
bottom: { bottom: "0" },
bottomRight: { width: "15px" },
}}
>
<div
ref={imgRef}
contentEditable={false}
style={{
width: "100%",
height: "100%",
...(isHighlight ? { border: "1px solid #6c5ce7" } : {}),
}}
>
<img
src={assetUrl}
onError={(event) => {
event.currentTarget.src = "https://placehold.co/600x400"
}}
style={{
width: "100%",
height: "auto",
aspectRatio: element.attrs.MediaConversions[0].AspectRatio,
borderRadius: "8px",
}}
alt={element.attrs.altText}
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>
</Resizable>
{children}
</span>
</Tooltip>
</div>
)
}