aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2023-07-24 00:31:02 +0800
committercrupest <crupest@outlook.com>2023-07-24 00:31:02 +0800
commitc7934c59cb17a4266ea882cdb40be79f22043d10 (patch)
treeff368fe5611014cd20db5a1f53d4dcbd5fdabf9f
parentae1b296b5e967d1e329f5a1e6165ca0f05dce0cb (diff)
downloadtimeline-c7934c59cb17a4266ea882cdb40be79f22043d10.tar.gz
timeline-c7934c59cb17a4266ea882cdb40be79f22043d10.tar.bz2
timeline-c7934c59cb17a4266ea882cdb40be79f22043d10.zip
...
-rw-r--r--FrontEnd/package.json4
-rw-r--r--FrontEnd/pnpm-lock.yaml75
-rw-r--r--FrontEnd/src/locales/en/translation.json2
-rw-r--r--FrontEnd/src/utilities/base64.ts21
-rw-r--r--FrontEnd/src/views/common/dialog/OperationDialog.tsx375
-rw-r--r--FrontEnd/src/views/common/input/InputPanel.tsx7
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,
);
};