refactor(mac-address-generator): improved ux

This commit is contained in:
Corentin Thomasset 2023-10-31 12:24:02 +01:00
parent e48fd59cfb
commit a3efaae04b
No known key found for this signature in database
GPG Key ID: DBD997E935996158
5 changed files with 125 additions and 33 deletions

View File

@ -1,12 +1,12 @@
import { Atom2 } from '@vicons/tabler'; import { Devices } from '@vicons/tabler';
import { defineTool } from '../tool'; import { defineTool } from '../tool';
export const tool = defineTool({ export const tool = defineTool({
name: 'MAC address generator', name: 'MAC address generator',
path: '/mac-address-generator', path: '/mac-address-generator',
description: 'Enter the quantity and prefix. MAC addresses will be generated in your chosen case (uppercase or lowercase)', description: 'Enter the quantity and prefix. MAC addresses will be generated in your chosen case (uppercase or lowercase)',
keywords: ['mac', 'address', 'generator'], keywords: ['mac', 'address', 'generator', 'random', 'prefix'],
component: () => import('./mac-address-generator.vue'), component: () => import('./mac-address-generator.vue'),
icon: Atom2, icon: Devices,
createdAt: new Date('2023-10-11'), createdAt: new Date('2023-11-31'),
}); });

View File

@ -1,61 +1,92 @@
<script setup lang="ts"> <script setup lang="ts">
import _ from 'lodash'; import _ from 'lodash';
import { generateRandomMacAddress } from './mac-adress-generator.models';
import { computedRefreshable } from '@/composable/computedRefreshable'; import { computedRefreshable } from '@/composable/computedRefreshable';
import { useCopy } from '@/composable/copy'; import { useCopy } from '@/composable/copy';
import { partialMacAddressValidation, partialMacAddressValidationRules } from '@/utils/macAddress'; import { useValidation } from '@/composable/validation';
import { usePartialMacAddressValidation } from '@/utils/macAddress';
const amount = useStorage('mac-address-generator-amount', 1); const amount = useStorage('mac-address-generator-amount', 1);
const uppercase = useStorage('mac-address-generator-uppercase', true);
const macAddressPrefix = useStorage('mac-address-generator-prefix', '64:16:7F'); const macAddressPrefix = useStorage('mac-address-generator-prefix', '64:16:7F');
const prefixValidation = usePartialMacAddressValidation(macAddressPrefix);
const casesTransformers = [
{ label: 'Uppercase', value: (value: string) => value.toUpperCase() },
{ label: 'Lowercase', value: (value: string) => value.toLowerCase() },
];
const caseTransformer = ref(casesTransformers[0].value);
const separators = [
{
label: ':',
value: ':',
},
{
label: '-',
value: '-',
},
{
label: '.',
value: '.',
},
{
label: 'None',
value: '',
},
];
const separator = useStorage('mac-address-generator-separator', separators[0].value);
const [macAddresses, refreshMacAddresses] = computedRefreshable(() => { const [macAddresses, refreshMacAddresses] = computedRefreshable(() => {
if (!partialMacAddressValidation(macAddressPrefix).isValid) { if (!prefixValidation.isValid) {
return ''; return '';
} }
const ids = _.times(amount.value, () => generateMac(macAddressPrefix.value)); const ids = _.times(amount.value, () => caseTransformer.value(generateRandomMacAddress({
prefix: macAddressPrefix.value,
separator: separator.value,
})));
return ids.join('\n'); return ids.join('\n');
}); });
const { copy } = useCopy({ source: macAddresses, text: 'MAC addresses copied to the clipboard' }); const { copy } = useCopy({ source: macAddresses, text: 'MAC addresses copied to the clipboard' });
function generateMac(prefix: string = ''): string {
let mac = prefix;
for (let i = prefix.length; i < 17; i++) {
if (i % 3 === 2) { // Place ':' after every 2 hex characters
mac += ':';
}
else {
mac += Math.floor(Math.random() * 16).toString(16);
}
}
return uppercase.value ? mac.toUpperCase() : mac;
}
</script> </script>
<template> <template>
<div flex flex-col justify-center gap-2> <div flex flex-col justify-center gap-2>
<div flex items-center> <div flex items-center>
<label w-75px> Quantity:</label> <label w-150px pr-12px text-right> Quantity:</label>
<n-input-number v-model:value="amount" min="1" max="100" flex-1 /> <n-input-number v-model:value="amount" min="1" max="100" flex-1 />
</div> </div>
<c-input-text <c-input-text
v-model:value="macAddressPrefix" v-model:value="macAddressPrefix"
label="MAC address prefix:" label="MAC address prefix:"
placeholder="Type a MAC address" placeholder="Set a prefix, e.g. 64:16:7F"
clearable clearable
label-position="left" label-position="left"
spellcheck="false" spellcheck="false"
:validation-rules="partialMacAddressValidationRules" :validation="prefixValidation"
mb-5 raw-text
label-width="150px"
label-align="right"
/> />
<n-checkbox v-model:checked="uppercase"> <c-buttons-select
Uppercase v-model:value="caseTransformer"
</n-checkbox> :options="casesTransformers"
label="Case:"
label-width="150px"
label-align="right"
/>
<c-buttons-select
v-model:value="separator"
:options="separators"
label="Separator:"
label-width="150px"
label-align="right"
/>
<c-card mt-5 flex data-test-id="ulids"> <c-card mt-5 flex data-test-id="ulids">
<pre m-0 m-x-auto>{{ macAddresses }}</pre> <pre m-0 m-x-auto>{{ macAddresses }}</pre>

View File

@ -0,0 +1,43 @@
import { describe, expect, it } from 'vitest';
import { generateRandomMacAddress, splitPrefix } from './mac-adress-generator.models';
describe('mac-adress-generator models', () => {
describe('splitPrefix', () => {
it('a mac address prefix is splitted around non hex characters', () => {
expect(splitPrefix('')).toEqual([]);
expect(splitPrefix('01')).toEqual(['01']);
expect(splitPrefix('01:')).toEqual(['01']);
expect(splitPrefix('01:23')).toEqual(['01', '23']);
expect(splitPrefix('01-23')).toEqual(['01', '23']);
});
it('when a prefix contains only hex characters, they are grouped by 2', () => {
expect(splitPrefix('0123')).toEqual(['01', '23']);
expect(splitPrefix('012345')).toEqual(['01', '23', '45']);
expect(splitPrefix('0123456')).toEqual(['01', '23', '45', '06']);
});
});
describe('generateRandomMacAddress', () => {
const createRandomByteGenerator = () => {
let i = 0;
return () => (i++).toString(16).padStart(2, '0');
};
it('generates a random mac address', () => {
expect(generateRandomMacAddress({ getRandomByte: createRandomByteGenerator() })).toBe('00:01:02:03:04:05');
});
it('generates a random mac address with a prefix', () => {
expect(generateRandomMacAddress({ prefix: 'ff:ee:aa', getRandomByte: createRandomByteGenerator() })).toBe('ff:ee:aa:00:01:02');
expect(generateRandomMacAddress({ prefix: 'ff:ee:a', getRandomByte: createRandomByteGenerator() })).toBe('ff:ee:0a:00:01:02');
});
it('generates a random mac address with a prefix and a different separator', () => {
expect(generateRandomMacAddress({ prefix: 'ff-ee-aa', separator: '-', getRandomByte: createRandomByteGenerator() })).toBe('ff-ee-aa-00-01-02');
expect(generateRandomMacAddress({ prefix: 'ff:ee:aa', separator: '-', getRandomByte: createRandomByteGenerator() })).toBe('ff-ee-aa-00-01-02');
expect(generateRandomMacAddress({ prefix: 'ff-ee:aa', separator: '-', getRandomByte: createRandomByteGenerator() })).toBe('ff-ee-aa-00-01-02');
expect(generateRandomMacAddress({ prefix: 'ff ee:aa', separator: '-', getRandomByte: createRandomByteGenerator() })).toBe('ff-ee-aa-00-01-02');
});
});
});

View File

@ -0,0 +1,18 @@
import _ from 'lodash';
export { splitPrefix, generateRandomMacAddress };
function splitPrefix(prefix: string): string[] {
const base = prefix.match(/[^0-9a-f]/i) === null ? prefix.match(/.{1,2}/g) ?? [] : prefix.split(/[^0-9a-f]/i);
return base.filter(Boolean).map(byte => byte.padStart(2, '0'));
}
function generateRandomMacAddress({ prefix: rawPrefix = '', separator = ':', getRandomByte = () => _.random(0, 255).toString(16).padStart(2, '0') }: { prefix?: string; separator?: string; getRandomByte?: () => string } = {}) {
const prefix = splitPrefix(rawPrefix);
const randomBytes = _.times(6 - prefix.length, getRandomByte);
const bytes = [...prefix, ...randomBytes];
return bytes.join(separator);
}

View File

@ -18,15 +18,15 @@ function macAddressValidation(value: Ref) {
const partialMacAddressValidationRules = [ const partialMacAddressValidationRules = [
{ {
message: 'Invalid partial MAC address', message: 'Invalid partial MAC address',
validator: (value: string) => value.trim().match(/^([0-9A-Fa-f]{2}[:-]){0,5}([0-9A-Fa-f]{0,2})$/), validator: (value: string) => value.trim().match(/^([0-9a-f]{2}[:\-. ]){0,5}([0-9a-f]{0,2})$/i),
}, },
]; ];
function partialMacAddressValidation(value: Ref) { function usePartialMacAddressValidation(value: Ref) {
return useValidation({ return useValidation({
source: value, source: value,
rules: partialMacAddressValidationRules, rules: partialMacAddressValidationRules,
}); });
} }
export { macAddressValidation, macAddressValidationRules, partialMacAddressValidation, partialMacAddressValidationRules }; export { macAddressValidation, macAddressValidationRules, usePartialMacAddressValidation, partialMacAddressValidationRules };