feat: implemented focal point picker inside ImageVault
This commit is contained in:
47
remix/app/components/FocalPointPicker/focalPointPicker.css
Normal file
47
remix/app/components/FocalPointPicker/focalPointPicker.css
Normal file
@@ -0,0 +1,47 @@
|
||||
.container {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
border-right: 1px solid #eee;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.focalPointWrapper {
|
||||
position: relative;
|
||||
user-select: none;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.focalPointButton {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: rgba(255, 0, 0, 0.4);
|
||||
border: 3px solid red;
|
||||
display: block;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.focalPointImage {
|
||||
height: 100%;
|
||||
max-height: 350px;
|
||||
}
|
||||
|
||||
.examples {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
max-height: 400px;
|
||||
display: grid;
|
||||
grid-template-columns: 3fr 1fr;
|
||||
grid-template-rows: 2fr 1fr;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.examples img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
overflow: hidden;
|
||||
}
|
||||
50
remix/app/components/FocalPointPicker/index.tsx
Normal file
50
remix/app/components/FocalPointPicker/index.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { FocalPoint } from "~/types/imagevault"
|
||||
import "./focalPointPicker.css"
|
||||
import useFocalPoint from "~/hooks/useFocalPoint"
|
||||
|
||||
export interface FocalPointPickerProps {
|
||||
focalPoint?: FocalPoint
|
||||
imageSrc: string
|
||||
onChange: (focalPoint: FocalPoint) => void
|
||||
}
|
||||
|
||||
export default function FocalPointPicker({
|
||||
focalPoint,
|
||||
imageSrc,
|
||||
onChange,
|
||||
}: FocalPointPickerProps) {
|
||||
const { ref, x, y, onMove, canMove, setCanMove } = useFocalPoint({
|
||||
focalPoint,
|
||||
onChange,
|
||||
})
|
||||
|
||||
const imagesArray = Array.from({ length: 4 })
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="focalPointWrapper" ref={ref} onMouseMove={onMove}>
|
||||
<button
|
||||
className="focalPointButton"
|
||||
style={{
|
||||
left: `${x}%`,
|
||||
top: `${y}%`,
|
||||
cursor: canMove ? "grabbing" : "grab",
|
||||
}}
|
||||
onMouseDown={() => setCanMove(true)}
|
||||
onMouseUp={() => setCanMove(false)}
|
||||
></button>
|
||||
<img className="focalPointImage" src={imageSrc} alt="" />
|
||||
</div>
|
||||
<div className="examples">
|
||||
{imagesArray.map((_, idx) => (
|
||||
<img
|
||||
key={idx}
|
||||
src={imageSrc}
|
||||
alt=""
|
||||
style={{ objectPosition: `${x}% ${y}%` }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -10,7 +10,8 @@ import {
|
||||
TextInput,
|
||||
} from "@contentstack/venus-components"
|
||||
|
||||
import type { InsertResponse } from "~/types/imagevault"
|
||||
import type { FocalPoint, InsertResponse } from "~/types/imagevault"
|
||||
import FocalPointPicker from "./FocalPointPicker"
|
||||
|
||||
type ImageEditModalProps = {
|
||||
fieldData: InsertResponse
|
||||
@@ -25,6 +26,7 @@ export default function ImageEditModal({
|
||||
}: ImageEditModalProps) {
|
||||
const [altText, setAltText] = useState("")
|
||||
const [caption, setCaption] = useState("")
|
||||
const [focalPoint, setFocalPoint] = useState<FocalPoint>({ x: 50, y: 50 })
|
||||
|
||||
const assetUrl = fieldData.MediaConversions[0].Url
|
||||
|
||||
@@ -43,6 +45,12 @@ export default function ImageEditModal({
|
||||
}
|
||||
}, [fieldData.Metadata])
|
||||
|
||||
useEffect(() => {
|
||||
if (fieldData.FocalPoint) {
|
||||
setFocalPoint(fieldData.FocalPoint)
|
||||
}
|
||||
}, [fieldData.FocalPoint])
|
||||
|
||||
function handleSave() {
|
||||
const metaData = fieldData.Metadata ?? []
|
||||
|
||||
@@ -59,31 +67,34 @@ export default function ImageEditModal({
|
||||
setData({
|
||||
...fieldData,
|
||||
Metadata: newMetadata,
|
||||
FocalPoint: focalPoint,
|
||||
})
|
||||
closeModal()
|
||||
}
|
||||
|
||||
function changeFocalPoint(focalPoint: FocalPoint) {
|
||||
setFocalPoint(focalPoint)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ModalHeader title="Update image" closeModal={closeModal} />
|
||||
<ModalBody
|
||||
style={{
|
||||
display: "flex",
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr minmax(max-content, 250px)",
|
||||
gap: "1rem",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
width: "auto",
|
||||
maxHeight: "none",
|
||||
}}
|
||||
>
|
||||
<div style={{ flex: 1, overflowY: "auto" }}>
|
||||
<img
|
||||
src={assetUrl}
|
||||
alt={altText}
|
||||
height="100%"
|
||||
style={{ maxHeight: "345px" }}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<FocalPointPicker
|
||||
imageSrc={assetUrl}
|
||||
focalPoint={focalPoint}
|
||||
onChange={changeFocalPoint}
|
||||
/>
|
||||
<div>
|
||||
<FieldComponent>
|
||||
<FieldLabel htmlFor="alt">Alt text</FieldLabel>
|
||||
<TextInput
|
||||
@@ -107,6 +118,16 @@ export default function ImageEditModal({
|
||||
}
|
||||
/>
|
||||
</FieldComponent>
|
||||
|
||||
<FieldComponent>
|
||||
<FieldLabel htmlFor="focalPoint">Focal Point</FieldLabel>
|
||||
<TextInput
|
||||
value={`X: ${focalPoint.x}, Y: ${focalPoint.y}`}
|
||||
placeholder="Caption for image..."
|
||||
name="caption"
|
||||
disabled
|
||||
/>
|
||||
</FieldComponent>
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
|
||||
@@ -138,9 +138,15 @@ export default function ImageVaultDAM({
|
||||
(result?: InsertResponse) => {
|
||||
if (field) {
|
||||
flushSync(() => {
|
||||
setMedia(result || null)
|
||||
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(result || {})
|
||||
field.setData(data || {})
|
||||
document.body.style.overflow = "hidden"
|
||||
})
|
||||
}
|
||||
@@ -168,6 +174,13 @@ export default function ImageVaultDAM({
|
||||
),
|
||||
modalProps: {
|
||||
size: "max",
|
||||
style: {
|
||||
content: {
|
||||
maxHeight: "90dvh",
|
||||
width: "auto",
|
||||
},
|
||||
overlay: {},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user