From 29da3cef05ef138af2028ad44a27d706e9ae2df8 Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 26 Jun 2021 18:46:58 +0800 Subject: ... --- FrontEnd/src/views/common/OperationDialog.tsx | 471 -------------------------- 1 file changed, 471 deletions(-) delete mode 100644 FrontEnd/src/views/common/OperationDialog.tsx (limited to 'FrontEnd/src/views/common/OperationDialog.tsx') diff --git a/FrontEnd/src/views/common/OperationDialog.tsx b/FrontEnd/src/views/common/OperationDialog.tsx deleted file mode 100644 index ac4c51b9..00000000 --- a/FrontEnd/src/views/common/OperationDialog.tsx +++ /dev/null @@ -1,471 +0,0 @@ -import React, { useState } from "react"; -import { useTranslation } from "react-i18next"; -import { Form, Button, Modal } from "react-bootstrap"; -import { TwitterPicker } from "react-color"; -import moment from "moment"; - -import { convertI18nText, I18nText, UiLogicError } from "@/common"; - -import LoadingButton from "./LoadingButton"; - -interface DefaultErrorPromptProps { - error?: string; -} - -const DefaultErrorPrompt: React.FC = (props) => { - const { t } = useTranslation(); - - let result =

{t("operationDialog.error")}

; - - if (props.error != null) { - result = ( - <> - {result} -

{props.error}

- - ); - } - - return result; -}; - -export interface OperationDialogTextInput { - type: "text"; - label?: I18nText; - password?: boolean; - initValue?: string; - textFieldProps?: Omit< - React.InputHTMLAttributes, - "type" | "value" | "onChange" | "aria-relevant" - >; - helperText?: string; -} - -export interface OperationDialogBoolInput { - type: "bool"; - label: I18nText; - initValue?: boolean; -} - -export interface OperationDialogSelectInputOption { - value: string; - label: I18nText; - icon?: React.ReactElement; -} - -export interface OperationDialogSelectInput { - type: "select"; - label: I18nText; - options: OperationDialogSelectInputOption[]; - initValue?: string; -} - -export interface OperationDialogColorInput { - type: "color"; - label?: I18nText; - initValue?: string | null; - canBeNull?: boolean; -} - -export interface OperationDialogDateTimeInput { - type: "datetime"; - label?: I18nText; - initValue?: string; -} - -export type OperationDialogInput = - | OperationDialogTextInput - | OperationDialogBoolInput - | OperationDialogSelectInput - | OperationDialogColorInput - | OperationDialogDateTimeInput; - -interface OperationInputTypeStringToValueTypeMap { - text: string; - bool: boolean; - select: string; - color: string | null; - datetime: string; -} - -type MapOperationInputTypeStringToValueType = - Type extends keyof OperationInputTypeStringToValueTypeMap - ? OperationInputTypeStringToValueTypeMap[Type] - : never; - -type MapOperationInputInfoValueType = T extends OperationDialogInput - ? MapOperationInputTypeStringToValueType - : T; - -const initValueMapperMap: { - [T in OperationDialogInput as T["type"]]: ( - item: T - ) => MapOperationInputInfoValueType; -} = { - bool: (item) => item.initValue ?? false, - color: (item) => item.initValue ?? null, - datetime: (item) => { - if (item.initValue != null) { - return moment(item.initValue).format("YYYY-MM-DDTHH:mm:ss"); - } else { - return ""; - } - }, - select: (item) => item.initValue ?? item.options[0].value, - text: (item) => item.initValue ?? "", -}; - -type MapOperationInputInfoValueTypeList< - Tuple extends readonly OperationDialogInput[] -> = { - [Index in keyof Tuple]: MapOperationInputInfoValueType; -} & { length: Tuple["length"] }; - -export type OperationInputError = - | { - [index: number]: I18nText | null | undefined; - } - | null - | undefined; - -const isNoError = (error: OperationInputError): boolean => { - if (error == null) return true; - for (const key in error) { - if (error[key] != null) return false; - } - return true; -}; - -export interface OperationDialogProps< - TData, - OperationInputInfoList extends readonly OperationDialogInput[] -> { - open: boolean; - close: () => void; - title: I18nText | (() => React.ReactNode); - themeColor?: "danger" | "success" | string; - onProcess: ( - inputs: MapOperationInputInfoValueTypeList - ) => Promise; - inputScheme?: OperationInputInfoList; - inputValidator?: ( - inputs: MapOperationInputInfoValueTypeList - ) => OperationInputError; - inputPrompt?: I18nText | (() => React.ReactNode); - processPrompt?: () => React.ReactNode; - successPrompt?: (data: TData) => React.ReactNode; - failurePrompt?: (error: unknown) => React.ReactNode; - onSuccessAndClose?: (data: TData) => void; -} - -const OperationDialog = < - TData, - OperationInputInfoList extends readonly OperationDialogInput[] ->( - props: OperationDialogProps -): React.ReactElement => { - const inputScheme = (props.inputScheme ?? - []) as readonly OperationDialogInput[]; - - const { t } = useTranslation(); - - type Step = - | { type: "input" } - | { type: "process" } - | { - type: "success"; - data: TData; - } - | { - type: "failure"; - data: unknown; - }; - const [step, setStep] = useState({ type: "input" }); - - type ValueType = boolean | string | null | undefined; - - const [values, setValues] = useState( - inputScheme.map((item) => { - if (item.type in initValueMapperMap) { - return ( - initValueMapperMap[item.type] as ( - i: OperationDialogInput - ) => ValueType - )(item); - } else { - throw new UiLogicError("Unknown input scheme."); - } - }) - ); - const [dirtyList, setDirtyList] = useState(() => - inputScheme.map(() => false) - ); - const [inputError, setInputError] = useState(); - - const close = (): void => { - if (step.type !== "process") { - props.close(); - if (step.type === "success" && props.onSuccessAndClose) { - props.onSuccessAndClose(step.data); - } - } else { - console.log("Attempt to close modal when processing."); - } - }; - - const onConfirm = (): void => { - setStep({ type: "process" }); - props - .onProcess( - values.map((v, index) => { - if (inputScheme[index].type === "datetime" && v !== "") - return new Date(v as string).toISOString(); - else return v; - }) as unknown as MapOperationInputInfoValueTypeList - ) - .then( - (d) => { - setStep({ - type: "success", - data: d, - }); - }, - (e: unknown) => { - setStep({ - type: "failure", - data: e, - }); - } - ); - }; - - let body: React.ReactNode; - if (step.type === "input" || step.type === "process") { - const process = step.type === "process"; - - let inputPrompt = - typeof props.inputPrompt === "function" - ? props.inputPrompt() - : convertI18nText(props.inputPrompt, t); - inputPrompt =
{inputPrompt}
; - - const validate = (values: ValueType[]): boolean => { - const { inputValidator } = props; - if (inputValidator != null) { - const result = inputValidator( - values as unknown as MapOperationInputInfoValueTypeList - ); - setInputError(result); - return isNoError(result); - } - return true; - }; - - const updateValue = (index: number, newValue: ValueType): void => { - const oldValues = values; - const newValues = oldValues.slice(); - newValues[index] = newValue; - setValues(newValues); - if (dirtyList[index] === false) { - const newDirtyList = dirtyList.slice(); - newDirtyList[index] = true; - setDirtyList(newDirtyList); - } - validate(newValues); - }; - - const canProcess = isNoError(inputError); - - body = ( - <> - - {inputPrompt} - {inputScheme.map((item, index) => { - const value = values[index]; - const error: string | null = - dirtyList[index] && inputError != null - ? convertI18nText(inputError[index], t) - : null; - - if (item.type === "text") { - return ( - - {item.label && ( - {convertI18nText(item.label, t)} - )} - { - const v = e.target.value; - updateValue(index, v); - }} - isInvalid={error != null} - disabled={process} - /> - {error != null && ( - - {error} - - )} - {item.helperText && ( - {t(item.helperText)} - )} - - ); - } else if (item.type === "bool") { - return ( - - - type="checkbox" - checked={value as boolean} - onChange={(event) => { - updateValue(index, event.currentTarget.checked); - }} - label={convertI18nText(item.label, t)} - disabled={process} - /> - - ); - } else if (item.type === "select") { - return ( - - {convertI18nText(item.label, t)} - { - updateValue(index, event.target.value); - }} - disabled={process} - > - {item.options.map((option, i) => { - return ( - - ); - })} - - - ); - } else if (item.type === "color") { - return ( - - {item.canBeNull ? ( - - type="checkbox" - checked={value !== null} - onChange={(event) => { - if (event.currentTarget.checked) { - updateValue(index, "#007bff"); - } else { - updateValue(index, null); - } - }} - label={convertI18nText(item.label, t)} - disabled={process} - /> - ) : ( - {convertI18nText(item.label, t)} - )} - {value !== null && ( - updateValue(index, result.hex)} - /> - )} - - ); - } else if (item.type === "datetime") { - return ( - - {item.label && ( - {convertI18nText(item.label, t)} - )} - { - const v = e.target.value; - updateValue(index, v); - }} - isInvalid={error != null} - disabled={process} - /> - {error != null && ( - - {error} - - )} - - ); - } - })} - - - - { - setDirtyList(inputScheme.map(() => true)); - if (validate(values)) { - onConfirm(); - } - }} - > - {t("operationDialog.confirm")} - - - - ); - } else { - let content: React.ReactNode; - const result = step; - if (result.type === "success") { - content = - props.successPrompt?.(result.data) ?? t("operationDialog.success"); - if (typeof content === "string") - content =

{content}

; - } else { - content = props.failurePrompt?.(result.data) ?? ; - if (typeof content === "string") - content = ; - } - body = ( - <> - {content} - - - - - ); - } - - const title = - typeof props.title === "function" - ? props.title() - : convertI18nText(props.title, t); - - return ( - - - {title} - - {body} - - ); -}; - -export default OperationDialog; -- cgit v1.2.3