mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-04-22 15:56:15 -04:00
feat: TextStats + better searchbar
Signed-off-by: Corentin Thomasset <corentin.thomasset74@gmail.com>
This commit is contained in:
parent
abc7fc6259
commit
e5969a534f
6 changed files with 177 additions and 54 deletions
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…
Add table
Add a link
Reference in a new issue