mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-05-04 13:29:13 -04:00
Merge bad0262275
into e1b4f9aafe
This commit is contained in:
commit
205760a042
8 changed files with 425 additions and 54 deletions
6
components.d.ts
vendored
6
components.d.ts
vendored
|
@ -127,24 +127,26 @@ 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']
|
||||
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
||||
NCode: typeof import('naive-ui')['NCode']
|
||||
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
|
||||
NColorPicker: typeof import('naive-ui')['NColorPicker']
|
||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||
NDivider: typeof import('naive-ui')['NDivider']
|
||||
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
||||
NForm: typeof import('naive-ui')['NForm']
|
||||
NFormItem: typeof import('naive-ui')['NFormItem']
|
||||
NGi: typeof import('naive-ui')['NGi']
|
||||
NGrid: typeof import('naive-ui')['NGrid']
|
||||
NH1: typeof import('naive-ui')['NH1']
|
||||
NH3: typeof import('naive-ui')['NH3']
|
||||
NIcon: typeof import('naive-ui')['NIcon']
|
||||
NImage: typeof import('naive-ui')['NImage']
|
||||
NInputNumber: typeof import('naive-ui')['NInputNumber']
|
||||
NLabel: typeof import('naive-ui')['NLabel']
|
||||
NLayout: typeof import('naive-ui')['NLayout']
|
||||
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
|
||||
NMenu: typeof import('naive-ui')['NMenu']
|
||||
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
||||
NSpin: typeof import('naive-ui')['NSpin']
|
||||
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
|
||||
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']
|
||||
PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default']
|
||||
|
|
|
@ -80,7 +80,9 @@
|
|||
"pdf-signature-reader": "^1.4.2",
|
||||
"pinia": "^2.0.34",
|
||||
"plausible-tracker": "^0.3.8",
|
||||
"pp-qr-code": "^0.6.3",
|
||||
"qrcode": "^1.5.1",
|
||||
"qrcode-terminal-nooctal": "^0.12.1",
|
||||
"sql-formatter": "^13.0.0",
|
||||
"ua-parser-js": "^1.0.35",
|
||||
"ulid": "^2.3.0",
|
||||
|
|
40
pnpm-lock.yaml
generated
40
pnpm-lock.yaml
generated
|
@ -140,9 +140,15 @@ dependencies:
|
|||
plausible-tracker:
|
||||
specifier: ^0.3.8
|
||||
version: 0.3.8
|
||||
pp-qr-code:
|
||||
specifier: ^0.6.3
|
||||
version: 0.6.4
|
||||
qrcode:
|
||||
specifier: ^1.5.1
|
||||
version: 1.5.1
|
||||
qrcode-terminal-nooctal:
|
||||
specifier: ^0.12.1
|
||||
version: 0.12.1
|
||||
sql-formatter:
|
||||
specifier: ^13.0.0
|
||||
version: 13.0.0
|
||||
|
@ -3354,7 +3360,7 @@ packages:
|
|||
dependencies:
|
||||
'@unhead/dom': 0.5.1
|
||||
'@unhead/schema': 0.5.1
|
||||
'@vueuse/shared': 10.8.0(vue@3.3.4)
|
||||
'@vueuse/shared': 10.11.0(vue@3.3.4)
|
||||
unhead: 0.5.1
|
||||
vue: 3.3.4
|
||||
transitivePeerDependencies:
|
||||
|
@ -3987,19 +3993,19 @@ packages:
|
|||
- vue
|
||||
dev: false
|
||||
|
||||
/@vueuse/shared@10.3.0(vue@3.3.4):
|
||||
resolution: {integrity: sha512-kGqCTEuFPMK4+fNWy6dUOiYmxGcUbtznMwBZLC1PubidF4VZY05B+Oht7Jh7/6x4VOWGpvu3R37WHi81cKpiqg==}
|
||||
/@vueuse/shared@10.11.0(vue@3.3.4):
|
||||
resolution: {integrity: sha512-fyNoIXEq3PfX1L3NkNhtVQUSRtqYwJtJg+Bp9rIzculIZWHTkKSysujrOk2J+NrRulLTQH9+3gGSfYLWSEWU1A==}
|
||||
dependencies:
|
||||
vue-demi: 0.14.5(vue@3.3.4)
|
||||
vue-demi: 0.14.8(vue@3.3.4)
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
- vue
|
||||
dev: false
|
||||
|
||||
/@vueuse/shared@10.8.0(vue@3.3.4):
|
||||
resolution: {integrity: sha512-dUdy6zwHhULGxmr9YUg8e+EnB39gcM4Fe2oKBSrh3cOsV30JcMPtsyuspgFCUo5xxFNaeMf/W2yyKfST7Bg8oQ==}
|
||||
/@vueuse/shared@10.3.0(vue@3.3.4):
|
||||
resolution: {integrity: sha512-kGqCTEuFPMK4+fNWy6dUOiYmxGcUbtznMwBZLC1PubidF4VZY05B+Oht7Jh7/6x4VOWGpvu3R37WHi81cKpiqg==}
|
||||
dependencies:
|
||||
vue-demi: 0.14.7(vue@3.3.4)
|
||||
vue-demi: 0.14.5(vue@3.3.4)
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
- vue
|
||||
|
@ -7470,6 +7476,12 @@ packages:
|
|||
source-map-js: 1.0.2
|
||||
dev: true
|
||||
|
||||
/pp-qr-code@0.6.4:
|
||||
resolution: {integrity: sha512-6OhfzsFgZClbBh3XzfwysSwH4injljeMreKaZyxWydthkhXAdjSbFfEtrvfHt1SN65PmAHEOux+dOVvJRiOaDw==}
|
||||
dependencies:
|
||||
qrcode-generator: 1.4.4
|
||||
dev: false
|
||||
|
||||
/prelude-ls@1.2.1:
|
||||
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
@ -7680,6 +7692,15 @@ packages:
|
|||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/qrcode-generator@1.4.4:
|
||||
resolution: {integrity: sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw==}
|
||||
dev: false
|
||||
|
||||
/qrcode-terminal-nooctal@0.12.1:
|
||||
resolution: {integrity: sha512-jy/kkD0iIMDjTucB+5T6KBsnirlhegDH47vHgrj5MejchSQmi/EAMM0xMFeePgV9CJkkAapNakpVUWYgHvtdKg==}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/qrcode@1.5.1:
|
||||
resolution: {integrity: sha512-nS8NJ1Z3md8uTjKtP+SGGhfqmTCs5flU/xR623oI0JX+Wepz9R8UrRVCTBTJm3qGw3rH6jJ6MUHjkDx15cxSSg==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
@ -9158,8 +9179,8 @@ packages:
|
|||
vue: 3.3.4
|
||||
dev: false
|
||||
|
||||
/vue-demi@0.14.7(vue@3.3.4):
|
||||
resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==}
|
||||
/vue-demi@0.14.8(vue@3.3.4):
|
||||
resolution: {integrity: sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==}
|
||||
engines: {node: '>=12'}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
|
@ -9449,6 +9470,7 @@ packages:
|
|||
|
||||
/workbox-google-analytics@7.0.0:
|
||||
resolution: {integrity: sha512-MEYM1JTn/qiC3DbpvP2BVhyIH+dV/5BjHk756u9VbwuAhu0QHyKscTnisQuz21lfRpOwiS9z4XdqeVAKol0bzg==}
|
||||
deprecated: It is not compatible with newer versions of GA starting with v4, as long as you are using GAv3 it should be ok, but the package is not longer being maintained
|
||||
dependencies:
|
||||
workbox-background-sync: 7.0.0
|
||||
workbox-core: 7.0.0
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { extension as getExtensionFromMimeType, extension as getMimeTypeFromExtension } from 'mime-types';
|
||||
import type { Ref } from 'vue';
|
||||
import type { MaybeRef, Ref } from 'vue';
|
||||
import _ from 'lodash';
|
||||
import { get } from '@vueuse/core';
|
||||
|
||||
export {
|
||||
getMimeTypeFromBase64,
|
||||
|
@ -75,21 +76,11 @@ function downloadFromBase64({ sourceValue, filename, extension, fileMimeType }:
|
|||
}
|
||||
|
||||
function useDownloadFileFromBase64(
|
||||
{ source, filename, extension, fileMimeType }:
|
||||
{ source: Ref<string>; filename?: string; extension?: string; fileMimeType?: string }) {
|
||||
return {
|
||||
download() {
|
||||
downloadFromBase64({ sourceValue: source.value, filename, extension, fileMimeType });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function useDownloadFileFromBase64Refs(
|
||||
{ source, filename, extension }:
|
||||
{ source: Ref<string>; filename?: Ref<string>; extension?: Ref<string> }) {
|
||||
{ source: MaybeRef<string>; filename?: MaybeRef<string>; extension?: MaybeRef<string> }) {
|
||||
return {
|
||||
download() {
|
||||
downloadFromBase64({ sourceValue: source.value, filename: filename?.value, extension: extension?.value });
|
||||
downloadFromBase64({ sourceValue: get(source), filename: get(filename), extension: get(extension) });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -116,3 +107,13 @@ function previewImageFromBase64(base64String: string): HTMLImageElement {
|
|||
|
||||
return img;
|
||||
}
|
||||
|
||||
function useDownloadFileFromBase64Refs(
|
||||
{ source, filename, extension }:
|
||||
{ source: Ref<string>; filename?: Ref<string>; extension?: Ref<string> }) {
|
||||
return {
|
||||
download() {
|
||||
downloadFromBase64({ sourceValue: source.value, filename: filename?.value, extension: extension?.value });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { useRouteQuery } from '@vueuse/router';
|
||||
import { computed } from 'vue';
|
||||
import { useStorage } from '@vueuse/core';
|
||||
|
||||
export { useQueryParam };
|
||||
export { useQueryParam, useQueryParamOrStorage };
|
||||
|
||||
const transformers = {
|
||||
number: {
|
||||
|
@ -16,6 +17,12 @@ const transformers = {
|
|||
fromQuery: (value: string) => value.toLowerCase() === 'true',
|
||||
toQuery: (value: boolean) => (value ? 'true' : 'false'),
|
||||
},
|
||||
object: {
|
||||
fromQuery: (value: string) => {
|
||||
return JSON.parse(value);
|
||||
},
|
||||
toQuery: (value: object) => JSON.stringify(value),
|
||||
},
|
||||
};
|
||||
|
||||
function useQueryParam<T>({ name, defaultValue }: { name: string; defaultValue: T }) {
|
||||
|
@ -33,3 +40,27 @@ function useQueryParam<T>({ name, defaultValue }: { name: string; defaultValue:
|
|||
},
|
||||
});
|
||||
}
|
||||
|
||||
function useQueryParamOrStorage<T>({ name, storageName, defaultValue }: { name: string; storageName: string; defaultValue: T }) {
|
||||
const type = typeof defaultValue;
|
||||
const transformer = transformers[type as keyof typeof transformers] ?? transformers.string;
|
||||
|
||||
const storageRef = useStorage(storageName, defaultValue);
|
||||
const proxyDefaultValue = transformer.toQuery(defaultValue as never);
|
||||
const proxy = useRouteQuery(name, proxyDefaultValue);
|
||||
|
||||
const r = ref(defaultValue);
|
||||
|
||||
watch(r,
|
||||
(value) => {
|
||||
proxy.value = transformer.toQuery(value as never);
|
||||
storageRef.value = value as never;
|
||||
},
|
||||
{ deep: true });
|
||||
|
||||
r.value = (proxy.value && proxy.value !== proxyDefaultValue
|
||||
? transformer.fromQuery(proxy.value) as unknown as T
|
||||
: storageRef.value as T) as never;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
|
|
@ -1,26 +1,89 @@
|
|||
<script setup lang="ts">
|
||||
import type { QRCodeErrorCorrectionLevel } from 'qrcode';
|
||||
import { useQRCode } from './useQRCode';
|
||||
import type {
|
||||
CornerDotType,
|
||||
CornerSquareType,
|
||||
DotType,
|
||||
ErrorCorrectionLevel,
|
||||
FileExtension,
|
||||
} from 'pp-qr-code';
|
||||
import qrcodeConsole from 'qrcode-terminal-nooctal';
|
||||
import { useQRCodeStyling } from './useQRCode';
|
||||
import { useDownloadFileFromBase64 } from '@/composable/downloadBase64';
|
||||
import { useQueryParamOrStorage } from '@/composable/queryParams';
|
||||
|
||||
const foreground = ref('#000000ff');
|
||||
const background = ref('#ffffffff');
|
||||
const errorCorrectionLevel = ref<QRCodeErrorCorrectionLevel>('medium');
|
||||
const foreground = useQueryParamOrStorage({ name: 'fg', storageName: 'qr-code-gen:fg', defaultValue: '#000000ff' });
|
||||
const background = useQueryParamOrStorage({ name: 'bg', storageName: 'qr-code-gen:bg', defaultValue: '#ffffffff' });
|
||||
const errorCorrectionLevelSelectValue = useQueryParamOrStorage<string>({ name: 'level', storageName: 'qr-code-gen:level', defaultValue: 'medium' });
|
||||
const errorCorrectionLevel = computed(() => errorCorrectionLevelSelectValue.value.toString()[0].toUpperCase() as ErrorCorrectionLevel);
|
||||
const width = useQueryParamOrStorage({ name: 'width', storageName: 'qr-code-gen:width', defaultValue: 1024 });
|
||||
const margin = useQueryParamOrStorage({ name: 'margin', storageName: 'qr-code-gen:margin', defaultValue: 10 });
|
||||
const imageSize = useQueryParamOrStorage({ name: 'imgsize', storageName: 'qr-code-gen:imsz', defaultValue: 0.4 });
|
||||
const imageMargin = useQueryParamOrStorage({ name: 'imgmargin', storageName: 'qr-code-gen:immg', defaultValue: 20 });
|
||||
const outputType = useQueryParamOrStorage<FileExtension>({ name: 'out', storageName: 'qr-code-gen:out', defaultValue: 'png' });
|
||||
const dotType = useQueryParamOrStorage<DotType>({ name: 'dot', storageName: 'qr-code-gen:dot', defaultValue: 'square' });
|
||||
const dotColor = useQueryParamOrStorage<string>({ name: 'dotc', storageName: 'qr-code-gen:dotc', defaultValue: '#ffffffff' });
|
||||
const cornersDotType = useQueryParamOrStorage<CornerDotType>({ name: 'cdt', storageName: 'qr-code-gen:cdt', defaultValue: 'square' });
|
||||
const cornersDotColor = useQueryParamOrStorage<string>({ name: 'cdtc', storageName: 'qr-code-gen:cdtc', defaultValue: '#ffffffff' });
|
||||
const cornersSquareType = useQueryParamOrStorage<CornerSquareType>({ name: 'cst', storageName: 'qr-code-gen:cst', defaultValue: 'square' });
|
||||
const cornersSquareColor = useQueryParamOrStorage<string>({ name: 'cstc', storageName: 'qr-code-gen:cstc', defaultValue: '#ffffffff' });
|
||||
const smallTerminal = useQueryParamOrStorage<boolean>({ name: 'sml', storageName: 'qr-code-gen:sml', defaultValue: false });
|
||||
const fileInput = ref() as Ref<File>;
|
||||
const { base64: imageBase64 } = useBase64(fileInput);
|
||||
async function onUpload(file: File) {
|
||||
if (file) {
|
||||
fileInput.value = file;
|
||||
}
|
||||
}
|
||||
|
||||
const errorCorrectionLevels = ['low', 'medium', 'quartile', 'high'];
|
||||
const outputTypes = ['svg', 'png', 'jpeg', 'webp'];
|
||||
const dotTypes = ['dots',
|
||||
'random-dots',
|
||||
'rounded',
|
||||
'vertical-lines',
|
||||
'horizontal-lines',
|
||||
'classy',
|
||||
'classy-rounded',
|
||||
'square',
|
||||
'extra-rounded'];
|
||||
const cornersDotTypes = ['dot', 'square', 'heart'];
|
||||
const cornersSquareTypes = ['dot', 'square', 'extra-rounded'];
|
||||
|
||||
const text = ref('https://it-tools.tech');
|
||||
const { qrcode } = useQRCode({
|
||||
const { qrcode } = useQRCodeStyling({
|
||||
text,
|
||||
color: {
|
||||
background,
|
||||
foreground,
|
||||
},
|
||||
color: { background, foreground },
|
||||
errorCorrectionLevel,
|
||||
options: { width: 1024 },
|
||||
imageBase64,
|
||||
imageOptions: { imageSize, margin: imageMargin },
|
||||
dotOptions: { type: dotType, color: dotColor },
|
||||
cornersSquareOptions: { type: cornersSquareType, color: cornersSquareColor },
|
||||
cornersDotOptions: { type: cornersDotType, color: cornersDotColor },
|
||||
outputType,
|
||||
width,
|
||||
margin,
|
||||
});
|
||||
|
||||
const { download } = useDownloadFileFromBase64({ source: qrcode, filename: 'qr-code.png' });
|
||||
const qrcodeTerminal = computedAsync(() => {
|
||||
const textValue = text.value;
|
||||
const level = errorCorrectionLevel.value;
|
||||
const small = smallTerminal.value;
|
||||
return new Promise<string>((resolve, _reject) => {
|
||||
try {
|
||||
qrcodeConsole.setErrorLevel(level);
|
||||
qrcodeConsole.generate(textValue, { small }, (qrcode: string) => {
|
||||
resolve(qrcode);
|
||||
});
|
||||
}
|
||||
catch (_) {
|
||||
resolve('');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const filename = ref('qr-code');
|
||||
const extension = computed(() => outputType.value.toString());
|
||||
const { download } = useDownloadFileFromBase64({ source: qrcode, filename, extension });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -46,8 +109,14 @@ const { download } = useDownloadFileFromBase64({ source: qrcode, filename: 'qr-c
|
|||
<n-form-item label="Background color:">
|
||||
<n-color-picker v-model:value="background" :modes="['hex']" />
|
||||
</n-form-item>
|
||||
<n-form-item label="Width:">
|
||||
<n-input-number v-model:value="width" :min="0" />
|
||||
</n-form-item>
|
||||
<n-form-item label="Margin:">
|
||||
<n-input-number v-model:value="margin" :min="0" />
|
||||
</n-form-item>
|
||||
<c-select
|
||||
v-model:value="errorCorrectionLevel"
|
||||
v-model:value="errorCorrectionLevelSelectValue"
|
||||
label="Error resistance:"
|
||||
label-position="left"
|
||||
label-width="130px"
|
||||
|
@ -55,14 +124,104 @@ const { download } = useDownloadFileFromBase64({ source: qrcode, filename: 'qr-c
|
|||
:options="errorCorrectionLevels.map((value) => ({ label: value, value }))"
|
||||
/>
|
||||
</n-form>
|
||||
<c-card title="Image" mt-3>
|
||||
<c-file-upload title="Drag and drop an image here, or click to select an image" @file-upload="onUpload" />
|
||||
|
||||
<n-form label-width="130" label-placement="left" mt-3>
|
||||
<n-form-item label="Size:">
|
||||
<n-input-number v-model:value="imageSize" :min="0" step="0.1" />
|
||||
</n-form-item>
|
||||
<n-form-item label="Margin:">
|
||||
<n-input-number v-model:value="imageMargin" :min="0" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</c-card>
|
||||
<c-card mt-3>
|
||||
<details>
|
||||
<summary>Dots Options</summary>
|
||||
<n-form label-width="130" label-placement="left">
|
||||
<n-form-item label="Color:">
|
||||
<n-color-picker v-model:value="dotColor" :modes="['hex']" />
|
||||
</n-form-item>
|
||||
<c-select
|
||||
v-model:value="dotType"
|
||||
label="Type:"
|
||||
label-position="left"
|
||||
label-width="130px"
|
||||
label-align="right"
|
||||
:options="dotTypes.map((value) => ({ label: value, value }))"
|
||||
/>
|
||||
</n-form>
|
||||
</details>
|
||||
</c-card>
|
||||
<c-card mt-3>
|
||||
<details>
|
||||
<summary>Corners Dots Options</summary>
|
||||
<n-form label-width="130" label-placement="left">
|
||||
<n-form-item label="Color:">
|
||||
<n-color-picker v-model:value="cornersDotColor" :modes="['hex']" />
|
||||
</n-form-item>
|
||||
<c-select
|
||||
v-model:value="cornersDotType"
|
||||
label="Type:"
|
||||
label-position="left"
|
||||
label-width="130px"
|
||||
label-align="right"
|
||||
:options="cornersDotTypes.map((value) => ({ label: value, value }))"
|
||||
/>
|
||||
</n-form>
|
||||
</details>
|
||||
</c-card>
|
||||
<c-card mt-3>
|
||||
<details>
|
||||
<summary>Corners Square Options</summary>
|
||||
<n-form label-width="130" label-placement="left">
|
||||
<n-form-item label="Color:">
|
||||
<n-color-picker v-model:value="cornersSquareColor" :modes="['hex']" />
|
||||
</n-form-item>
|
||||
<c-select
|
||||
v-model:value="cornersSquareType"
|
||||
label="Type:"
|
||||
label-position="left"
|
||||
label-width="130px"
|
||||
label-align="right"
|
||||
:options="cornersSquareTypes.map((value) => ({ label: value, value }))"
|
||||
/>
|
||||
</n-form>
|
||||
</details>
|
||||
</c-card>
|
||||
<c-select
|
||||
v-model:value="outputType"
|
||||
mt-3
|
||||
label="Output format:"
|
||||
label-position="left"
|
||||
label-width="130px"
|
||||
label-align="right"
|
||||
:options="outputTypes.map((value) => ({ label: value.toUpperCase(), value }))"
|
||||
/>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<div flex flex-col items-center gap-3>
|
||||
<n-image :src="qrcode" width="200" />
|
||||
<c-button @click="download">
|
||||
Download qr-code
|
||||
Download qr-code ({{ outputType.toString().toUpperCase() }})
|
||||
</c-button>
|
||||
</div>
|
||||
|
||||
<n-divider />
|
||||
|
||||
<n-checkbox v-model:checked="smallTerminal">
|
||||
Small Terminal
|
||||
</n-checkbox>
|
||||
<n-form-item label="Terminal output:" mt-1>
|
||||
<TextareaCopyable
|
||||
:value="qrcodeTerminal"
|
||||
multiline
|
||||
rows="5"
|
||||
mb-1 mt-1
|
||||
copy-placement="outside"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
</c-card>
|
||||
|
|
5
src/tools/qr-code-generator/qrcode-terminal-nooctal.d.ts
vendored
Normal file
5
src/tools/qr-code-generator/qrcode-terminal-nooctal.d.ts
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
declare module 'qrcode-terminal-nooctal' {
|
||||
export const error: 0 | 1 | 2 | 3;
|
||||
export function generate(input: string, opts?: { small: boolean }, callback?: (qrcode: string) => void): void;
|
||||
export function setErrorLevel(error: "L" | "M" | "Q" | "H"): void;
|
||||
}
|
|
@ -1,33 +1,182 @@
|
|||
import { type MaybeRef, get } from '@vueuse/core';
|
||||
import QRCode, { type QRCodeErrorCorrectionLevel, type QRCodeToDataURLOptions } from 'qrcode';
|
||||
import QRCode, { type QRCodeDataURLType, type QRCodeErrorCorrectionLevel, type QRCodeRenderersOptions, type QRCodeStringType } from 'qrcode';
|
||||
import { isRef, ref, watch } from 'vue';
|
||||
import type {
|
||||
CornerDotType,
|
||||
CornerSquareType,
|
||||
DotType,
|
||||
DrawType,
|
||||
ErrorCorrectionLevel,
|
||||
FileExtension,
|
||||
Mode,
|
||||
TypeNumber,
|
||||
} from 'pp-qr-code';
|
||||
import QRCodeStyling from 'pp-qr-code';
|
||||
|
||||
export function useQRCode({
|
||||
function blobToBase64(blob: Blob | null): Promise<string> {
|
||||
if (blob === null) {
|
||||
return Promise.resolve('');
|
||||
}
|
||||
return new Promise((resolve, _reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => resolve(reader.result as string);
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
}
|
||||
|
||||
export function useQRCodeStyling({
|
||||
text,
|
||||
color: { background, foreground },
|
||||
errorCorrectionLevel,
|
||||
options,
|
||||
imageBase64,
|
||||
imageOptions: { imageSize, margin: imageMargin },
|
||||
dotOptions: { type: dotType, color: dotColor },
|
||||
cornersSquareOptions: { type: cornersSquareType, color: cornersSquareColor },
|
||||
cornersDotOptions: { type: cornersDotType, color: cornersDotColor },
|
||||
outputType,
|
||||
width,
|
||||
margin,
|
||||
}: {
|
||||
text: MaybeRef<string>
|
||||
color: { foreground: MaybeRef<string>; background: MaybeRef<string> }
|
||||
errorCorrectionLevel?: MaybeRef<QRCodeErrorCorrectionLevel>
|
||||
options?: QRCodeToDataURLOptions
|
||||
outputType: MaybeRef<FileExtension>
|
||||
errorCorrectionLevel?: MaybeRef<ErrorCorrectionLevel>
|
||||
imageBase64?: MaybeRef<string>
|
||||
imageOptions: { imageSize: MaybeRef<number>; margin: MaybeRef<number> }
|
||||
dotOptions: { type: MaybeRef<DotType>; color: MaybeRef<string> }
|
||||
cornersSquareOptions: { type: MaybeRef<CornerSquareType>; color: MaybeRef<string> }
|
||||
cornersDotOptions: { type: MaybeRef<CornerDotType>; color: MaybeRef<string> }
|
||||
width?: MaybeRef<number>
|
||||
margin?: MaybeRef<number>
|
||||
}) {
|
||||
const qrcode = ref('');
|
||||
|
||||
watch(
|
||||
[text, background, foreground, errorCorrectionLevel].filter(isRef),
|
||||
[text, background, foreground, errorCorrectionLevel,
|
||||
imageBase64, imageSize, imageMargin,
|
||||
dotType, dotColor, cornersSquareType, cornersSquareColor,
|
||||
cornersDotType, cornersDotColor,
|
||||
outputType, width, margin].filter(isRef),
|
||||
async () => {
|
||||
if (get(text)) {
|
||||
qrcode.value = await QRCode.toDataURL(get(text).trim(), {
|
||||
color: {
|
||||
dark: get(foreground),
|
||||
light: get(background),
|
||||
...options?.color,
|
||||
const qrCodeText = get(text)?.trim();
|
||||
const qrCodeOutputType = get(outputType) || 'png';
|
||||
if (qrCodeText) {
|
||||
const getOrForeground = (colorRef: MaybeRef<string>) => {
|
||||
const color = get(colorRef) || get(foreground);
|
||||
if (color?.toLowerCase() === '#ffffffff') {
|
||||
return get(foreground);
|
||||
}
|
||||
return color;
|
||||
};
|
||||
const qrCodeOptions = {
|
||||
width: get(width) ?? 1024,
|
||||
height: get(width) ?? 1024,
|
||||
type: 'svg' as DrawType,
|
||||
data: qrCodeText,
|
||||
image: get(imageBase64),
|
||||
margin: get(margin) || 10,
|
||||
qrOptions: {
|
||||
typeNumber: 0 as TypeNumber,
|
||||
mode: 'Byte' as Mode,
|
||||
errorCorrectionLevel: get(errorCorrectionLevel) || 'Q',
|
||||
},
|
||||
errorCorrectionLevel: get(errorCorrectionLevel) ?? 'M',
|
||||
...options,
|
||||
});
|
||||
imageOptions: {
|
||||
hideBackgroundDots: true,
|
||||
imageSize: get(imageSize) || 0.4,
|
||||
margin: get(imageMargin) || 20,
|
||||
crossOrigin: 'anonymous',
|
||||
},
|
||||
dotsOptions: {
|
||||
color: getOrForeground(dotColor),
|
||||
// gradient: {
|
||||
// type: 'linear', // 'radial'
|
||||
// rotation: 0,
|
||||
// colorStops: [{ offset: 0, color: '#8688B2' }, { offset: 1, color: '#77779C' }]
|
||||
// },
|
||||
type: get(dotType) || 'square',
|
||||
},
|
||||
backgroundOptions: {
|
||||
color: get(background),
|
||||
// gradient: {
|
||||
// type: 'linear', // 'radial'
|
||||
// rotation: 0,
|
||||
// colorStops: [{ offset: 0, color: '#ededff' }, { offset: 1, color: '#e6e7ff' }]
|
||||
// },
|
||||
},
|
||||
cornersSquareOptions: {
|
||||
color: getOrForeground(cornersSquareColor),
|
||||
type: get(cornersSquareType) || 'extra-rounded',
|
||||
// gradient: {
|
||||
// type: 'linear', // 'radial'
|
||||
// rotation: 180,
|
||||
// colorStops: [{ offset: 0, color: '#25456e' }, { offset: 1, color: '#4267b2' }]
|
||||
// },
|
||||
},
|
||||
cornersDotOptions: {
|
||||
color: getOrForeground(cornersDotColor),
|
||||
type: get(cornersDotType) || 'dot',
|
||||
// gradient: {
|
||||
// type: 'linear', // 'radial'
|
||||
// rotation: 180,
|
||||
// colorStops: [{ offset: 0, color: '#00266e' }, { offset: 1, color: '#4060b3' }]
|
||||
// },
|
||||
},
|
||||
};
|
||||
|
||||
const qrGenerator = new QRCodeStyling(qrCodeOptions);
|
||||
qrcode.value = await blobToBase64(await qrGenerator.getRawData(qrCodeOutputType));
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
return { qrcode };
|
||||
}
|
||||
|
||||
export function useQRCode<QRCodeOptions extends QRCodeRenderersOptions>({
|
||||
text,
|
||||
color: { background, foreground },
|
||||
errorCorrectionLevel,
|
||||
outputType,
|
||||
width,
|
||||
options,
|
||||
}: {
|
||||
text: MaybeRef<string>
|
||||
color: { foreground: MaybeRef<string>; background: MaybeRef<string> }
|
||||
errorCorrectionLevel?: MaybeRef<QRCodeErrorCorrectionLevel>
|
||||
outputType?: MaybeRef<QRCodeDataURLType | QRCodeStringType>
|
||||
width?: MaybeRef<number>
|
||||
options?: QRCodeOptions
|
||||
}) {
|
||||
const qrcode = ref('');
|
||||
|
||||
watch(
|
||||
[text, background, foreground, errorCorrectionLevel, outputType, width].filter(isRef),
|
||||
async () => {
|
||||
const qrCodeText = get(text)?.trim();
|
||||
const qrCodeType = get(outputType) || '';
|
||||
if (qrCodeText) {
|
||||
const qrCodeOptions = {
|
||||
color: {
|
||||
dark: get(foreground),
|
||||
light: get(background),
|
||||
...options?.color,
|
||||
},
|
||||
errorCorrectionLevel: get(errorCorrectionLevel) ?? 'M',
|
||||
width: get(width) ?? 1024,
|
||||
...options,
|
||||
};
|
||||
if (['utf8', 'svg', 'terminal'].includes(qrCodeType)) {
|
||||
qrcode.value = await QRCode.toString(qrCodeText, {
|
||||
type: qrCodeType as QRCodeStringType,
|
||||
});
|
||||
}
|
||||
else {
|
||||
qrcode.value = await QRCode.toDataURL(qrCodeText, {
|
||||
...qrCodeOptions,
|
||||
type: qrCodeType as QRCodeDataURLType,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue