mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-11 01:36:37 +08:00
prettier
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
'use client';
|
||||
|
||||
import {CropIcon, RotateCcwIcon} from "lucide-react"
|
||||
import {Slot} from "@radix-ui/react-slot"
|
||||
import { CropIcon, RotateCcwIcon } from 'lucide-react';
|
||||
import { Slot } from '@radix-ui/react-slot';
|
||||
import {
|
||||
type ComponentProps,
|
||||
createContext,
|
||||
@@ -15,18 +15,18 @@ import {
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react"
|
||||
} from 'react';
|
||||
import ReactCrop, {
|
||||
centerCrop,
|
||||
makeAspectCrop,
|
||||
type PercentCrop,
|
||||
type PixelCrop,
|
||||
type ReactCropProps,
|
||||
} from "react-image-crop"
|
||||
import {Button} from "@/components/ui/button"
|
||||
import {cn} from "@/lib/utils"
|
||||
} from 'react-image-crop';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
import "react-image-crop/dist/ReactCrop.css"
|
||||
import 'react-image-crop/dist/ReactCrop.css';
|
||||
|
||||
const centerAspectCrop = (
|
||||
mediaWidth: number,
|
||||
@@ -37,17 +37,17 @@ const centerAspectCrop = (
|
||||
aspect
|
||||
? makeAspectCrop(
|
||||
{
|
||||
unit: "%",
|
||||
unit: '%',
|
||||
width: 90,
|
||||
},
|
||||
aspect,
|
||||
mediaWidth,
|
||||
mediaHeight,
|
||||
)
|
||||
: { x: 0, y: 0, width: 90, height: 90, unit: "%" },
|
||||
: { x: 0, y: 0, width: 90, height: 90, unit: '%' },
|
||||
mediaWidth,
|
||||
mediaHeight,
|
||||
)
|
||||
);
|
||||
|
||||
const getCroppedPngImage = async (
|
||||
imageSrc: HTMLImageElement,
|
||||
@@ -55,19 +55,19 @@ const getCroppedPngImage = async (
|
||||
pixelCrop: PixelCrop,
|
||||
maxImageSize: number,
|
||||
): Promise<string> => {
|
||||
const canvas = document.createElement("canvas")
|
||||
const ctx = canvas.getContext("2d")
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
if (!ctx) {
|
||||
throw new Error("Context is null, this should never happen.")
|
||||
throw new Error('Context is null, this should never happen.');
|
||||
}
|
||||
|
||||
const scaleX = imageSrc.naturalWidth / imageSrc.width
|
||||
const scaleY = imageSrc.naturalHeight / imageSrc.height
|
||||
const scaleX = imageSrc.naturalWidth / imageSrc.width;
|
||||
const scaleY = imageSrc.naturalHeight / imageSrc.height;
|
||||
|
||||
ctx.imageSmoothingEnabled = false
|
||||
canvas.width = pixelCrop.width
|
||||
canvas.height = pixelCrop.height
|
||||
ctx.imageSmoothingEnabled = false;
|
||||
canvas.width = pixelCrop.width;
|
||||
canvas.height = pixelCrop.height;
|
||||
|
||||
ctx.drawImage(
|
||||
imageSrc,
|
||||
@@ -79,53 +79,61 @@ const getCroppedPngImage = async (
|
||||
0,
|
||||
canvas.width,
|
||||
canvas.height,
|
||||
)
|
||||
);
|
||||
|
||||
const croppedImageUrl = canvas.toDataURL("image/png")
|
||||
const response = await fetch(croppedImageUrl)
|
||||
const blob = await response.blob()
|
||||
const croppedImageUrl = canvas.toDataURL('image/png');
|
||||
const response = await fetch(croppedImageUrl);
|
||||
const blob = await response.blob();
|
||||
|
||||
if (blob.size > maxImageSize) {
|
||||
return await getCroppedPngImage(imageSrc, scaleFactor * 0.9, pixelCrop, maxImageSize)
|
||||
return await getCroppedPngImage(
|
||||
imageSrc,
|
||||
scaleFactor * 0.9,
|
||||
pixelCrop,
|
||||
maxImageSize,
|
||||
);
|
||||
}
|
||||
|
||||
return croppedImageUrl
|
||||
}
|
||||
return croppedImageUrl;
|
||||
};
|
||||
|
||||
interface ImageCropContextType {
|
||||
file: File
|
||||
maxImageSize: number
|
||||
imgSrc: string
|
||||
crop: PercentCrop | undefined
|
||||
completedCrop: PixelCrop | null
|
||||
imgRef: RefObject<HTMLImageElement | null>
|
||||
onCrop?: (croppedImage: string) => void
|
||||
reactCropProps: Omit<ReactCropProps, "onChange" | "onComplete" | "children">
|
||||
handleChange: (pixelCrop: PixelCrop, percentCrop: PercentCrop) => void
|
||||
handleComplete: (pixelCrop: PixelCrop, percentCrop: PercentCrop) => Promise<void>
|
||||
onImageLoad: (e: SyntheticEvent<HTMLImageElement>) => void
|
||||
applyCrop: () => Promise<void>
|
||||
resetCrop: () => void
|
||||
file: File;
|
||||
maxImageSize: number;
|
||||
imgSrc: string;
|
||||
crop: PercentCrop | undefined;
|
||||
completedCrop: PixelCrop | null;
|
||||
imgRef: RefObject<HTMLImageElement | null>;
|
||||
onCrop?: (croppedImage: string) => void;
|
||||
reactCropProps: Omit<ReactCropProps, 'onChange' | 'onComplete' | 'children'>;
|
||||
handleChange: (pixelCrop: PixelCrop, percentCrop: PercentCrop) => void;
|
||||
handleComplete: (
|
||||
pixelCrop: PixelCrop,
|
||||
percentCrop: PercentCrop,
|
||||
) => Promise<void>;
|
||||
onImageLoad: (e: SyntheticEvent<HTMLImageElement>) => void;
|
||||
applyCrop: () => Promise<void>;
|
||||
resetCrop: () => void;
|
||||
}
|
||||
|
||||
const ImageCropContext = createContext<ImageCropContextType | null>(null)
|
||||
const ImageCropContext = createContext<ImageCropContextType | null>(null);
|
||||
|
||||
const useImageCrop = () => {
|
||||
const context = useContext(ImageCropContext)
|
||||
const context = useContext(ImageCropContext);
|
||||
if (!context) {
|
||||
throw new Error("ImageCrop components must be used within ImageCrop")
|
||||
throw new Error('ImageCrop components must be used within ImageCrop');
|
||||
}
|
||||
return context
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
export type ImageCropProps = {
|
||||
file: File
|
||||
maxImageSize?: number
|
||||
onCrop?: (croppedImage: string) => void
|
||||
children: ReactNode
|
||||
onChange?: ReactCropProps["onChange"]
|
||||
onComplete?: ReactCropProps["onComplete"]
|
||||
} & Omit<ReactCropProps, "onChange" | "onComplete" | "children">
|
||||
file: File;
|
||||
maxImageSize?: number;
|
||||
onCrop?: (croppedImage: string) => void;
|
||||
children: ReactNode;
|
||||
onChange?: ReactCropProps['onChange'];
|
||||
onComplete?: ReactCropProps['onComplete'];
|
||||
} & Omit<ReactCropProps, 'onChange' | 'onComplete' | 'children'>;
|
||||
|
||||
export const ImageCrop = ({
|
||||
file,
|
||||
@@ -136,54 +144,64 @@ export const ImageCrop = ({
|
||||
onComplete,
|
||||
...reactCropProps
|
||||
}: ImageCropProps) => {
|
||||
const imgRef = useRef<HTMLImageElement | null>(null)
|
||||
const [imgSrc, setImgSrc] = useState<string>("")
|
||||
const [crop, setCrop] = useState<PercentCrop>()
|
||||
const [completedCrop, setCompletedCrop] = useState<PixelCrop | null>(null)
|
||||
const [initialCrop, setInitialCrop] = useState<PercentCrop>()
|
||||
const imgRef = useRef<HTMLImageElement | null>(null);
|
||||
const [imgSrc, setImgSrc] = useState<string>('');
|
||||
const [crop, setCrop] = useState<PercentCrop>();
|
||||
const [completedCrop, setCompletedCrop] = useState<PixelCrop | null>(null);
|
||||
const [initialCrop, setInitialCrop] = useState<PercentCrop>();
|
||||
|
||||
useEffect(() => {
|
||||
const reader = new FileReader()
|
||||
reader.addEventListener("load", () => setImgSrc(reader.result?.toString() || ""))
|
||||
reader.readAsDataURL(file)
|
||||
}, [file])
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener('load', () =>
|
||||
setImgSrc(reader.result?.toString() || ''),
|
||||
);
|
||||
reader.readAsDataURL(file);
|
||||
}, [file]);
|
||||
|
||||
const onImageLoad = useCallback(
|
||||
(e: SyntheticEvent<HTMLImageElement>) => {
|
||||
const { width, height } = e.currentTarget
|
||||
const newCrop = centerAspectCrop(width, height, reactCropProps.aspect)
|
||||
setCrop(newCrop)
|
||||
setInitialCrop(newCrop)
|
||||
const { width, height } = e.currentTarget;
|
||||
const newCrop = centerAspectCrop(width, height, reactCropProps.aspect);
|
||||
setCrop(newCrop);
|
||||
setInitialCrop(newCrop);
|
||||
},
|
||||
[reactCropProps.aspect],
|
||||
)
|
||||
);
|
||||
|
||||
const handleChange = (pixelCrop: PixelCrop, percentCrop: PercentCrop) => {
|
||||
setCrop(percentCrop)
|
||||
onChange?.(pixelCrop, percentCrop)
|
||||
}
|
||||
setCrop(percentCrop);
|
||||
onChange?.(pixelCrop, percentCrop);
|
||||
};
|
||||
|
||||
const handleComplete = async (pixelCrop: PixelCrop, percentCrop: PercentCrop) => {
|
||||
setCompletedCrop(pixelCrop)
|
||||
onComplete?.(pixelCrop, percentCrop)
|
||||
}
|
||||
const handleComplete = async (
|
||||
pixelCrop: PixelCrop,
|
||||
percentCrop: PercentCrop,
|
||||
) => {
|
||||
setCompletedCrop(pixelCrop);
|
||||
onComplete?.(pixelCrop, percentCrop);
|
||||
};
|
||||
|
||||
const applyCrop = async () => {
|
||||
if (!(imgRef.current && completedCrop)) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
const croppedImage = await getCroppedPngImage(imgRef.current, 1, completedCrop, maxImageSize)
|
||||
const croppedImage = await getCroppedPngImage(
|
||||
imgRef.current,
|
||||
1,
|
||||
completedCrop,
|
||||
maxImageSize,
|
||||
);
|
||||
|
||||
onCrop?.(croppedImage)
|
||||
}
|
||||
onCrop?.(croppedImage);
|
||||
};
|
||||
|
||||
const resetCrop = () => {
|
||||
if (initialCrop) {
|
||||
setCrop(initialCrop)
|
||||
setCompletedCrop(null)
|
||||
setCrop(initialCrop);
|
||||
setCompletedCrop(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const contextValue: ImageCropContextType = {
|
||||
file,
|
||||
@@ -199,28 +217,42 @@ export const ImageCrop = ({
|
||||
onImageLoad,
|
||||
applyCrop,
|
||||
resetCrop,
|
||||
}
|
||||
};
|
||||
|
||||
return <ImageCropContext.Provider value={contextValue}>{children}</ImageCropContext.Provider>
|
||||
}
|
||||
return (
|
||||
<ImageCropContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</ImageCropContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export interface ImageCropContentProps {
|
||||
style?: CSSProperties
|
||||
className?: string
|
||||
style?: CSSProperties;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const ImageCropContent = ({ style, className }: ImageCropContentProps) => {
|
||||
const { imgSrc, crop, handleChange, handleComplete, onImageLoad, imgRef, reactCropProps } =
|
||||
useImageCrop()
|
||||
export const ImageCropContent = ({
|
||||
style,
|
||||
className,
|
||||
}: ImageCropContentProps) => {
|
||||
const {
|
||||
imgSrc,
|
||||
crop,
|
||||
handleChange,
|
||||
handleComplete,
|
||||
onImageLoad,
|
||||
imgRef,
|
||||
reactCropProps,
|
||||
} = useImageCrop();
|
||||
|
||||
const shadcnStyle = {
|
||||
"--rc-border-color": "var(--color-border)",
|
||||
"--rc-focus-color": "var(--color-primary)",
|
||||
} as CSSProperties
|
||||
'--rc-border-color': 'var(--color-border)',
|
||||
'--rc-focus-color': 'var(--color-primary)',
|
||||
} as CSSProperties;
|
||||
|
||||
return (
|
||||
<ReactCrop
|
||||
className={cn("max-h-[277px] max-w-full", className)}
|
||||
className={cn('max-h-[277px] max-w-full', className)}
|
||||
crop={crop}
|
||||
onChange={handleChange}
|
||||
onComplete={handleComplete}
|
||||
@@ -230,8 +262,8 @@ export const ImageCropContent = ({ style, className }: ImageCropContentProps) =>
|
||||
{imgSrc && (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img
|
||||
alt="crop"
|
||||
className="size-full"
|
||||
alt='crop'
|
||||
className='size-full'
|
||||
height={400}
|
||||
onLoad={onImageLoad}
|
||||
ref={imgRef}
|
||||
@@ -240,12 +272,12 @@ export const ImageCropContent = ({ style, className }: ImageCropContentProps) =>
|
||||
/>
|
||||
)}
|
||||
</ReactCrop>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export type ImageCropApplyProps = ComponentProps<"button"> & {
|
||||
asChild?: boolean
|
||||
}
|
||||
export type ImageCropApplyProps = ComponentProps<'button'> & {
|
||||
asChild?: boolean;
|
||||
};
|
||||
|
||||
export const ImageCropApply = ({
|
||||
asChild = false,
|
||||
@@ -253,31 +285,36 @@ export const ImageCropApply = ({
|
||||
onClick,
|
||||
...props
|
||||
}: ImageCropApplyProps) => {
|
||||
const { applyCrop } = useImageCrop()
|
||||
const { applyCrop } = useImageCrop();
|
||||
|
||||
const handleClick = async (e: MouseEvent<HTMLButtonElement>) => {
|
||||
await applyCrop()
|
||||
onClick?.(e)
|
||||
}
|
||||
await applyCrop();
|
||||
onClick?.(e);
|
||||
};
|
||||
|
||||
if (asChild) {
|
||||
return (
|
||||
<Slot onClick={handleClick} {...(props as ComponentProps<"button">)}>
|
||||
<Slot onClick={handleClick} {...(props as ComponentProps<'button'>)}>
|
||||
{children}
|
||||
</Slot>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button onClick={handleClick} size="icon" variant="ghost" {...(props as ComponentProps<"button">)}>
|
||||
{children ?? <CropIcon className="size-4" />}
|
||||
<Button
|
||||
onClick={handleClick}
|
||||
size='icon'
|
||||
variant='ghost'
|
||||
{...(props as ComponentProps<'button'>)}
|
||||
>
|
||||
{children ?? <CropIcon className='size-4' />}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export type ImageCropResetProps = ComponentProps<"button"> & {
|
||||
asChild?: boolean
|
||||
}
|
||||
export type ImageCropResetProps = ComponentProps<'button'> & {
|
||||
asChild?: boolean;
|
||||
};
|
||||
|
||||
export const ImageCropReset = ({
|
||||
asChild = false,
|
||||
@@ -285,35 +322,40 @@ export const ImageCropReset = ({
|
||||
onClick,
|
||||
...props
|
||||
}: ImageCropResetProps) => {
|
||||
const { resetCrop } = useImageCrop()
|
||||
const { resetCrop } = useImageCrop();
|
||||
|
||||
const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
|
||||
resetCrop()
|
||||
onClick?.(e)
|
||||
}
|
||||
resetCrop();
|
||||
onClick?.(e);
|
||||
};
|
||||
|
||||
if (asChild) {
|
||||
return (
|
||||
<Slot onClick={handleClick} {...(props as ComponentProps<"button">)}>
|
||||
<Slot onClick={handleClick} {...(props as ComponentProps<'button'>)}>
|
||||
{children}
|
||||
</Slot>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button onClick={handleClick} size="icon" variant="ghost" {...(props as ComponentProps<"button">)}>
|
||||
{children ?? <RotateCcwIcon className="size-4" />}
|
||||
<Button
|
||||
onClick={handleClick}
|
||||
size='icon'
|
||||
variant='ghost'
|
||||
{...(props as ComponentProps<'button'>)}
|
||||
>
|
||||
{children ?? <RotateCcwIcon className='size-4' />}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Keep the original Cropper component for backward compatibility
|
||||
export type CropperProps = Omit<ReactCropProps, "onChange"> & {
|
||||
file: File
|
||||
maxImageSize?: number
|
||||
onCrop?: (croppedImage: string) => void
|
||||
onChange?: ReactCropProps["onChange"]
|
||||
}
|
||||
export type CropperProps = Omit<ReactCropProps, 'onChange'> & {
|
||||
file: File;
|
||||
maxImageSize?: number;
|
||||
onCrop?: (croppedImage: string) => void;
|
||||
onChange?: ReactCropProps['onChange'];
|
||||
};
|
||||
|
||||
export const Cropper = ({
|
||||
onChange,
|
||||
@@ -331,8 +373,8 @@ export const Cropper = ({
|
||||
onChange={onChange}
|
||||
onComplete={onComplete}
|
||||
onCrop={onCrop}
|
||||
{...(props as Omit<ReactCropProps, "onChange" | "onComplete" | "children">)}
|
||||
{...(props as Omit<ReactCropProps, 'onChange' | 'onComplete' | 'children'>)}
|
||||
>
|
||||
<ImageCropContent className={className} style={style} />
|
||||
</ImageCrop>
|
||||
)
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user