diff --git a/README.md b/README.md index ed64c95..0e9c356 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ A web clone of [DevToys](https://github.com/veler/DevToys) - [x] Converters - [x] Encoders / Decoders - [x] Formatters - - [ ] Generators + - [x] Generators - [ ] Text - [ ] Graphic - [ ] Support dark mode diff --git a/package.json b/package.json index 347afda..9a75898 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "react-ace": "^9.5.0", "react-dom": "17.0.2", "recoil": "^0.6.1", + "uuid": "^8.3.2", "yaml": "^1.10.2" }, "devDependencies": { @@ -43,6 +44,7 @@ "@types/create-hash": "^1.2.2", "@types/node": "^16.11.45", "@types/react": "17.0.41", + "@types/uuid": "^8.3.4", "@typescript-eslint/eslint-plugin": "^5.15.0", "@typescript-eslint/parser": "^5.15.0", "babel-plugin-import": "^1.13.3", diff --git a/src/components/pages/generators/uuid/Configuration.tsx b/src/components/pages/generators/uuid/Configuration.tsx new file mode 100644 index 0000000..7bc48d1 --- /dev/null +++ b/src/components/pages/generators/uuid/Configuration.tsx @@ -0,0 +1,87 @@ +import { FontDownload, HorizontalRule, Tune } from "@mui/icons-material"; +import { FormControl, FormControlLabel, MenuItem, Select, Switch } from "@mui/material"; +import { SwitchBaseProps } from "@mui/material/internal/SwitchBase"; +import { SelectInputProps } from "@mui/material/Select/SelectInput"; +import { css } from "@mui/material/styles"; +import { memo } from "react"; + +import { Configurations } from "@/components/common"; + +const uuidVersions = [1, 4] as const; +export type UuidVersion = typeof uuidVersions[number]; +export const isUuidVersion = (x: number): x is UuidVersion => + uuidVersions.includes(x as UuidVersion); + +type SwitchChecked = NonNullable; +type OnSwitchChange = NonNullable; +type OnSelectChange = NonNullable["onChange"]>; + +type Props = { + hyphens: SwitchChecked; + uppercase: SwitchChecked; + uuidVersion: UuidVersion; + onHyphensChange: OnSwitchChange; + onCaseChange: OnSwitchChange; + onUuidVersionChange: OnSelectChange; +}; + +const select = css` + & .MuiSelect-select:focus { + background-color: transparent; + } +`; + +const StyledComponent = ({ + hyphens, + uppercase, + uuidVersion, + onHyphensChange, + onCaseChange, + onUuidVersionChange, +}: Props) => ( + , + title: "Hyphens", + input: ( + } + /> + ), + }, + { + icon: , + title: "Uppercase", + input: ( + } + /> + ), + }, + { + icon: , + title: "UUID version", + input: ( + + + + ), + }, + ]} + /> +); + +export const Component = memo(StyledComponent); + +export default Component; diff --git a/src/components/pages/generators/uuid/Content.tsx b/src/components/pages/generators/uuid/Content.tsx new file mode 100644 index 0000000..0e6346a --- /dev/null +++ b/src/components/pages/generators/uuid/Content.tsx @@ -0,0 +1,115 @@ +import { Button, Stack, Typography } from "@mui/material"; +import { range } from "fp-ts/NonEmptyArray"; +import { ComponentPropsWithoutRef, memo, useCallback, useState } from "react"; + +import { Main, MainItem, TextField } from "@/components/common"; +import { uuid } from "@/libs/uuid"; + +import Configuration, { isUuidVersion, UuidVersion } from "./Configuration"; + +type TextFieldValue = ComponentPropsWithoutRef["value"]; +type OnTextFieldChange = NonNullable["onChange"]>; +type OnButtonClick = NonNullable["onChange"]>; + +type Props = { + generates: TextFieldValue; + uuids: TextFieldValue; + onGeneratesChange: OnTextFieldChange; + onGenerateClick: OnButtonClick; +} & ComponentPropsWithoutRef; + +const StyledComponent = ({ + hyphens, + uppercase, + uuidVersion, + generates, + uuids, + onHyphensChange, + onCaseChange, + onUuidVersionChange, + onGeneratesChange, + onGenerateClick, +}: Props) => ( +
+ + + + + + + theme.typography.fontSize * 2.2}>× + + + + + + +
+); + +export const Component = memo(StyledComponent); + +const Container = () => { + const [hyphens, setHyphens] = useState(true); + const [uppercase, setUppercase] = useState(false); + const [uuidVersion, setUuidVersion] = useState(4); + const [generates, setGenerates] = useState(1); + const [uuidArray, setUuidArray] = useState([]); + + const onHyphensChange: Props["onHyphensChange"] = useCallback((_e, checked) => { + setHyphens(checked); + }, []); + + const onCaseChange: Props["onCaseChange"] = useCallback((_e, checked) => { + setUppercase(checked); + }, []); + + const onUuidVersionChange: Props["onUuidVersionChange"] = useCallback(({ target: { value } }) => { + const newUuidVersion = Number(value); + + if (isUuidVersion(newUuidVersion)) { + setUuidVersion(newUuidVersion); + } + }, []); + + const onGeneratesChange: Props["onGeneratesChange"] = useCallback( + ({ currentTarget: { value } }) => { + const newGenerates = Number(value); + + if (newGenerates >= 1 && newGenerates <= 1000) { + setGenerates(newGenerates); + } + }, + [] + ); + + const onGenerateClick: Props["onGenerateClick"] = useCallback(() => { + const newUuids = range(1, generates).map(_ => uuid(uuidVersion, hyphens, uppercase)); + setUuidArray([...uuidArray, ...newUuids]); + }, [hyphens, uppercase, uuidVersion, generates, uuidArray]); + + const uuids = uuidArray.join("\n"); + + return ( + + ); +}; + +export default Container; diff --git a/src/components/pages/generators/uuid/index.ts b/src/components/pages/generators/uuid/index.ts new file mode 100644 index 0000000..ae1b8af --- /dev/null +++ b/src/components/pages/generators/uuid/index.ts @@ -0,0 +1,3 @@ +import Content from "./Content"; + +export { Content }; diff --git a/src/data/tools.tsx b/src/data/tools.tsx index 09ee012..351f4ff 100644 --- a/src/data/tools.tsx +++ b/src/data/tools.tsx @@ -121,8 +121,8 @@ const toolGroups = [ longTitle: "UUID Generator", description: "Generate UUIDs version 1 and 4", keywords: "guid uuid1 uuid4 generator", - href: pagesPath.$url(), - disabled: true, + href: pagesPath.generators.uuid.$url(), + disabled: false, }, ], }, diff --git a/src/libs/$path.ts b/src/libs/$path.ts index 5556bc5..e975e5f 100644 --- a/src/libs/$path.ts +++ b/src/libs/$path.ts @@ -54,6 +54,12 @@ export const pagesPath = { hash: url?.hash, }), }, + uuid: { + $url: (url?: { hash?: string }) => ({ + pathname: "/generators/uuid" as const, + hash: url?.hash, + }), + }, }, search: { $url: (url?: { hash?: string }) => ({ pathname: "/search" as const, hash: url?.hash }), diff --git a/src/libs/uuid.ts b/src/libs/uuid.ts new file mode 100644 index 0000000..d3365d4 --- /dev/null +++ b/src/libs/uuid.ts @@ -0,0 +1,15 @@ +import { v1 as uuidv1, v4 as uuidv4 } from "uuid"; + +export const uuid = (version: 1 | 4, hyphens: boolean, uppercase: boolean) => { + let generated = version === 1 ? uuidv1() : uuidv4(); + + if (!hyphens) { + generated = generated.replaceAll("-", ""); + } + + if (uppercase) { + generated = generated.toUpperCase(); + } + + return generated; +}; diff --git a/src/pages/generators/uuid.tsx b/src/pages/generators/uuid.tsx new file mode 100644 index 0000000..ae4983e --- /dev/null +++ b/src/pages/generators/uuid.tsx @@ -0,0 +1,7 @@ +import type { NextPage } from "next"; + +import { Content } from "@/components/pages/generators/uuid"; + +const Page: NextPage = Content; + +export default Page; diff --git a/yarn.lock b/yarn.lock index c213ca0..6cc962c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -459,6 +459,11 @@ resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz" integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== +"@types/uuid@^8.3.4": + version "8.3.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== + "@typescript-eslint/eslint-plugin@^5.15.0": version "5.15.0" resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.15.0.tgz" @@ -2655,6 +2660,11 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1: resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + v8-compile-cache@^2.0.3: version "2.3.0" resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz"