diff --git a/.eslintrc-auto-import.json b/.eslintrc-auto-import.json index 4084d922..d4fd5aac 100644 --- a/.eslintrc-auto-import.json +++ b/.eslintrc-auto-import.json @@ -286,6 +286,9 @@ "watchTriggerable": true, "watchWithFilter": true, "whenever": true, - "toValue": true + "toValue": true, + "injectLocal": true, + "provideLocal": true, + "useClipboardItems": true } } diff --git a/auto-imports.d.ts b/auto-imports.d.ts index 186963f1..35bda113 100644 --- a/auto-imports.d.ts +++ b/auto-imports.d.ts @@ -36,6 +36,7 @@ declare global { const h: typeof import('vue')['h'] const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch'] const inject: typeof import('vue')['inject'] + const injectLocal: typeof import('@vueuse/core')['injectLocal'] const isDefined: typeof import('@vueuse/core')['isDefined'] const isProxy: typeof import('vue')['isProxy'] const isReactive: typeof import('vue')['isReactive'] @@ -65,6 +66,7 @@ declare global { const onUpdated: typeof import('vue')['onUpdated'] const pausableWatch: typeof import('@vueuse/core')['pausableWatch'] const provide: typeof import('vue')['provide'] + const provideLocal: typeof import('@vueuse/core')['provideLocal'] const reactify: typeof import('@vueuse/core')['reactify'] const reactifyObject: typeof import('@vueuse/core')['reactifyObject'] const reactive: typeof import('vue')['reactive'] @@ -128,6 +130,7 @@ declare global { const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation'] const useCached: typeof import('@vueuse/core')['useCached'] const useClipboard: typeof import('@vueuse/core')['useClipboard'] + const useClipboardItems: typeof import('@vueuse/core')['useClipboardItems'] const useCloned: typeof import('@vueuse/core')['useCloned'] const useColorMode: typeof import('@vueuse/core')['useColorMode'] const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog'] @@ -326,6 +329,7 @@ declare module 'vue' { readonly h: UnwrapRef readonly ignorableWatch: UnwrapRef readonly inject: UnwrapRef + readonly injectLocal: UnwrapRef readonly isDefined: UnwrapRef readonly isProxy: UnwrapRef readonly isReactive: UnwrapRef @@ -355,6 +359,7 @@ declare module 'vue' { readonly onUpdated: UnwrapRef readonly pausableWatch: UnwrapRef readonly provide: UnwrapRef + readonly provideLocal: UnwrapRef readonly reactify: UnwrapRef readonly reactifyObject: UnwrapRef readonly reactive: UnwrapRef @@ -418,6 +423,7 @@ declare module 'vue' { readonly useBrowserLocation: UnwrapRef readonly useCached: UnwrapRef readonly useClipboard: UnwrapRef + readonly useClipboardItems: UnwrapRef readonly useCloned: UnwrapRef readonly useColorMode: UnwrapRef readonly useConfirmDialog: UnwrapRef @@ -610,6 +616,7 @@ declare module '@vue/runtime-core' { readonly h: UnwrapRef readonly ignorableWatch: UnwrapRef readonly inject: UnwrapRef + readonly injectLocal: UnwrapRef readonly isDefined: UnwrapRef readonly isProxy: UnwrapRef readonly isReactive: UnwrapRef @@ -639,6 +646,7 @@ declare module '@vue/runtime-core' { readonly onUpdated: UnwrapRef readonly pausableWatch: UnwrapRef readonly provide: UnwrapRef + readonly provideLocal: UnwrapRef readonly reactify: UnwrapRef readonly reactifyObject: UnwrapRef readonly reactive: UnwrapRef @@ -702,6 +710,7 @@ declare module '@vue/runtime-core' { readonly useBrowserLocation: UnwrapRef readonly useCached: UnwrapRef readonly useClipboard: UnwrapRef + readonly useClipboardItems: UnwrapRef readonly useCloned: UnwrapRef readonly useColorMode: UnwrapRef readonly useConfirmDialog: UnwrapRef diff --git a/components.d.ts b/components.d.ts index 3e65c3cc..e916815f 100644 --- a/components.d.ts +++ b/components.d.ts @@ -64,6 +64,8 @@ declare module '@vue/runtime-core' { 'CTextCopyable.demo': typeof import('./src/ui/c-text-copyable/c-text-copyable.demo.vue')['default'] CTooltip: typeof import('./src/ui/c-tooltip/c-tooltip.vue')['default'] 'CTooltip.demo': typeof import('./src/ui/c-tooltip/c-tooltip.demo.vue')['default'] + DataStorageUnitConverter: typeof import('./src/tools/data-storage-unit-converter/data-storage-unit-converter.vue')['default'] + DataTransferRateConverter: typeof import('./src/tools/data-transfer-rate-converter/data-transfer-rate-converter.vue')['default'] DateTimeConverter: typeof import('./src/tools/date-time-converter/date-time-converter.vue')['default'] 'DemoHome.page': typeof import('./src/ui/demo/demo-home.page.vue')['default'] DemoWrapper: typeof import('./src/ui/demo/demo-wrapper.vue')['default'] @@ -130,7 +132,9 @@ declare module '@vue/runtime-core' { MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default'] MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default'] NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default'] + NButton: typeof import('naive-ui')['NButton'] NCheckbox: typeof import('naive-ui')['NCheckbox'] + NCode: typeof import('naive-ui')['NCode'] NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] NConfigProvider: typeof import('naive-ui')['NConfigProvider'] NDivider: typeof import('naive-ui')['NDivider'] @@ -138,6 +142,8 @@ declare module '@vue/runtime-core' { NH1: typeof import('naive-ui')['NH1'] NH3: typeof import('naive-ui')['NH3'] NIcon: typeof import('naive-ui')['NIcon'] + NInput: typeof import('naive-ui')['NInput'] + NInputNumber: typeof import('naive-ui')['NInputNumber'] NLayout: typeof import('naive-ui')['NLayout'] NLayoutSider: typeof import('naive-ui')['NLayoutSider'] NMenu: typeof import('naive-ui')['NMenu'] diff --git a/src/tools/data-storage-unit-converter/data-storage-unit-converter.service.test.ts b/src/tools/data-storage-unit-converter/data-storage-unit-converter.service.test.ts new file mode 100644 index 00000000..32786478 --- /dev/null +++ b/src/tools/data-storage-unit-converter/data-storage-unit-converter.service.test.ts @@ -0,0 +1,54 @@ +import { describe, expect, it } from 'vitest'; +import { convertStorageAndRateUnitsDisplay, displayStorageAndRateUnits } from './data-storage-unit-converter.service'; + +describe('data-storage-unit-converter', () => { + describe('convertStorageAndRateUnitsDisplay', () => { + it('convert from same base units', () => { + expect(convertStorageAndRateUnitsDisplay({ value: 1024 * 1024, fromUnit: 'B', toUnit: 'MiB' })).toBe('1'); + expect(convertStorageAndRateUnitsDisplay({ value: 1024, fromUnit: 'KiB', toUnit: 'MiB' })).toBe('1'); + expect(convertStorageAndRateUnitsDisplay({ value: 1, fromUnit: 'MiB', toUnit: 'KiB' })).toBe('1024'); + expect(convertStorageAndRateUnitsDisplay({ value: 1000, fromUnit: 'MB', toUnit: 'GB' })).toBe('1'); + expect(convertStorageAndRateUnitsDisplay({ value: 1024, fromUnit: 'MB', toUnit: 'MB' })).toBe('1024'); + expect(convertStorageAndRateUnitsDisplay({ value: 1, fromUnit: 'MB', toUnit: 'KB' })).toBe('1000'); + expect(convertStorageAndRateUnitsDisplay({ value: 1024, fromUnit: 'MiB', toUnit: 'GiB' })).toBe('1'); + expect(convertStorageAndRateUnitsDisplay({ value: 1000, fromUnit: 'MB', toUnit: 'GB' })).toBe('1'); + expect(convertStorageAndRateUnitsDisplay({ value: 1000, fromUnit: 'Mb', toUnit: 'Gb' })).toBe('1'); + }); + + it('convert between base units', () => { + expect(convertStorageAndRateUnitsDisplay({ value: 1, fromUnit: 'MB', toUnit: 'MiB' })).toBe('0.954'); + expect(convertStorageAndRateUnitsDisplay({ value: 1, fromUnit: 'MiB', toUnit: 'MB' })).toBe('1.049'); + expect(convertStorageAndRateUnitsDisplay({ value: 1000 * 1000, fromUnit: 'B', toUnit: 'MiB' })).toBe('0.954'); + expect(convertStorageAndRateUnitsDisplay({ value: 1024, fromUnit: 'KB', toUnit: 'MiB' })).toBe('0.977'); + expect(convertStorageAndRateUnitsDisplay({ value: 1000, fromUnit: 'MiB', toUnit: 'MB' })).toBe('1048.576'); + expect(convertStorageAndRateUnitsDisplay({ value: 1, fromUnit: 'MB', toUnit: 'Mb' })).toBe('8'); + expect(convertStorageAndRateUnitsDisplay({ value: 1000, fromUnit: 'KB', toUnit: 'Kb' })).toBe('8000'); + expect(convertStorageAndRateUnitsDisplay({ value: 1000, fromUnit: 'KiB', toUnit: 'Kb' })).toBe('8192'); + expect(convertStorageAndRateUnitsDisplay({ value: 8, fromUnit: 'Mb', toUnit: 'MB' })).toBe('1'); + + expect(convertStorageAndRateUnitsDisplay({ value: 1, fromUnit: 'Mb', toUnit: 'KB' })).toBe('125'); + expect(convertStorageAndRateUnitsDisplay({ value: 125, fromUnit: 'KB', toUnit: 'Mb' })).toBe('1'); + + expect(convertStorageAndRateUnitsDisplay({ value: 1, fromUnit: 'MiB', toUnit: 'Kb' })).toBe('8388.608'); + expect(convertStorageAndRateUnitsDisplay({ value: 8388.608, fromUnit: 'Kb', toUnit: 'MiB' })).toBe('1'); + }); + it('convert with unit display', () => { + expect(convertStorageAndRateUnitsDisplay({ value: 1024 * 1024, fromUnit: 'B', toUnit: 'MiB', appendUnit: true })).toBe('1MiB'); + }); + + // + }); + describe('displayStorageAndRateUnits', () => { + it('convert to correct display value', () => { + expect(displayStorageAndRateUnits({ + value: 1.234567, unit: 'MB', appendUnit: false, + })).toBe('1.235'); + expect(displayStorageAndRateUnits({ + value: 1.234567, unit: 'MB', appendUnit: true, + })).toBe('1.235MB'); + expect(displayStorageAndRateUnits({ + value: 1.234567, unit: 'MB', appendUnit: true, precision: 5, + })).toBe('1.23457MB'); + }); + }); +}); diff --git a/src/tools/data-storage-unit-converter/data-storage-unit-converter.service.ts b/src/tools/data-storage-unit-converter/data-storage-unit-converter.service.ts new file mode 100644 index 00000000..e5e19a29 --- /dev/null +++ b/src/tools/data-storage-unit-converter/data-storage-unit-converter.service.ts @@ -0,0 +1,45 @@ +export type BibytesUnits = 'iB' | 'KiB' | 'MiB' | 'GiB' | 'TiB' | 'PiB' | 'EiB' | 'ZiB' | 'YiB'; +export type BytesUnits = 'B' | 'KB' | 'MB' | 'GB' | 'TB' | 'PB' | 'EB' | 'ZB' | 'YB'; +export type BitsUnits = 'b' | 'Kb' | 'Mb' | 'Gb' | 'Tb' | 'Pb' | 'Eb' | 'Zb' | 'Yb'; +export type AllSupportedUnits = BibytesUnits | BytesUnits | BitsUnits; + +export function displayStorageAndRateUnits( + { value, unit, precision = 3, appendUnit = false }: + { value: number; unit: AllSupportedUnits; precision?: number ; appendUnit?: boolean }): string { + return value.toFixed(precision).replace(/0+$/, '').replace(/\.$/, '') + (appendUnit ? unit : ''); // NOSONAR +} + +export function convertStorageAndRateUnitsDisplay( + { value, fromUnit, toUnit, precision = 3, appendUnit = false }: + { value: number; fromUnit: AllSupportedUnits; toUnit: AllSupportedUnits; precision?: number; appendUnit?: boolean }): string { + return displayStorageAndRateUnits({ + precision, + unit: toUnit, + appendUnit, + value: convertStorageAndRateUnits({ + value, fromUnit, toUnit, + }), + }); +} + +export function convertStorageAndRateUnits( + { value, fromUnit, toUnit }: + { value: number; fromUnit: AllSupportedUnits; toUnit: AllSupportedUnits }): number { + const units = [ + 'iB', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB', + 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB', + 'b', 'Kb', 'Mb', 'Gb', 'Tb', 'Pb', 'Eb', 'Zb', 'Yb', + ]; + + const fromIndex = units.indexOf(fromUnit); + const fromFactor = fromIndex / 9 > 1 ? 1000 : 1024; + const fromDivisor = fromIndex / 9 > 2 ? 8 : 1; + const toIndex = units.indexOf(toUnit); + const toFactor = toIndex / 9 > 1 ? 1000 : 1024; + const toDivisor = toIndex / 9 > 2 ? 8 : 1; + + const fromBase = (fromFactor ** (fromIndex % 9)) / fromDivisor; + const toBase = (toFactor ** (toIndex % 9)) / toDivisor; + + return value * fromBase / toBase; +} diff --git a/src/tools/data-storage-unit-converter/data-storage-unit-converter.vue b/src/tools/data-storage-unit-converter/data-storage-unit-converter.vue new file mode 100644 index 00000000..44233734 --- /dev/null +++ b/src/tools/data-storage-unit-converter/data-storage-unit-converter.vue @@ -0,0 +1,96 @@ + + + diff --git a/src/tools/data-storage-unit-converter/index.ts b/src/tools/data-storage-unit-converter/index.ts new file mode 100644 index 00000000..b1ccc2a9 --- /dev/null +++ b/src/tools/data-storage-unit-converter/index.ts @@ -0,0 +1,16 @@ +import { ArrowsLeftRight } from '@vicons/tabler'; +import { defineTool } from '../tool'; + +export const tool = defineTool({ + name: 'Data Storage Unit converter', + path: '/data-storage-unit-converter', + description: 'Convert data storage or transfer units (bytes, bibytes, bits, kilobytes...)', + keywords: ['data', 'storage', 'unit', 'conversion', + 'bits', 'bytes', 'bibytes', 'binary', + 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB', + 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB', + 'b', 'Kb', 'Mb', 'Gb', 'Tb', 'Pb', 'Eb', 'Zb', 'Yb'], + component: () => import('./data-storage-unit-converter.vue'), + icon: ArrowsLeftRight, + createdAt: new Date('2024-08-15'), +}); diff --git a/src/tools/data-transfer-rate-converter/data-transfer-rate-converter.service.test.ts b/src/tools/data-transfer-rate-converter/data-transfer-rate-converter.service.test.ts new file mode 100644 index 00000000..860ed096 --- /dev/null +++ b/src/tools/data-transfer-rate-converter/data-transfer-rate-converter.service.test.ts @@ -0,0 +1,47 @@ +import { describe, expect, it } from 'vitest'; +import { amountTransferable, transferSpeedRate, transferTimeSeconds } from './data-transfer-rate-converter.service'; + +describe('data-transfer-converter', () => { + describe('transferTimeSeconds', () => { + it('compute transfer time in seconds', () => { + expect(transferTimeSeconds({ + dataSize: 100, + dataSizeUnit: 'MB', + bitRate: 10, + bitRateUnit: 'Mb', + })).toBe(80); + }); + }); + describe('transferSpeedRate', () => { + it('compute transferSpeedRate', () => { + expect(transferSpeedRate({ + dataSize: 100, + dataSizeUnit: 'MB', + hours: 0, + minutes: 1, + seconds: 20, + bitRateUnit: 'Mb', + })).toBe(10); + expect(transferSpeedRate({ + dataSize: 100, + dataSizeUnit: 'MB', + hours: 0, + minutes: 1, + seconds: 20, + bitRateUnit: 'MB', + })).toBe(1.25); + }); + }); + describe('amountTransferable', () => { + it('compute amount transfered', () => { + expect(amountTransferable({ + bitRate: 10, + bitRateUnit: 'Mb', + hours: 1, + minutes: 0, + seconds: 0, + dataSizeUnit: 'MB', + })).toBe(4500); + }); + }); +}); diff --git a/src/tools/data-transfer-rate-converter/data-transfer-rate-converter.service.ts b/src/tools/data-transfer-rate-converter/data-transfer-rate-converter.service.ts new file mode 100644 index 00000000..0ececcc1 --- /dev/null +++ b/src/tools/data-transfer-rate-converter/data-transfer-rate-converter.service.ts @@ -0,0 +1,57 @@ +import { type AllSupportedUnits, convertStorageAndRateUnits } from '../data-storage-unit-converter/data-storage-unit-converter.service'; + +export function transferTimeSeconds({ + dataSize, + dataSizeUnit, + bitRate, + bitRateUnit, +}: { + dataSize: number + dataSizeUnit: AllSupportedUnits + bitRate: number + bitRateUnit: AllSupportedUnits +}): number { + const dataSizeInBytes = convertStorageAndRateUnits({ value: dataSize, fromUnit: dataSizeUnit, toUnit: 'B' }); + const bitRateInBytes = convertStorageAndRateUnits({ value: bitRate, fromUnit: bitRateUnit, toUnit: 'B' }); + return bitRateInBytes > 0 ? dataSizeInBytes / bitRateInBytes : 0; +} + +export function transferSpeedRate({ + dataSize, + dataSizeUnit, + hours, + minutes, + seconds, + bitRateUnit, +}: { + dataSize: number + dataSizeUnit: AllSupportedUnits + hours: number + minutes: number + seconds: number + bitRateUnit: AllSupportedUnits +}): number { + const dataSizeInBits = convertStorageAndRateUnits({ value: dataSize, fromUnit: dataSizeUnit, toUnit: 'b' }); + const timeInSeconds = hours * 3600 + minutes * 60 + seconds; + return convertStorageAndRateUnits({ value: timeInSeconds > 0 ? dataSizeInBits / timeInSeconds : 0, fromUnit: 'b', toUnit: bitRateUnit }); +} + +export function amountTransferable({ + bitRate, + bitRateUnit, + hours, + minutes, + seconds, + dataSizeUnit, +}: { + bitRate: number + bitRateUnit: AllSupportedUnits + hours: number + minutes: number + seconds: number + dataSizeUnit: AllSupportedUnits +}): number { + const bitRateInBytes = convertStorageAndRateUnits({ value: bitRate, fromUnit: bitRateUnit, toUnit: 'B' }); + const timeInSeconds = hours * 3600 + minutes * 60 + seconds; + return convertStorageAndRateUnits({ value: bitRateInBytes * timeInSeconds, fromUnit: 'B', toUnit: dataSizeUnit }); +} diff --git a/src/tools/data-transfer-rate-converter/data-transfer-rate-converter.vue b/src/tools/data-transfer-rate-converter/data-transfer-rate-converter.vue new file mode 100644 index 00000000..a8128dcb --- /dev/null +++ b/src/tools/data-transfer-rate-converter/data-transfer-rate-converter.vue @@ -0,0 +1,246 @@ + + + diff --git a/src/tools/data-transfer-rate-converter/index.ts b/src/tools/data-transfer-rate-converter/index.ts new file mode 100644 index 00000000..cf3f520a --- /dev/null +++ b/src/tools/data-transfer-rate-converter/index.ts @@ -0,0 +1,12 @@ +import { TransferIn } from '@vicons/tabler'; +import { defineTool } from '../tool'; + +export const tool = defineTool({ + name: 'Data Transfer Rate Calculator', + path: '/data-transfer-rate-converter', + description: 'Compute Data Transfer times, rates and amount of data', + keywords: ['data', 'transfer', 'rate', 'convert', 'time'], + component: () => import('./data-transfer-rate-converter.vue'), + icon: TransferIn, + createdAt: new Date('2024-08-15'), +}); diff --git a/src/tools/index.ts b/src/tools/index.ts index 388cfaf4..c0e79858 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -2,6 +2,8 @@ import { tool as base64FileConverter } from './base64-file-converter'; import { tool as base64StringConverter } from './base64-string-converter'; import { tool as basicAuthGenerator } from './basic-auth-generator'; import { tool as emailNormalizer } from './email-normalizer'; +import { tool as dataTransferRateConverter } from './data-transfer-rate-converter'; +import { tool as dataStorageUnitConverter } from './data-storage-unit-converter'; import { tool as asciiTextDrawer } from './ascii-text-drawer'; @@ -168,7 +170,13 @@ export const toolsByCategory: ToolCategory[] = [ }, { name: 'Math', - components: [mathEvaluator, etaCalculator, percentageCalculator], + components: [ + mathEvaluator, + etaCalculator, + percentageCalculator, + dataTransferRateConverter, + dataStorageUnitConverter, + ], }, { name: 'Measurement',