feat(tool): added qr-code generator
This commit is contained in:
		
							parent
							
								
									27f3826d5f
								
							
						
					
					
						commit
						c16e537abf
					
				
							
								
								
									
										57
									
								
								components/ColorInput.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								components/ColorInput.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,57 @@ | |||||||
|  | <template> | ||||||
|  |   <v-text-field | ||||||
|  |     v-model="color" | ||||||
|  |     hide-details | ||||||
|  |     class="ma-0 pa-0" | ||||||
|  |     outlined | ||||||
|  |     :label="label" | ||||||
|  |     @input="$emit('input', color)" | ||||||
|  |   > | ||||||
|  |     <template v-slot:append> | ||||||
|  |       <v-menu v-model="menu" top nudge-bottom="101" nudge-left="16" :close-on-content-click="false"> | ||||||
|  |         <template v-slot:activator="{ on }"> | ||||||
|  |           <div :style="swatchStyle" v-on="on"/> | ||||||
|  |         </template> | ||||||
|  |         <v-card> | ||||||
|  |           <v-card-text class="pa-0"> | ||||||
|  |             <v-color-picker v-model="color" flat @input="$emit('input', color)"/> | ||||||
|  |           </v-card-text> | ||||||
|  |         </v-card> | ||||||
|  |       </v-menu> | ||||||
|  |     </template> | ||||||
|  |   </v-text-field> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts"> | ||||||
|  | import {Component, Prop, Vue} from 'nuxt-property-decorator' | ||||||
|  | // Adapted from: https://codepen.io/JamieCurnow/pen/KKPjraK | ||||||
|  | 
 | ||||||
