Merge branch 'main' into card-hover

This commit is contained in:
Corentin THOMASSET 2023-09-06 10:53:40 +02:00 committed by GitHub
commit e9e0884789
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 330 additions and 16 deletions

1
components.d.ts vendored
View file

@ -188,6 +188,7 @@ declare module '@vue/runtime-core' {
UserAgentParser: typeof import('./src/tools/user-agent-parser/user-agent-parser.vue')['default'] UserAgentParser: typeof import('./src/tools/user-agent-parser/user-agent-parser.vue')['default']
UserAgentResultCards: typeof import('./src/tools/user-agent-parser/user-agent-result-cards.vue')['default'] UserAgentResultCards: typeof import('./src/tools/user-agent-parser/user-agent-result-cards.vue')['default']
UuidGenerator: typeof import('./src/tools/uuid-generator/uuid-generator.vue')['default'] UuidGenerator: typeof import('./src/tools/uuid-generator/uuid-generator.vue')['default']
WifiQrCodeGenerator: typeof import('./src/tools/wifi-qr-code-generator/wifi-qr-code-generator.vue')['default']
XmlFormatter: typeof import('./src/tools/xml-formatter/xml-formatter.vue')['default'] XmlFormatter: typeof import('./src/tools/xml-formatter/xml-formatter.vue')['default']
YamlToJson: typeof import('./src/tools/yaml-to-json-converter/yaml-to-json.vue')['default'] YamlToJson: typeof import('./src/tools/yaml-to-json-converter/yaml-to-json.vue')['default']
YamlToToml: typeof import('./src/tools/yaml-to-toml/yaml-to-toml.vue')['default'] YamlToToml: typeof import('./src/tools/yaml-to-toml/yaml-to-toml.vue')['default']

View file

@ -146,7 +146,7 @@ function formatDateUsingFormatter(formatter: (date: Date) => string, date?: Date
<c-input-text <c-input-text
v-model:value="inputDate" v-model:value="inputDate"
autofocus autofocus
placeholder="Put you date string here..." placeholder="Put your date string here..."
clearable clearable
test-id="date-time-converter-input" test-id="date-time-converter-input"
:validation="validation" :validation="validation"

View file

@ -56,6 +56,7 @@ import { tool as metaTagGenerator } from './meta-tag-generator';
import { tool as mimeTypes } from './mime-types'; import { tool as mimeTypes } from './mime-types';
import { tool as otpCodeGeneratorAndValidator } from './otp-code-generator-and-validator'; import { tool as otpCodeGeneratorAndValidator } from './otp-code-generator-and-validator';
import { tool as qrCodeGenerator } from './qr-code-generator'; import { tool as qrCodeGenerator } from './qr-code-generator';
import { tool as wifiQrCodeGenerator } from './wifi-qr-code-generator';
import { tool as randomPortGenerator } from './random-port-generator'; import { tool as randomPortGenerator } from './random-port-generator';
import { tool as romanNumeralConverter } from './roman-numeral-converter'; import { tool as romanNumeralConverter } from './roman-numeral-converter';
import { tool as sqlPrettify } from './sql-prettify'; import { tool as sqlPrettify } from './sql-prettify';
@ -117,7 +118,7 @@ export const toolsByCategory: ToolCategory[] = [
}, },
{ {
name: 'Images and videos', name: 'Images and videos',
components: [qrCodeGenerator, svgPlaceholderGenerator, cameraRecorder], components: [qrCodeGenerator, wifiQrCodeGenerator, svgPlaceholderGenerator, cameraRecorder],
}, },
{ {
name: 'Development', name: 'Development',

View file

@ -14,6 +14,6 @@ test.describe('Tool - Password strength analyser', () => {
const crackDuration = await page.getByTestId('crack-duration').textContent(); const crackDuration = await page.getByTestId('crack-duration').textContent();
expect(crackDuration).toEqual('15,091 milleniums, 3 centurys'); expect(crackDuration).toEqual('15,091 millennia, 3 centuries');
}); });
}); });

View file

