From 82a3641a6d4e85ad0b78b3fceec7ad55b0b845d9 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Fri, 10 Feb 2023 20:16:44 +0100 Subject: [PATCH] feat: Add StringPoolField --- package.json | 2 + public/locales/en-US/translation.json | 20 +- src/apis/get-admin-settings.ts | 2 +- .../widgets/StringPoolField/AddNewDialog.tsx | 68 +++++++ .../StringPoolField/StringPoolField.tsx | 183 ++++++++++++++++++ .../widgets/StringPoolField/index.ts | 2 + src/components/widgets/index.ts | 1 + 7 files changed, 276 insertions(+), 2 deletions(-) create mode 100644 src/components/widgets/StringPoolField/AddNewDialog.tsx create mode 100644 src/components/widgets/StringPoolField/StringPoolField.tsx create mode 100644 src/components/widgets/StringPoolField/index.ts diff --git a/package.json b/package.json index d458915..8d6e10d 100755 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "immutability-helper": "^3.1.1", "in-milliseconds": "^1.2.0", "in-seconds": "^1.2.0", + "lodash": "^4.17.21", "notistack": "^2.0.8", "openpgp": "^5.5.0", "react": "^18.2.0", @@ -56,6 +57,7 @@ "@types/deep-equal": "^1.0.1", "@types/group-array": "^1.0.1", "@types/jest": "^29.2.4", + "@types/lodash": "^4.14.191", "@types/node": "^18.11.18", "@types/openpgp": "^4.4.18", "@types/react-icons": "^3.0.0", diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index 6db885a..d1419ef 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -3,6 +3,7 @@ "cancelLabel": "Cancel", "emptyValue": "-", "emptyUnavailableValue": "Unavailable", + "saveLabel": "Save", "defaultValueSelection": "Default <{{value}}>", "defaultValueSelectionRaw": "<{{value}}>", @@ -345,7 +346,7 @@ "label": "Random alias character pool", "description": "Characters that are used to generate random emails." }, - "randomEmailIdLengthIncreaseOnPercentage": { + "randomEmailLengthIncreaseOnPercentage": { "label": "Percentage of used aliases", "description": "If the percentage of used random email IDs is higher than this value, the length of the random email ID will be increased. This is used to prevent spammers from guessing the email ID." }, @@ -378,6 +379,10 @@ "description": "If enabled, your instance will collect anonymous statistics and share them. They will only be stored locally on this instance but made public." } } + }, + "settings": { + "title": "Global Settings", + "description": "Configure global settings for your instance." } } }, @@ -458,6 +463,19 @@ "doNotShare": "Do not share", "decideLater": "Decide later", "doNotAskAgain": "Do not ask again" + }, + "StringPoolField": { + "addCustom": { + "label": "Add custom" + }, + "forms": { + "addNew": { + "title": "Add new value", + "description": "Enter your characters you would like to include", + "label": "Characters", + "submit": "Add" + } + } } }, diff --git a/src/apis/get-admin-settings.ts b/src/apis/get-admin-settings.ts index b262a91..afa9427 100644 --- a/src/apis/get-admin-settings.ts +++ b/src/apis/get-admin-settings.ts @@ -1,7 +1,7 @@ import {client} from "~/constants/axios-client" import {AdminSettings} from "~/server-types" -export default async function getAdminSettings(): Promise { +export default async function getAdminSettings(): Promise> { const {data} = await client.get(`${import.meta.env.VITE_SERVER_BASE_URL}/v1/admin/settings`, { withCredentials: true, }) diff --git a/src/components/widgets/StringPoolField/AddNewDialog.tsx b/src/components/widgets/StringPoolField/AddNewDialog.tsx new file mode 100644 index 0000000..9ef899f --- /dev/null +++ b/src/components/widgets/StringPoolField/AddNewDialog.tsx @@ -0,0 +1,68 @@ +import {ReactElement, useState} from "react" +import {useTranslation} from "react-i18next" +import {MdCheck} from "react-icons/md" +import {TiCancel} from "react-icons/ti" + +import { + Box, + Button, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + TextField, +} from "@mui/material" + +import {whenEnterPressed} from "~/utils" + +export interface StringPoolFieldProps { + onCreated: (value: string) => void + onClose: () => void + + open?: boolean +} + +export default function AddNewDialog({ + onCreated, + open = false, + onClose, +}: StringPoolFieldProps): ReactElement { + const {t} = useTranslation() + + const [value, setValue] = useState("") + + return ( + + {t("components.StringPoolField.forms.addNew.title")} + + + {t("components.StringPoolField.forms.addNew.description")} + + + setValue(e.target.value)} + label={t("components.StringPoolField.forms.addNew.label")} + name="addNew" + fullWidth + autoFocus + onKeyUp={whenEnterPressed(() => onCreated(value))} + /> + + + + + + + + ) +} diff --git a/src/components/widgets/StringPoolField/StringPoolField.tsx b/src/components/widgets/StringPoolField/StringPoolField.tsx new file mode 100644 index 0000000..ecc2e69 --- /dev/null +++ b/src/components/widgets/StringPoolField/StringPoolField.tsx @@ -0,0 +1,183 @@ +import {useTranslation} from "react-i18next" +import {MdAdd} from "react-icons/md" +import React, {ReactElement, useLayoutEffect, useMemo, useState} from "react" + +import { + Box, + Checkbox, + Chip, + FormControl, + FormHelperText, + InputLabel, + ListItemIcon, + ListItemText, + MenuItem, + Select, + SelectProps, +} from "@mui/material" + +import AddNewDialog from "./AddNewDialog" + +export interface StringPoolFieldProps + extends Omit, "onChange" | "value" | "multiple" | "labelId" | "label"> { + pools: Record + label: string + value: string + onChange: SelectProps["onChange"] + id: string + + allowCustom?: boolean + helperText?: string | string[] + error?: boolean +} + +export function createPool(pools: Record): Record { + return Object.fromEntries( + Object.entries(pools).map(([key, value]) => [key.split("").sort().join(""), value]), + ) +} + +export default function StringPoolField({ + pools, + value, + helperText, + id, + error, + label, + onChange, + onOpen, + allowCustom, + name, + fullWidth, + ...props +}: StringPoolFieldProps): ReactElement { + const {t} = useTranslation() + + const reversedPoolsMap = useMemo( + () => Object.fromEntries(Object.entries(pools).map(([key, value]) => [value, key])), + [pools], + ) + const [isInAddMode, setIsInAddMode] = useState(false) + const [uiRemainingValue, setUiRemainingValue] = useState("") + + const selectedValueMaps = Object.entries(pools) + .filter(([key]) => value.includes(key)) + .map(([, value]) => value) + const remainingValue = (() => { + // List of all characters inside the pools + const charactersInPools = Object.keys(pools).join("") + + return value + .split("") + .filter(char => !charactersInPools.includes(char)) + .join("") + })() + const selectValue = [...selectedValueMaps, remainingValue].filter(Boolean) + + useLayoutEffect(() => { + if (remainingValue) { + setUiRemainingValue(remainingValue) + } + }, [remainingValue]) + + return ( + <> + + + {label} + + + multiple + name={name} + labelId={id} + renderValue={(selected: string[]) => ( + + {selected.map(value => ( + + ))} + + )} + value={selectValue} + label={label} + onOpen={event => { + if (!remainingValue) { + setUiRemainingValue("") + } + + onOpen?.(event) + }} + onChange={(event, child) => { + if (!Array.isArray(event.target.value)) { + return + } + + const value = event.target.value.reduce((acc, value) => { + if (reversedPoolsMap[value]) { + return acc + reversedPoolsMap[value] + } + + return acc + value + }, "") + + onChange!( + // @ts-ignore + { + ...event, + target: { + ...event.target, + value: value as string, + }, + }, + child, + ) + }} + {...props} + > + {Object.entries(pools).map(([poolValue, label]) => ( + + + + + ))} + {uiRemainingValue && ( + + + + + )} + {allowCustom && ( + { + event.preventDefault() + setIsInAddMode(true) + }} + > + + + + + + )} + + {helperText ? {helperText} : null} + + { + setIsInAddMode(false) + + // @ts-ignore: This is enough for formik. + onChange({ + target: { + name, + value: value + newValue, + }, + }) + }} + onClose={() => setIsInAddMode(false)} + open={isInAddMode} + /> + + ) +} diff --git a/src/components/widgets/StringPoolField/index.ts b/src/components/widgets/StringPoolField/index.ts new file mode 100644 index 0000000..a9a2018 --- /dev/null +++ b/src/components/widgets/StringPoolField/index.ts @@ -0,0 +1,2 @@ +export * from "./StringPoolField" +export {default as StringPoolField} from "./StringPoolField" diff --git a/src/components/widgets/index.ts b/src/components/widgets/index.ts index b8b3f5e..d25c848 100644 --- a/src/components/widgets/index.ts +++ b/src/components/widgets/index.ts @@ -45,5 +45,6 @@ export {default as LoadingData} from "./LoadingData" export * from "./ExternalLinkIndication" export {default as ExternalLinkIndication} from "./ExternalLinkIndication" export {default as ExtensionSignalHandler} from "./ExtensionalSignalHandler" +export * from "./StringPoolField" export * as SimplePageBuilder from "./simple-page-builder"