feature: base64 tool page
This commit is contained in:
parent
969ed948b5
commit
ff0d05542d
@ -1,10 +1,77 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import Switcher from "@/components/switcher";
|
||||||
|
import Notice from "@/components/tools/notice";
|
||||||
|
import base64ToHex from "@/lib/base64ToHex";
|
||||||
|
import copyToClipboard from "@/lib/copy";
|
||||||
|
import normalizeHex from "@/lib/normalizeHex";
|
||||||
|
import { validBase64 } from "@/lib/onesearch/baseCheck";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { utoa, atou } from "unicode-encode";
|
||||||
|
|
||||||
export default function Base64() {
|
export default function Base64() {
|
||||||
const t = useTranslations("tools");
|
const t = useTranslations("tools");
|
||||||
|
const [mode, setMode] = useState("Encode");
|
||||||
|
const [message, setMessage] = useState("");
|
||||||
|
const [messageResult, setMessageResult] = useState("");
|
||||||
|
const [isHex, setHex] = useState(false);
|
||||||
|
const [info, setInfo] = useState("");
|
||||||
|
const [type, setType] = useState("");
|
||||||
|
useEffect(() => {
|
||||||
|
setHex(false);
|
||||||
|
if (mode == "Encode") {
|
||||||
|
setMessageResult(utoa(message));
|
||||||
|
} else {
|
||||||
|
if (validBase64(message)) {
|
||||||
|
try {
|
||||||
|
setMessageResult(atou(message));
|
||||||
|
} catch (e) {
|
||||||
|
setMessageResult(normalizeHex(base64ToHex(message)));
|
||||||
|
setHex(true);
|
||||||
|
}
|
||||||
|
} else if (message.trim() !== "") {
|
||||||
|
setMessageResult("Invalid Base64");
|
||||||
|
} else {
|
||||||
|
setMessageResult("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-semibold">{t("base64.title")}</h1>
|
<h1 className="text-3xl font-semibold">{t("base64.title")}</h1>
|
||||||
|
<Switcher items={["Encode", "Decode"]} selected={mode} setSelected={setMode} class="mt-4" />
|
||||||
|
<textarea
|
||||||
|
value={message}
|
||||||
|
onChange={(e) => setMessage(e.target.value)}
|
||||||
|
className="w-full h-80 mt-4 p-4 rounded-lg bg-zinc-100 dark:bg-zinc-800 resize-none outline-none duration-200 transition-colors-opacity border-2 border-transparent focus:border-zinc-600 dark:focus:border-zinc-300"
|
||||||
|
/>
|
||||||
|
<div className="w-full h-12 mt-4">
|
||||||
|
<span className="w-fit text-2xl font-bold leading-10">Result:</span>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
copyToClipboard(messageResult);
|
||||||
|
setType("info");
|
||||||
|
setInfo("Copied");
|
||||||
|
setTimeout(() => {
|
||||||
|
setInfo("");
|
||||||
|
setType("");
|
||||||
|
}, 3000);
|
||||||
|
}}
|
||||||
|
className="absolute right-0 w-fit h-10 rounded-md leading-10 bg-zinc-100 dark:bg-zinc-800 hover:bg-zinc-300 hover:dark:bg-zinc-700 px-5 z-10 cursor-pointer duration-300"
|
||||||
|
>
|
||||||
|
Copy
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`empty:py-0 mt-6 w-full h-fit rounded-md leading-10 bg-zinc-100 dark:bg-zinc-800 py-2 px-5 z-10 cursor-pointer duration-100 break-all ${
|
||||||
|
isHex ? "font-mono" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{messageResult.length > 0 ? messageResult : "Waiting for input..."}
|
||||||
|
</div>
|
||||||
|
<Notice type={type} info={info} class="mt-4" />
|
||||||
|
<Notice type="info" info="HI." class="mt-4" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
31
components/switcher.tsx
Normal file
31
components/switcher.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
|
|
||||||
|
export default function Switcher(props: { items: string[]; selected: string, setSelected: Function, class?: string }) {
|
||||||
|
const selectedRef = useRef(null);
|
||||||
|
const [selectedCoordinate, setSelectedCoordinate] = useState(0);
|
||||||
|
const [selectedWidth, setSelectedWidth] = useState(0);
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedRef.current){
|
||||||
|
setSelectedCoordinate((selectedRef.current as HTMLElement)?.offsetLeft);
|
||||||
|
setSelectedWidth((selectedRef.current as HTMLElement)?.getBoundingClientRect().width);
|
||||||
|
}
|
||||||
|
}, [props.selected]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`relative w-fit h-12 px-1 flex rounded-lg bg-zinc-100 dark:bg-zinc-800 z-0 ${props.class}`}>
|
||||||
|
{props.items.map((item, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="relative mt-[0.375rem] rounded-md w-fit h-9 leading-9 px-4 mx-1 z-20 cursor-pointer duration-100"
|
||||||
|
ref={item == props.selected ? selectedRef : null}
|
||||||
|
onClick={() => props.setSelected(item)}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className="absolute mt-[0.375rem] rounded-md h-9 bg-zinc-300 dark:bg-zinc-600 z-10 duration-250 ease-[cubic-bezier(.15,.16,.2,1.2)]" style={{ left: selectedCoordinate, width: selectedWidth }}></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
32
components/tools/notice.tsx
Normal file
32
components/tools/notice.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
|
import { Icon } from "@iconify-icon/react";
|
||||||
|
|
||||||
|
const typeToColor: Record<string, string> = {
|
||||||
|
success: "bg-green-500",
|
||||||
|
info: "bg-blue-500",
|
||||||
|
warning: "bg-orange-500",
|
||||||
|
error: "bg-red-500"
|
||||||
|
};
|
||||||
|
|
||||||
|
const typeToIcon: Record<string, string> = {
|
||||||
|
success: "material-symbols:check-circle",
|
||||||
|
info: "material-symbols:info",
|
||||||
|
warning: "material-symbols:warning",
|
||||||
|
error: "material-symbols:error"
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Notice(props: { type: string; info: string; class?: string }) {
|
||||||
|
if (props.type && props.info)
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`relative ${props.class} ${
|
||||||
|
typeToColor[props.type]
|
||||||
|
} rounded-md w-full min-h-9 h-fit empty:px-0 px-4 mx-1 z-20 cursor-pointer duration-100`}
|
||||||
|
>
|
||||||
|
<Icon className="text-2xl mt-3" icon={typeToIcon[props.type]} />
|
||||||
|
<span className="">{props.info}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
15
lib/base64ToHex.ts
Normal file
15
lib/base64ToHex.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* Converts a base64 string to a hexadecimal string.
|
||||||
|
*
|
||||||
|
* @param {string} base64String - The base64 string to convert.
|
||||||
|
* @return {string} The hexadecimal representation of the base64 string.
|
||||||
|
*/
|
||||||
|
export default function base64ToHex(base64String: string): string {
|
||||||
|
const raw = atob(base64String);
|
||||||
|
let result = "";
|
||||||
|
for (let i = 0; i < raw.length; i++) {
|
||||||
|
const hex = raw.charCodeAt(i).toString(16);
|
||||||
|
result += hex.length === 2 ? hex : "0" + hex;
|
||||||
|
}
|
||||||
|
return result.toUpperCase();
|
||||||
|
}
|
16
lib/normalizeHex.ts
Normal file
16
lib/normalizeHex.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* A description of the entire function.
|
||||||
|
*
|
||||||
|
* @param {string} hexString - The input hexadecimal string to normalize.
|
||||||
|
* @return {string} The normalized hexadecimal string.
|
||||||
|
*/
|
||||||
|
export default function normalizeHex(hexString: string): string {
|
||||||
|
const chunkSize = 4;
|
||||||
|
const chunks: string[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < hexString.length; i += chunkSize) {
|
||||||
|
chunks.push(hexString.substr(i, chunkSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunks.join(' ');
|
||||||
|
}
|
@ -24,10 +24,12 @@
|
|||||||
"search-engine-autocomplete": "^0.4.3",
|
"search-engine-autocomplete": "^0.4.3",
|
||||||
"tailwind-merge": "^2.3.0",
|
"tailwind-merge": "^2.3.0",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
|
"unicode-encode": "^1.4.2",
|
||||||
"valid-url": "^1.0.9",
|
"valid-url": "^1.0.9",
|
||||||
"validate-color": "^2.2.4"
|
"validate-color": "^2.2.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@iconify-icon/react": "^2.1.0",
|
||||||
"@jest/globals": "^29.7.0",
|
"@jest/globals": "^29.7.0",
|
||||||
"@testing-library/jest-dom": "^6.4.2",
|
"@testing-library/jest-dom": "^6.4.2",
|
||||||
"@testing-library/react": "^14.3.1",
|
"@testing-library/react": "^14.3.1",
|
||||||
|
@ -47,6 +47,9 @@ dependencies:
|
|||||||
ts-node:
|
ts-node:
|
||||||
specifier: ^10.9.2
|
specifier: ^10.9.2
|
||||||
version: 10.9.2(@types/node@20.12.7)(typescript@5.4.5)
|
version: 10.9.2(@types/node@20.12.7)(typescript@5.4.5)
|
||||||
|
unicode-encode:
|
||||||
|
specifier: ^1.4.2
|
||||||
|
version: 1.4.2
|
||||||
valid-url:
|
valid-url:
|
||||||
specifier: ^1.0.9
|
specifier: ^1.0.9
|
||||||
version: 1.0.9
|
version: 1.0.9
|
||||||
@ -55,6 +58,9 @@ dependencies:
|
|||||||
version: 2.2.4
|
version: 2.2.4
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@iconify-icon/react':
|
||||||
|
specifier: ^2.1.0
|
||||||
|
version: 2.1.0(react@18.3.0)
|
||||||
'@jest/globals':
|
'@jest/globals':
|
||||||
specifier: ^29.7.0
|
specifier: ^29.7.0
|
||||||
version: 29.7.0
|
version: 29.7.0
|
||||||
@ -516,6 +522,19 @@ packages:
|
|||||||
tslib: 2.6.2
|
tslib: 2.6.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@iconify-icon/react@2.1.0(react@18.3.0):
|
||||||
|
resolution: {integrity: sha512-OuEsW5Y474rg3WlseLFQ0uuJjnyk1DhLN1Ire5JGjF4sF8/rNxGJDLSItEogRcKuUbL+zzuoBsaTUVVInuixRA==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16'
|
||||||
|
dependencies:
|
||||||
|
iconify-icon: 2.1.0
|
||||||
|
react: 18.3.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@iconify/types@2.0.0:
|
||||||
|
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@internationalized/date@3.5.2:
|
/@internationalized/date@3.5.2:
|
||||||
resolution: {integrity: sha512-vo1yOMUt2hzp63IutEaTUxROdvQg1qlMRsbCvbay2AK2Gai7wIgCyK5weEX3nHkiLgo4qCXHijFNC/ILhlRpOQ==}
|
resolution: {integrity: sha512-vo1yOMUt2hzp63IutEaTUxROdvQg1qlMRsbCvbay2AK2Gai7wIgCyK5weEX3nHkiLgo4qCXHijFNC/ILhlRpOQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -3720,6 +3739,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/atob@2.1.2:
|
||||||
|
resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==}
|
||||||
|
engines: {node: '>= 4.5.0'}
|
||||||
|
hasBin: true
|
||||||
|
dev: false
|
||||||
|
|
||||||
/atomic-sleep@1.0.0:
|
/atomic-sleep@1.0.0:
|
||||||
resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
|
resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
|
||||||
engines: {node: '>=8.0.0'}
|
engines: {node: '>=8.0.0'}
|
||||||
@ -3872,6 +3897,12 @@ packages:
|
|||||||
node-int64: 0.4.0
|
node-int64: 0.4.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/btoa@1.2.1:
|
||||||
|
resolution: {integrity: sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==}
|
||||||
|
engines: {node: '>= 0.4.0'}
|
||||||
|
hasBin: true
|
||||||
|
dev: false
|
||||||
|
|
||||||
/buffer-from@1.1.2:
|
/buffer-from@1.1.2:
|
||||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -4664,6 +4695,12 @@ packages:
|
|||||||
engines: {node: '>=10.17.0'}
|
engines: {node: '>=10.17.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/iconify-icon@2.1.0:
|
||||||
|
resolution: {integrity: sha512-lto4XU3bwTQnb+D/CsJ4dWAo0aDe+uPMxEtxyOodw9l7R9QnJUUab3GCehlw2M8mDHdeUu/ufx8PvRQiJphhXg==}
|
||||||
|
dependencies:
|
||||||
|
'@iconify/types': 2.0.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
/iconv-lite@0.6.3:
|
/iconv-lite@0.6.3:
|
||||||
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@ -6730,6 +6767,13 @@ packages:
|
|||||||
/undici-types@5.26.5:
|
/undici-types@5.26.5:
|
||||||
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
|
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
|
||||||
|
|
||||||
|
/unicode-encode@1.4.2:
|
||||||
|
resolution: {integrity: sha512-xwGGPy/masrMFrf7c+jlGj0SOO7Z9LpC6wAZbAaAM6Dyz/Azm1iuHDpWHDPot8IDpmhN9pGhUqZkE+5ykOKAjg==}
|
||||||
|
dependencies:
|
||||||
|
atob: 2.1.2
|
||||||
|
btoa: 1.2.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/universalify@0.2.0:
|
/universalify@0.2.0:
|
||||||
resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
|
resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
|
||||||
engines: {node: '>= 4.0.0'}
|
engines: {node: '>= 4.0.0'}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import type { Config } from "tailwindcss";
|
import type { Config } from "tailwindcss";
|
||||||
import {nextui} from "@nextui-org/react";
|
import { nextui } from "@nextui-org/react";
|
||||||
|
|
||||||
const config: Config = {
|
const config: Config = {
|
||||||
content: [
|
content: [
|
||||||
|
Loading…
Reference in New Issue
Block a user