@ -19,20 +19,20 @@ function getHumanFriendlyDuration({ seconds }: { seconds: number }) {
} }
const timeUnits = [ const timeUnits = [
{ unit: 'millenium', secondsInUnit: 31536000000, format: prettifyExponentialNotation }, { unit: 'millenium', secondsInUnit: 31536000000, format: prettifyExponentialNotation, plural: 'millennia' },
{ unit: 'century', secondsInUnit: 3153600000 }, { unit: 'century', secondsInUnit: 3153600000, plural: 'centuries' },
{ unit: 'decade', secondsInUnit: 315360000 }, { unit: 'decade', secondsInUnit: 315360000, plural: 'decades' },
{ unit: 'year', secondsInUnit: 31536000 }, { unit: 'year', secondsInUnit: 31536000, plural: 'years' },
{ unit: 'month', secondsInUnit: 2592000 }, { unit: 'month', secondsInUnit: 2592000, plural: 'months' },
{ unit: 'week', secondsInUnit: 604800 }, { unit: 'week', secondsInUnit: 604800, plural: 'weeks' },
{ unit: 'day', secondsInUnit: 86400 }, { unit: 'day', secondsInUnit: 86400, plural: 'days' },
{ unit: 'hour', secondsInUnit: 3600 }, { unit: 'hour', secondsInUnit: 3600, plural: 'hours' },
{ unit: 'minute', secondsInUnit: 60 }, { unit: 'minute', secondsInUnit: 60, plural: 'minutes' },
{ unit: 'second', secondsInUnit: 1 }, { unit: 'second', secondsInUnit: 1, plural: 'seconds' },
]; ];
return _.chain(timeUnits) return _.chain(timeUnits)
.map(({ unit, secondsInUnit, format = _.identity }) => { .map(({ unit, secondsInUnit, plural, format = _.identity }) => {
const quantity = Math.floor(seconds / secondsInUnit); const quantity = Math.floor(seconds / secondsInUnit);
seconds %= secondsInUnit; seconds %= secondsInUnit;
@ -41,7 +41,7 @@ function getHumanFriendlyDuration({ seconds }: { seconds: number }) {
} }
const formattedQuantity = format(quantity); const formattedQuantity = format(quantity);
return `${formattedQuantity} ${unit}${quantity > 1 ? 's' : ''}`; return `${formattedQuantity} ${quantity > 1 ? plural : unit}`;
}) })
.compact() .compact()
.take(2) .take(2)

View file

