UNPKG

react-text-editor-kit

Version:

React Text Editor Kit is a customizable rich text editor component for React applications. It provides a flexible and easy-to-use interface for users to create and edit content with various formatting options. And also easy to integrate in react applicati

343 lines (323 loc) 10.7 kB
import React, { useCallback, useEffect, useState } from "react"; import LockIcon from "./SVGImages/LockIcon"; import UnlockIcon from "./SVGImages/UnlockIcon"; import Styles from "../css/style.module.css"; import { ImageCropper } from "./ImageCropper"; import { getCroppedImage } from "./constant"; import CropIcon from "./SVGImages/CropIcon"; export default function ImageModal(props) { const { onImageInsert, item, setIsLoading, image_handler, selectedData } = props; const [errorMessage, setErrorMessage] = useState(""); const [heightRatio, setHeightRatio] = useState(0); const [isLocked, setIsLocked] = useState(true); const [allowSHW, setAllowSHW] = useState(false); const [showComponent, setShowComponent] = useState("default"); const [completedCrop, setCompletedCrop] = useState(null); const [imageRef, setImageRef] = useState(null); const [inputs, setInputs] = useState({ link: "", height: "", width: "", image: null, type: "general", }); const handleChangeFile = (event) => { const { name, files } = event.target; setInputs((oldInputs) => ({ ...oldInputs, [name]: files[0], height: "", width: "", })); }; const MAX_RETRIES = 3; const get_image_dimentions = (image_path, retries = 0) => { let img_width = 0; let img_height = 0; const img = new Image(); img.onload = function () { img_width = img.width; img_height = img.height; let ratio = img_width / img_height; setHeightRatio(ratio); setAllowSHW(true); setInputs((old) => ({ ...old, width: img_width, height: img_height, })); setIsLoading(false); }; img.onerror = function () { setIsLoading(false); if (retries < MAX_RETRIES) { get_image_dimentions(image_path, retries + 1); } }; img.src = image_path; }; const handleLinkInsert = async (e) => { e.preventDefault(); if (inputs.type === "general") { if (!inputs.link) { let error_message = "Image source is required"; setErrorMessage(error_message); return; } onImageInsert(inputs, "_"); } else { if (!inputs.image) { let error_message = "Please upload image"; setErrorMessage(error_message); return; } if (image_handler) { setIsLoading(true); let image_path = await image_handler({ ...inputs }, item); if (image_path) { get_image_dimentions(image_path); setInputs((old) => ({ ...old, type: "general", link: image_path })); } else { setIsLoading(false); } return; } else { inputs.link = URL.createObjectURL(inputs.image); inputs.width = ""; inputs.height = ""; } if (!inputs.link) { setIsLoading(false); return; } onImageInsert(inputs, "_"); } }; const handleChangeHW = (event) => { const { name, value } = event.target; let height = inputs.height; let width = inputs.width; if (name === "width") { height = value / heightRatio; width = value; } else { width = value * heightRatio; height = value; } height = Math.round(height); width = Math.round(width); setInputs((old) => ({ ...old, height, width })); }; const handleChange = (event) => { const { name, value } = event.target; if (isLocked && name !== "link" && allowSHW && inputs.link) { handleChangeHW(event); return; } if (name === "link" && value === "") { setInputs((old) => ({ ...old, height: 0, width: 0 })); setAllowSHW(false); } setInputs((old) => ({ ...old, [name]: value })); }; const handleChangeType = (e, value) => { e.preventDefault(); setInputs((old) => ({ ...old, type: value, image: null })); setErrorMessage(""); }; const handleCropComplete = useCallback((crop, imgRef) => { setCompletedCrop(crop); setImageRef(imgRef); }, []); const handleCancel = useCallback(() => { setShowComponent("default"); setCompletedCrop(null); }, []); const applyCrop = useCallback(async () => { if (!completedCrop || !imageRef) { console.error("No crop data or image reference"); return; } const croppedImageFile = await getCroppedImage( imageRef, completedCrop, inputs.image, ); if (!croppedImageFile) { console.error("Failed to crop image"); return; } setInputs((old) => ({ ...old, image: croppedImageFile })); handleCancel(); }, [completedCrop, imageRef, handleCancel, inputs.image]); useEffect(() => { if (selectedData?.link) { let height = parseFloat(selectedData.height); let width = parseFloat(selectedData.width); let ratio = width / height; setHeightRatio(ratio); setAllowSHW(true); setInputs({ ...inputs, ...selectedData, height, width }); } }, [selectedData, inputs]); useEffect(() => { if (showComponent === "default") return; const reactCropElem = document.querySelector(".ReactCrop"); console.log(reactCropElem, "reactCropElemreactCropElem"); reactCropElem.style.maxHeight = "350px"; }, [showComponent]); return ( <> {showComponent === "crop" ? ( <div className={Styles.image__cropper__box}> {inputs.image && ( <ImageCropper imageSrc={URL.createObjectURL(inputs.image)} onCropComplete={handleCropComplete} /> )} <div className={Styles.image__cropper_btn}> <button type="button" onClick={applyCrop} disabled={!completedCrop} className={Styles.saveButton} > Apply Crop </button> <button type="button" onClick={handleCancel} className={Styles.cancel__btn} > Cancel </button> </div> </div> ) : ( <> <div className={Styles.selectType}> <button type="button" className={`${ inputs.type === "general" ? `${Styles.selectedType}` : "" }`} onClick={(e) => handleChangeType(e, "general")} > General </button> <button type="button" className={`${ inputs.type === "upload" ? `${Styles.selectedType}` : "" }`} onClick={(e) => handleChangeType(e, "upload")} > Upload </button> </div> <> {inputs.type === "general" ? ( <> <div className={Styles.reactEditorMt10}> <label htmlFor="link">Source</label> <input id="link" type="text" name="link" autoFocus className={Styles.formControlInput} value={inputs.link} onChange={handleChange} /> {errorMessage && ( <div className={Styles.editorErrorMessage}> *{`${errorMessage}`} </div> )} </div> <div className={`${Styles.reactEditorDFlex} ${Styles.justifyContentBetween}`} > <div className={`${Styles.reactEditorMt10} ${Styles.reactEditorW45}`} > <label htmlFor="height">Height</label> <input id="height" type="number" name="height" value={inputs.height} onChange={handleChange} className={Styles.formControlInput} /> </div> <div className={`${Styles.reactEditorMt10} ${Styles.reactEditorW45}`} > <label htmlFor="width">Width</label> <input id="width" type="number" name="width" value={inputs.width} onChange={handleChange} className={Styles.formControlInput} /> </div> <div className={Styles.lockUnlockIcon} onClick={() => setIsLocked(!isLocked)} > {isLocked ? <LockIcon /> : <UnlockIcon />} </div> </div> </> ) : ( <div className={Styles.reactEditorMt10}> <label htmlFor="image">Choose File</label> <input type="file" id="image" name="image" className={Styles.formControlInput} accept="image/*" onChange={handleChangeFile} /> {errorMessage && ( <div className={Styles.editorErrorMessage}> *{`${errorMessage}`} </div> )} </div> )} <div className={Styles.image__submit__container}> <div> {inputs.type === "upload" && inputs.image && ( <div className={Styles.image__preview__box}> <CropIcon /> <img src={URL.createObjectURL(inputs.image)} alt="" srcset="" title="Click to crop image" onClick={() => setShowComponent("crop")} /> </div> )} </div> <button type="button" className={Styles.saveButton} onClick={handleLinkInsert} > Save </button> </div> </> </> )} </> ); }