diff --git a/public/locales/en/number.json b/public/locales/en/number.json index bc2cfb4..f07f195 100644 --- a/public/locales/en/number.json +++ b/public/locales/en/number.json @@ -188,5 +188,18 @@ "longDescription": "This calculator helps determine the voltage drop and power loss in a two-conductor electrical cable. It takes into account the cable length, wire gauge (cross-sectional area), material resistivity, and current flow. The tool calculates the round-trip voltage drop, total resistance of the cable, and the power dissipated as heat. This is particularly useful for electrical engineers, electricians, and hobbyists when designing electrical systems to ensure voltage levels remain within acceptable limits at the load.", "shortDescription": "Calculate voltage drop and power loss in electrical cables based on length, material, and current", "title": "Round trip voltage drop in cable" + }, + "byteConverter": { + "description": "Convert data values between different byte units, supporting both SI (decimal) and IEC (binary) systems. It supports multiple lines conversion.", + "title": "Byte Converter", + "shortDescription": "Quickly convert values between byte units", + "inputTitle": "Input Values", + "outputTitle": "Converted Output", + "unit": { + "title": "Conversion Options", + "from": "Select the unit of the input values you are providing (e.g., KB, MB, GiB).", + "to": "Select the unit you want the input values to be converted into.", + "precision": "Number of decimal places to display in the converted output." + } } } diff --git a/src/pages/tools/number/byte-converter/byte-converter.test.ts b/src/pages/tools/number/byte-converter/byte-converter.test.ts new file mode 100644 index 0000000..37d2df7 --- /dev/null +++ b/src/pages/tools/number/byte-converter/byte-converter.test.ts @@ -0,0 +1,77 @@ +import { describe, expect, it } from 'vitest'; +import { byteConverter } from './service'; +import { InitialValuesType, DataUnit } from './types'; + +describe('byteConverter', () => { + const makeOptions = ( + fromUnit: DataUnit, + toUnit: DataUnit, + precision: number + ): InitialValuesType => ({ + fromUnit, + toUnit, + precision + }); + + it('converts bits to bytes', () => { + expect(byteConverter('8', makeOptions('b', 'B', 2))).toBe('1'); + expect(byteConverter('1', makeOptions('b', 'B', 4))).toBe('0.125'); + }); + + it('converts bytes to bits', () => { + expect(byteConverter('1', makeOptions('B', 'b', 0))).toBe('8'); + }); + + it('converts decimal units correctly', () => { + expect(byteConverter('1', makeOptions('KB', 'B', 0))).toBe('1000'); + expect(byteConverter('1', makeOptions('MB', 'KB', 0))).toBe('1000'); + expect(byteConverter('1', makeOptions('GB', 'MB', 0))).toBe('1000'); + }); + + it('converts binary units correctly', () => { + expect(byteConverter('1', makeOptions('KiB', 'B', 0))).toBe('1024'); + expect(byteConverter('1', makeOptions('MiB', 'KiB', 0))).toBe('1024'); + expect(byteConverter('1', makeOptions('GiB', 'MiB', 0))).toBe('1024'); + }); + + it('converts decimal to binary units correctly', () => { + expect(byteConverter('1', makeOptions('GB', 'MiB', 2))).toBe('953.67'); + expect(byteConverter('1', makeOptions('TB', 'GiB', 2))).toBe('931.32'); + }); + + it('converts binary to decimal units correctly', () => { + expect(byteConverter('1', makeOptions('GiB', 'MB', 2))).toBe('1073.74'); + expect(byteConverter('1', makeOptions('TiB', 'GB', 2))).toBe('1099.51'); + }); + + it('respects precision rounding', () => { + expect(byteConverter('1', makeOptions('GB', 'MiB', 0))).toBe('954'); + expect(byteConverter('1', makeOptions('GB', 'MiB', 1))).toBe('953.7'); + expect(byteConverter('1', makeOptions('GB', 'MiB', 3))).toBe('953.674'); + }); + + it('handles zero correctly', () => { + expect(byteConverter('0', makeOptions('GB', 'MB', 2))).toBe('0'); + }); + + it('handles identity conversions', () => { + expect(byteConverter('123', makeOptions('MB', 'MB', 2))).toBe('123'); + expect(byteConverter('1', makeOptions('GiB', 'GiB', 5))).toBe('1'); + }); + + it('handles large values', () => { + expect(byteConverter('1024', makeOptions('GiB', 'TiB', 4))).toBe('1'); + }); + + it('handles multiline input', () => { + const input = '1\n2\n3'; + const result = byteConverter(input, makeOptions('GB', 'MB', 0)); + expect(result).toBe('1000\n2000\n3000'); + }); + + it('ignores empty lines', () => { + const input = '1\n\n3'; + const result = byteConverter(input, makeOptions('GB', 'MB', 0)); + expect(result).toBe('1000\n\n3000'); + }); +}); diff --git a/src/pages/tools/number/byte-converter/index.tsx b/src/pages/tools/number/byte-converter/index.tsx new file mode 100644 index 0000000..d3cfb01 --- /dev/null +++ b/src/pages/tools/number/byte-converter/index.tsx @@ -0,0 +1,167 @@ +import { Box } from '@mui/material'; +import React, { useState } from 'react'; +import ToolContent from '@components/ToolContent'; +import ToolTextInput from '@components/input/ToolTextInput'; +import ToolTextResult from '@components/result/ToolTextResult'; +import { byteConverter } from './service'; +import { CardExampleType } from '@components/examples/ToolExamples'; +import SelectWithDesc from '@components/options/SelectWithDesc'; +import TextFieldWithDesc from '@components/options/TextFieldWithDesc'; +import { ToolComponentProps } from '@tools/defineTool'; +import { useTranslation } from 'react-i18next'; +import { InitialValuesType, DATA_UNITS } from './types'; + +const initialValues: InitialValuesType = { + fromUnit: 'GB', + toUnit: 'KB', + precision: 2 +}; + +export const exampleCards: CardExampleType[] = [ + { + title: 'Convert Gigabytes to Megabytes', + description: + 'This example converts a list of storage values in Gigabytes (GB) into Megabytes (MB) using the SI system. Each line represents a different storage size. The output shows the same values converted to MB with the specified precision.', + sampleText: `1 +2.5 +10 +0.75 +50`, + sampleResult: `1000 +2500 +10000 +750 +50000`, + sampleOptions: { + fromUnit: 'GB', + toUnit: 'MB', + precision: 0 + } + }, + { + title: 'Convert MiB to GiB (IEC System)', + description: + 'This example converts values in Mebibytes (MiB) to Gibibytes (GiB) using the IEC binary system. Each line of input represents a different memory size, and the output shows the conversion rounded to 2 decimal places.', + sampleText: `1024 +2048 +5120 +256 +8192`, + sampleResult: `1 +2 +5 +0.25 +8`, + sampleOptions: { + fromUnit: 'MiB', + toUnit: 'GiB', + precision: 2 + } + }, + { + title: 'Cross-System Conversion: GB to GiB', + description: + 'This example demonstrates a cross-system conversion from SI units (GB) to IEC units (GiB). Since GB and GiB are slightly different, the output shows fractional values with high precision. This is useful when comparing storage reported by operating systems vs manufacturers.', + sampleText: `1 +5 +10 +50 +100`, + sampleResult: `0.931 +4.655 +9.313 +46.566 +93.132`, + sampleOptions: { + fromUnit: 'GB', + toUnit: 'GiB', + precision: 3 + } + }, + { + title: 'Convert Bytes to Bits', + description: + 'This example converts values from Bytes (B) to Bits (b). Since 1 Byte = 8 Bits, each input value is multiplied by 8. The precision is set to 0 because the result is always an integer.', + sampleText: `1 +5 +10 +50 +100`, + sampleResult: `8 +40 +80 +400 +800`, + sampleOptions: { + fromUnit: 'B', + toUnit: 'b', + precision: 0 + } + } +]; + +export default function ByteConverter({ title }: ToolComponentProps) { + const { t } = useTranslation('number'); + const [input, setInput] = useState(''); + const [result, setResult] = useState(''); + + const compute = (optionsValues: InitialValuesType, input: any) => { + setResult(byteConverter(input, optionsValues)); + }; + + return ( + + } + resultComponent={ + + } + getGroups={({ values, updateField }) => [ + { + title: t('byteConverter.unit.title'), + component: ( + + ({ + label: unit, + value: unit + }))} + onChange={(value) => updateField('fromUnit', value)} + description={t('byteConverter.unit.from')} + /> + + ({ + label: unit, + value: unit + }))} + onChange={(value) => updateField('toUnit', value)} + description={t('byteConverter.unit.to')} + /> + + updateField('precision', Number(val))} + type={'number'} + /> + + ) + } + ]} + compute={compute} + /> + ); +} diff --git a/src/pages/tools/number/byte-converter/meta.ts b/src/pages/tools/number/byte-converter/meta.ts new file mode 100644 index 0000000..8df1c8b --- /dev/null +++ b/src/pages/tools/number/byte-converter/meta.ts @@ -0,0 +1,16 @@ +import { defineTool } from '@tools/defineTool'; +import { lazy } from 'react'; + +export const tool = defineTool('number', { + path: 'byte-converter', + icon: 'fluent:calculator-arrow-clockwise-24-regular', + + keywords: ['byte', 'converter'], + component: lazy(() => import('./index')), + i18n: { + name: 'number:byteConverter.title', + description: 'number:byteConverter.description', + shortDescription: 'number:byteConverter.shortDescription', + userTypes: ['generalUsers', 'developers'] + } +}); diff --git a/src/pages/tools/number/byte-converter/service.ts b/src/pages/tools/number/byte-converter/service.ts new file mode 100644 index 0000000..493cde1 --- /dev/null +++ b/src/pages/tools/number/byte-converter/service.ts @@ -0,0 +1,36 @@ +import { DataUnit, InitialValuesType, UNIT_MAP } from './types'; + +function compute( + value: number, + fromUnit: DataUnit, + toUnit: DataUnit, + precision: number +): number { + if (precision < 0 || value < 0) return 0; + const bytes = value * UNIT_MAP[fromUnit]; + const result = bytes / UNIT_MAP[toUnit]; + return Number(result.toFixed(precision)); +} + +export function byteConverter(input: string, options: InitialValuesType) { + if (!input) return ''; + + const values = input.split('\n'); + const result: string[] = []; + + const { fromUnit, toUnit, precision } = options; + + for (const elem of values) { + const trimmed = elem.trim(); + + if (!trimmed) { + result.push(''); + continue; + } + + const computed = compute(Number(trimmed), fromUnit, toUnit, precision); + result.push(String(computed)); + } + + return result.join('\n'); +} diff --git a/src/pages/tools/number/byte-converter/types.ts b/src/pages/tools/number/byte-converter/types.ts new file mode 100644 index 0000000..3b85903 --- /dev/null +++ b/src/pages/tools/number/byte-converter/types.ts @@ -0,0 +1,44 @@ +type SIUnit = 'b' | 'B' | 'KB' | 'MB' | 'GB' | 'TB' | 'PB' | 'EB'; +type IECUnit = 'b' | 'B' | 'KiB' | 'MiB' | 'GiB' | 'TiB' | 'PiB' | 'EiB'; + +export type DataUnit = SIUnit | IECUnit; + +const SI_UNITS: SIUnit[] = ['b', 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB']; +const IEC_UNITS: IECUnit[] = [ + 'b', + 'B', + 'KiB', + 'MiB', + 'GiB', + 'TiB', + 'PiB', + 'EiB' +]; + +export const DATA_UNITS: DataUnit[] = [ + ...SI_UNITS, + ...IEC_UNITS.filter((unit) => !SI_UNITS.includes(unit as SIUnit)) +]; + +export const UNIT_MAP: Record = { + b: 1 / 8, + B: 1, + KB: 1e3, + MB: 1e6, + GB: 1e9, + TB: 1e12, + PB: 1e15, + EB: 1e18, + KiB: 1024, + MiB: 1024 ** 2, + GiB: 1024 ** 3, + TiB: 1024 ** 4, + PiB: 1024 ** 5, + EiB: 1024 ** 6 +}; + +export type InitialValuesType = { + fromUnit: DataUnit; + toUnit: DataUnit; + precision: number; +}; diff --git a/src/pages/tools/number/index.ts b/src/pages/tools/number/index.ts index fa4facc..90d6f6f 100644 --- a/src/pages/tools/number/index.ts +++ b/src/pages/tools/number/index.ts @@ -3,12 +3,15 @@ import { tool as numberRandomNumberGenerator } from './random-number-generator/m import { tool as numberSum } from './sum/meta'; import { tool as numberGenerate } from './generate/meta'; import { tool as numberArithmeticSequence } from './arithmetic-sequence/meta'; +import { tool as numberByteConverter } from './byte-converter/meta'; import { tools as genericCalcTools } from './generic-calc/meta'; + export const numberTools = [ numberSum, numberGenerate, numberArithmeticSequence, numberRandomPortGenerator, numberRandomNumberGenerator, + numberByteConverter, ...genericCalcTools ];