@ -5,7 +5,7 @@ export const tool = defineTool({
name: 'UUIDs v4 generator', name: 'UUIDs v4 generator',
path: '/uuid-generator', path: '/uuid-generator',
description: description:
'A universally unique identifier (UUID) is a 128-bit number used to identify information in computer systems. The number of possible UUIDs is 16^32, which is 2^128 or about 3.4x10^38 (which is a lot !).', 'A Universally Unique Identifier (UUID) is a 128-bit number used to identify information in computer systems. The number of possible UUIDs is 16^32, which is 2^128 or about 3.4x10^38 (which is a lot!).',
keywords: ['uuid', 'v4', 'random', 'id', 'alphanumeric', 'identity', 'token', 'string', 'identifier', 'unique'], keywords: ['uuid', 'v4', 'random', 'id', 'alphanumeric', 'identity', 'token', 'string', 'identifier', 'unique'],
component: () => import('./uuid-generator.vue'), component: () => import('./uuid-generator.vue'),
icon: Fingerprint, icon: Fingerprint,

View file

@ -0,0 +1,13 @@
import { Qrcode } from '@vicons/tabler';
import { defineTool } from '../tool';
export const tool = defineTool({
name: 'WiFi QR Code generator',
path: '/wifi-qrcode-generator',
description:
'Generate and download QR-codes for quick connections to WiFi networks.',
keywords: ['qr', 'code', 'generator', 'square', 'color', 'link', 'low', 'medium', 'quartile', 'high', 'transparent', 'wifi'],
component: () => import('./wifi-qr-code-generator.vue'),
icon: Qrcode,
createdAt: new Date('2023-09-06'),
});

View file

@ -0,0 +1,146 @@
import { type MaybeRef, get } from '@vueuse/core';
import QRCode, { type QRCodeToDataURLOptions } from 'qrcode';
import { isRef, ref, watch } from 'vue';
export const wifiEncryptions = ['WEP', 'WPA', 'nopass', 'WPA2-EAP'] as const;
export type WifiEncryption = typeof wifiEncryptions[number];
// @see https://en.wikipedia.org/wiki/Extensible_Authentication_Protocol
// for a list of available EAP methods. There are a lot (40!) of them.
export const EAPMethods = [
'MD5',
'POTP',
'GTC',
'TLS',
'IKEv2',
'SIM',
'AKA',
'AKA\'',
'TTLS',
'PWD',
'LEAP',
'PSK',
'FAST',
'TEAP',
'EKE',
'NOOB',
'PEAP',
] as const;
export type EAPMethod = typeof EAPMethods[number];
export const EAPPhase2Methods = [
'None',
'MSCHAPV2',
] as const;
export type EAPPhase2Method = typeof EAPPhase2Methods[number];
interface IWifiQRCodeOptions {
ssid: MaybeRef<string>
password: MaybeRef<string>
eapMethod: MaybeRef<EAPMethod>
isHiddenSSID: MaybeRef<boolean>
eapAnonymous: MaybeRef<boolean>
eapIdentity: MaybeRef<string>
eapPhase2Method: MaybeRef<EAPPhase2Method>
color: { foreground: MaybeRef<string>; background: MaybeRef<string> }
options?: QRCodeToDataURLOptions
}
interface GetQrCodeTextOptions {
ssid: string
password: string
encryption: WifiEncryption
eapMethod: EAPMethod
isHiddenSSID: boolean
eapAnonymous: boolean
eapIdentity: string
eapPhase2Method: EAPPhase2Method
}
function escapeString(str: string) {
// replaces \, ;, ,, " and : with the same character preceded by a backslash
return str.replace(/([\\;,:"])/g, '\\$1');
}
function getQrCodeText(options: GetQrCodeTextOptions): string | null {
const { ssid, password, encryption, eapMethod, isHiddenSSID, eapAnonymous, eapIdentity, eapPhase2Method } = options;
if (!ssid) {
return null;
}
if (encryption === 'nopass') {
return `WIFI:S:${escapeString(ssid)};;`; // type can be omitted in that case, and password is not needed, makes the QR Code smaller
}
if (encryption !== 'WPA2-EAP' && password) {
// EAP has a lot of options, so we'll handle it separately
// WPA and WEP are pretty simple though.
return `WIFI:S:${escapeString(ssid)};T:${encryption};P:${escapeString(password)};${isHiddenSSID ? 'H:true' : ''};`;
}
if (encryption === 'WPA2-EAP' && password && eapMethod) {
// WPA2-EAP string is a lot more complex, first off, we drop the text if there is no identity, and it's not anonymous.
if (!eapIdentity && !eapAnonymous) {
return null;
}
// From reading, I could only find that a phase 2 is required for the PEAP method, I may be wrong though, I didn't read the whole spec.
if (eapMethod === 'PEAP' && !eapPhase2Method) {
return null;
}
// The string is built in the following order:
// 1. SSID
// 2. Authentication type
// 3. Password
// 4. EAP method
// 5. EAP phase 2 method
// 6. Identity or anonymous if checked
// 7. Hidden SSID if checked
const identity = eapAnonymous ? 'A:anon' : `I:${escapeString(eapIdentity)}`;
const phase2 = eapPhase2Method !== 'None' ? `PH2:${eapPhase2Method};` : '';
return `WIFI:S:${escapeString(ssid)};T:WPA2-EAP;P:${escapeString(password)};E:${eapMethod};${phase2}${identity};${isHiddenSSID ? 'H:true' : ''};`;
}
return null;
}
export function useWifiQRCode({
ssid,
password,
eapMethod,
isHiddenSSID,
eapAnonymous,
eapIdentity,
eapPhase2Method,
color: { background, foreground },
options,
}: IWifiQRCodeOptions) {
const qrcode = ref('');
const encryption = ref<WifiEncryption>('WPA');
watch(
[ssid, password, encryption, eapMethod, isHiddenSSID, eapAnonymous, eapIdentity, eapPhase2Method, background, foreground].filter(isRef),
async () => {
// @see https://github.com/zxing/zxing/wiki/Barcode-Contents#wi-fi-network-config-android-ios-11
// This is the full spec, there's quite a bit of logic to generate the string embeddedin the QR code.
const text = getQrCodeText({
ssid: get(ssid),
password: get(password),
encryption: get(encryption),
eapMethod: get(eapMethod),
isHiddenSSID: get(isHiddenSSID),
eapAnonymous: get(eapAnonymous),
eapIdentity: get(eapIdentity),
eapPhase2Method: get(eapPhase2Method),
});
if (text) {
qrcode.value = await QRCode.toDataURL(get(text).trim(), {
color: {
dark: get(foreground),
light: get(background),
...options?.color,
},
errorCorrectionLevel: 'M',
...options,
});
}
},
{ immediate: true },
);
return { qrcode, encryption };
}

View file

@ -0,0 +1,153 @@
<script setup lang="ts">
import {
EAPMethods,
EAPPhase2Methods,
useWifiQRCode,
} from './useQRCode';
import { useDownloadFileFromBase64 } from '@/composable/downloadBase64';
const foreground = ref('#000000ff');
const background = ref('#ffffffff');
const ssid = ref();
const password = ref();
const eapMethod = ref();
const isHiddenSSID = ref(false);
const eapAnonymous = ref(false);
const eapIdentity = ref();
const eapPhase2Method = ref();
const { qrcode, encryption } = useWifiQRCode({
ssid,
password,
eapMethod,
isHiddenSSID,
eapAnonymous,
eapIdentity,
eapPhase2Method,
color: {
background,
foreground,
},
options: { width: 1024 },
});
const { download } = useDownloadFileFromBase64({ source: qrcode, filename: 'qr-code.png' });
</script>
<template>
<c-card>
<div grid grid-cols-1 gap-12>
<div>
<c-select
v-model:value="encryption"
mb-4
label="Encryption method"
default-value="WPA"
label-position="left"
label-width="130px"
label-align="right"
:options="[
{
label: 'No password',
value: 'nopass',
},
{
label: 'WPA/WPA2',
value: 'WPA',
},
{
label: 'WEP',
value: 'WEP',
},
{
label: 'WPA2-EAP',
value: 'WPA2-EAP',
},
]"
/>
<div class="mb-6 flex flex-row items-center gap-2">
<c-input-text
v-model:value="ssid"
label-position="left"
label-width="130px"
label-align="right"
label="SSID:"
rows="1"
autosize
placeholder="Your WiFi SSID..."
mb-6
/>
<n-checkbox v-model:checked="isHiddenSSID">
Hidden SSID
</n-checkbox>
</div>
<c-input-text
v-if="encryption !== 'nopass'"
v-model:value="password"
label-position="left"
label-width="130px"
label-align="right"
label="Password:"
rows="1"
autosize
type="password"
placeholder="Your WiFi Password..."
mb-6
/>
<c-select
v-if="encryption === 'WPA2-EAP'"
v-model:value="eapMethod"
label="EAP method"
label-position="left"
label-width="130px"
label-align="right"
:options="EAPMethods.map((method) => ({ label: method, value: method }))"
searchable mb-4
/>
<div v-if="encryption === 'WPA2-EAP'" class="mb-6 flex flex-row items-center gap-2">
<c-input-text
v-model:value="eapIdentity"
label-position="left"
label-width="130px"
label-align="right"
label="Identity:"
rows="1"
autosize
placeholder="Your EAP Identity..."
mb-6
/>
<n-checkbox v-model:checked="eapAnonymous">
Anonymous?
</n-checkbox>
</div>
<c-select
v-if="encryption === 'WPA2-EAP'"
v-model:value="eapPhase2Method"
label="EAP Phase 2 method"
label-position="left"
label-width="130px"
label-align="right"
:options="EAPPhase2Methods.map((method) => ({ label: method, value: method }))"
searchable mb-4
/>
<n-form label-width="130" label-placement="left">
<n-form-item label="Foreground color:">
<n-color-picker v-model:value="foreground" :modes="['hex']" />
</n-form-item>
<n-form-item label="Background color:">
<n-color-picker v-model:value="background" :modes="['hex']" />
</n-form-item>
</n-form>
</div>
<div v-if="qrcode">
<div flex flex-col items-center gap-3>
<img alt="wifi-qrcode" :src="qrcode" width="200">
<c-button @click="download">
Download qr-code
</c-button>
</div>
</div>
</div>
</c-card>
</template>