feat: added qr-code generator
Signed-off-by: Corentin Thomasset <corentin.thomasset74@gmail.com>
This commit is contained in:
		
							parent
							
								
									b75603f311
								
							
						
					
					
						commit
						a20858dfb8
					
				| @ -3,6 +3,9 @@ All notable changes to this project will be documented in this file. | |||||||
| The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), | ||||||
| and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | ||||||
| 
 | 
 | ||||||
|  | ## Next | ||||||
|  | - [feat] added [qr-code generator](/#/qrcode-generator) | ||||||
|  | 
 | ||||||
| ## 1.4.0 | ## 1.4.0 | ||||||
| - [ui] condensed + colored sidenav | - [ui] condensed + colored sidenav | ||||||
| - [feat] added [git memo](/#/git-memo) | - [feat] added [git memo](/#/git-memo) | ||||||
|  | |||||||
| @ -18,10 +18,10 @@ Here is an unordered list of the current functionalities, and some that may come | |||||||
| - [x] Markdown editor | - [x] Markdown editor | ||||||
| - [x] Lorem ipsum text generator | - [x] Lorem ipsum text generator | ||||||
| - [x] Git memo (cheat sheet) | - [x] Git memo (cheat sheet) | ||||||
|  | - [x] QR code generator | ||||||
| - [ ] CSS memo (cheat sheet) | - [ ] CSS memo (cheat sheet) | ||||||
| - [ ] REGEX memo (cheat sheet) + tester? | - [ ] REGEX memo (cheat sheet) + tester? | ||||||
| - [ ] Image exif editor/remover | - [ ] Image exif editor/remover | ||||||
| - [ ] QR code generator |  | ||||||
| - [ ] Bip39 pass-phrase generator | - [ ] Bip39 pass-phrase generator | ||||||
| - [ ] Crontab friendly generator | - [ ] Crontab friendly generator | ||||||
| - [ ] Image format converter? | - [ ] Image format converter? | ||||||
|  | |||||||
							
								
								
									
										5
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -9990,6 +9990,11 @@ | |||||||
|       "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", |       "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|  |     "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.5.2", |       "version": "6.5.2", | ||||||
|       "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", |       "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", | ||||||
|  | |||||||
| @ -15,6 +15,7 @@ | |||||||
|     "core-js": "^3.6.4", |     "core-js": "^3.6.4", | ||||||
|     "dompurify": "^2.0.11", |     "dompurify": "^2.0.11", | ||||||
|     "marked": "^1.1.0", |     "marked": "^1.1.0", | ||||||
|  |     "qrcode.vue": "^1.7.0", | ||||||
|     "register-service-worker": "^1.7.1", |     "register-service-worker": "^1.7.1", | ||||||
|     "roboto-fontface": "*", |     "roboto-fontface": "*", | ||||||
|     "vue": "^2.6.11", |     "vue": "^2.6.11", | ||||||
|  | |||||||
							
								
								
									
										58
									
								
								src/components/ColorInput.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/components/ColorInput.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,58 @@ | |||||||
|  | <template> | ||||||
|  |     <v-text-field v-model="color" hide-details class="ma-0 pa-0" outlined :label="label" v-on: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 v-on:input="$emit('input', color)"/> | ||||||
|  |                     </v-card-text> | ||||||
|  |                 </v-card> | ||||||
|  |             </v-menu> | ||||||
|  |         </template> | ||||||
|  |     </v-text-field> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script> | ||||||
|  |     // From: https://codepen.io/JamieCurnow/pen/KKPjraK | ||||||
|  | 
 | ||||||
|  |     export default { | ||||||
|  |         name: "ColorInput", | ||||||
|  |         props:{ | ||||||
|  |             value: { | ||||||
|  |                 type: String, | ||||||
|  |                 default: '#FFFFFF' | ||||||
|  |             }, | ||||||
|  |             label:String | ||||||
|  |         }, | ||||||
|  |         data: () => ({ | ||||||
|  |             menu: false, | ||||||
|  |             color:'' | ||||||
|  |         }), | ||||||
|  |         mounted() { | ||||||
|  |             this.color = this.value | ||||||
|  |         }, | ||||||
|  |         computed: { | ||||||
|  |             swatchStyle() { | ||||||
|  |                 const { color, menu } = this | ||||||
|  |                 return { | ||||||
|  |                     backgroundColor: color, | ||||||
|  |                     cursor: 'pointer', | ||||||
|  |                     height: '30px', | ||||||
|  |                     width: '30px', | ||||||
|  |                     borderRadius: 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> | ||||||
| @ -121,7 +121,18 @@ const toolsComponents = [ | |||||||
|                 keywords: ['git', 'push', 'rebase', 'merge', 'tag', 'commit', 'checkout'] |                 keywords: ['git', 'push', 'rebase', 'merge', 'tag', 'commit', 'checkout'] | ||||||
|             } |             } | ||||||
|         ] |         ] | ||||||
| 
 |     }, | ||||||
|  |     { | ||||||
|  |         title: 'Miscellaneous', | ||||||
|  |         child: [ | ||||||
|  |             { | ||||||
|  |                 text: 'QR Code generator', | ||||||
|  |                 path: '/qrcode-generator', | ||||||
|  |                 icon: 'fa-qrcode', | ||||||
|  |                 component: () => import('./routes/tools/QRCodeGenerator'), | ||||||
|  |                 keywords: [] | ||||||
|  |             } | ||||||
|  |         ] | ||||||
|     } |     } | ||||||
| ]; | ]; | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										137
									
								
								src/routes/tools/QRCodeGenerator.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								src/routes/tools/QRCodeGenerator.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,137 @@ | |||||||