|  | @Component | ||||||
|  | export default class ColorInput extends Vue { | ||||||
|  |   @Prop({default: '#ffffff'}) readonly value!: string; | ||||||
|  |   @Prop() readonly label: string | undefined; | ||||||
|  |   menu = false | ||||||
|  |   color = '' | ||||||
|  | 
 | ||||||
|  |   created() { | ||||||
|  |     this.color = this.value | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   get swatchStyle() { | ||||||
|  |     return { | ||||||
|  |       backgroundColor: this.color, | ||||||
|  |       cursor: 'pointer', | ||||||
|  |       height: '30px', | ||||||
|  |       width: '30px', | ||||||
|  |       borderRadius: this.menu ? '50%' : '4px', | ||||||
|  |       transition: 'border-radius 200ms ease-in-out' | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style scoped lang="less"> | ||||||
|  | ::v-deep .v-input__append-inner { | ||||||
|  |   margin-top: 13px; | ||||||
|  | } | ||||||
|  | </style> | ||||||
							
								
								
									
										5
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -14222,6 +14222,11 @@ | |||||||
|       "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", |       "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", | ||||||
|       "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" |       "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" | ||||||
|     }, |     }, | ||||||
|  |     "qrcode.vue": { | ||||||
|  |       "version": "1.7.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/qrcode.vue/-/qrcode.vue-1.7.0.tgz", | ||||||
|  |       "integrity": "sha512-R7t6Y3fDDtcU7L4rtqwGUDP9xD64gJhIwpfjhRCTKmBoYF6SS49PIJHRJ048cse6OI7iwTwgyy2C46N9Ygoc6g==" | ||||||
|  |     }, | ||||||
|     "qs": { |     "qs": { | ||||||
|       "version": "6.10.1", |       "version": "6.10.1", | ||||||
|       "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", |       "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", | ||||||
|  | |||||||
| @ -25,6 +25,7 @@ | |||||||
|     "crypto-js": "^4.0.0", |     "crypto-js": "^4.0.0", | ||||||
|     "nuxt": "^2.15.6", |     "nuxt": "^2.15.6", | ||||||
|     "nuxt-i18n": "^6.27.0", |     "nuxt-i18n": "^6.27.0", | ||||||
|  |     "qrcode.vue": "^1.7.0", | ||||||
|     "vuetify": "^2.5.0", |     "vuetify": "^2.5.0", | ||||||
|     "vuetify-toast-snackbar": "^0.6.1" |     "vuetify-toast-snackbar": "^0.6.1" | ||||||
|   }, |   }, | ||||||
|  | |||||||
							
								
								
									
										134
									
								
								pages/tools/web/qrcode-generator.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								pages/tools/web/qrcode-generator.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,134 @@ | |||||||
|  | <template> | ||||||
|  |   <ToolWrapper :config="config()"> | ||||||
|  |     <v-text-field | ||||||
|  |       v-model="value" | ||||||
|  |       outlined | ||||||
|  |       label="Data" | ||||||
|  |       :rules="rules.value" | ||||||
|  |     /> | ||||||
|  |     <v-slider v-model="size" min="100" max="1920" label="Size (preview will not change): " thumb-label /> | ||||||
|  |     <v-select | ||||||
|  |       v-model="level" | ||||||
|  |       outlined | ||||||
|  |       :items="levels" | ||||||
|  |       label="Error resistance" | ||||||
|  |     /> | ||||||
|  |     <v-row> | ||||||
|  |       <v-col cols="12" md="6" sm="12"> | ||||||
|  |         <ColorInput v-model="fgColor" label="Foreground color" /> | ||||||
|  |       </v-col> | ||||||
|  |       <v-col cols="12" md="6" sm="12"> | ||||||
|  |         <ColorInput v-model="bgColor" label="Background color" /> | ||||||
|  |       </v-col> | ||||||
|  |     </v-row> | ||||||
|  | 
 | ||||||
|  |     <div class="text-center mt-5 mb-5"> | ||||||
|  |       <qrcode-vue | ||||||
|  |         :value="value" | ||||||
|  |         :size="size" | ||||||
|  |         :level="level" | ||||||
|  |         :background="bgColor" | ||||||
|  |         :foreground="fgColor" | ||||||
|  |         render-as="svg" | ||||||
|  |         class-name="qrcode-wrapper" | ||||||
|  |       /> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     <div class="text-center mb-sm-2"> | ||||||
|  |       <v-btn class="mr-1" color="primary" @click="download('png')"> | ||||||
|  |         download as png | ||||||
|  |       </v-btn> | ||||||
|  |       <v-btn class="ml-1" color="primary" @click="download('svg')"> | ||||||
|  |         download as svg | ||||||
|  |       </v-btn> | ||||||
|  |     </div> | ||||||
|  |   </ToolWrapper> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts"> | ||||||
|  | import {Component} from 'nuxt-property-decorator' | ||||||
|  | import QrcodeVue from 'qrcode.vue' | ||||||
|  | import colors from 'color-name' | ||||||
|  | import {CopyableMixin} from '~/mixins/copyable.mixin' | ||||||
|  | import Tool from '~/components/Tool.vue' | ||||||
|  | import type {ToolConfig} from '~/types/ToolConfig' | ||||||
|  | import {downloadBase64File} from '~/utils/file' | ||||||
|  | import {stringToBase64} from '~/utils/convert' | ||||||
|  | import ColorInput from '~/components/ColorInput.vue' | ||||||
|  | 
 | ||||||
|  | @Component({ | ||||||
|  |   components: {QrcodeVue, ColorInput}, | ||||||
|  |   mixins: [CopyableMixin] | ||||||
|  | }) | ||||||
|  | export default class QrcodeGenerator extends Tool { | ||||||
|  |   config(): ToolConfig { | ||||||
|  |     return { | ||||||
|  |       title: 'QR-code generator', | ||||||
|  |       description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Delectus distinctio dolor dolorum eaque eligendi, facilis impedit laboriosam odit placeat.', | ||||||
|  |       icon: 'mdi-qrcode', | ||||||
|  |       keywords: ['editor'] | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   value = 'https://it-tools.tech' | ||||||
|  |   size = 300 | ||||||
|  |   level = 'M' | ||||||
|  |   bgColor = 'transparent' | ||||||
|  |   fgColor = '#ffffff' | ||||||
|  |   levels = [ | ||||||
|  |     {text: 'Low', value: 'L'}, | ||||||
|  |     {text: 'Medium', value: 'M'}, | ||||||
|  |     {text: 'Quartile', value: 'Q'}, | ||||||
|  |     {text: 'High', value: 'H'} | ||||||
|  |   ] | ||||||
|  | 
 | ||||||
|  |   rules = { | ||||||
|  |     value: [ | ||||||
|  |       (v: string) => v.length > 0 || 'Value is needed' | ||||||
|  |     ], | ||||||
|  |     color: [ | ||||||
|  |       (v: string) => { | ||||||
|  |         v = v.trim() | ||||||
|  |         const isFFFFFF = /^#[0-9a-fA-F]{6}$/.test(v) | ||||||
|  |         const isFFF = /^#[0-9a-fA-F]{3}$/.test(v) | ||||||
|  |         const isRGB = /^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/.test(v) | ||||||
|  |         const isHSL = /^hsl\((\d+),\s*(\d+)%,\s*(\d+)%\)$/.test(v) | ||||||
|  |         const isKeyword = v in colors | ||||||
|  |         const isTransparent = v === 'transparent' | ||||||
|  |         return isFFFFFF || isFFF || isKeyword || isTransparent || isRGB || isHSL || 'Incorrect color.' | ||||||
|  |       } | ||||||
|  |     ] | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   download(type: string) { | ||||||
|  |     const svgEl = this.$el.querySelector('.qrcode-wrapper svg')! | ||||||
|  |     const svgString = new XMLSerializer().serializeToString(svgEl) | ||||||
|  |     const svgUrl = `data:image/svg+xml;base64,${stringToBase64(svgString)}` | ||||||
|  |     if (type === 'png') { | ||||||
|  |       const canvas = document.createElement('canvas') | ||||||
|  |       canvas.width = this.size | ||||||
|  |       canvas.height = this.size | ||||||
|  |       const ctx = canvas.getContext('2d')! | ||||||
|  |       const image = new Image() | ||||||
|  |       image.onload = function () { | ||||||
|  |         ctx.drawImage(image, 0, 0) | ||||||
|  |         const result = canvas.toDataURL() | ||||||
|  |         downloadBase64File(result, 'qr-code') | ||||||
|  |       } | ||||||
|  |       image.src = svgUrl | ||||||
|  |     } else { | ||||||
|  |       downloadBase64File(svgUrl, 'qr-code') | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style scoped lang="less"> | ||||||
|  | ::v-deep .qrcode-wrapper { | ||||||
|  |   & > * { | ||||||
|  |     width: 300px !important; | ||||||
|  |     height: 300px !important; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | </style> | ||||||
							
								
								
									
										1
									
								
								types/qrcode.vue.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								types/qrcode.vue.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | declare module 'qrcode.vue'; | ||||||
							
								
								
									
										10
									
								
								utils/file.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								utils/file.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | |||||||
|  | const downloadBase64File = (dataUrl: string, name = 'file') => { | ||||||
|  |   const a = document.createElement('a') | ||||||
|  |   a.href = dataUrl | ||||||
|  |   a.download = name | ||||||
|  |   a.click() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export { | ||||||
|  |   downloadBase64File | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user