feat(SW-2541): Changed asset types to only add the data needed
This commit is contained in:
@@ -1,154 +1,65 @@
|
||||
import {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Field,
|
||||
Field as FieldComponent,
|
||||
FieldLabel,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
Select,
|
||||
TextInput,
|
||||
} from "@contentstack/venus-components"
|
||||
import React, { ChangeEvent, useEffect, useState } from "react"
|
||||
import { Path } from "slate"
|
||||
|
||||
import type {
|
||||
IRteElementType,
|
||||
IRteParam,
|
||||
} from "@contentstack/app-sdk/dist/src/RTE/types"
|
||||
import type { IRteElementType } from "@contentstack/app-sdk/dist/src/RTE/types"
|
||||
import FocalPointPicker from "~/shared-components/FocalPointPicker"
|
||||
|
||||
import type { FocalPoint, InsertResponse } 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",
|
||||
},
|
||||
]
|
||||
import type { FocalPoint, ImageVaultAsset } from "~/types/imagevault"
|
||||
|
||||
type ImageEditModalProps = {
|
||||
element: IRteElementType & {
|
||||
attrs: InsertResponse
|
||||
attrs: ImageVaultAsset
|
||||
}
|
||||
rte: IRteParam
|
||||
setData: (data: ImageVaultAsset) => void
|
||||
closeModal: () => void
|
||||
path: Path
|
||||
}
|
||||
|
||||
export default function ImageEditModal({
|
||||
element,
|
||||
setData,
|
||||
closeModal,
|
||||
path,
|
||||
rte,
|
||||
}: ImageEditModalProps) {
|
||||
const [alignment, setAlignment] = useState<DropDownItem>({
|
||||
label: "None",
|
||||
value: DropdownValues.none,
|
||||
type: "select",
|
||||
})
|
||||
const imageVaultAsset: ImageVaultAsset = element.attrs
|
||||
const [altText, setAltText] = useState("")
|
||||
const [caption, setCaption] = useState("")
|
||||
const [focalPoint, setFocalPoint] = useState<FocalPoint>({ x: 50, y: 50 })
|
||||
|
||||
const assetUrl = element.attrs.MediaConversions[0].Url
|
||||
const assetUrl = imageVaultAsset.url
|
||||
|
||||
useEffect(() => {
|
||||
if (element.attrs.Metadata && element.attrs.Metadata.length) {
|
||||
const altText = element.attrs.Metadata.find((meta) =>
|
||||
meta.Name.includes("AltText_")
|
||||
)?.Value
|
||||
|
||||
const caption = element.attrs.Metadata.find((meta) =>
|
||||
meta.Name.includes("Title_")
|
||||
)?.Value
|
||||
|
||||
setAltText(altText ?? "")
|
||||
setCaption(caption ?? "")
|
||||
if (imageVaultAsset.meta.alt) {
|
||||
setAltText(imageVaultAsset.meta.alt)
|
||||
}
|
||||
}, [element.attrs.Metadata])
|
||||
if (imageVaultAsset.meta.caption) {
|
||||
setCaption(imageVaultAsset.meta.caption)
|
||||
}
|
||||
}, [imageVaultAsset.meta])
|
||||
|
||||
useEffect(() => {
|
||||
if (element.attrs.FocalPoint) {
|
||||
setFocalPoint(element.attrs.FocalPoint)
|
||||
if (imageVaultAsset.focalPoint) {
|
||||
setFocalPoint(imageVaultAsset.focalPoint)
|
||||
}
|
||||
}, [element.attrs.FocalPoint])
|
||||
}, [imageVaultAsset.focalPoint])
|
||||
|
||||
function handleSave() {
|
||||
let newStyle
|
||||
|
||||
switch (alignment.value) {
|
||||
case DropdownValues.center:
|
||||
case DropdownValues.left:
|
||||
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,
|
||||
},
|
||||
const newData = {
|
||||
...imageVaultAsset,
|
||||
meta: {
|
||||
alt: altText,
|
||||
caption,
|
||||
},
|
||||
{ at: path }
|
||||
)
|
||||
|
||||
focalPoint,
|
||||
}
|
||||
setData(newData)
|
||||
closeModal()
|
||||
}
|
||||
|
||||
@@ -177,18 +88,7 @@ export default function ImageEditModal({
|
||||
onChange={changeFocalPoint}
|
||||
/>
|
||||
<div>
|
||||
<Field>
|
||||
{/* @ts-expect-error: Type incompatibility with Venus components */}
|
||||
<Select
|
||||
selectLabel="Alignment"
|
||||
value={alignment}
|
||||
onChange={(e: DropDownItem) => {
|
||||
setAlignment(e)
|
||||
}}
|
||||
options={dropdownList}
|
||||
/>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldComponent>
|
||||
<FieldLabel htmlFor="alt">Alt text</FieldLabel>
|
||||
<TextInput
|
||||
value={altText}
|
||||
@@ -198,9 +98,9 @@ export default function ImageEditModal({
|
||||
setAltText(e.target.value)
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
</FieldComponent>
|
||||
|
||||
<Field>
|
||||
<FieldComponent>
|
||||
<FieldLabel htmlFor="caption">Caption</FieldLabel>
|
||||
<TextInput
|
||||
value={caption}
|
||||
@@ -210,16 +110,25 @@ export default function ImageEditModal({
|
||||
setCaption(e.target.value)
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
</FieldComponent>
|
||||
|
||||
<Field>
|
||||
<FieldComponent>
|
||||
<FieldLabel htmlFor="focalPoint">Focal Point</FieldLabel>
|
||||
<TextInput
|
||||
value={`X: ${focalPoint.x}, Y: ${focalPoint.y}`}
|
||||
name="focalPoint"
|
||||
disabled
|
||||
/>
|
||||
</Field>
|
||||
</FieldComponent>
|
||||
|
||||
<FieldComponent>
|
||||
<FieldLabel htmlFor="imageVaultId">Imagevault Id</FieldLabel>
|
||||
<TextInput
|
||||
value={imageVaultAsset.imageVaultId}
|
||||
name="imageVaultId"
|
||||
disabled
|
||||
/>
|
||||
</FieldComponent>
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 ImageEditModal from "./ImageEditModal"
|
||||
@@ -8,28 +8,46 @@ import type {
|
||||
IRteElementType,
|
||||
IRteParam,
|
||||
} 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 & {
|
||||
element: IRteElementType & { attrs: InsertResponse }
|
||||
element: IRteElementType & { attrs: ImageVaultAsset | 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 imageVaultAsset = getImageVaultAssetFromData(element.attrs)
|
||||
const isSelected = rte.selection.isSelected()
|
||||
const isFocused = rte.selection.isFocused()
|
||||
const path = rte.getPath(element)
|
||||
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(() => {
|
||||
cbModal({
|
||||
// @ts-expect-error: Component is badly typed
|
||||
component: (compProps) =>
|
||||
ImageEditModal({
|
||||
element,
|
||||
rte,
|
||||
path: rte.getPath(element),
|
||||
...compProps,
|
||||
}),
|
||||
component: (compProps) => (
|
||||
<ImageEditModal
|
||||
element={element}
|
||||
setData={handleMedia}
|
||||
{...compProps}
|
||||
/>
|
||||
),
|
||||
modalProps: {
|
||||
size: "max",
|
||||
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 = () => {
|
||||
return (
|
||||
@@ -54,7 +80,7 @@ export function ImageElement({ children, element, rte }: ImageElementProps) {
|
||||
<EmbedBtn
|
||||
title="remove"
|
||||
content={"Remove"}
|
||||
onClick={() => rte?.removeNode(element)}
|
||||
onClick={() => rte.removeNode(element)}
|
||||
>
|
||||
<Icon icon="Trash" />
|
||||
</EmbedBtn>
|
||||
@@ -62,78 +88,46 @@ export function ImageElement({ children, element, rte }: ImageElementProps) {
|
||||
)
|
||||
}
|
||||
|
||||
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]
|
||||
if (!imageVaultAsset) {
|
||||
return <>{children}</>
|
||||
}
|
||||
|
||||
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 />}
|
||||
>
|
||||
<Tooltip
|
||||
zIndex={909}
|
||||
className="p-0"
|
||||
style={{ marginBottom: "10px" }}
|
||||
position="top"
|
||||
variantType="light"
|
||||
offset={[0, -15]}
|
||||
content={<ToolTipButtons />}
|
||||
>
|
||||
<span
|
||||
data-type="asset"
|
||||
<span data-type="asset" contentEditable={false}>
|
||||
<div
|
||||
contentEditable={false}
|
||||
style={element.attrs?.style}
|
||||
style={{
|
||||
width: "280px",
|
||||
height: "auto",
|
||||
...(isHighlight ? { border: "1px solid #6c5ce7" } : {}),
|
||||
}}
|
||||
>
|
||||
<div
|
||||
contentEditable={false}
|
||||
style={{
|
||||
width: "280px",
|
||||
height: "auto",
|
||||
...(isHighlight ? { border: "1px solid #6c5ce7" } : {}),
|
||||
<img
|
||||
src={imageVaultAsset.url}
|
||||
onError={(event) => {
|
||||
event.currentTarget.src = "https://placehold.co/600x400"
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
{children}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "auto",
|
||||
aspectRatio: imageVaultAsset.dimensions.aspectRatio,
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
alt={imageVaultAsset.meta.alt}
|
||||
title={`Id: ${imageVaultAsset.imageVaultId} - ${imageVaultAsset.fileName}`}
|
||||
/>
|
||||
</div>
|
||||
{children}
|
||||
</span>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
12
rte/main.tsx
12
rte/main.tsx
@@ -1,11 +1,10 @@
|
||||
import ContentstackSDK from "@contentstack/app-sdk"
|
||||
import React from "react"
|
||||
|
||||
import { ImageElement } from "~/components/ImageElement"
|
||||
|
||||
import { Icon } from "@contentstack/venus-components"
|
||||
import { openImageVault } from "~/utils/imagevault"
|
||||
|
||||
import { ImageElement } from "~/components/ImageElement"
|
||||
import type {
|
||||
ContentstackEmbeddedData,
|
||||
ContentstackPluginDefinition,
|
||||
@@ -121,10 +120,10 @@ function loadIV(plugin: ContentstackPluginDefinition) {
|
||||
}
|
||||
}
|
||||
|
||||
export default ContentstackSDK.init().then(async (sdk) => {
|
||||
export default ContentstackSDK.init().then((sdk) => {
|
||||
try {
|
||||
const extensionObj = await sdk["location"]
|
||||
const RTE = await extensionObj["RTEPlugin"]
|
||||
const extensionObj = sdk["location"]
|
||||
const RTE = extensionObj["RTEPlugin"]
|
||||
|
||||
if (!RTE) {
|
||||
return
|
||||
@@ -178,7 +177,7 @@ export default ContentstackSDK.init().then(async (sdk) => {
|
||||
|
||||
ImageVault.on("exec", async (rte) => {
|
||||
if (rte) {
|
||||
const savedSelection = rte?.selection?.get() ?? undefined
|
||||
const savedSelection = rte.selection.get() ?? undefined
|
||||
|
||||
const appConfig = await rte.getConfig()
|
||||
|
||||
@@ -214,7 +213,6 @@ export default ContentstackSDK.init().then(async (sdk) => {
|
||||
type: "ImageVault",
|
||||
attrs: {
|
||||
...result,
|
||||
FocalPoint: result.FocalPoint || { x: 50, y: 50 },
|
||||
},
|
||||
uid: crypto.randomUUID(),
|
||||
children: [{ text: "" }],
|
||||
|
||||
Reference in New Issue
Block a user