From 68ca8b0976efe90c0c40bcae69f0825671b98bad Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 30 May 2020 16:23:25 +0800 Subject: Merge front end to this repo. But I need to wait for aspnet core support for custom port and package manager for dev server. --- Timeline/ClientApp/src/user/ChangeAvatarDialog.tsx | 305 +++++++++++++++++++++ 1 file changed, 305 insertions(+) create mode 100644 Timeline/ClientApp/src/user/ChangeAvatarDialog.tsx (limited to 'Timeline/ClientApp/src/user/ChangeAvatarDialog.tsx') diff --git a/Timeline/ClientApp/src/user/ChangeAvatarDialog.tsx b/Timeline/ClientApp/src/user/ChangeAvatarDialog.tsx new file mode 100644 index 00000000..e082d5a0 --- /dev/null +++ b/Timeline/ClientApp/src/user/ChangeAvatarDialog.tsx @@ -0,0 +1,305 @@ +import React, { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + Modal, + ModalHeader, + Row, + Button, + ModalBody, + ModalFooter, +} from 'reactstrap'; +import { AxiosError } from 'axios'; + +import ImageCropper, { Clip, applyClipToImage } from '../common/ImageCropper'; + +export interface ChangeAvatarDialogProps { + open: boolean; + close: () => void; + process: (blob: Blob) => Promise; +} + +const ChangeAvatarDialog: React.FC = (props) => { + const { t } = useTranslation(); + + 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 + >('userPage.dialogChangeAvatar.prompt.select'); + + const trueMessage = + message == null + ? null + : typeof message === 'string' + ? t(message) + : message.text; + + const closeDialog = props.close; + + const toggle = 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 Error('Invalid state.'); + } + + setState('processcrop'); + 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 process = props.process; + + const upload = React.useCallback(() => { + if (resultBlob == null) { + throw new Error('Invalid state.'); + } + + setState('uploading'); + process(resultBlob).then( + () => { + setState('success'); + }, + (e: unknown) => { + setState('error'); + setMessage({ type: 'custom', text: (e as AxiosError).message }); + } + ); + }, [resultBlob, process]); + + const createPreviewRow = (): React.ReactElement => { + if (resultUrl == null) { + throw new Error('Invalid state.'); + } + return ( + + {t('userPage.dialogChangeAvatar.previewImgAlt')} + + ); + }; + + return ( + + {t('userPage.dialogChangeAvatar.title')} + {(() => { + if (state === 'select') { + return ( + <> + + {t('userPage.dialogChangeAvatar.prompt.select')} + + + + + + + + + ); + } else if (state === 'crop') { + if (fileUrl == null) { + throw new Error('Invalid state.'); + } + return ( + <> + + + + + {t('userPage.dialogChangeAvatar.prompt.crop')} + + + + + + + + ); + } else if (state === 'processcrop') { + return ( + <> + + + {t('userPage.dialogChangeAvatar.prompt.processingCrop')} + + + + + + + + ); + } else if (state === 'preview') { + return ( + <> + + {createPreviewRow()} + {t('userPage.dialogChangeAvatar.prompt.preview')} + + + + + + + + ); + } else if (state === 'uploading') { + return ( + <> + + {createPreviewRow()} + {t('userPage.dialogChangeAvatar.prompt.uploading')} + + + + ); + } else if (state === 'success') { + return ( + <> + + + {t('operationDialog.success')} + + + + + + + ); + } else { + return ( + <> + + {createPreviewRow()} + {trueMessage} + + + + + + + ); + } + })()} + + ); +}; + +export default ChangeAvatarDialog; -- cgit v1.2.3