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']
|
MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default']
|
||||||
MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default']
|
MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default']
|
||||||
NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default']
|
NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default']
|
||||||
|
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
||||||
NCode: typeof import('naive-ui')['NCode']
|
NCode: typeof import('naive-ui')['NCode']
|
||||||
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
|
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
|
||||||
|
NColorPicker: typeof import('naive-ui')['NColorPicker']
|
||||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||||
NDivider: typeof import('naive-ui')['NDivider']
|
NDivider: typeof import('naive-ui')['NDivider']
|
||||||
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
||||||
|
NForm: typeof import('naive-ui')['NForm']
|
||||||
NFormItem: typeof import('naive-ui')['NFormItem']
|
NFormItem: typeof import('naive-ui')['NFormItem']
|
||||||
NGi: typeof import('naive-ui')['NGi']
|
NGi: typeof import('naive-ui')['NGi']
|
||||||
NGrid: typeof import('naive-ui')['NGrid']
|
NGrid: typeof import('naive-ui')['NGrid']
|
||||||
NH1: typeof import('naive-ui')['NH1']
|
NH1: typeof import('naive-ui')['NH1']
|
||||||
NH3: typeof import('naive-ui')['NH3']
|
NH3: typeof import('naive-ui')['NH3']
|
||||||
NIcon: typeof import('naive-ui')['NIcon']
|
NIcon: typeof import('naive-ui')['NIcon']
|
||||||
|
NImage: typeof import('naive-ui')['NImage']
|
||||||
NInputNumber: typeof import('naive-ui')['NInputNumber']
|
NInputNumber: typeof import('naive-ui')['NInputNumber']
|
||||||
NLabel: typeof import('naive-ui')['NLabel']
|
|
||||||
NLayout: typeof import('naive-ui')['NLayout']
|
NLayout: typeof import('naive-ui')['NLayout']
|
||||||
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
|
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
|
||||||
NMenu: typeof import('naive-ui')['NMenu']
|
NMenu: typeof import('naive-ui')['NMenu']
|
||||||
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
||||||
NSpin: typeof import('naive-ui')['NSpin']
|
|
||||||
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
|
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']
|
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']
|
PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default']
|
||||||
|
|
|
@ -80,7 +80,9 @@
|
||||||
"pdf-signature-reader": "^1.4.2",
|
"pdf-signature-reader": "^1.4.2",
|
||||||
"pinia": "^2.0.34",
|
"pinia": "^2.0.34",
|
||||||
"plausible-tracker": "^0.3.8",
|
"plausible-tracker": "^0.3.8",
|
||||||
|
"pp-qr-code": "^0.6.3",
|
||||||
"qrcode": "^1.5.1",
|
"qrcode": "^1.5.1",
|
||||||
|
"qrcode-terminal-nooctal": "^0.12.1",
|
||||||
"sql-formatter": "^13.0.0",
|
"sql-formatter": "^13.0.0",
|
||||||
"ua-parser-js": "^1.0.35",
|
"ua-parser-js": "^1.0.35",
|
||||||
"ulid": "^2.3.0",
|
"ulid": "^2.3.0",
|
||||||
|
|
40
pnpm-lock.yaml
generated
40
pnpm-lock.yaml
generated
|
@ -140,9 +140,15 @@ dependencies:
|
||||||
plausible-tracker:
|
plausible-tracker:
|
||||||
specifier: ^0.3.8
|
specifier: ^0.3.8
|
||||||
version: 0.3.8
|
version: 0.3.8
|
||||||
|
pp-qr-code:
|
||||||
|
specifier: ^0.6.3
|
||||||
|
version: 0.6.4
|
||||||
qrcode:
|
qrcode:
|
||||||
specifier: ^1.5.1
|
specifier: ^1.5.1
|
||||||
version: 1.5.1
|
version: 1.5.1
|
||||||
|
qrcode-terminal-nooctal:
|
||||||
|
specifier: ^0.12.1
|
||||||
|
version: 0.12.1
|
||||||
sql-formatter:
|
sql-formatter:
|
||||||
specifier: ^13.0.0
|
specifier: ^13.0.0
|
||||||
version: 13.0.0
|
version: 13.0.0
|
||||||
|
@ -3354,7 +3360,7 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@unhead/dom': 0.5.1
|
'@unhead/dom': 0.5.1
|
||||||
'@unhead/schema': 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
|
unhead: 0.5.1
|
||||||
vue: 3.3.4
|
vue: 3.3.4
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
|
@ -3987,19 +3993,19 @@ packages:
|
||||||
- vue
|
- vue
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@vueuse/shared@10.3.0(vue@3.3.4):
|
/@vueuse/shared@10.11.0(vue@3.3.4):
|
||||||
resolution: {integrity: sha512-kGqCTEuFPMK4+fNWy6dUOiYmxGcUbtznMwBZLC1PubidF4VZY05B+Oht7Jh7/6x4VOWGpvu3R37WHi81cKpiqg==}
|
resolution: {integrity: sha512-fyNoIXEq3PfX1L3NkNhtVQUSRtqYwJtJg+Bp9rIzculIZWHTkKSysujrOk2J+NrRulLTQH9+3gGSfYLWSEWU1A==}
|
||||||
dependencies:
|
dependencies:
|
||||||
vue-demi: 0.14.5(vue@3.3.4)
|
vue-demi: 0.14.8(vue@3.3.4)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@vue/composition-api'
|
- '@vue/composition-api'
|
||||||
- vue
|
- vue
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@vueuse/shared@10.8.0(vue@3.3.4):
|
/@vueuse/shared@10.3.0(vue@3.3.4):
|
||||||
resolution: {integrity: sha512-dUdy6zwHhULGxmr9YUg8e+EnB39gcM4Fe2oKBSrh3cOsV30JcMPtsyuspgFCUo5xxFNaeMf/W2yyKfST7Bg8oQ==}
|
resolution: {integrity: sha512-kGqCTEuFPMK4+fNWy6dUOiYmxGcUbtznMwBZLC1PubidF4VZY05B+Oht7Jh7/6x4VOWGpvu3R37WHi81cKpiqg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
vue-demi: 0.14.7(vue@3.3.4)
|
vue-demi: 0.14.5(vue@3.3.4)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@vue/composition-api'
|
- '@vue/composition-api'
|
||||||
- vue
|
- vue
|
||||||
|
@ -7470,6 +7476,12 @@ packages:
|
||||||
source-map-js: 1.0.2
|
source-map-js: 1.0.2
|
||||||
dev: true
|
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:
|
/prelude-ls@1.2.1:
|
||||||
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
@ -7680,6 +7692,15 @@ packages:
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
dev: true
|
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:
|
/qrcode@1.5.1:
|
||||||
resolution: {integrity: sha512-nS8NJ1Z3md8uTjKtP+SGGhfqmTCs5flU/xR623oI0JX+Wepz9R8UrRVCTBTJm3qGw3rH6jJ6MUHjkDx15cxSSg==}
|
resolution: {integrity: sha512-nS8NJ1Z3md8uTjKtP+SGGhfqmTCs5flU/xR623oI0JX+Wepz9R8UrRVCTBTJm3qGw3rH6jJ6MUHjkDx15cxSSg==}
|
||||||
engines: {node: '>=10.13.0'}
|
engines: {node: '>=10.13.0'}
|
||||||
|
@ -9158,8 +9179,8 @@ packages:
|
||||||
vue: 3.3.4
|
vue: 3.3.4
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/vue-demi@0.14.7(vue@3.3.4):
|
/vue-demi@0.14.8(vue@3.3.4):
|
||||||
resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==}
|
resolution: {integrity: sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
|
@ -9449,6 +9470,7 @@ packages:
|
||||||
|
|
||||||
/workbox-google-analytics@7.0.0:
|
/workbox-google-analytics@7.0.0:
|
||||||
resolution: {integrity: sha512-MEYM1JTn/qiC3DbpvP2BVhyIH+dV/5BjHk756u9VbwuAhu0QHyKscTnisQuz21lfRpOwiS9z4XdqeVAKol0bzg==}
|
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:
|
dependencies:
|
||||||
workbox-background-sync: 7.0.0
|
workbox-background-sync: 7.0.0
|
||||||
workbox-core: 7.0.0
|
workbox-core: 7.0.0
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { extension as getExtensionFromMimeType, extension as getMimeTypeFromExtension } from 'mime-types';
|
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 _ from 'lodash';
|
||||||
|
import { get } from '@vueuse/core';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
getMimeTypeFromBase64,
|
getMimeTypeFromBase64,
|
||||||
|
@ -75,21 +76,11 @@ function downloadFromBase64({ sourceValue, filename, extension, fileMimeType }:
|
||||||
}
|
}
|
||||||
|
|
||||||
function useDownloadFileFromBase64(
|
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, filename, extension }:
|
||||||
{ source: Ref<string>; filename?: Ref<string>; extension?: Ref<string> }) {
|
{ source: MaybeRef<string>; filename?: MaybeRef<string>; extension?: MaybeRef<string> }) {
|
||||||
return {
|
return {
|
||||||
download() {
|
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;
|
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 { useRouteQuery } from '@vueuse/router';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
import { useStorage } from '@vueuse/core';
|
||||||
|
|
||||||
export { useQueryParam };
|
export { useQueryParam, useQueryParamOrStorage };
|
||||||
|
|
||||||
const transformers = {
|
const transformers = {
|
||||||
number: {
|
number: {
|
||||||
|
@ -16,6 +17,12 @@ const transformers = {
|
||||||
fromQuery: (value: string) => value.toLowerCase() === 'true',
|
fromQuery: (value: string) => value.toLowerCase() === 'true',
|
||||||
toQuery: (value: boolean) => (value ? 'true' : 'false'),
|
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 }) {
|
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">
|
<script setup lang="ts">
|
||||||
import type { QRCodeErrorCorrectionLevel } from 'qrcode';
|
import type {
|
||||||
import { useQRCode } from './useQRCode';
|
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 { useDownloadFileFromBase64 } from '@/composable/downloadBase64';
|
||||||
|
import { useQueryParamOrStorage } from '@/composable/queryParams';
|
||||||
|
|
||||||
const foreground = ref('#000000ff');
|
const foreground = useQueryParamOrStorage({ name: 'fg', storageName: 'qr-code-gen:fg', defaultValue: '#000000ff' });
|
||||||
const background = ref('#ffffffff');
|
const background = useQueryParamOrStorage({ name: 'bg', storageName: 'qr-code-gen:bg', defaultValue: '#ffffffff' });
|
||||||
const errorCorrectionLevel = ref<QRCodeErrorCorrectionLevel>('medium');
|
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 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 text = ref('https://it-tools.tech');
|
||||||
const { qrcode } = useQRCode({
|
const { qrcode } = useQRCodeStyling({
|
||||||
text,
|
text,
|
||||||
color: {
|
color: { background, foreground },
|
||||||
background,
|
|
||||||
foreground,
|
|
||||||
},
|
|
||||||
errorCorrectionLevel,
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -46,8 +109,14 @@ const { download } = useDownloadFileFromBase64({ source: qrcode, filename: 'qr-c
|
||||||
<n-form-item label="Background color:">
|
<n-form-item label="Background color:">
|
||||||
<n-color-picker v-model:value="background" :modes="['hex']" />
|
<n-color-picker v-model:value="background" :modes="['hex']" />
|
||||||
</n-form-item>
|
</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
|
<c-select
|
||||||
v-model:value="errorCorrectionLevel"
|
v-model:value="errorCorrectionLevelSelectValue"
|
||||||
label="Error resistance:"
|
label="Error resistance:"
|
||||||
label-position="left"
|
label-position="left"
|
||||||
label-width="130px"
|
label-width="130px"
|
||||||
|
@ -55,14 +124,104 @@ const { download } = useDownloadFileFromBase64({ source: qrcode, filename: 'qr-c
|
||||||
:options="errorCorrectionLevels.map((value) => ({ label: value, value }))"
|
:options="errorCorrectionLevels.map((value) => ({ label: value, value }))"
|
||||||
/>
|
/>
|
||||||
</n-form>
|
</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>
|
||||||
<n-gi>
|
<n-gi>
|
||||||
<div flex flex-col items-center gap-3>
|
<div flex flex-col items-center gap-3>
|
||||||
<n-image :src="qrcode" width="200" />
|
<n-image :src="qrcode" width="200" />
|
||||||
<c-button @click="download">
|
<c-button @click="download">
|
||||||
Download qr-code
|
Download qr-code ({{ outputType.toString().toUpperCase() }})
|
||||||
</c-button>
|
</c-button>
|
||||||
</div>
|
</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-gi>
|
||||||
</n-grid>
|
</n-grid>
|
||||||
</c-card>
|
</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 { 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 { 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,
|
text,
|
||||||
color: { background, foreground },
|
color: { background, foreground },
|
||||||
errorCorrectionLevel,
|
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>
|
text: MaybeRef<string>
|
||||||
color: { foreground: MaybeRef<string>; background: MaybeRef<string> }
|
color: { foreground: MaybeRef<string>; background: MaybeRef<string> }
|
||||||
errorCorrectionLevel?: MaybeRef<QRCodeErrorCorrectionLevel>
|
outputType: MaybeRef<FileExtension>
|
||||||
options?: QRCodeToDataURLOptions
|
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('');
|
const qrcode = ref('');
|
||||||
|
|
||||||
watch(
|
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 () => {
|
async () => {
|
||||||
if (get(text)) {
|
const qrCodeText = get(text)?.trim();
|
||||||
qrcode.value = await QRCode.toDataURL(get(text).trim(), {
|
const qrCodeOutputType = get(outputType) || 'png';
|
||||||
color: {
|
if (qrCodeText) {
|
||||||
dark: get(foreground),
|
const getOrForeground = (colorRef: MaybeRef<string>) => {
|
||||||
light: get(background),
|
const color = get(colorRef) || get(foreground);
|
||||||
...options?.color,
|
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',
|
imageOptions: {
|
||||||
...options,
|
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 },
|
{ immediate: true },
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue