feat: TextStats + better searchbar
Signed-off-by: Corentin Thomasset <corentin.thomasset74@gmail.com>
This commit is contained in:
		
							parent
							
								
									85587beb0d
								
							
						
					
					
						commit
						8c78e8ad77
					
				
							
								
								
									
										27
									
								
								src/App.vue
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								src/App.vue
									
									
									
									
									
								
							| @ -9,7 +9,7 @@ | |||||||
|                 <div v-for="section in items" :key="section.title"> |                 <div v-for="section in items" :key="section.title"> | ||||||
|                     <v-subheader class="mt-4 pl-4">{{section.title}}</v-subheader> |                     <v-subheader class="mt-4 pl-4">{{section.title}}</v-subheader> | ||||||
| 
 | 
 | ||||||
|                     <v-list-item v-for="item in section.child" :key="item.text" :to="item.link"> |                     <v-list-item v-for="item in section.child" :key="item.text" :to="item.path"> | ||||||
|                         <v-list-item-action> |                         <v-list-item-action> | ||||||
|                             <v-icon>{{ item.icon }}</v-icon> |                             <v-icon>{{ item.icon }}</v-icon> | ||||||
|                         </v-list-item-action> |                         </v-list-item-action> | ||||||
| @ -82,6 +82,7 @@ | |||||||
| 
 | 
 | ||||||
| <script> | <script> | ||||||
|     import SearchBar from "./components/SearchBar"; |     import SearchBar from "./components/SearchBar"; | ||||||
|  |     import {toolsComponents} from "./router"; | ||||||
| 
 | 
 | ||||||
