mirror of
https://github.com/CorentinTh/it-tools.git
synced 2026-10-22 05:41:13 +00:00
Merge 606a41ddd1 into d505845f91
This commit is contained in:
commit
4ac502a42e
6 changed files with 247 additions and 1 deletions
|
|
@ -75,6 +75,10 @@ tools:
|
|||
title: Password strength analyser
|
||||
description: Discover the strength of your password with this client-side-only password strength analyser and crack time estimation tool.
|
||||
|
||||
data-storage-converter:
|
||||
title: Data storage converter
|
||||
description: Convert data storage units (bytes, kilobytes, megabytes, gigabytes, terabytes, petabytes, and their binary IEC counterparts) in real time.
|
||||
|
||||
chronometer:
|
||||
title: Chronometer
|
||||
description: Monitor the duration of a thing. Basically a chronometer with simple chronometer features.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import { bitsToUnit, formatPrecision, unitToBits } from './data-storage-converter.models';
|
||||
|
||||
describe('data-storage-converter models', () => {
|
||||
it('converts bits and bytes', () => {
|
||||
expect(unitToBits(1, 'byte')).toBe(8);
|
||||
expect(bitsToUnit(8, 'byte')).toBe(1);
|
||||
expect(unitToBits(1, 'bit')).toBe(1);
|
||||
});
|
||||
|
||||
it('converts decimal (SI) units', () => {
|
||||
expect(unitToBits(1, 'kilobyte')).toBe(8000);
|
||||
expect(bitsToUnit(8e9, 'gigabyte')).toBe(1);
|
||||
expect(bitsToUnit(unitToBits(1, 'petabyte'), 'gigabyte')).toBe(1e6);
|
||||
expect(bitsToUnit(unitToBits(1, 'megabit'), 'kilobit')).toBe(1000);
|
||||
});
|
||||
|
||||
it('converts binary (IEC) units', () => {
|
||||
expect(unitToBits(1, 'kibibyte')).toBe(1024 * 8);
|
||||
expect(bitsToUnit(unitToBits(1, 'gibibyte'), 'megabyte')).toBeCloseTo(1073.741824, 6);
|
||||
expect(bitsToUnit(unitToBits(1, 'pebibyte'), 'terabyte')).toBeCloseTo(1125.899906842624, 6);
|
||||
});
|
||||
|
||||
it('round-trips without loss of precision', () => {
|
||||
for (const unit of ['bit', 'byte', 'megabyte', 'gibibyte', 'terabit', 'pebibyte'] as const) {
|
||||
const bits = unitToBits(123.456, unit);
|
||||
expect(bitsToUnit(bits, unit)).toBeCloseTo(123.456, 6);
|
||||
}
|
||||
});
|
||||
|
||||
it('formats precision by trimming trailing zeros', () => {
|
||||
expect(formatPrecision(0)).toBe('0');
|
||||
expect(formatPrecision(1024)).toBe('1024');
|
||||
expect(formatPrecision(1073.741824)).toBe('1073.741824');
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
export type StorageUnit
|
||||
= | 'bit'
|
||||
| 'byte'
|
||||
| 'kilobit'
|
||||
| 'kilobyte'
|
||||
| 'megabit'
|
||||
| 'megabyte'
|
||||
| 'gigabit'
|
||||
| 'gigabyte'
|
||||
| 'terabit'
|
||||
| 'terabyte'
|
||||
| 'petabit'
|
||||
| 'petabyte'
|
||||
| 'kibibit'
|
||||
| 'kibibyte'
|
||||
| 'mebibit'
|
||||
| 'mebibyte'
|
||||
| 'gibibit'
|
||||
| 'gibibyte'
|
||||
| 'tebibit'
|
||||
| 'tebibyte'
|
||||
| 'pebibit'
|
||||
| 'pebibyte';
|
||||
|
||||
const BITS_PER_BYTE = 8;
|
||||
|
||||
// Decimal (SI) prefixes: powers of 1000
|
||||
const SI_PREFIXES: Partial<Record<StorageUnit, number>> = {
|
||||
kilobit: 1e3,
|
||||
kilobyte: 1e3,
|
||||
megabit: 1e6,
|
||||
megabyte: 1e6,
|
||||
gigabit: 1e9,
|
||||
gigabyte: 1e9,
|
||||
terabit: 1e12,
|
||||
terabyte: 1e12,
|
||||
petabit: 1e15,
|
||||
petabyte: 1e15,
|
||||
};
|
||||
|
||||
// Binary (IEC) prefixes: powers of 1024
|
||||
const IEC_PREFIXES: Partial<Record<StorageUnit, number>> = {
|
||||
kibibit: 1024,
|
||||
kibibyte: 1024,
|
||||
mebibit: 1024 ** 2,
|
||||
mebibyte: 1024 ** 2,
|
||||
gibibit: 1024 ** 3,
|
||||
gibibyte: 1024 ** 3,
|
||||
tebibit: 1024 ** 4,
|
||||
tebibyte: 1024 ** 4,
|
||||
pebibit: 1024 ** 5,
|
||||
pebibyte: 1024 ** 5,
|
||||
};
|
||||
|
||||
export function unitToBits(value: number, unit: StorageUnit): number {
|
||||
let bits: number;
|
||||
|
||||
switch (unit) {
|
||||
case 'bit':
|
||||
bits = value;
|
||||
break;
|
||||
case 'byte':
|
||||
bits = value * BITS_PER_BYTE;
|
||||
break;
|
||||
default: {
|
||||
const factor = SI_PREFIXES[unit] ?? IEC_PREFIXES[unit] ?? 1;
|
||||
bits = unit.endsWith('bit') ? value * factor : value * factor * BITS_PER_BYTE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return bits;
|
||||
}
|
||||
|
||||
export function bitsToUnit(bits: number, unit: StorageUnit): number {
|
||||
let value: number;
|
||||
|
||||
switch (unit) {
|
||||
case 'bit':
|
||||
value = bits;
|
||||
break;
|
||||
case 'byte':
|
||||
value = bits / BITS_PER_BYTE;
|
||||
break;
|
||||
default: {
|
||||
const factor = SI_PREFIXES[unit] ?? IEC_PREFIXES[unit] ?? 1;
|
||||
value = unit.endsWith('bit') ? bits / factor : bits / factor / BITS_PER_BYTE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
export function formatPrecision(value: number): string {
|
||||
if (Number.isNaN(value) || !Number.isFinite(value)) {
|
||||
return String(value);
|
||||
}
|
||||
|
||||
if (value === 0) {
|
||||
return '0';
|
||||
}
|
||||
|
||||
const abs = Math.abs(value);
|
||||
|
||||
// Use full double precision (up to 12 decimal places), trimming trailing zeros
|
||||
const digits = Math.max(0, 12 - Math.max(0, Math.floor(Math.log10(abs))) - 1);
|
||||
return String(Number(value.toFixed(Math.min(digits, 12))));
|
||||
}
|
||||
61
src/tools/data-storage-converter/data-storage-converter.vue
Normal file
61
src/tools/data-storage-converter/data-storage-converter.vue
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<script setup lang="ts">
|
||||
import _ from 'lodash';
|
||||
import { unitToBits, bitsToUnit, formatPrecision, type StorageUnit } from './data-storage-converter.models';
|
||||
|
||||
const units = reactive<Record<StorageUnit, { title: string; ref: number }>>({
|
||||
bit: { title: 'Bits', ref: 0 },
|
||||
kilobit: { title: 'Kilobits', ref: 0 },
|
||||
kilobyte: { title: 'Kilobytes', ref: 0 },
|
||||
kibibit: { title: 'Kibibits', ref: 0 },
|
||||
kibibyte: { title: 'Kibibytes', ref: 0 },
|
||||
megabit: { title: 'Megabits', ref: 0 },
|
||||
megabyte: { title: 'Megabytes', ref: 0 },
|
||||
mebibit: { title: 'Mebibits', ref: 0 },
|
||||
mebibyte: { title: 'Mebibytes', ref: 0 },
|
||||
gigabit: { title: 'Gigabits', ref: 0 },
|
||||
gigabyte: { title: 'Gigabytes', ref: 0 },
|
||||
gibibit: { title: 'Gibibits', ref: 0 },
|
||||
gibibyte: { title: 'Gibibytes', ref: 0 },
|
||||
terabit: { title: 'Terabits', ref: 0 },
|
||||
terabyte: { title: 'Terabytes', ref: 0 },
|
||||
tebibit: { title: 'Tebibits', ref: 0 },
|
||||
tebibyte: { title: 'Tebibytes', ref: 0 },
|
||||
petabit: { title: 'Petabits', ref: 0 },
|
||||
petabyte: { title: 'Petabytes', ref: 0 },
|
||||
pebibit: { title: 'Pebibits', ref: 0 },
|
||||
pebibyte: { title: 'Pebibytes', ref: 0 },
|
||||
byte: { title: 'Bytes', ref: 0 },
|
||||
});
|
||||
|
||||
function update(key: StorageUnit) {
|
||||
const { ref: value } = units[key];
|
||||
|
||||
const bits = unitToBits(value ?? 0, key);
|
||||
|
||||
_.chain(units)
|
||||
.omit(key)
|
||||
.forEach((_, index) => {
|
||||
units[index as StorageUnit].ref = bitsToUnit(bits, index as StorageUnit);
|
||||
})
|
||||
.value();
|
||||
}
|
||||
|
||||
update('megabyte');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<n-input-group v-for="[key, { title }] in Object.entries(units) as [StorageUnit, { title: string }][]" :key="key" mb-3 w-full>
|
||||
<n-input-group-label style="width: 120px">
|
||||
{{ title }}
|
||||
</n-input-group-label>
|
||||
|
||||
<n-input-number
|
||||
v-model:value="units[key].ref"
|
||||
style="flex: 1"
|
||||
:placeholder="`Value in ${title.toLowerCase()}`"
|
||||
@update:value="() => update(key)"
|
||||
/>
|
||||
</n-input-group>
|
||||
</div>
|
||||
</template>
|
||||
35
src/tools/data-storage-converter/index.ts
Normal file
35
src/tools/data-storage-converter/index.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { Database } from '@vicons/tabler';
|
||||
import { defineTool } from '../tool';
|
||||
import { translate } from '@/plugins/i18n.plugin';
|
||||
|
||||
export const tool = defineTool({
|
||||
name: translate('tools.data-storage-converter.title'),
|
||||
path: '/data-storage-converter',
|
||||
description: translate('tools.data-storage-converter.description'),
|
||||
keywords: [
|
||||
'data',
|
||||
'storage',
|
||||
'converter',
|
||||
'bytes',
|
||||
'bits',
|
||||
'kilobyte',
|
||||
'megabyte',
|
||||
'gigabyte',
|
||||
'terabyte',
|
||||
'petabyte',
|
||||
'kibibyte',
|
||||
'mebibyte',
|
||||
'gibibyte',
|
||||
'tebibyte',
|
||||
'pebibyte',
|
||||
'bandwidth',
|
||||
'transfer',
|
||||
'speed',
|
||||
'bps',
|
||||
'kbps',
|
||||
'mbps',
|
||||
'gbps',
|
||||
],
|
||||
component: () => import('./data-storage-converter.vue'),
|
||||
icon: Database,
|
||||
});
|
||||
|
|
@ -78,6 +78,7 @@ import { tool as romanNumeralConverter } from './roman-numeral-converter';
|
|||
import { tool as sqlPrettify } from './sql-prettify';
|
||||
import { tool as svgPlaceholderGenerator } from './svg-placeholder-generator';
|
||||
import { tool as temperatureConverter } from './temperature-converter';
|
||||
import { tool as dataStorageConverter } from './data-storage-converter';
|
||||
import { tool as textStatistics } from './text-statistics';
|
||||
import { tool as tokenGenerator } from './token-generator';
|
||||
import type { ToolCategory } from './tools.types';
|
||||
|
|
@ -172,7 +173,7 @@ export const toolsByCategory: ToolCategory[] = [
|
|||
},
|
||||
{
|
||||
name: 'Measurement',
|
||||
components: [chronometer, temperatureConverter, benchmarkBuilder],
|
||||
components: [chronometer, temperatureConverter, benchmarkBuilder, dataStorageConverter],
|
||||
},
|
||||
{
|
||||
name: 'Text',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue