Skip to content

Commit 5c5e865

Browse files
committed
Merge branch 'feat/qr-code-enh' into chore/all-my-stuffs
# Conflicts: # components.d.ts # package.json # pnpm-lock.yaml
2 parents 87de5f9 + bad0262 commit 5c5e865

File tree

5 files changed

+361
-22
lines changed

5 files changed

+361
-22
lines changed

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,9 @@
197197
"turndown": "^7.1.2",
198198
"roboto-base64": "^0.1.2",
199199
"silly-password-generator": "^1.0.28",
200+
"pp-qr-code": "^0.6.3",
201+
"qrcode": "^1.5.1",
202+
"qrcode-terminal-nooctal": "^0.12.1",
200203
"sql-formatter": "^13.0.0",
201204
"svg2png-wasm": "^1.4.1",
202205
"svg-to-url": "^4.0.0",

pnpm-lock.yaml

Lines changed: 24 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/tools/qr-code-generator/qr-code-generator.vue

Lines changed: 173 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,89 @@
11
<script setup lang="ts">
2-
import type { QRCodeErrorCorrectionLevel } from 'qrcode';
3-
import { useQRCode } from './useQRCode';
2+
import type {
3+
CornerDotType,
4+
CornerSquareType,
5+
DotType,
6+
ErrorCorrectionLevel,
7+
FileExtension,
8+
} from 'pp-qr-code';
9+
import qrcodeConsole from 'qrcode-terminal-nooctal';
10+
import { useQRCodeStyling } from './useQRCode';
411
import { useDownloadFileFromBase64 } from '@/composable/downloadBase64';
12+
import { useQueryParamOrStorage } from '@/composable/queryParams';
513
6-
const foreground = ref('#000000ff');
7-
const background = ref('#ffffffff');
8-
const errorCorrectionLevel = ref<QRCodeErrorCorrectionLevel>('medium');
14+
const foreground = useQueryParamOrStorage({ name: 'fg', storageName: 'qr-code-gen:fg', defaultValue: '#000000ff' });
15+
const background = useQueryParamOrStorage({ name: 'bg', storageName: 'qr-code-gen:bg', defaultValue: '#ffffffff' });
16+
const errorCorrectionLevelSelectValue = useQueryParamOrStorage<string>({ name: 'level', storageName: 'qr-code-gen:level', defaultValue: 'medium' });
17+
const errorCorrectionLevel = computed(() => errorCorrectionLevelSelectValue.value.toString()[0].toUpperCase() as ErrorCorrectionLevel);
18+
const width = useQueryParamOrStorage({ name: 'width', storageName: 'qr-code-gen:width', defaultValue: 1024 });
19+
const margin = useQueryParamOrStorage({ name: 'margin', storageName: 'qr-code-gen:margin', defaultValue: 10 });
20+
const imageSize = useQueryParamOrStorage({ name: 'imgsize', storageName: 'qr-code-gen:imsz', defaultValue: 0.4 });
21+
const imageMargin = useQueryParamOrStorage({ name: 'imgmargin', storageName: 'qr-code-gen:immg', defaultValue: 20 });
22+
const outputType = useQueryParamOrStorage<FileExtension>({ name: 'out', storageName: 'qr-code-gen:out', defaultValue: 'png' });
23+
const dotType = useQueryParamOrStorage<DotType>({ name: 'dot', storageName: 'qr-code-gen:dot', defaultValue: 'square' });
24+
const dotColor = useQueryParamOrStorage<string>({ name: 'dotc', storageName: 'qr-code-gen:dotc', defaultValue: '#ffffffff' });
25+
const cornersDotType = useQueryParamOrStorage<CornerDotType>({ name: 'cdt', storageName: 'qr-code-gen:cdt', defaultValue: 'square' });
26+
const cornersDotColor = useQueryParamOrStorage<string>({ name: 'cdtc', storageName: 'qr-code-gen:cdtc', defaultValue: '#ffffffff' });
27+
const cornersSquareType = useQueryParamOrStorage<CornerSquareType>({ name: 'cst', storageName: 'qr-code-gen:cst', defaultValue: 'square' });
28+
const cornersSquareColor = useQueryParamOrStorage<string>({ name: 'cstc', storageName: 'qr-code-gen:cstc', defaultValue: '#ffffffff' });
29+
const smallTerminal = useQueryParamOrStorage<boolean>({ name: 'sml', storageName: 'qr-code-gen:sml', defaultValue: false });
30+
const fileInput = ref() as Ref<File>;
31+
const { base64: imageBase64 } = useBase64(fileInput);
32+
async function onUpload(file: File) {
33+
if (file) {
34+
fileInput.value = file;
35+
}
36+
}
937
1038
const errorCorrectionLevels = ['low', 'medium', 'quartile', 'high'];
39+
const outputTypes = ['svg', 'png', 'jpeg', 'webp'];
40+
const dotTypes = ['dots',
41+
'random-dots',
42+
'rounded',
43+
'vertical-lines',
44+
'horizontal-lines',
45+
'classy',
46+
'classy-rounded',
47+
'square',
48+
'extra-rounded'];
49+
const cornersDotTypes = ['dot', 'square', 'heart'];
50+
const cornersSquareTypes = ['dot', 'square', 'extra-rounded'];
1151
1252
const text = ref('https://it-tools.tech');
13-
const { qrcode } = useQRCode({
53+
const { qrcode } = useQRCodeStyling({
1454
text,
15-
color: {
16-
background,
17-
foreground,
18-
},
55+
color: { background, foreground },
1956
errorCorrectionLevel,
20-
options: { width: 1024 },
57+
imageBase64,
58+
imageOptions: { imageSize, margin: imageMargin },
59+
dotOptions: { type: dotType, color: dotColor },
60+
cornersSquareOptions: { type: cornersSquareType, color: cornersSquareColor },
61+
cornersDotOptions: { type: cornersDotType, color: cornersDotColor },
62+
outputType,
63+
width,
64+
margin,
2165
});
2266
23-
const { download } = useDownloadFileFromBase64({ source: qrcode, filename: 'qr-code.png' });
67+
const qrcodeTerminal = computedAsync(() => {
68+
const textValue = text.value;
69+
const level = errorCorrectionLevel.value;
70+
const small = smallTerminal.value;
71+
return new Promise<string>((resolve, _reject) => {
72+
try {
73+
qrcodeConsole.setErrorLevel(level);
74+
qrcodeConsole.generate(textValue, { small }, (qrcode: string) => {
75+
resolve(qrcode);
76+
});
77+
}
78+
catch (_) {
79+
resolve('');
80+
}
81+
});
82+
});
83+
84+
const filename = ref('qr-code');
85+
const extension = computed(() => outputType.value.toString());
86+
const { download } = useDownloadFileFromBase64({ source: qrcode, filename, extension });
2487
</script>
2588

2689
<template>
@@ -46,23 +109,119 @@ const { download } = useDownloadFileFromBase64({ source: qrcode, filename: 'qr-c
46109
<n-form-item label="Background color:">
47110
<n-color-picker v-model:value="background" :modes="['hex']" />
48111
</n-form-item>
112+
<n-form-item label="Width:">
113+
<n-input-number v-model:value="width" :min="0" />
114+
</n-form-item>
115+
<n-form-item label="Margin:">
116+
<n-input-number v-model:value="margin" :min="0" />
117+
</n-form-item>
49118
<c-select
50-
v-model:value="errorCorrectionLevel"
119+
v-model:value="errorCorrectionLevelSelectValue"
51120
label="Error resistance:"
52121
label-position="left"
53122
label-width="130px"
54123
label-align="right"
55124
:options="errorCorrectionLevels.map((value) => ({ label: value, value }))"
56125
/>
57126
</n-form>
127+
<c-card title="Image" mt-3>
128+
<c-file-upload title="Drag and drop an image here, or click to select an image" @file-upload="onUpload" />
129+
130+
<n-form label-width="130" label-placement="left" mt-3>
131+
<n-form-item label="Size:">
132+
<n-input-number v-model:value="imageSize" :min="0" step="0.1" />
133+
</n-form-item>
134+
<n-form-item label="Margin:">
135+
<n-input-number v-model:value="imageMargin" :min="0" />
136+
</n-form-item>
137+
</n-form>
138+
</c-card>
139+
<c-card mt-3>
140+
<details>
141+
<summary>Dots Options</summary>
142+
<n-form label-width="130" label-placement="left">
143+
<n-form-item label="Color:">
144+
<n-color-picker v-model:value="dotColor" :modes="['hex']" />
145+
</n-form-item>
146+
<c-select
147+
v-model:value="dotType"
148+
label="Type:"
149+
label-position="left"
150+
label-width="130px"
151+
label-align="right"
152+
:options="dotTypes.map((value) => ({ label: value, value }))"
153+
/>
154+
</n-form>
155+
</details>
156+
</c-card>
157+
<c-card mt-3>
158+
<details>
159+
<summary>Corners Dots Options</summary>
160+
<n-form label-width="130" label-placement="left">
161+
<n-form-item label="Color:">
162+
<n-color-picker v-model:value="cornersDotColor" :modes="['hex']" />
163+
</n-form-item>
164+
<c-select
165+
v-model:value="cornersDotType"
166+
label="Type:"
167+
label-position="left"
168+
label-width="130px"
169+
label-align="right"
170+
:options="cornersDotTypes.map((value) => ({ label: value, value }))"
171+
/>
172+
</n-form>
173+
</details>
174+
</c-card>
175+
<c-card mt-3>
176+
<details>
177+
<summary>Corners Square Options</summary>
178+
<n-form label-width="130" label-placement="left">
179+
<n-form-item label="Color:">
180+
<n-color-picker v-model:value="cornersSquareColor" :modes="['hex']" />
181+
</n-form-item>
182+
<c-select
183+
v-model:value="cornersSquareType"
184+
label="Type:"
185+
label-position="left"
186+
label-width="130px"
187+
label-align="right"
188+
:options="cornersSquareTypes.map((value) => ({ label: value, value }))"
189+
/>
190+
</n-form>
191+
</details>
192+
</c-card>
193+
<c-select
194+
v-model:value="outputType"
195+
mt-3
196+
label="Output format:"
197+
label-position="left"
198+
label-width="130px"
199+
label-align="right"
200+
:options="outputTypes.map((value) => ({ label: value.toUpperCase(), value }))"
201+
/>
58202
</n-gi>
59203
<n-gi>
60204
<div flex flex-col items-center gap-3>
61205
<n-image :src="qrcode" width="200" />
62206
<c-button @click="download">
63-
Download qr-code
207+
Download qr-code ({{ outputType.toString().toUpperCase() }})
64208
</c-button>
65209
</div>
210+
211+
<n-divider />
212+
213+
<n-checkbox v-model:checked="smallTerminal">
214+
Small Terminal
215+
</n-checkbox>
216+
<n-form-item label="Terminal output:" mt-1>
217+
<TextareaCopyable
218+
:value="qrcodeTerminal"
219+
multiline
220+
rows="5"
221+
mb-1 mt-1
222+
copy-placement="outside"
223+
/>
224+
</n-form-item>
66225
</n-gi>
67226
</n-grid>
68227
</c-card>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
declare module 'qrcode-terminal-nooctal' {
2+
export const error: 0 | 1 | 2 | 3;
3+
export function generate(input: string, opts?: { small: boolean }, callback?: (qrcode: string) => void): void;
4+
export function setErrorLevel(error: "L" | "M" | "Q" | "H"): void;
5+
}

0 commit comments

Comments
 (0)