|     export default { |     export default { | ||||||
|         props: { |         props: { | ||||||
| @ -91,29 +92,7 @@ | |||||||
|         data: () => ({ |         data: () => ({ | ||||||
|             appVersion: 'v' + process.env.APPLICATION_VERSION, |             appVersion: 'v' + process.env.APPLICATION_VERSION, | ||||||
|             drawer: null, |             drawer: null, | ||||||
|             items: [ |             items: toolsComponents | ||||||
|                 { |  | ||||||
|                     title: 'Crypto', |  | ||||||
|                     child: [ |  | ||||||
|                         {icon: 'fa-key', text: 'Token generator', link: '/token-generator'}, |  | ||||||
|                         {icon: 'fa-font', text: 'Hash text', link: '/hash'}, |  | ||||||
|                         {icon: 'fa-lock', text: 'Cypher/uncypher text', link: '/cypher'}, |  | ||||||
|                     ], |  | ||||||
|                 }, |  | ||||||
|                 { |  | ||||||
|                     title: 'Converter', |  | ||||||
|                     child: [ |  | ||||||
|                         {icon: 'fa-calendar', text: 'Date/Time converter', link: '/date-converter'}, |  | ||||||
|                     ], |  | ||||||
|                 }, |  | ||||||
|                 { |  | ||||||
|                     title: 'Web', |  | ||||||
|                     child: [ |  | ||||||
|                         {icon: 'fa-link', text: 'URL encode/decode', link: '/url-encoder'}, |  | ||||||
|                         {icon: 'fa-file-image-o', text: 'File to Base64', link: '/file-to-base64'}, |  | ||||||
|                     ], |  | ||||||
|                 } |  | ||||||
|             ], |  | ||||||
|         }), |         }), | ||||||
|         created() { |         created() { | ||||||
|             this.$vuetify.theme.dark = true |             this.$vuetify.theme.dark = true | ||||||
|  | |||||||
| @ -6,10 +6,13 @@ | |||||||
|             color="white" |             color="white" | ||||||
|             hide-details |             hide-details | ||||||
|             :items="items" |             :items="items" | ||||||
|             item-text="component.name" |             item-text="text" | ||||||
|             item-value="path" |             item-value="path" | ||||||
|             solo-inverted |             solo-inverted | ||||||
|             @change="choose" |             @change="choose" | ||||||
|  |             :filter="filter" | ||||||
|  |             clearable | ||||||
|  |             cache-items | ||||||
|     > |     > | ||||||
|         <template v-slot:no-data> |         <template v-slot:no-data> | ||||||
|             <v-list-item> |             <v-list-item> | ||||||
| @ -22,23 +25,36 @@ | |||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script> | <script> | ||||||
|  |     import {toolsComponentsFlat} from '../router' | ||||||
| 
 | 
 | ||||||
|     import {toolsRoutes} from '../router' |     console.log(toolsComponentsFlat); | ||||||
| 
 |  | ||||||
|     export default { |     export default { | ||||||
|         name: "SearchBar", |         name: "SearchBar", | ||||||
|         data(){ |         data() { | ||||||
|             const vm = this; |             const vm = this; | ||||||
|             return { |             return { | ||||||
|                 items:toolsRoutes, |                 items: toolsComponentsFlat, | ||||||
|                 choose(path){ |                 choose(path) { | ||||||
|                     vm.$router.push(path) |                     vm.$router.push(path).catch(() => { | ||||||
|  |                     }) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|  |         methods: { | ||||||
|  |             filter(item, queryText, itemText) { | ||||||
|  |                 const query = queryText.trim().toLowerCase(); | ||||||
|  |                 const nameContainsText = itemText.toLowerCase().includes(query); | ||||||
|  |                 const keywordContainsText = item.keywords ? item.keywords.some(keyword => keyword.toLowerCase().includes(query)) : false; | ||||||
|  |                 return nameContainsText || keywordContainsText; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <style scoped> | <style scoped lang="less"> | ||||||
|  |     ::v-deep .v-list-item__mask{ | ||||||
|  |         color: inherit !important; | ||||||
|  |         background: inherit !important; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| </style> | </style> | ||||||
| @ -7,38 +7,81 @@ import DateConverter from "./routes/tools/DateConverter"; | |||||||
| import UrlEncoder from "./routes/tools/UrlEncoder"; | import UrlEncoder from "./routes/tools/UrlEncoder"; | ||||||
| import FileToBase64 from "./routes/tools/FileToBase64"; | import FileToBase64 from "./routes/tools/FileToBase64"; | ||||||
| import TextCypher from "./routes/tools/TextCypher"; | import TextCypher from "./routes/tools/TextCypher"; | ||||||
|  | import TextStats from "./routes/tools/TextStats"; | ||||||
| 
 | 
 | ||||||
| Vue.use(VueRouter) | Vue.use(VueRouter) | ||||||
| 
 | 
 | ||||||
| const toolsRoutes = [ | 
 | ||||||
|  | const toolsComponents = [ | ||||||
|     { |     { | ||||||
|  |         title: 'Crypto', | ||||||
|  |         child: [ | ||||||
|  |             { | ||||||
|  |                 icon: 'fa-key', | ||||||
|  |                 text: 'Token generator', | ||||||
|                 path: '/token-generator', |                 path: '/token-generator', | ||||||
|         component: TokenGenerator |                 component: TokenGenerator, | ||||||
|  |                 keywords: ['md5'] | ||||||
|             }, |             }, | ||||||
|             { |             { | ||||||
|  |                 icon: 'fa-font', | ||||||
|  |                 text: 'Hash text', | ||||||
|                 path: '/hash', |                 path: '/hash', | ||||||
|                 component: Hash |                 component: Hash | ||||||
|             }, |             }, | ||||||
|             { |             { | ||||||
|  |                 icon: 'fa-lock', | ||||||
|  |                 text: 'Cypher/uncypher text', | ||||||
|  |                 path: '/cypher', | ||||||
|  |                 component: TextCypher | ||||||
|  |             }, | ||||||
|  |         ], | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         title: 'Converter', | ||||||
|  |         child: [ | ||||||
|  |             { | ||||||
|  |                 icon: 'fa-calendar', | ||||||
|  |                 text: 'Date/Time converter', | ||||||
|                 path: '/date-converter', |                 path: '/date-converter', | ||||||
|                 component: DateConverter |                 component: DateConverter | ||||||
|             }, |             }, | ||||||
|  |         ], | ||||||
|  |     }, | ||||||
|     { |     { | ||||||
|  |         title: 'Web', | ||||||
|  |         child: [ | ||||||
|  |             { | ||||||
|  |                 icon: 'fa-link', | ||||||
|  |                 text: 'URL encode/decode', | ||||||
|                 path: '/url-encoder', |                 path: '/url-encoder', | ||||||
|                 component: UrlEncoder |                 component: UrlEncoder | ||||||
|             }, |             }, | ||||||
|             { |             { | ||||||
|  |                 icon: 'fa-file-image-o', | ||||||
|  |                 text: 'File to Base64', | ||||||
|                 path: '/file-to-base64', |                 path: '/file-to-base64', | ||||||
|                 component: FileToBase64 |                 component: FileToBase64 | ||||||
|             }, |             }, | ||||||
|  |         ], | ||||||
|  |     }, | ||||||
|     { |     { | ||||||
|         path: '/cypher', |         title: 'Miscellaneous', | ||||||
|         component: TextCypher |         child: [ | ||||||
|  |             { | ||||||
|  |                 icon: 'fa-file-text', | ||||||
|  |                 text: 'Text stats', | ||||||
|  |                 path: '/text-stats', | ||||||
|  |                 component: TextStats | ||||||
|  |             }, | ||||||
|  |         ], | ||||||
|     } |     } | ||||||
| ] | ]; | ||||||
|  | 
 | ||||||
|  | const toolsComponentsFlat = toolsComponents.reduce((acc, section) => [...acc, ...section.child], []) | ||||||
| 
 | 
 | ||||||
| const routes = [ | const routes = [ | ||||||
|     ...toolsRoutes, |     ...toolsComponentsFlat, | ||||||
|     { |     { | ||||||
|         path: '/', |         path: '/', | ||||||
|         component: Home |         component: Home | ||||||
| @ -59,5 +102,6 @@ const router = new VueRouter({ | |||||||
| export default router; | export default router; | ||||||
| export { | export { | ||||||
|     routes, |     routes, | ||||||
|     toolsRoutes |     toolsComponents, | ||||||
|  |     toolsComponentsFlat | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -2,7 +2,6 @@ | |||||||
|   <v-card> |   <v-card> | ||||||
|     <v-card-text> |     <v-card-text> | ||||||
|       Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ad at cumque dolore dolores, dolorum eius error est eum eveniet hic illum ipsum labore minus odio quibusdam repudiandae sed ullam veritatis! |       Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ad at cumque dolore dolores, dolorum eius error est eum eveniet hic illum ipsum labore minus odio quibusdam repudiandae sed ullam veritatis! | ||||||
| 
 |  | ||||||
|     </v-card-text> |     </v-card-text> | ||||||
|   </v-card> |   </v-card> | ||||||
| </template> | </template> | ||||||
|  | |||||||
							
								
								
									
										72
									
								
								src/routes/tools/TextStats.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/routes/tools/TextStats.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,72 @@ | |||||||
|  | <template> | ||||||
|  |     <v-card class="single-card"> | ||||||
|  |         <v-card-title>Text stats</v-card-title> | ||||||
|  |         <v-card-text> | ||||||
|  |             <v-textarea | ||||||
|  |                     outlined | ||||||
|  |                     v-model="inputText" | ||||||
|  |                     label="Input text" | ||||||
|  |             /> | ||||||
|  | 
 | ||||||
|  |             <table> | ||||||
|  |                 <tr> | ||||||
|  |                     <td><strong>Character count:</strong></td> | ||||||
|  |                     <td>{{ inputText.length }}</td> | ||||||
|  |                 </tr> | ||||||
|  |                 <tr> | ||||||
|  |                     <td><strong>Character count (without spaces):</strong></td> | ||||||
|  |                     <td>{{ inputText.split(' ').join('').length }}</td> | ||||||
|  |                 </tr> | ||||||
|  |                 <tr> | ||||||
|  |                     <td><strong>Word count:</strong></td> | ||||||
|  |                     <td>{{ inputText.length > 0 ? inputText.trim().split(/\s+/).length : 0 }}</td> | ||||||
|  |                 </tr> | ||||||
|  |                 <tr> | ||||||
|  |                     <td><strong>Line count:</strong></td> | ||||||
|  |                     <td>{{ inputText.length > 0 ? inputText.split(/\r\n|\r|\n/).length : 0 }}</td> | ||||||
|  |                 </tr> | ||||||
|  |                 <tr> | ||||||
|  |                     <td><strong>Byte size:</strong></td> | ||||||
|  |                     <td>{{ formatBytes(bytesSize) }} <span v-if="bytesSize >= 1024">({{bytesSize}} Bytes)</span></td> | ||||||
|  |                 </tr> | ||||||
|  |             </table> | ||||||
|  |         </v-card-text> | ||||||
|  |     </v-card> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script> | ||||||
|  |     import {formatBytes} from "../../utils/helpers"; | ||||||
|  | 
 | ||||||
|  |     export default { | ||||||
|  |         name: "TextStats", | ||||||
|  |         data() { | ||||||
|  |             return { | ||||||
|  |                 Blob: Blob, | ||||||
|  |                 formatBytes, | ||||||
|  |                 inputText: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit.', | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         computed: { | ||||||
|  |             bytesSize() { | ||||||
|  |                 return new Blob([this.inputText]).size | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style scoped lang="less"> | ||||||
|  |     table { | ||||||
|  |         width: 100%; | ||||||
|  | 
 | ||||||
|  |         tr { | ||||||
|  |             td { | ||||||
|  |                 width: 50%; | ||||||
|  |                 padding: 5px; | ||||||
|  | 
 | ||||||
|  |                 &:first-child { | ||||||
|  |                     text-align: right; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | </style> | ||||||
| @ -12,7 +12,20 @@ const fileIsImage = (file) => { | |||||||
|     return file.type.split('/')[0] === 'image'; |     return file.type.split('/')[0] === 'image'; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | const formatBytes = (bytes, decimals = 2) => { | ||||||
|  |     if (bytes === 0) return '0 Bytes'; | ||||||
|  | 
 | ||||||
|  |     const k = 1024; | ||||||
|  |     const dm = decimals < 0 ? 0 : decimals; | ||||||
|  |     const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; | ||||||
|  | 
 | ||||||
|  |     const i = Math.floor(Math.log(bytes) / Math.log(k)); | ||||||
|  | 
 | ||||||
|  |     return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export { | export { | ||||||
|     copyToClipboard, |     copyToClipboard, | ||||||
|     fileIsImage |     fileIsImage, | ||||||
|  |     formatBytes | ||||||
| } | } | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user