diff options
Diffstat (limited to 'FrontEnd')
-rw-r--r-- | FrontEnd/package.json | 4 | ||||
-rw-r--r-- | FrontEnd/pnpm-lock.yaml | 75 | ||||
-rw-r--r-- | FrontEnd/src/locales/en/translation.json | 2 | ||||
-rw-r--r-- | FrontEnd/src/utilities/base64.ts | 21 | ||||
-rw-r--r-- | FrontEnd/src/views/common/dialog/OperationDialog.tsx | 375 | ||||
-rw-r--r-- | FrontEnd/src/views/common/input/InputPanel.tsx | 7 |
6 files changed, 185 insertions, 299 deletions
diff --git a/FrontEnd/package.json b/FrontEnd/package.json index 268e9636..725b09dd 100644 --- a/FrontEnd/package.json +++ b/FrontEnd/package.json @@ -22,19 +22,16 @@ "core-js": "^3.31.1", "i18next": "^23.2.8", "i18next-browser-languagedetector": "^7.1.0", - "js-base64": "^3.7.5", "lodash": "^4.17.21", "marked": "^5.1.1", "moment": "^2.29.4", "react": "^18.2.0", - "react-color": "^2.19.3", "react-dom": "^18.2.0", "react-i18next": "^13.0.1", "react-popper": "^2.3.0", "react-responsive": "^9.0.2", "react-router-dom": "^6.14.1", "react-transition-group": "^4.4.5", - "regenerator-runtime": "^0.13.11", "rxjs": "^7.8.1", "xregexp": "^5.1.1" }, @@ -54,7 +51,6 @@ "@types/react-transition-group": "^4.4.6", "@typescript-eslint/eslint-plugin": "^5.61.0", "@typescript-eslint/parser": "^5.61.0", - "buffer": "^6.0.0", "eslint": "^8.44.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", diff --git a/FrontEnd/pnpm-lock.yaml b/FrontEnd/pnpm-lock.yaml index e57ef2f4..a31e5a2c 100644 --- a/FrontEnd/pnpm-lock.yaml +++ b/FrontEnd/pnpm-lock.yaml @@ -35,9 +35,6 @@ dependencies: i18next-browser-languagedetector: specifier: ^7.1.0 version: 7.1.0 - js-base64: - specifier: ^3.7.5 - version: 3.7.5 lodash: specifier: ^4.17.21 version: 4.17.21 @@ -50,9 +47,6 @@ dependencies: react: specifier: ^18.2.0 version: 18.2.0 - react-color: - specifier: ^2.19.3 - version: 2.19.3(react@18.2.0) react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) @@ -71,9 +65,6 @@ dependencies: react-transition-group: specifier: ^4.4.5 version: 4.4.5(react-dom@18.2.0)(react@18.2.0) - regenerator-runtime: - specifier: ^0.13.11 - version: 0.13.11 rxjs: specifier: ^7.8.1 version: 7.8.1 @@ -127,9 +118,6 @@ devDependencies: '@typescript-eslint/parser': specifier: ^5.61.0 version: 5.61.0(eslint@8.44.0)(typescript@5.1.6) - buffer: - specifier: ^6.0.0 - version: 6.0.3 eslint: specifier: ^8.44.0 version: 8.44.0 @@ -268,14 +256,6 @@ packages: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} dev: true - /@icons/material@0.2.4(react@18.2.0): - resolution: {integrity: sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==} - peerDependencies: - react: '*' - dependencies: - react: 18.2.0 - dev: false - /@jridgewell/resolve-uri@3.1.1: resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} engines: {node: '>=6.0.0'} @@ -1768,10 +1748,6 @@ packages: safe-buffer: 5.2.1 dev: true - /base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - dev: true - /boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} dev: true @@ -1813,13 +1789,6 @@ packages: update-browserslist-db: 1.0.11(browserslist@4.21.9) dev: true - /buffer@6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - dev: true - /call-bind@1.0.2: resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} dependencies: @@ -2679,10 +2648,6 @@ packages: '@babel/runtime': 7.22.6 dev: false - /ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - dev: true - /ignore@5.2.4: resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} engines: {node: '>= 4'} @@ -2856,10 +2821,6 @@ packages: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true - /js-base64@3.7.5: - resolution: {integrity: sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA==} - dev: false - /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -3024,10 +2985,6 @@ packages: p-locate: 5.0.0 dev: true - /lodash-es@4.17.21: - resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} - dev: false - /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true @@ -3065,10 +3022,6 @@ packages: css-mediaquery: 0.1.2 dev: false - /material-colors@1.2.6: - resolution: {integrity: sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==} - dev: false - /mdn-data@2.0.14: resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} dev: true @@ -3452,21 +3405,6 @@ packages: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true - /react-color@2.19.3(react@18.2.0): - resolution: {integrity: sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA==} - peerDependencies: - react: '*' - dependencies: - '@icons/material': 0.2.4(react@18.2.0) - lodash: 4.17.21 - lodash-es: 4.17.21 - material-colors: 1.2.6 - prop-types: 15.8.1 - react: 18.2.0 - reactcss: 1.2.3(react@18.2.0) - tinycolor2: 1.6.0 - dev: false - /react-dom@18.2.0(react@18.2.0): resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} peerDependencies: @@ -3584,15 +3522,6 @@ packages: loose-envify: 1.4.0 dev: false - /reactcss@1.2.3(react@18.2.0): - resolution: {integrity: sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==} - peerDependencies: - react: '*' - dependencies: - lodash: 4.17.21 - react: 18.2.0 - dev: false - /regenerator-runtime@0.13.11: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} @@ -3828,10 +3757,6 @@ packages: resolution: {integrity: sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==} dev: true - /tinycolor2@1.6.0: - resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} - dev: false - /to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} diff --git a/FrontEnd/src/locales/en/translation.json b/FrontEnd/src/locales/en/translation.json index a73472d2..a7e4efe5 100644 --- a/FrontEnd/src/locales/en/translation.json +++ b/FrontEnd/src/locales/en/translation.json @@ -86,7 +86,7 @@ "ok": "OK!", "processing": "Processing...", "success": "Success!", - "error": "An error occured." + "error": "An error occurred." }, "timeline": { "messageCantSee": "Sorry, you are not allowed to see this timeline.😅", diff --git a/FrontEnd/src/utilities/base64.ts b/FrontEnd/src/utilities/base64.ts index 59de7512..6eece979 100644 --- a/FrontEnd/src/utilities/base64.ts +++ b/FrontEnd/src/utilities/base64.ts @@ -1,8 +1,19 @@ -import { Base64 } from "js-base64"; +function bytesToBase64(bytes: Uint8Array): string { + const binString = Array.from(bytes, (x) => String.fromCodePoint(x)).join(""); + return btoa(binString); +} + +export default function base64( + data: Blob | Uint8Array | string, +): Promise<string> { + if (typeof data === "string") { + // From https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem + const binString = new TextEncoder().encode(data); + return Promise.resolve(bytesToBase64(binString)); + } -export default function base64(blob: Blob | string): Promise<string> { - if (typeof blob === "string") { - return Promise.resolve(Base64.encode(blob)); + if (data instanceof Uint8Array) { + return Promise.resolve(bytesToBase64(data)); } return new Promise<string>((resolve) => { @@ -10,6 +21,6 @@ export default function base64(blob: Blob | string): Promise<string> { reader.onload = function () { resolve((reader.result as string).replace(/^data:.*;base64,/, "")); }; - reader.readAsDataURL(blob); + reader.readAsDataURL(data); }); } diff --git a/FrontEnd/src/views/common/dialog/OperationDialog.tsx b/FrontEnd/src/views/common/dialog/OperationDialog.tsx index 71be030a..ad00c424 100644 --- a/FrontEnd/src/views/common/dialog/OperationDialog.tsx +++ b/FrontEnd/src/views/common/dialog/OperationDialog.tsx @@ -1,13 +1,8 @@ -import { useState } from "react"; -import * as React from "react"; -import { useTranslation } from "react-i18next"; -import { TwitterPicker } from "react-color"; +import { useState, ReactNode, ComponentPropsWithoutRef } from "react"; import classNames from "classnames"; import moment from "moment"; -import { convertI18nText, I18nText, UiLogicError } from "@/common"; - -import { PaletteColorType } from "@/palette"; +import { useC, Text, ThemeColor } from "../common"; import Button from "../button/Button"; import LoadingButton from "../button/LoadingButton"; @@ -15,69 +10,61 @@ import Dialog from "./Dialog"; import "./OperationDialog.css"; -interface DefaultErrorPromptProps { - error?: string; +interface DefaultPromptProps { + color?: ThemeColor; + message?: Text; + customMessage?: ReactNode; + className?: string; } -const DefaultErrorPrompt: React.FC<DefaultErrorPromptProps> = (props) => { - const { t } = useTranslation(); +function DefaultPrompt(props: DefaultPromptProps) { + const { color, message, customMessage, className } = props; - let result = <p className="cru-color-danger">{t("operationDialog.error")}</p>; + const c = useC(); - if (props.error != null) { - result = ( - <> - {result} - <p className="cru-color-danger">{props.error}</p> - </> - ); - } - - return result; -}; + return ( + <div className={classNames(className, `cru-${color ?? "primary"}`)}> + <p>{c(message)}</p> + {customMessage} + </div> + ); +} export interface OperationDialogTextInput { type: "text"; - label?: I18nText; + label?: Text; password?: boolean; initValue?: string; textFieldProps?: Omit< - React.InputHTMLAttributes<HTMLInputElement>, + ComponentPropsWithoutRef<"input">, "type" | "value" | "onChange" >; - helperText?: string; + helperText?: Text; } export interface OperationDialogBoolInput { type: "bool"; - label: I18nText; + label: Text; initValue?: boolean; - helperText?: string; + helperText?: Text; } export interface OperationDialogSelectInputOption { value: string; - label: I18nText; - icon?: React.ReactElement; + label: Text; + icon?: ReactNode; } export interface OperationDialogSelectInput { type: "select"; - label: I18nText; + label: Text; options: OperationDialogSelectInputOption[]; initValue?: string; } -export interface OperationDialogColorInput { - type: "color"; - label?: I18nText; - initValue?: string | null; - canBeNull?: boolean; -} - export interface OperationDialogDateTimeInput { type: "datetime"; - label?: I18nText; + label?: Text; initValue?: string; helperText?: string; } @@ -86,17 +73,18 @@ export type OperationDialogInput = | OperationDialogTextInput | OperationDialogBoolInput | OperationDialogSelectInput - | OperationDialogColorInput | OperationDialogDateTimeInput; interface OperationInputTypeStringToValueTypeMap { text: string; bool: boolean; select: string; - color: string | null; datetime: string; } +type OperationInputValueType = + OperationInputTypeStringToValueTypeMap[keyof OperationInputTypeStringToValueTypeMap]; + type MapOperationInputTypeStringToValueType<Type> = Type extends keyof OperationInputTypeStringToValueTypeMap ? OperationInputTypeStringToValueTypeMap[Type] @@ -106,33 +94,15 @@ type MapOperationInputInfoValueType<T> = T extends OperationDialogInput ? MapOperationInputTypeStringToValueType<T["type"]> : T; -const initValueMapperMap: { - [T in OperationDialogInput as T["type"]]: ( - item: T - ) => MapOperationInputInfoValueType<T>; -} = { - 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[] + Tuple extends readonly OperationDialogInput[], > = { [Index in keyof Tuple]: MapOperationInputInfoValueType<Tuple[Index]>; -} & { length: Tuple["length"] }; +}; export type OperationInputError = | { - [index: number]: I18nText | null | undefined; + [index: number]: Text | null | undefined; } | null | undefined; @@ -145,38 +115,68 @@ const isNoError = (error: OperationInputError): boolean => { return true; }; +type ItemValueMapper = { + [T in OperationDialogInput as T["type"]]: ( + item: T, + ) => MapOperationInputInfoValueType<T>; +}; + +type ValueValueMapper = { + [T in OperationDialogInput as T["type"]]: ( + item: MapOperationInputInfoValueType<T>, + ) => MapOperationInputInfoValueType<T>; +}; + +const initValueMapperMap: ItemValueMapper = { + bool: (item) => item.initValue ?? false, + datetime: (item) => + item.initValue != null + ? /* cspell: disable-next-line */ + moment(item.initValue).format("YYYY-MM-DDTHH:mm:ss") + : "", + select: (item) => item.initValue ?? item.options[0].value, + text: (item) => item.initValue ?? "", +}; + +const finalValueMapperMap: ValueValueMapper = { + bool: (value) => value, + datetime: (value) => new Date(value).toISOString(), + select: (value) => value, + text: (value) => value, +}; + export interface OperationDialogProps< TData, - OperationInputInfoList extends readonly OperationDialogInput[] + OperationInputInfoList extends readonly OperationDialogInput[], > { open: boolean; onClose: () => void; - title: I18nText | (() => React.ReactNode); - themeColor?: PaletteColorType; - onProcess: ( - inputs: MapOperationInputInfoValueTypeList<OperationInputInfoList> - ) => Promise<TData>; + + themeColor?: ThemeColor; + title: Text; + inputPrompt?: Text; + processPrompt?: Text; + successPrompt?: (data: TData) => ReactNode; + failurePrompt?: (error: unknown) => ReactNode; + inputScheme?: OperationInputInfoList; inputValidator?: ( - inputs: MapOperationInputInfoValueTypeList<OperationInputInfoList> + inputs: MapOperationInputInfoValueTypeList<OperationInputInfoList>, ) => OperationInputError; - inputPrompt?: I18nText | (() => React.ReactNode); - processPrompt?: () => React.ReactNode; - successPrompt?: (data: TData) => React.ReactNode; - failurePrompt?: (error: unknown) => React.ReactNode; + + onProcess: ( + inputs: MapOperationInputInfoValueTypeList<OperationInputInfoList>, + ) => Promise<TData>; onSuccessAndClose?: (data: TData) => void; } -const OperationDialog = < +function OperationDialog< TData, - OperationInputInfoList extends readonly OperationDialogInput[] ->( - props: OperationDialogProps<TData, OperationInputInfoList> -): React.ReactElement => { - const inputScheme = (props.inputScheme ?? - []) as readonly OperationDialogInput[]; + OperationInputInfoList extends readonly OperationDialogInput[], +>(props: OperationDialogProps<TData, OperationInputInfoList>) { + const inputScheme = props.inputScheme ?? ([] as const); - const { t } = useTranslation(); + const c = useC(); type Step = | { type: "input" } @@ -189,48 +189,42 @@ const OperationDialog = < type: "failure"; data: unknown; }; + const [step, setStep] = useState<Step>({ type: "input" }); - type ValueType = boolean | string | null | undefined; - - const [values, setValues] = useState<ValueType[]>( - inputScheme.map((item) => { - if (item.type in initValueMapperMap) { - return ( - initValueMapperMap[item.type] as ( - i: OperationDialogInput - ) => ValueType - )(item); - } else { - throw new UiLogicError("Unknown input scheme."); - } - }) + type Values = MapOperationInputInfoValueTypeList<OperationInputInfoList>; + + const [values, setValues] = useState<Values>( + () => + inputScheme.map((item) => + initValueMapperMap[item.type](item as never), + ) as Values, ); + const [dirtyList, setDirtyList] = useState<boolean[]>(() => - inputScheme.map(() => false) + inputScheme.map(() => false), ); + const [inputError, setInputError] = useState<OperationInputError>(); - const close = (): void => { + function close() { if (step.type !== "process") { props.onClose(); if (step.type === "success" && props.onSuccessAndClose) { props.onSuccessAndClose(step.data); } } else { - console.log("Attempt to close modal when processing."); + console.log("Attempt to close modal dialog when processing."); } - }; + } - const onConfirm = (): void => { + function onConfirm() { 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<OperationInputInfoList> + values.map((value, index) => + finalValueMapperMap[inputScheme[index].type](value as never), + ) as Values, ) .then( (d) => { @@ -244,56 +238,51 @@ const OperationDialog = < type: "failure", data: e, }); - } + }, ); - }; + } - let body: React.ReactNode; + let body: 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 = <h6>{inputPrompt}</h6>; - - const validate = (values: ValueType[]): boolean => { + const validate = (values: Values): boolean => { const { inputValidator } = props; if (inputValidator != null) { - const result = inputValidator( - values as unknown as MapOperationInputInfoValueTypeList<OperationInputInfoList> - ); + const result = inputValidator(values); setInputError(result); return isNoError(result); } return true; }; - const updateValue = (index: number, newValue: ValueType): void => { + const updateValue = ( + index: number, + newValue: OperationInputValueType, + ): void => { const oldValues = values; const newValues = oldValues.slice(); newValues[index] = newValue; - setValues(newValues); + setValues(newValues as Values); if (dirtyList[index] === false) { const newDirtyList = dirtyList.slice(); newDirtyList[index] = true; setDirtyList(newDirtyList); } - validate(newValues); + validate(newValues as Values); }; const canProcess = isNoError(inputError); body = ( - <> + <div className="cru-operation-dialog-main-area"> <div> - {inputPrompt} - {inputScheme.map((item, index) => { + <div>{c(props.inputPrompt)}</div> + {inputScheme.map((item: OperationDialogInput, index: number) => { const value = values[index]; const error: string | null = dirtyList[index] && inputError != null - ? convertI18nText(inputError[index], t) + ? c(inputError[index]) : null; if (item.type === "text") { @@ -302,31 +291,31 @@ const OperationDialog = < key={index} className={classNames( "cru-operation-dialog-group", - error != null ? "error" : null + error && "error", )} > {item.label && ( <label className="cru-operation-dialog-label"> - {convertI18nText(item.label, t)} + {c(item.label)} </label> )} <input type={item.password === true ? "password" : "text"} value={value as string} - onChange={(e) => { - const v = e.target.value; + onChange={(event) => { + const v = event.target.value; updateValue(index, v); }} disabled={process} /> - {error != null && ( + {error && ( <div className="cru-operation-dialog-error-text"> {error} </div> )} {item.helperText && ( <div className="cru-operation-dialog-helper-text"> - {t(item.helperText)} + {c(item.helperText)} </div> )} </div> @@ -337,28 +326,29 @@ const OperationDialog = < key={index} className={classNames( "cru-operation-dialog-group", - error != null ? "error" : null + error && "error", )} > <input type="checkbox" checked={value as boolean} onChange={(event) => { - updateValue(index, event.currentTarget.checked); + const v = event.currentTarget.checked; + updateValue(index, v); }} disabled={process} /> <label className="cru-operation-dialog-inline-label"> - {convertI18nText(item.label, t)} + {c(item.label)} </label> - {error != null && ( + {error && ( <div className="cru-operation-dialog-error-text"> {error} </div> )} {item.helperText && ( <div className="cru-operation-dialog-helper-text"> - {t(item.helperText)} + {c(item.helperText)} </div> )} </div> @@ -369,16 +359,17 @@ const OperationDialog = < key={index} className={classNames( "cru-operation-dialog-group", - error != null ? "error" : null + error && "error", )} > <label className="cru-operation-dialog-label"> - {convertI18nText(item.label, t)} + {c(item.label)} </label> <select value={value as string} onChange={(event) => { - updateValue(index, event.target.value); + const e = event.target.value; + updateValue(index, e); }} disabled={process} > @@ -386,72 +377,41 @@ const OperationDialog = < return ( <option value={option.value} key={i}> {option.icon} - {convertI18nText(option.label, t)} + {c(option.label)} </option> ); })} </select> </div> ); - } else if (item.type === "color") { - return ( - <div - key={index} - className={classNames( - "cru-operation-dialog-group", - error != null ? "error" : null - )} - > - {item.canBeNull ? ( - <input - type="checkbox" - checked={value !== null} - onChange={(event) => { - if (event.currentTarget.checked) { - updateValue(index, "#007bff"); - } else { - updateValue(index, null); - } - }} - disabled={process} - /> - ) : null} - <label className="cru-operation-dialog-inline-label"> - {convertI18nText(item.label, t)} - </label> - {value !== null && ( - <TwitterPicker - color={value as string} - triangle="hide" - onChange={(result) => updateValue(index, result.hex)} - /> - )} - </div> - ); } else if (item.type === "datetime") { return ( <div key={index} className={classNames( "cru-operation-dialog-group", - error != null ? "error" : null + error && "error", )} > {item.label && ( <label className="cru-operation-dialog-label"> - {convertI18nText(item.label, t)} + {c(item.label)} </label> )} <input type="datetime-local" value={value as string} - onChange={(e) => { - const v = e.target.value; + onChange={(event) => { + const v = event.target.value; updateValue(index, v); }} disabled={process} /> - {error != null && <div>{error}</div>} + {error && ( + <div className="cru-operation-dialog-error-text"> + {error} + </div> + )} </div> ); } @@ -477,55 +437,50 @@ const OperationDialog = < } }} > - {t("operationDialog.confirm")} + {c("operationDialog.confirm")} </LoadingButton> </div> - </> + </div> ); } 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 = <p className="cru-color-success">{content}</p>; - } else { - content = props.failurePrompt?.(result.data) ?? <DefaultErrorPrompt />; - if (typeof content === "string") - content = <DefaultErrorPrompt error={content} />; - } + + const promptProps: DefaultPromptProps = + result.type === "success" + ? { + color: "success", + message: "operationDialog.success", + customMessage: props.successPrompt?.(result.data), + } + : { + color: "danger", + message: "operationDialog.error", + customMessage: props.failurePrompt?.(result.data), + }; body = ( - <> - <div>{content}</div> + <div className="cru-operation-dialog-main-area"> + <DefaultPrompt {...promptProps} /> <hr /> <div className="cru-dialog-bottom-area"> <Button text="operationDialog.ok" color="primary" onClick={close} /> </div> - </> + </div> ); } - const title = - typeof props.title === "function" - ? props.title() - : convertI18nText(props.title, t); - return ( <Dialog open={props.open} onClose={close}> - <h3 - className={ - props.themeColor != null - ? "cru-color-" + props.themeColor - : "cru-color-primary" - } + <div + className={`cru-operation-dialog-title cru-${ + props.themeColor ?? "primary" + }`} > - {title} - </h3> + {c(props.title)} + </div> <hr /> {body} </Dialog> ); -}; +} export default OperationDialog; diff --git a/FrontEnd/src/views/common/input/InputPanel.tsx b/FrontEnd/src/views/common/input/InputPanel.tsx index 234ed267..27937a05 100644 --- a/FrontEnd/src/views/common/input/InputPanel.tsx +++ b/FrontEnd/src/views/common/input/InputPanel.tsx @@ -1,7 +1,6 @@ import * as React from "react"; import classNames from "classnames"; import { useTranslation } from "react-i18next"; -import { TwitterPicker } from "react-color"; import { convertI18nText, I18nText } from "@/common"; @@ -89,14 +88,14 @@ export interface InputPanelProps<InputList extends readonly Input[]> { values: MapInputListToValueTypeList<InputList>; onChange: ( values: MapInputListToValueTypeList<InputList>, - index: number + index: number, ) => void; error?: InputPanelError; disable?: boolean; } const InputPanel = <InputList extends readonly Input[]>( - props: InputPanelProps<InputList> + props: InputPanelProps<InputList>, ): React.ReactElement => { const { values, onChange, scheme, error, disable } = props; @@ -108,7 +107,7 @@ const InputPanel = <InputList extends readonly Input[]>( newValues[index] = newValue; onChange( newValues as unknown as MapInputListToValueTypeList<InputList>, - index + index, ); }; |