import React, { useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; import { AxiosError } from "axios"; import { Modal, Row, Button } from "react-bootstrap"; import { UiLogicError } from "@/common"; import { useUserLoggedIn } from "@/services/user"; import { getHttpUserClient } from "@/http/user"; import ImageCropper, { Clip, applyClipToImage } from "../common/ImageCropper"; export interface ChangeAvatarDialogProps { open: boolean; close: () => void; } const ChangeAvatarDialog: React.FC = (props) => { const { t } = useTranslation(); const user = useUserLoggedIn(); const [file, setFile] = React.useState(null); const [fileUrl, setFileUrl] = React.useState(null); const [clip, setClip] = React.useState(null); const [ cropImgElement, setCropImgElement, ] = React.useState(null); const [resultBlob, setResultBlob] = React.useState(null); const [resultUrl, setResultUrl] = React.useState(null); const [state, setState] = React.useState< | "select" | "crop" | "processcrop" | "preview" | "uploading" | "success" | "error" >("select"); const [message, setMessage] = useState< string | { type: "custom"; text: string } | null >("settings.dialogChangeAvatar.prompt.select"); const trueMessage = message == null ? null : typeof message === "string" ? t(message) : message.text; const closeDialog = props.close; const close = React.useCallback((): void => { if (!(state === "uploading")) { closeDialog(); } }, [state, closeDialog]); useEffect(() => { if (file != null) { const url = URL.createObjectURL(file); setClip(null); setFileUrl(url); setState("crop"); return () => { URL.revokeObjectURL(url); }; } else { setFileUrl(null); setState("select"); } }, [file]); React.useEffect(() => { if (resultBlob != null) { const url = URL.createObjectURL(resultBlob); setResultUrl(url); setState("preview"); return () => { URL.revokeObjectURL(url); }; } else { setResultUrl(null); } }, [resultBlob]); const onSelectFile = React.useCallback( (e: React.ChangeEvent): void => { const files = e.target.files; if (files == null || files.length === 0) { setFile(null); } else { setFile(files[0]); } }, [] ); const onCropNext = React.useCallback(() => { if ( cropImgElement == null || clip == null || clip.width === 0 || file == null ) { throw new UiLogicError(); } setState("processcrop"); void applyClipToImage(cropImgElement, clip, file.type).then((b) => { setResultBlob(b); }); }, [cropImgElement, clip, file]); const onCropPrevious = React.useCallback(() => { setFile(null); setState("select"); }, []); const onPreviewPrevious = React.useCallback(() => { setResultBlob(null); setState("crop"); }, []); const upload = React.useCallback(() => { if (resultBlob == null) { throw new UiLogicError(); } setState("uploading"); getHttpUserClient() .putAvatar(user.username, resultBlob) .then( () => { setState("success"); }, (e: unknown) => { setState("error"); setMessage({ type: "custom", text: (e as AxiosError).message }); } ); }, [user.username, resultBlob]); const createPreviewRow = (): React.ReactElement => { if (resultUrl == null) { throw new UiLogicError(); } return ( {t("settings.dialogChangeAvatar.previewImgAlt")} ); }; return ( {t("settings.dialogChangeAvatar.title")} {(() => { if (state === "select") { return ( <> {t("settings.dialogChangeAvatar.prompt.select")} ); } else if (state === "crop") { if (fileUrl == null) { throw new UiLogicError(); } return ( <> {t("settings.dialogChangeAvatar.prompt.crop")} ); } else if (state === "processcrop") { return ( <> {t("settings.dialogChangeAvatar.prompt.processingCrop")} ); } else if (state === "preview") { return ( <> {createPreviewRow()} {t("settings.dialogChangeAvatar.prompt.preview")} ); } else if (state === "uploading") { return ( <> {createPreviewRow()} {t("settings.dialogChangeAvatar.prompt.uploading")} ); } else if (state === "success") { return ( <> {t("operationDialog.success")} ); } else { return ( <> {createPreviewRow()} {trueMessage} ); } })()} ); }; export default ChangeAvatarDialog;