it-tools/src/tools/qr-code-generator/qr-code-generator.vue
2024-07-14 23:33:38 +02:00

229 lines
8.8 KiB
Vue

<script setup lang="ts">
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 = 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 } = useQRCodeStyling({
text,
color: { background, foreground },
errorCorrectionLevel,
imageBase64,
imageOptions: { imageSize, margin: imageMargin },
dotOptions: { type: dotType, color: dotColor },
cornersSquareOptions: { type: cornersSquareType, color: cornersSquareColor },
cornersDotOptions: { type: cornersDotType, color: cornersDotColor },
outputType,
width,
margin,
});
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>
<c-card>
<n-grid x-gap="12" y-gap="12" cols="1 600:3">
<n-gi span="2">
<c-input-text
v-model:value="text"
label-position="left"
label-width="130px"
label-align="right"
label="Text:"
multiline
rows="1"
autosize
placeholder="Your link or text..."
mb-6
/>
<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-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="errorCorrectionLevelSelectValue"
label="Error resistance:"
label-position="left"
label-width="130px"
label-align="right"
: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 ({{ 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>
</template>