From 0d81fa8b0971fe99329a7a773e42d7b879000406 Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 16 Aug 2023 02:15:21 +0800 Subject: ... --- FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx | 436 +++++++++++----------- FrontEnd/src/pages/setting/index.css | 4 +- 2 files changed, 230 insertions(+), 210 deletions(-) (limited to 'FrontEnd/src/pages') diff --git a/FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx b/FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx index 8c8e04fe..7ac7dcad 100644 --- a/FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx +++ b/FrontEnd/src/pages/setting/ChangeAvatarDialog.tsx @@ -1,9 +1,12 @@ -import { useState, useEffect } from "react"; -import * as React from "react"; -import { useTranslation } from "react-i18next"; +import { + useState, + useEffect, + ChangeEvent, + ComponentPropsWithoutRef, +} from "react"; import { AxiosError } from "axios"; -import { convertI18nText, I18nText, UiLogicError } from "@/common"; +import { useC, Text, UiLogicError } from "@/common"; import { useUser } from "@/services/user"; @@ -14,49 +17,53 @@ import ImageCropper, { applyClipToImage, } from "@/views/common/ImageCropper"; import Button from "@/views/common/button/Button"; +import ButtonRow from "@/views/common/button/ButtonRow"; import Dialog from "@/views/common/dialog/Dialog"; +import DialogContainer from "@/views/common/dialog/DialogContainer"; -export interface ChangeAvatarDialogProps { +interface ChangeAvatarDialogProps { open: boolean; onClose: () => void; } -const ChangeAvatarDialog: React.FC = (props) => { - const { t } = useTranslation(); +export default function ChangeAvatarDialog({ + open, + onClose, +}: ChangeAvatarDialogProps) { + const c = useC(); const user = useUser(); - 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 [file, setFile] = useState(null); + const [fileUrl, setFileUrl] = useState(null); + const [clip, setClip] = useState(null); + const [cropImgElement, setCropImgElement] = useState( + null, + ); + const [resultBlob, setResultBlob] = useState(null); + const [resultUrl, setResultUrl] = useState(null); - const [state, setState] = React.useState< + type State = | "select" | "crop" - | "processcrop" + | "process-crop" | "preview" | "uploading" | "success" - | "error" - >("select"); + | "error"; + const [state, setState] = useState("select"); - const [message, setMessage] = useState( + const [message, setMessage] = useState( "settings.dialogChangeAvatar.prompt.select", ); - const trueMessage = convertI18nText(message, t); - - const closeDialog = props.onClose; + const trueMessage = c(message); - const close = React.useCallback((): void => { + const close = (): void => { if (!(state === "uploading")) { - closeDialog(); + onClose(); } - }, [state, closeDialog]); + }; useEffect(() => { if (file != null) { @@ -73,7 +80,7 @@ const ChangeAvatarDialog: React.FC = (props) => { } }, [file]); - React.useEffect(() => { + useEffect(() => { if (resultBlob != null) { const url = URL.createObjectURL(resultBlob); setResultUrl(url); @@ -86,19 +93,16 @@ const ChangeAvatarDialog: React.FC = (props) => { } }, [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 onSelectFile = (e: ChangeEvent): void => { + const files = e.target.files; + if (files == null || files.length === 0) { + setFile(null); + } else { + setFile(files[0]); + } + }; - const onCropNext = React.useCallback(() => { + const onCropNext = () => { if ( cropImgElement == null || clip == null || @@ -108,23 +112,23 @@ const ChangeAvatarDialog: React.FC = (props) => { throw new UiLogicError(); } - setState("processcrop"); + setState("process-crop"); void applyClipToImage(cropImgElement, clip, file.type).then((b) => { setResultBlob(b); }); - }, [cropImgElement, clip, file]); + }; - const onCropPrevious = React.useCallback(() => { + const onCropPrevious = () => { setFile(null); setState("select"); - }, []); + }; - const onPreviewPrevious = React.useCallback(() => { + const onPreviewPrevious = () => { setResultBlob(null); setState("crop"); - }, []); + }; - const upload = React.useCallback(() => { + const upload = () => { if (resultBlob == null) { throw new UiLogicError(); } @@ -145,7 +149,7 @@ const ChangeAvatarDialog: React.FC = (props) => { setMessage({ type: "custom", value: (e as AxiosError).message }); }, ); - }, [user, resultBlob]); + }; const createPreviewRow = (): React.ReactElement => { if (resultUrl == null) { @@ -157,26 +161,77 @@ const ChangeAvatarDialog: React.FC = (props) => { {t("settings.dialogChangeAvatar.previewImgAlt") ); }; + const buttonsMap: Record< + State, + ComponentPropsWithoutRef["buttons"] + > = { + select: [ + { + key: "cancel", + type: "normal", + props: { + outline: true, + color: "secondary", + text: "operationDialog.cancel", + onClick: close, + }, + }, + ], + crop: [ + { + key: "cancel", + type: "normal", + props: { + outline: true, + color: "secondary", + text: "operationDialog.cancel", + onClick: close, + }, + }, + { + key: "previous", + type: "normal", + props: { + outline: true, + color: "secondary", + text: "operationDialog.previousStep", + onClick: onCropPrevious, + }, + }, + { + key: "next", + type: "normal", + props: { + color: "primary", + text: "operationDialog.nextStep", + onClick: onCropNext, + disabled: cropImgElement == null || clip == null || clip.width === 0, + }, + }, + ], + }; + return ( - -

- {t("settings.dialogChangeAvatar.title")} -

-
- {(() => { - if (state === "select") { - return ( - <> -
+ + + {(() => { + if (state === "select") { + return ( +
- {t("settings.dialogChangeAvatar.prompt.select")} + {c("settings.dialogChangeAvatar.prompt.select")}
= (props) => { />
-
-
-
- - ); - } else if (state === "crop") { - if (fileUrl == null) { - throw new UiLogicError(); - } - return ( - <> + ); + } else if (state === "crop") { + if (fileUrl == null) { + throw new UiLogicError(); + } + return (
= (props) => { />
- {t("settings.dialogChangeAvatar.prompt.crop")} + {c("settings.dialogChangeAvatar.prompt.crop")}
-
-
-
- - ); - } else if (state === "processcrop") { - return ( - <> -
-
- {t("settings.dialogChangeAvatar.prompt.processingCrop")} + ); + } else if (state === "process-crop") { + return ( + <> +
+
+ {c("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 === "preview") { + return ( + <> +
+ {createPreviewRow()} +
+ {t("settings.dialogChangeAvatar.prompt.preview")} +
-
- - ); - } else if (state === "success") { - return ( - <> -
-
- {t("operationDialog.success")} +
+
+
-
-
-
-
- - ); - } else { - return ( - <> -
- {createPreviewRow()} -
{trueMessage}
-
-
-
-
- - ); - } - })()} + + ); + } 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; +} diff --git a/FrontEnd/src/pages/setting/index.css b/FrontEnd/src/pages/setting/index.css index c1950f9c..8af65e93 100644 --- a/FrontEnd/src/pages/setting/index.css +++ b/FrontEnd/src/pages/setting/index.css @@ -61,8 +61,8 @@ } .register-code { - background: var(--cru-surface-container-highest-color); - border: 1px solid var(--cru-surface-container-highest-color); + background: var(--cru-text-primary-color); + color: var(--cru-background-color); border-radius: 3px; padding: 0.2em; cursor: pointer; -- cgit v1.2.3