//=========================================== // THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template //=========================================== import { fileToBase64 } from '@hexclave/shared/dist/utils/base64'; import { runAsynchronouslyWithAlert } from '@hexclave/shared/dist/utils/promises'; import { Button, Slider, Typography } from '@hexclave/ui'; import imageCompression from 'browser-image-compression'; import { Upload } from 'lucide-react'; import { ComponentProps, useCallback, useState } from 'react'; import Cropper, { Area } from 'react-easy-crop'; import { useTranslation } from '../lib/translations'; import { UserAvatar } from './elements/user-avatar'; export async function checkImageUrl(url: string){ try { const res = await fetch(url, { method: 'HEAD' }); const buff = await res.blob(); return buff.type.startsWith('image/'); } catch (e) { return false; } } const createImage = (url: string): Promise => new Promise((resolve, reject) => { const image = new Image(); image.addEventListener('load', () => resolve(image)); image.addEventListener('error', (error) => reject(error)); image.setAttribute('crossOrigin', 'anonymous'); image.src = url; }); export async function getCroppedImg(imageSrc: string, pixelCrop: Area): Promise { const image = await createImage(imageSrc); const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); if (!ctx) { return null; } const safeCrop = { x: Math.max(0, pixelCrop.x), y: Math.max(0, pixelCrop.y), width: Math.max(1, pixelCrop.width), height: Math.max(1, pixelCrop.height), }; canvas.width = safeCrop.width; canvas.height = safeCrop.height; ctx.drawImage( image, safeCrop.x, safeCrop.y, safeCrop.width, safeCrop.height, 0, 0, safeCrop.width, safeCrop.height ); return canvas.toDataURL('image/jpeg'); } export function ProfileImageEditor(props: { user: NonNullable['user']>, onProfileImageUrlChange: (profileImageUrl: string | null) => void | Promise, }) { const { t } = useTranslation(); const [rawUrl, setRawUrl] = useState(null); const [error, setError] = useState(null); const [crop, setCrop] = useState({ x: 0, y: 0 }); const [zoom, setZoom] = useState(1); const [croppedAreaPixels, setCroppedAreaPixels] = useState(null); function reset() { setRawUrl(null); setError(null); setCrop({ x: 0, y: 0 }); setZoom(1); setCroppedAreaPixels(null); } const onCropChange = useCallback((crop: { x: number, y: number }) => { setCrop(crop); }, []); const onCropComplete = useCallback((croppedArea: Area, croppedAreaPixels: Area) => { setCroppedAreaPixels(croppedAreaPixels); }, []); const onZoomChange = useCallback((zoom: number) => { setZoom(zoom); }, []); function upload() { const input = document.createElement('input'); input.type = 'file'; input.onchange = (e) => { const file = (e.target as HTMLInputElement).files?.[0]; if (!file) return; runAsynchronouslyWithAlert(async () => { const rawUrl = await fileToBase64(file); if (await checkImageUrl(rawUrl)) { setRawUrl(rawUrl); setError(null); } else { setError(t('Invalid image')); } input.remove(); }); }; input.click(); } if (!rawUrl) { return
{error && {error}}
; } return (
onZoomChange(v[0])} />
); }