|  | <template> | ||||||
|  |     <v-card class="single-card"> | ||||||
|  |         <v-card-title>QR-code generator</v-card-title> | ||||||
|  |         <v-card-text> | ||||||
|  |             <v-row justify="center" align="center"> | ||||||
|  |                 <v-col cols="12" lg="6" sm="12"> | ||||||
|  |                     <v-text-field | ||||||
|  |                             outlined | ||||||
|  |                             v-model="value" | ||||||
|  |                             label="Data" | ||||||
|  |                             :rules="rules.value" | ||||||
|  |                     /> | ||||||
|  |                     <v-slider v-model="size" min="100" max="1920" label="Size (preview will not change): " thumb-label/> | ||||||
|  |                     <v-select | ||||||
|  |                             outlined | ||||||
|  |                             v-model="level" | ||||||
|  |                             :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> | ||||||
|  | 
 | ||||||
|  |                 </v-col> | ||||||
|  | 
 | ||||||
|  |                 <v-col cols="12" lg="6" sm="12" class="text-center"> | ||||||
|  |                     <qrcode-vue | ||||||
|  |                             :value="input" | ||||||
|  |                             :size="size" | ||||||
|  |                             :level="level" | ||||||
|  |                             :background="bgcolor" | ||||||
|  |                             :foreground="fgcolor" | ||||||
|  |                             render-as="svg" | ||||||
|  |                             class-name="qrcode-wrapper" | ||||||
|  |                     /> | ||||||
|  |                 </v-col> | ||||||
|  |             </v-row> | ||||||
|  | 
 | ||||||
|  |             <div class="text-center mt-3 mb-sm-2"> | ||||||
|  |                 <v-btn @click="download('png')" class="mr-1" color="primary">download as png</v-btn> | ||||||
|  |                 <v-btn @click="download('svg')" class="ml-1" color="primary">download as svg</v-btn> | ||||||
|  |             </div> | ||||||
|  |         </v-card-text> | ||||||
|  |     </v-card> | ||||||
|  | 
 | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script> | ||||||
|  |     import QrcodeVue from 'qrcode.vue' | ||||||
|  |     import colors from "color-name"; | ||||||
|  |     import ColorInput from "../../components/ColorInput"; | ||||||
|  |     import {downloadBase64File} from "../../utils/helpers"; | ||||||
|  | 
 | ||||||
|  |     export default { | ||||||
|  |         name: "QRCodeGenerator", | ||||||
|  |         data: () => ({ | ||||||
|  |             value: 'https://it-tools.tech', | ||||||
|  |             size: 300, | ||||||
|  |             level: 'M', | ||||||
|  |             bgcolor: '#ffffff', | ||||||
|  |             fgcolor: '#000000', | ||||||
|  |             levels: [ | ||||||
|  |                 {text: 'Low', value: 'L'}, | ||||||
|  |                 {text: 'Medium', value: 'M'}, | ||||||
|  |                 {text: 'Quartile', value: 'Q'}, | ||||||
|  |                 {text: 'High', value: 'H'} | ||||||
|  |             ], | ||||||
|  |             rules: { | ||||||
|  |                 value: [ | ||||||
|  |                     v => v.length > 0 || 'Value is needed' | ||||||
|  |                 ], | ||||||
|  |                 color: [ | ||||||
|  |                     v => { | ||||||
|  |                         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.' | ||||||
|  |                     } | ||||||
|  |                 ] | ||||||
|  |             } | ||||||
|  |         }), | ||||||
|  |         methods: { | ||||||
|  |             download(type) { | ||||||
|  |                 const svgEl = this.$el.querySelector('.qrcode-wrapper svg'); | ||||||
|  |                 const svgString = new XMLSerializer().serializeToString(svgEl); | ||||||
|  |                 const svgUrl = `data:image/svg+xml;base64,${btoa(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'); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         computed: { | ||||||
|  |             input() { | ||||||
|  |                 return this.value | ||||||
|  | 
 | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         components: { | ||||||
|  |             QrcodeVue, | ||||||
|  |             ColorInput | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style scoped lang="less"> | ||||||
|  |     ::v-deep .qrcode-wrapper { | ||||||
|  |         & > * { | ||||||
|  |             width: 300px !important; | ||||||
|  |             height: 300px !important; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | </style> | ||||||
| @ -41,6 +41,13 @@ const randFromArray = (array) => array[Math.floor(Math.random() * array.length)] | |||||||
| 
 | 
 | ||||||
| const randIntFromInterval = (min, max) => Math.floor(Math.random() * (max - min) + min) | const randIntFromInterval = (min, max) => Math.floor(Math.random() * (max - min) + min) | ||||||
| 
 | 
 | ||||||
|  | const downloadBase64File = (dataUrl, name = 'file') => { | ||||||
|  |     const a = document.createElement("a"); | ||||||
|  |     a.href = dataUrl; | ||||||
|  |     a.download = name; | ||||||
|  |     a.click(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export { | export { | ||||||
|     copyToClipboard, |     copyToClipboard, | ||||||
|     fileIsImage, |     fileIsImage, | ||||||
| @ -48,5 +55,6 @@ export { | |||||||
|     isInt, |     isInt, | ||||||
|     debounce, |     debounce, | ||||||
|     randFromArray, |     randFromArray, | ||||||
|     randIntFromInterval |     randIntFromInterval, | ||||||
|  |     downloadBase64File | ||||||
| } | } | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user